poll 是对 select 的改进版 I/O 多路复用 机制。它通过结构体数组解决了文件描述符的数量限制问题。
核心逻辑
poll 不再使用位图,而是维护一个 pollfd 结构体数组。
- 结构化监控:每个 FD 对应一个
pollfd结构,包含fd(文件描述符)、events(期望事件)和revents(实际发生事件)。 - 配置复用:由于内核只修改
revents字段,用户定义的监控配置(events)不会被破坏,无需像select那样重复初始化。 - 突破限制:基于链表/数组实现,不再受 1024 数量限制,仅受系统内存约束。
sequenceDiagram participant U as 用户进程 participant K as 内核 (Kernel) U->>U: 创建 pollfd 数组 U->>K: poll(fds, nfds, timeout) Note over K: 拷贝数组到内核 Note over K: 线性扫描所有 pollfd K-->>K: 发现数据就绪 Note over K: 仅修改对应 fds[i].revents K->>U: 返回就绪数量 Note over U: O(N) 遍历数组检查 revents
struct pollfd fds[2];
fds[0].fd = fd1;
fds[0].events = POLLIN; // 关注读事件
// 阻塞等待
int ret = poll(fds, 2, -1);
if (fds[0].revents & POLLIN) {
// 数据就绪,进行处理
}致命缺陷
尽管解决了数量限制,但 poll 依然存在性能瓶颈:
- 全量拷贝:每次调用仍需将整个结构体数组在用户态与内核态之间来回拷贝。
- 线性遍历:内核和用户态仍然需要 遍历整个数组来确认就绪状态,FD 越多效率越低。