文件描述符的本质

1. 文件描述符的本质是数组元素的下标

Alt text

右侧的表称为i节点表,在整个系统中只有1张。该表可以视为结构体数组,该数组的一个元素对应于一个物理文件。

中间的表称为文件表,在整个系统中只有1张。该表可以视为结构体数组,一个结构体中有很多字段,其中有3个字段比较重要:

  1. file status flags:用于记录文件被打开来读的,还是写的。其实记录的就是open调用中用户指定的第2个参数
  2. current file offset:用于记录文件的当前读写位置(指针)。正是由于此字段的存在,使得一个文件被打开并读取后,下一次读取将从上一次读取的字符后开始读取
  3. v-node ptr:该字段是指针,指向右侧表的一个元素,从而关联了物理文件。

左侧的表称为文件描述符表,每个进程有且仅有1张。该表可以视为指针数组,数组的元素指向文件表的一个元素。最重要的是:数组元素的下标就是大名鼎鼎的文件描述符。

open系统调用执行的操作:新建一个i节点表元素,让其对应打开的物理文件(如果对应于该物理文件的i节点元素已经建立,就不做任何操作);新建一个文件表的元素,根据open的第2个参数设置file status flags字段,将current file offset字段置0,将v-node ptr指向刚建立的i节点表元素;在文件描述符表中,寻找1个尚未使用的元素,在该元素中填入一个指针值,让其指向刚建立的文件表元素。最重要的是:将该元素的下标作为open的返回值返回。

这样一来,当调用read(write)时,根据传入的文件描述符,OS就可以找到对应的文件描述符表元素,进而找到文件表的元素,进而找到i节点表元素,从而完成对物理文件的读写。

2. fork 对文件描述符的影响

fork会导致子进程继承父进程打开的文件描述符,其本质是将父进程的整个文件描述符表复制一份,放到子进程的PCB中。因此父、子进程中相同文件描述符(文件描述符为整数)指向的是同一个文件表元素,这将导致父(子)进程读取文件后,子(父)进程将读取同一文件的后续内容。

案例分析(forkfd.c):

#include <stdio.h>  
#include <stdlib.h>  
#include <fcntl.h>  
#include <sys/types.h>  
#include <unistd.h>  
#include <sys/types.h>  
  
int main(void)  
{  
       int fd, pid, status;  
       char buf[10];  
         if ((fd = open("./test.txt", O_RDONLY)) < 0) {  
                 perror("open");  exit(-1);  
        }  
         if ((pid = fork()) < 0) {  
               perror("fork");  exit(-1);  
        } else if (pid == 0) {  //child  
                read(fd, buf, 2);  
                write(STDOUT_FILENO, buf, 2);  
        } else {  //parent  
                 sleep(2);  
                lseek(fd, SEEK_CUR, 1);  
                read(fd, buf, 3);  
                 write(STDOUT_FILENO, buf, 3);  
                 write(STDOUT_FILENO, "
", 1);  
        }  
         return 0;  
 }  

假设,./test.txt的内容是abcdefg。那么子进程的18行将读到字符ab;由于,父、子进程的文件描述符fd都指向同一个文件表元素,因此当父进程执行23行时,fd对应的文件的读写指针将移动到字符d,而不是字符b,从而24行读到的是字符def,而不是字符bcd。程序运行的最终结果是打印abdef,而不是abbcd。

相对应的,如果是两个进程独立调用open去打开同一个物理文件,就会有2个文件表元素被创建,并且他们都指向同一个i节点表元素。两个文件表元素都有自己独立的current file offset字段,这将导致2个进程独立的对同一个物理文件进行读写,因此第1个进程读取到文件的第1个字符后,第2个进程再去读取该文件时,仍然是读到的是文件的第1个字符,而不是第1个字符的后续字符。

对应用程序员而言,最重要结论是: 如果子进程不打算使用父进程打开的文件,那么应该在fork返回后立即调用close关闭该文件。

原文地址:https://www.cnblogs.com/dingxiaoqiang/p/7783391.html