自学内容网 自学内容网

文件系统及缓冲区

目录

一、回顾之前的文件操作

(1)test.c写文件

(2)hello.c读文件

二、Linux系统调用open和read

open接口介绍

三、open的返回值:文件描述符fd的概念

四、文件描述符fd的分配规则

五、重定向

​编辑

六、使用dup2系统调用来修改文件描述符

(1)dup2的介绍

(2)简单讨论为什么需要stderr和stdout两种显示器文件呢

七、缓冲区


一、回顾之前的文件操作

在学习Linux中的文件操作之前,我们先来回顾一下c语言中的文件操作:

(1)test.c写文件

(2)hello.c读文件

其实c语言中还有许多关于文件输出到显示器,以及从文件中读取数据的库函数,这些都是封装了Linux中的系统调用!在这里我们不过多讨论,有所了解即可

二、Linux系统调用open和read

open接口介绍

三、open的返回值:文件描述符fd的概念

在认识返回值之前,先来认识一下两个概念: 系统调用库函数
上面的 fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数(libc)。而, open close read write lseek 都属于系统提供的接口,称之为系统调用接口
回忆一下我们讲操作系统概念时,画的一张图:

系统调用接口和库函数的关系,一目了然。
所以,可以认为,f#系列的函数,都是对系统调用的封装,方便二次开发。

文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件

四、文件描述符fd的分配规则

再关闭0或者2来看:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
    close(0);
    //close(2);
    int fd = open("myfile", O_RDONLY);
    if(fd < 0)
    {
        perror("open");
        return 1;
    }
    printf("fd: %d\n", fd);
    
    close(fd);
    
    return 0;
}

发现是结果是: fd: 0 或者 fd 2 可见,文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。

五、重定向

        之前我们关闭的是文件描述符0或者2,那么现在我们关闭文件描述符1呢?

此时,我们发现,本来应该输出到显示器上的内容,输出到了文件 myfile 当中,其中,fd=1。这种现象叫做输出重定向。常见的重定向有:>, >>, <

通过这个图,我们可以清晰的看到,重定向的本质就是把files_struct这个指针数组中的第fd号元素改成了别的文件的指针,但是我们操作系统只会傻傻的按照文件描述符fd这个数组下标去找到对应的执行文件,并不会管到底是哪一个文件!

六、使用dup2系统调用来修改文件描述符

(1)dup2的介绍

        之前我们修改一个文件描述符,是先关闭一个不需要的文件描述符,然后马上创建一个新的文件,让这个新文件的文件描述符来取代原位置。但是这样不仅操作不方便,还容易记不住每个文件描述符对应的文件到底是什么,于是Linux就给我们提供了一个系统调用dup2,来方便我们操作文件描述符。

从man手册中可以看到,dup2是把newfd变成oldfd的拷贝,即在files_struct文件指针数组中,把下标为newfd的位置修改成下标为oldfd的元素内容

        我们之前写过一个简单的myshell命令行解释器,现在我们可以在里面加入重定向功能:即如果在用户输入的命令行中检测到了> < >>这种符号,就先把该进程的文件描述符通过dup2改写,并且以不同的方式"w" "r" "a"打开该文件,然后照常进行程序替换等。

        就好比ls原本是把结果输出到显示器(stdout)上的,但是实际操作系统看的是stdout中封装的文件描述符fd=1这个字段,然后把结果输出到1号文件中,现如今我们通过dup2把新的文件放到了1的位置,那么ls就会把内容输出到该新文件中,于是就实现了重定向!

(2)简单讨论为什么需要stderr和stdout两种显示器文件呢

七、缓冲区

因为IO相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过fd访问的。所以C库当中的FILE结构体内部,必定封装了fd。

对于这样一份代码,输出结果很明显是输出三行内容到显示器上,但是如果我们对进程增加一个重定向呢?./hello > file,结果却变成了下面的样子!

        我们发现 printf 和 fwrite (库函数)都输出了2次,而 write 只输出了一次(系统调用)。为什么呢?肯定和fork有关!

(1)一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲。
(2)printf fwrite 库函数会自带缓冲区(进度条例子就可以说明),当发生重定向到普通文件时,数据的缓冲方式由行缓冲变成了全缓冲。
(3)而我们放在缓冲区中的数据,就不会被立即刷新,甚至fork之后但是进程退出之后,会统一刷新,写入文件当中。
(4)但是fork的时候,父子数据会发生写时拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的一份数据,随即产生两份数据。
(5)write 没有变化,说明没有所谓的缓冲。

        综上: printf fwrite 库函数会自带缓冲区,而 write 系统调用没有带缓冲区。另外,我们这里所说的缓冲区,都是用户级缓冲区。其实为了提升整机性能,OS也会提供相关内核级缓冲区,不过不再我们讨论范围之内。
那这个缓冲区谁提供呢? printf fwrite 是库函数, write 是系统调用,库函数在系统调用的“上层”, 是对系统调用的“封装”,但是 write 没有缓冲区,而 printf fwrite 有,足以说明,该缓冲区是二次加上的,又因为是C,所以由C标准库提供,每一个用c语言打开的文件都会有一个c库提供的缓冲区,并且还有一个操作系统提供的内核级缓冲区。

        一旦从c库的缓冲区中刷新出来,这个缓冲区里面就被清空了,而之前的内容就交给了操作系统的内核级缓冲区,这个内核级缓冲区并不属于进程!

        所以c语言的ffulsh就是把c库提供的缓冲区刷新到Linux操作系统提供的内核级缓冲区中,因为write是系统调用,直接就把内容输出到了内核级缓冲区中,子进程看不到c库中write的部分,所以不会打印两次


原文地址:https://blog.csdn.net/2303_79336820/article/details/142983677

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!