一个 Linux 进程可以打开成百上千个文件 , 为了表示和区分已经打开的文件 , Linux 会给每个文件分配一个编号 ( 一个 ID ) , 这个编号就是一个整数 , 被称为文件描述符 ( File Descriptor ) 。
文件描述符 ( File descriptor ) 是计算机科学中的一个术语 , 是一个用于表述指向文件的引用的抽象化概念。
文件描述符在形式上是一个非负整数。实际上 , 它是一个索引值 , 指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时 , 内核向进程返回一个文件描述符。在程序设计中 , 一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。
文件描述符是一个非负的索引值 ( 一般从3开始 , 0、1、2已经被使用 ) , 指向内核中的 “文件记录表” , 内核为进程中要打开的文件维护者一个“文件记录表 ;
当打开一个现存文件或创建一个新文件时 , 内核就向进程返回一个文件描述符 ( 内核记录表某一栏的索引 ) ; 当需要读写文件时 , 也需要把文件描述符作为参数传递给相应的函数。Linux 下所有对设备和文件的操作都使用文件描述符来进行。
常见文件描述符类型
一个进程启动时, 会默认打开三个文件: 标准输入、标准输出和标准出错处理
0: 表示标准输入, 对应宏为:STDIN_FILENO, 函数scanf()使用的是标准输入;1: 表示标准输出, 对应宏为:STDOUT_FILENO, 函数printf()使用的是标准输出2: 表示标准出错处理, 对应宏为:STDERR_NO;
你也可以使用函数fscanf() 和fprintf() 使用不同的文件描述符重定向进程的I/O到不同的文件。
需要使用文件描述符的函数
若要访问文件, 而且调用的函数又是
write、read、open和close时 , 就必须要用到文件描述符(一般从3开始)。若调用的函数为
fwrite、fread、fopen和fclose时, 就可以绕过直接控制文件描述符 , 使用的则是与文件描述符对应的文件流。
一个 Linux 进程启动后 , 会在内核空间中创建一个 PCB 控制块 , PCB 内部有一个文件描述符表 ( File descriptor table ) , 记录着当前进程所有可用的文件描述符 , 也即当前进程所有打开的文件。
除了文件描述符表 , 系统还需要维护另外两张表 :
打开文件表(
Open file table)i-node表(i-node table)
文件描述符表每个进程都有一个 , 打开文件表和 i-node 表整个系统只有一个 , 它们三者之间的关系如下图所示。

从本质上讲 , 这三种表都是结构体数组 , 0、1、2、73、1976 等都是数组下标。表头只是我自己添加的注释 , 数组本身是没有的。实线箭头表示指针的指向 , 虚线箭头是我自己添加的注释。
你看 , 文件描述符只不过是一个数组下标吗!
通过文件描述符 , 可以找到文件指针 , 从而进入打开文件表。该表存储了以下信息 :
文件偏移量 , 也就是文件内部指针偏移量。调用
read()或者write()函数时 , 文件偏移量会自动更新 , 当然也可以使用lseek()直接修改。状态标志 , 比如只读模式、读写模式、追加模式、覆盖模式等。
i-node表指针。
然而 , 要想真正读写文件 , 还得通过打开文件表的 i-node 指针进入 i-node 表 , 该表包含了诸如以下的信息 :
文件类型 , 例如常规文件、套接字或
FIFO。文件大小。
时间戳 , 比如创建时间、更新时间。
文件锁。
对上图的进一步说明 :
在进程
A中 , 文件描述符1和20都指向了同一个打开文件表项 , 标号为23( 指向了打开文件表中下标为23的数组元素 ) , 这可能是通过调用dup()、dup2()、fcntl()或者对同一个文件多次调用了open()函数形成的。进程
A的文件描述符2和进程B的文件描述符2都指向了同一个文件 , 这可能是在调用fork()后出现的 ( 即进程A、B是父子进程关系 ) , 或者是不同的进程独自去调用open()函数打开了同一个文件 , 此时进程内部的描述符正好分配到与其他进程打开该文件的描述符一样。进程
A的描述符0和进程B的描述符3分别指向不同的打开文件表项 , 但这些表项均指向i-node表的同一个条目(标号为1976) ; 换言之 , 它们指向了同一个文件。发生这种情况是因为每个进程各自对同一个文件发起了open()调用。同一个进程两次打开同一个文件 , 也会发生类似情况。
有了以上对文件描述符的认知 , 我们很容易理解以下情形 :
同一个进程的不同文件描述符可以指向同一个文件 ;
不同进程可以拥有相同的文件描述符 ;
不同进程的同一个文件描述符可以指向不同的文件 ( 一般也是这样 , 除了
0、1、2这三个特殊的文件 ) ;不同进程的不同文件描述符也可以指向同一个文件。
参考链接