poll 是对 select 的改进版 I/O 多路复用 机制。它通过结构体数组解决了文件描述符的数量限制问题。

核心逻辑

poll 不再使用位图,而是维护一个 pollfd 结构体数组。

  1. 结构化监控:每个 FD 对应一个 pollfd 结构,包含 fd(文件描述符)、events(期望事件)和 revents(实际发生事件)。
  2. 配置复用:由于内核只修改 revents 字段,用户定义的监控配置(events)不会被破坏,无需像 select 那样重复初始化。
  3. 突破限制:基于链表/数组实现,不再受 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 越多效率越低。