在 UNIX 和 UNIX-like 作业系统中,档案描述符 (File Descriptor, FD) 是一个用于标识档案或其他输入/输出资源(如管道或网路套接字)的过程唯一标识符。这些档案描述符的值通常为非负整数,负值用来指示“没有值”或错误条件。档案描述符是 POSIX API 的一部分,每个 UNIX 过程(除了一些守护进程)应有三个标准的 POSIX 档案描述符,对应三个标准流:标准输入、标准输出和标准错误输出。
在传统的 UNIX 实现中,档案描述符索引到由内核维护的每个过程的档案描述符表,该表又索引到所有进程打开的档案的系统范围表,即档案表。
档案表记录档案的打开模式,如读取、写入、附加等。它还索引进入第三个表即 inode 表,该表描述了实际的底层档案。在进行输入或输出时,进程会通过系统呼叫将档案描述符传递给内核,内核则会代表该过程访问档案。进程无法直接访问档案或 inode 表。
在 Linux 中,可以通过路径 /proc/PID/fd/ 访问进程中打开的档案描述符,这里的 PID 是进程标识符。
UNIX-like 系统中的档案描述符可以引用任何在档案系统中命名的 UNIX 档案类型,除了常规档案外,这还包括目录、区块和字符设备(也称为“特殊档案”)、UNIX 域套接字以及命名管道。档案描述符还可以引用其他在档案系统中不常存在的对象,例如匿名管道和网路套接字。在 C 标准 I/O 库中的 FILE 资料结构通常包括一个底层档案描述符,这为所指对象提供了额外的抽象,因而称之为档案句柄。
现代 UNIX-like 系统中对档案描述符的典型操作包括创建档案描述符、单个档案描述符的操作以及多个档案描述符的操作等等。大多数这些函数在 <unistd.h>
标头中声明,但有些则在 <fcntl.h>
标头中声明。
常见的创建档案描述符的方法包括 open()
、socket()
和 epoll_create()
等。如果您需要处理多个档案描述符,这些函数使您能够以高效的方式进行管理。
对单个档案描述符进行的操作包括 read()
、write()
、lseek()
等。这些操作提供了对档案的基本读写能力,从而使开发者能够进行正常的资料处理。
在需要同时监控和处理多个档案描述符的情况下,select()
、poll()
、epoll_wait()
和 kqueue()
等函数尤为重要。这些函数能让开发者在高效的方式下处理大量的连接请求,尤其在网路伺服器中显得尤为重要。
fcntl()
函数可以用于对档案描述符执行各种操作。其命令参数可用来获取和设置与档案描述符相关的属性,包括 F_GETFD
和 F_SETFD
等。
在处理大量档案描述符时,epoll 和 kqueue 是两种非常有效的系统调用。 epoll
是 Linux 专有的解决方案,而 kqueue
则出现在 BSD 系统中。这两者都能在庞大的档案描述符集合中高效地寻找可用的档案描述符,从而实现资料的读写。
epoll 的优势在于其支持事件驱动的 I/O 模式,您可以在应用程序中注册各种事件来监控不同的档案描述符。
利用 epoll_ctl()
,用户可以方便地添加、删除和修改其注册的档案描述符,这改进了处理大量同时连接的效率。另一方面,kqueue
则通过事件通知机制,使得它在需要精细控制响应时间的应用中非常有效。
随着应用需求的增加,系统设计者必须越来越多地考虑如何有效地管理档案描述符,进而影响应用性能。如何选择最适合自己应用的技术方案,已成为现代开发中的重要挑战,您是否能够在您的工作中应用这些技术来提升性能呢?