文章参考:文件描述符的本质、文件描述符和文件指针的区别、文件描述符fd和文件指针flip的理解
系统中文件相关表
右侧的表称为i节点表,在整个系统中只有1张。该表可以视为结构体数组,该数组的一个元素对应于一个物理文件。
中间的表称为文件表,在整个系统中只有1张。该表可以视为结构体数组,一个结构体中有很多字段,其中有3个字段比较重要:
file status flags:用于记录文件被打开来读的,还是写的。其实记录的就是open调用中用户指定的第2个参数
current file offset:用于记录文件的当前读写位置(指针)。正是由于此字段的存在,使得一个文件被打开并读取后,下一次读取将从上一次读取的字符后开始读取
v-node ptr:该字段是指针,指向右侧表的一个元素,从而关联了物理文件。
左侧的表称为文件描述符表,每个进程有且仅有1张。该表可以视为指针数组,数组的元素指向文件表的一个元素。最重要的是:数组元素的下标就是大名鼎鼎的文件描述符,数组元素的值就是指向 File 结构体的指针。
系统调用时的操作
文件描述符的操作函数 open, 返回一个文件描述符,内核会在每个进程空间中维护一个文件描述符表,所有打开的文件都将通过此表中的文件描述符来引用。
新建一个i节点表元素,让其对应打开的物理文件(如果对应于该物理文件的i节点元素已经建立,就不做任何操作);
新建一个文件表的元素,根据open的第2个参数设置file status flags字段,将current file offset字段置0,将v-node ptr指向刚建立的i节点表元素;
在文件描述符表中,寻找1个尚未使用的元素,在该元素中填入一个指针值,让其指向刚建立的文件表元素。最重要的是:将该元素的下标作为open的返回值返回。
文件描述符的操作函数 read(write,)根据传入的文件描述符,OS就可以找到对应的文件描述符表元素,进而找到文件表的元素,进而找到i节点表元素,从而完成对物理文件的读写。
而流操作函数 fopen, 返回的是一个FILE结构指针,FILE结构是包含有文件描述符的,FILE结构函数可以看作是对fd直接操作的系统调用的封装,它的优点是带有I/O缓存。
文件描述符和文件指针的概念一总
文件描述符:在linux系统中打开文件就会获得文件描述符,它是个很小的正整数。每个进程在PCB(Process Control Block)中保存着一份文件描述符表,文件描述符就是这个表的索引,每个表项都有一个指向已打开文件的指针。即已打开的文件在内核中用 File 结构体表示,文件描述符表中的指针指向 File 结构体。优点是兼容POSIX标准,许多系统调用都依赖于它;缺点是不能移植到unix之外的系统上去。
文件指针:C语言中使用文件指针而不是文件描述符做为I/O的句柄。文件指针指向进程用户区中的一个被称为FILE结构的数据结构。FILE结构主要包括一个I/O缓冲区和一个文件描述符。而文件描述符是文件描述符表中的一个索引,因此从某种意义上说文件指针就是句柄的句柄(在Windows系统上,文件描述符被称作文件句柄)。相当于在上图中最左侧又虚拟出一个 struct File,其中 struct 中有个元素是文件描述符,指向进程表项中的 fd 索引(自己的理解,不知道正不正确)。文件指针的优点是C语言中的通用格式,便于移植。
既然FILE结构中含有文件描述符,那么可以使用fopen来获得文件指针,然后从文件指针获取文件描述符,文件描述符应该是唯一的,而文件指针却不是唯一的,但指向的对象是唯一的。
进程与文件描述符
内核会在每个进程空间中维护一个文件描述符表, 所有打开的文件都将通过此表中的文件描述符来引用; 而流(如: fopen)返回的是一个FILE结构指针, FILE结构是包含有文件描述符的,FILE结构函数可以看作是对fd直接操作的系统调用的封装, 它的优点是带有I/O 缓存。
每个进程在PCB(Process Control Block)即进程控制块中都保存着一份文件描述符表,文件描述符就是这个表的索引,文件描述表中每个表项都有一个指向已打开文件的指针,现在我们明确一下:已打开的文件在内核中用 FILE 结构体表示,文件描述符表中的指针指向 FILE 结构体。
fork 对文件描述符的影响
fork会导致子进程继承父进程打开的文件描述符,其本质是将父进程的整个文件描述符表复制一份,放到子进程的PCB中。因此父、子进程中相同文件描述符(文件描述符为整数)指向的是同一个文件表元素,这将导致父(子)进程读取文件后,子(父)进程将读取同一文件的后续内容。所以在创建守护进程时,都使用 close 关闭掉从父进程继承的文件描述符。
FILE的结构
struct _iobuf {
char *_ptr; //缓冲区当前指针
int _cnt; //文件的引用计数
char *_base; //缓冲区基址
int _flag; //文件读写模式
int _file; //文件描述符
int _charbuf; //缓冲区剩余自己个数
int _bufsiz; //缓冲区大小
char *_tmpfname;
};
typedef struct _iobuf FILE;
open和fopen的区别
open:返回一个文件描述符,无缓冲,与 write、read 配合使用
fopen:返回 File *,有缓冲,与 fwrite、fread 配合使用