共享文件

内核使用三个相关的数据结构来表示打开的文件:

  1. 描述符表(descriptor table): 每个进程都有它独立的描述符表,它的表项是由进程打开的文件描述符来索引的。每个打开的描述符表项指向文件表中的一个表项。
  2. 文件表(file table): 打开的文件的集合是由一张文件表来表示,所有的进程都共享这张表。每个文件表的表项的组成包括有当前的文件位置、引用计数(reference count)(即当前指向该表项的描述符表项的数),以及一个指向v-node表中对应表项的指针。关闭一个描述符会减少相应的文件表表项中的应用计数。内核不会删除这个文件表表项,直到它的引用计数为零。
  3. v-node表(v-node table): 同文件表一样,所有的进程共享这张v-node表。每个表项包含stat结构中的大多数信息,包括st_mode和st_size成员。

多个文件描述符可以通过不同文件表表项来引用同一个文件,例如,对一个filename调用open函数两次,就会产生两个文件描述符,指向两个不同的文件表表项,但两个文件表表项指向同一个v-node表表项,从而共享文件。关键思想是每个描述符指向不同的文件表表项,每个文件表表项都有它自己的文件位置,所以对不同的描述符的读写操作可以从文件的不同位置获取数据。

对于调用fork之后的父子进程共享文件,本质是子进程有一个父进程描述符表的副本。父子进程共享相同的打开文件表集合,因此共享相同的文件位置。一个很重要的结果就是,在内核删除相应的文件表表项之前,父子进程必须都关闭了它们的描述符。

I/O重定向

dup2(duplicate to)函数拷贝描述符表项oldfd到文件描述符表项newfd,覆盖描述表表项newfd以前的内容。如果newfd已经打开了,dup2会在拷贝oldfd之前关闭newfd。newfd指向的文件表项的引用计数会减一,oldfd指向的文件表项的引用计数增一.

#include <unistd.h>

// 若成功返回非负的描述符,出错返回-1
// newfd > oldfd
int dup2(int oldfd, int newfd);

通过拷贝oldfd到newfd,使得newfd指向的文件表项与oldfd的文件表项相同,从而现在对newfd的读写操作等同于作用于oldfd,完成文件重定向。