描述符表(descriptor table)
描述符 (file descriptor) 代表进程中打开的文件, 通常由 open() 函数返回。 另外 shell 中启动的进程默认会打开 3 个描述符, 分别是:
- 标准输入, 对应 STDIN_FILENO (值为 0)
- 标准输出, 对应 STDOUT_FILENO (值为 1)
- 标准错误, 对应 STDERR_FILENO (值为 2)
每个进程都有自己的描述符表,用以记录进程中所有打开的文件。一个描述符对应于一个打开的文件。
当调用 close() 函数时,系统会回收对应的描述符。
文件表(file table)
所有进程共享一个文件表。
文件表记录了被打开文件的一些状态信息, 包括 文件位置、引用计数以及指向v-node表的指针等
- 所有打开的文件都有一个文件位置,表示下一次读或写的起始位置。 seek, read, write 函数都可以修改该文件位置。
- 所有进程共享同一个文件表。文件表中的一个表项可能会被多个描述符指向,引用计数用来表示当前有多少个描述符指向该文件表表项。当调用 close() 函数时,系统会将描述符对应的文件表表项的引用计数减1,然后如果引用计数已经为0,则系统会删除此文件表表项。
当 shell 启动一个进程时,打开了 3 个描述符,每个描述符都指向了一个文件表表项。如下图:

当程序调用 fork() 函数时, 则会出现(不同进程的)多个描述符对应于同一个文件表表项的情况:
void main() {
int fd = open(name, O_RDONLY, 0);
fork();
// ...
}
在 fork() 调用返回之后, 文件表和描述符表的关系如下:

这里再介绍一个 dup2() 函数:
int dup2(int oldfd, int newfd);
dup2 函数用 oldfd 的内容替换掉 newfd 的内容。如果 newfd 是打开的,则会再替换之前先关闭 newfd。(思考一下,系统的重定向可能就是用这个函数实现的呢)
当程序调用 dup2 时,也会出现(同一个进程中的)多个描述符指向同一个文件表表项的情况:
void main() {
int fd = open(name, O_RDONLY, 0);
dup2(fd, STDOUT_FILENO);
}
// ...
在 dup2() 调用返回之后, 文件表和描述符表的关系如下:

v-node 表(v-node table)
所有进程共享同一个 v-node 表。
一个 v-node 表的表项对应于一个文件。它记录了文件的元数据信息。包括文件权限,文件大小, 文件类型等信息。
v-node 中的一个表项可能被多个文件表表项指向。
当对同一个文件调用多次 open() 函数,那么会产生多个文件表表项,但是只有一个 v-node 表表项。 如下面的程序:
int fd1 = open("hello.txt", O_RDONLY, 0);
int fd2 = open("hello.txt", O_RDONLY, 0);
它只有一个 v-node 表项,但却有两个描述符表表项和两个文件表表项:

参考书籍: 《深入理解计算机系统》