简介
文件描述符和打开的文件之间似乎呈现出一一对应的关系。然而,实际并非如此。多个文件描述符指向同一打开文件,这既有可能,也属必要。这些文件描述符可在相同或不同的进程中打开。
要理解具体情况如何,需要查看由内核维护的 3 个数据结构。
- 进程级的文件描述符表。
- 系统级的打开文件表。
- 文件系统的 i-node 表。
1.进程级的文件描述符表。
针对每个进程,内核为其维护打开文件的描述符表。该表的每一条目都记录了单个文件描述符的相关信息,如下所示。
- 控制文件描述符操作的一组标志。
- 对打开文件句柄的引用。
2.系统级的打开文件表。
内核对所有打开的文件维护有一个系统级的描述表格。有时,也称之为打开文件表,并将表中各条目称为打开文件句柄。一个打开文件句柄存储了与一个打开文件相关的全部信息,如下所示。
- 当前文件偏移量(调用 read()和 write()时更新,或使用 lseek()直接修改)。
- 打开文件时所使用的状态标志(即,open()的 flags 参数)。
- 文件访问模式(如调用 open()时所设置的只读模式、只写模式或读写模式)。
- 与信号驱动 I/O 相关的设置
- 对该文件 i-node 对象的引用。
3.文件系统的 i-node 表。
每个文件系统都会为驻留其上的所有文件建立一个 i-node 表。第 14 章将详细讨论 i-node 结构和文件系统的总体结构。这里只是列出每个文件的 i-node 信息,具体如下。
- 文件类型(例如,常规文件、套接字或 FIFO)和访问权限。
- 一个指针,指向该文件所持有的锁的列表。
- 文件的各种属性,包括文件大小以及与不同类型操作相关的时间戳。
此处将忽略 i-node 在磁盘和内存中的表示差异。磁盘上的 i-node 记录了文件的固有属性,诸如:文件类型、访问权限和时间戳。访问一个文件时,会在内存中为 i-node 创建一个副本,其中记录了引用该 i-node 的打开文件句柄数量以及该 i-node 所在设备的主、从设备号,还包括一些打开文件时与文件相关的临时属性,例如:文件锁。
图文细嗦
上图展示了文件描述符、打开的文件句柄以及 i-node 之间的关系。在下图中,两个进程拥有诸多打开的文件描述符。
在进程 A 中,文件描述符 1 和 20 都指向同一个打开的文件句柄(标号为 23)。这可能是通过调用 dup()、dup2()或 fcntl()而形成的
进程A的文件描述符2和进程B的文件描述符2都指向同一个打开的文件句柄(标号为73)。这种情形可能在调用 fork()后出现(即,进程 A 与进程 B 之间是父子关系),或者当某进程通过UNIX 域套接字将一个打开的文件描述符传递给另一进程时,也会发生
此外,进程 A 的描述符 0 和进程 B 的描述符 3 分别指向不同的打开文件句柄,但这些句柄均指向 i-node 表中的相同条目(1976),换言之,指向同一文件。发生这种情况是因为每个进程各自对同一文件发起了 open()调用。同一个进程两次打开同一文件,也会发生类似情况。
总结
上述讨论揭示出如下要点。
- 两个不同的文件描述符,若指向同一打开文件句柄,将共享同一文件偏移量。因此,如果通过其中一个文件描述符来修改文件偏移量(由调用 read()、write()或 lseek() 所致),那么从另一文件描述符中也会观察到这一变化。无论这两个文件描述符分属于不同进程,还是同属于一个进程,情况都是如此。
- 要获取和修改打开的文件标志(例如,O_APPEND、O_NONBLOCK 和 O_ASYNC),可执行 fcntl()的 F_GETFL 和 F_SETFL 操作,其对作用域的约束与上一条颇为类似。
- 相形之下,文件描述符标志(亦即,close-on-exec 标志)为进程和文件描述符所私有。对这一标志的修改将不会影响同一进程或不同进程中的其他文件描述符。