epoll 是 Linux 内核为处理大批量文件描述符而作了改进的 I/O 多路复用 机制。它通过红黑树管理监控对象,通过事件回调通知就绪状态,彻底解决了 select/poll 在高并发下的 性能瓶颈。
一、 核心架构
epoll 的高效源于其在内核中维护的两个核心数据结构:
| 组件 | 数据结构 | 作用 | 优势 |
|---|---|---|---|
| 监控列表 | 红黑树 (RB-Tree) | 存储所有被监控的 FD | 增删改查复杂度 ,避免全量拷贝 |
| 就绪队列 | 双向链表 (Ready List) | 存放已触发事件的 FD | epoll_wait 只需 检查链表是否为空 |
二、 工作流程
epoll 采用“被动通知”机制,其核心流程如下:
sequenceDiagram participant U as 用户进程 participant K as 内核 (Kernel) participant RB as 红黑树 (RB-Tree) participant RL as 就绪链表 (Ready List) Note over U, RL: 1. 设置阶段 U->>K: epoll_create() K->>K: 创建 epoll 实例 (RB + RL) rect rgb(240, 240, 240) Note over K, RL: 异步触发过程 (从注册起持续运行) U->>K: epoll_ctl(ADD, fd, event) K->>RB: 插入节点 K-->>K: 注册回调函数 (Callback) 到设备驱动 Note right of K: 硬件中断 / 数据到达 K->>RL: 触发回调,将 FD 移入 RL Note over U, RL: 2. 等待与处理阶段 U->>K: epoll_wait() K-->>K: 检查 RL 是否为空 (若空则阻塞) K->>U: 返回就绪 FD 数量 U->>U: 仅遍历 RL 中的就绪 FD end
三、 触发模式对比
| 模式 | 核心逻辑 | 适用场景 | 编程复杂度 |
|---|---|---|---|
| LT (水平触发) | 只要缓冲区有数据,就持续通知 | 默认模式,容错性高 | 简单 |
| ET (边缘触发) | 仅在状态改变时通知一次 | 高性能服务器 (如 Nginx) | 高 (需配合非阻塞 I/O) |
四、 为什么 epoll 更快?
- 减少拷贝:
epoll_ctl每次只操作一个 FD,不需要像select每次调用都拷贝整个 FD 集合。 - 减少遍历:内核通过回调机制直接填充就绪链表,无需像
poll那样线性扫描所有 FD。 - 无上限限制:监控的 FD 数量仅受系统内存限制。