文章目录
- [1 文件描述符(File Descriptor)](#1 文件描述符(File Descriptor))
-
- [1.1 什么是文件描述符?](#1.1 什么是文件描述符?)
- [1.2 文件描述符与文件的关系](#1.2 文件描述符与文件的关系)
- [2 文件描述符集合(File Descriptor Set)](#2 文件描述符集合(File Descriptor Set))
-
- [2.1 什么是文件描述符集合?](#2.1 什么是文件描述符集合?)
- [2.2 `fd_set` 结构体](#2.2
fd_set
结构体)
- [3 `select()` 函数的工作原理](#3
select()
函数的工作原理) -
- [3.1 `select()` 函数概述](#3.1
select()
函数概述) - [3.2 `select()` 的工作步骤](#3.2
select()
的工作步骤)
- [3.1 `select()` 函数概述](#3.1
- [4 `poll()` 函数的工作原理](#4
poll()
函数的工作原理) -
- [4.1 `poll()` 函数概述](#4.1
poll()
函数概述) - [4.2 `pollfd` 结构体](#4.2
pollfd
结构体) - [4.3 `poll()` 的工作步骤](#4.3
poll()
的工作步骤)
- [4.1 `poll()` 函数概述](#4.1
- [5 `select()` 和 `poll()` 的底层比较](#5
select()
和poll()
的底层比较) - [6 总结](#6 总结)
- 参考链接
- 封面
本文将详细解释文件描述符、文件描述符集合,以及 select()
和 poll()
的底层工作原理,以帮助理解 Linux 系统的 I/O 多路复用机制。
1 文件描述符(File Descriptor)
1.1 什么是文件描述符?
在 Unix 和 Linux 系统中,文件描述符(File Descriptor, FD) 是一个非负整数,用于表示已打开的文件、网络套接字、管道等 I/O 资源。每个进程都有一个文件描述符表,这个表记录了进程当前打开的所有文件。
1.2 文件描述符与文件的关系
当一个进程打开一个文件(或其他 I/O 资源),内核会为这个文件分配一个文件描述符,并将其返回给进程。这个文件描述符可以用来标识和访问该文件。例如:
c
int fd = open("example.txt", O_RDWR);
在这段代码中,open()
函数返回的 fd
就是文件描述符,它指向已打开的 example.txt
文件。之后,进程可以使用 fd
来读取或写入文件。
2 文件描述符集合(File Descriptor Set)
2.1 什么是文件描述符集合?
文件描述符集合 是一组文件描述符的集合,通常用于 I/O 多路复用函数 select()
中。它们用来表示一组文件描述符的状态(如可读、可写或有错误)。在 Linux 中,文件描述符集合通常由 fd_set
结构体表示。
2.2 fd_set
结构体
fd_set
是一个位图,每个比特位对应一个文件描述符。如果集合中包含某个文件描述符,则该比特位被设置为 1,否则为 0。以下是常用的操作:
- FD_ZERO:清空集合(将所有比特位设置为 0)。
- FD_SET:将一个文件描述符加入集合(将对应比特位设置为 1)。
- FD_CLR:从集合中移除一个文件描述符(将对应比特位设置为 0)。
- FD_ISSET:检查某个文件描述符是否在集合中(检查对应比特位是否为 1)。
3 select()
函数的工作原理
3.1 select()
函数概述
select()
是一种 I/O 多路复用技术,用于同时监视多个文件描述符的状态(如可读、可写或有错误),并在其中一个或多个文件描述符的状态发生变化时返回。select()
的函数签名如下:
c
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
3.2 select()
的工作步骤
-
初始化文件描述符集合:
- 使用
FD_ZERO
清空集合,然后用FD_SET
将感兴趣的文件描述符加入集合。
- 使用
-
调用
select()
:- 内核将挂起进程,并同时监控
readfds
、writefds
和exceptfds
中的文件描述符,直到其中一个或多个文件描述符的状态发生变化,或者超时。
- 内核将挂起进程,并同时监控
-
内核检查文件描述符状态:
- 内核会遍历所有文件描述符,检查它们的状态。如果一个文件描述符变得可读、可写或发生异常,内核会设置相应集合中的比特位。
-
select()
返回并处理结果:select()
返回文件描述符集合的状态。应用程序可以使用FD_ISSET
检查哪些文件描述符已经准备好进行 I/O 操作。
4 poll()
函数的工作原理
4.1 poll()
函数概述
poll()
是另一种 I/O 多路复用技术,与 select()
类似,但它使用不同的方式来表示文件描述符集合。poll()
的函数签名如下:
c
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
4.2 pollfd
结构体
pollfd
结构体包含了一个文件描述符及其感兴趣的事件和返回的状态:
c
struct pollfd {
int fd; /* 监视的文件描述符 */
short events; /* 感兴趣的事件 */
short revents; /* 返回的事件 */
};
4.3 poll()
的工作步骤
-
初始化
pollfd
结构体数组:- 填充
pollfd
结构体数组,指定每个文件描述符和感兴趣的事件。
- 填充
-
调用
poll()
:- 内核将挂起进程,并监视
fds
数组中所有文件描述符的状态,直到某个文件描述符的状态发生变化,或者超时。
- 内核将挂起进程,并监视
-
内核检查文件描述符状态:
- 内核逐个检查
fds
数组中的文件描述符,并更新revents
字段,表示实际发生的事件。
- 内核逐个检查
-
poll()
返回并处理结果:poll()
返回后,应用程序检查revents
字段,确定哪些文件描述符已经准备好进行 I/O 操作。
5 select()
和 poll()
的底层比较
-
效率:
select()
使用位图来表示文件描述符集合,这意味着它最多只能监视FD_SETSIZE
(通常是 1024)个文件描述符。同时,由于每次调用select()
都需要重新设置位图集合,因此在处理大量文件描述符时效率较低。poll()
使用数组来表示文件描述符集合,理论上可以监视任意数量的文件描述符。而且poll()
不需要每次重置整个数组,只需更新感兴趣的事件即可,因此效率更高。
-
灵活性:
poll()
提供了对更多事件类型的支持(如POLLPRI
,表示高优先级数据),而select()
只支持基本的可读、可写和异常事件。
-
扩展性:
select()
的文件描述符数量受限于FD_SETSIZE
,而poll()
没有这个限制,因此在需要处理大量文件描述符的场景,poll()
更具扩展性。
6 总结
- 文件描述符 是进程与文件或其他 I/O 资源交互的句柄,每个进程都有自己的一组文件描述符。
- 文件描述符集合 是多个文件描述符的集合,通常用于
select()
函数中,以同时监视多个文件描述符的状态。 select()
通过使用位图集合来监视多个文件描述符的状态,适合处理小数量的文件描述符。poll()
通过使用数组来监视多个文件描述符的状态,适合处理大量文件描述符,且效率更高。
无论是 select()
还是 poll()
,它们的核心作用都是提供一种机制来同时监视多个文件描述符的状态,并在这些文件描述符的状态发生变化时通知应用程序,从而实现高效的 I/O 多路复用。
参考链接
封面
由 DALL-E-3 生成