文件描述符
文件描述符 fd file descriptor
文件描述符背景及什么是文件描述符?
文件描述符背景?
Linux 系统中,把一切都看做是文件 (包括普通文件、目录文件、链接文件、Socket 及设备驱动等)。在操作这些文件时,每操作一次就找一次名字,会耗费大量的时间和效率,所以 Linux 中规定每一个文件对应一个索引,这样要操作文件的时候,直接找到索引就可以对其进行操作了。
什么是文件描述符?
当进程打开现有文件或创建新文件时,内核向进程返回一个文件描述符,文件描述符就是内核为了高效管理已被打开的文件所创建的索引,用来指向被打开的文件,所有执行 I/O 操作的系统调用都会通过文件描述符来实现。
- fd 就是一个非负整数,可以理解为进程文件描述表的索引;FD 实际上就是文件描述符表的数组下标。
- 规定 0 是标准输入、1 是标准输出、2 是标准错误,新开的的文件 fd 从 3 开始
进程文件描述表格 fd table
Linux 内核对所有打开的文件有一个文件描述符表格,里面存储了每个文件描述符作为索引与一个打开文件相对应的关系。
一个数组,文件描述符就是文件描述符表这个数组的下标,数组的内容就是指向一个个打开的文件的指针。
 实际上,Linux 内核维护了 3 个数据结构,建立了三个表: |
- 进程级的文件描述符表
- 系统级的打开文件描述符表
- 文件系统的 i-node 表
一个 Linux 进程启动后,会在内核空间中创建一个 PCB 控制块,PCB 内部有一个文件描述符表 (fd table),记录着当前进程所有可用的 fd,即当前进程所有打开的文件。进程级的描述符表的每一条记录了单个进程所使用的 fd 的相关信息,进程之间互相独立,一个进程使用了 fd:3,另一个进程也可以用 3。  可见: |
- 一个打开的文件可以对应多个文件描述符(不管是同进程还是不同进程),如 ProcessA 的 fd1 和 fd20
- 不同进程可以拥有相同的 fd,如 ProcessA 和 ProcessB
- 不同进程的同一个文件描述符可以指向不同的文件,如 ProcessA 的 fd2 和 ProcessB 的 fd2
- 不同进程的不同文件描述符也可以指向同一个文件,如 ProcessA 的 0 和 ProcessB 的 3
- 一个 inode 也可以对应多个打开的文件
- 打开文件表中的一行称为一条文件描述(file description),也经常称为文件句柄(file handle)。
fd 如何定位文件
当需要执行 I/O 操作时,会传入 fd 作为参数,先从进程文件描述表查找该 fd 对应的哪个条目,取出对应的那个已经打开的文件的句柄,根据文件句柄指向,去系统 fd 表中查找到该文件指向的 inode,从而定位到该文件的真正位置,进行 I/O 操作。
文件、文件描述符和进程
- 每个 fd 会与一个打开的文件相对应
- 不同的文件描述符也可能执向同一文件
- 相同的文件可以被不同的进程打开,也可以在同一个进程被多次打开
文件描述符限制
内核对 fd 有系统级的限制,以及用户级限制,不让某一个应用进程消耗掉所有的文件资源,可以使用 ulimit -n
查看。
FD 的一些疑惑
文件句柄和文件描述符
什么是文件描述符?
File descriptor 简称 fd,当应用程序打开/新建一个文件时,内核会返回给应用程序一个文件描述符对应这个打开/创建的文件,fd 本质是一个非负整数,是一个索引值,对应于该应用程序所维护该进程所打开的文件记录表中元素的索引值。
什么是句柄?
句柄:句柄可以理解为 windows 下的文件描述符。
- 无论是文件句柄(Windows 中概念),还是文件描述符(linux 中概念),其最终目的都是用来定位打开的文件在内存中的位置,只是它们映射的方式不一样
- 文件句柄定位到的是文件对象,而非文件。而文件对象是对这个文件的一些状态、属性的封装,例如读取到的文件位置等。
每个文件的文件描述符是固定的吗?
不是固定的,尽管是同一个文件,得到 FD 却不一样。
FD 每次都是从 3 开始的?
因为 0、1、2 被占用了,系统创建的每个进程默认会打开 3 个文件:
- 标准输入:0
- 标准输出:1
- 标准错误:2
FD 是递增的吗?
文件描述符不是递增的,文件描述符被回收后是可以再次分配的。
open 文件的示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
int main(int argc, char* argv[]) {
// 以只读模式打开 demo.txt 文件
int fd = open("demo.txt", O_RDONLY);
if (fd == -1) {
perror("open demo.txt error\n");
return EXIT_FAILURE;
}
// 打印获取到的文件描述符
printf("demo.txt fd = %d \n", fd);
return EXIT_SUCCESS;
}