epoll 系列阅读分工
| 篇目 | 路径 | 一句话 |
|---|---|---|
| 本篇(速览) | OS 58 | 结构全景 + LT/ET/惊群要点,作复习索引 |
| 内核主文 | 57|epoll 数据结构 | fs/eventpoll.c、回调、ovflist、ep_send_events |
| 网络工程 | 67|epoll 深度剖析 | ET/LT 编程范式、生产陷阱、timerfd 集成 |
| io_uring 对比 | epoll 与 io_uring 对比 | 就绪通知 vs 完成通知 |
推荐阅读路径:本篇 → 57 内核 → 67 工程 → io_uring 对比。
epoll 是 Linux 网络服务器的核心 I/O 多路复用机制。它为什么比 select/poll 快?内部怎么实现?下面用一图 + 要点速览;细节以算法 57 与网络 67 为准。
一、先看图
flowchart TD
subgraph eventpoll
RBTREE[红黑树<br/>管理所有 fd]
RDLLIST[就绪链表<br/>有事件的 fd]
WAIT[等待队列<br/>阻塞的 epoll_wait]
end
ADD[epoll_ctl ADD] --> RBTREE
MOD[epoll_ctl MOD] --> RBTREE
DEL[epoll_ctl DEL] --> RBTREE
RBTREE -->|回调触发| RDLLIST
RDLLIST --> EWAIT[epoll_wait<br/>返回就绪 fd]
EWAIT --> WAIT
classDef ep fill:#388bfd22,stroke:#388bfd,color:#adbac7;
classDef op fill:#3fb95022,stroke:#3fb950,color:#adbac7;
class RBTREE,RDLLIST,WAIT ep
class ADD,MOD,DEL,EWAIT op
二、核心结构
// fs/eventpoll.c
struct eventpoll {
struct rb_root_cached rbr; // 红黑树:所有注册的 fd
struct list_head rdllist; // 就绪链表
wait_queue_head_t wq; // epoll_wait 等待队列
// ...
};
struct epitem {
struct rb_node rbn; // 红黑树节点
struct list_head rdllink; // 就绪链表节点
struct epoll_filefd ffd; // fd + file*
struct epoll_event event; // 用户注册的事件
// ...
};三、epoll_ctl:注册 fd
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);- 分配
epitem - 插入红黑树(O(log n))
- 在目标 fd 的等待队列注册回调
ep_poll_callback
当 fd 有事件 → 回调把 epitem 加入就绪链表 → 唤醒 epoll_wait。
四、epoll_wait:收割事件
int n = epoll_wait(epfd, events, maxevents, timeout);- 检查就绪链表 → 非空则直接返回
- 空 → 睡在
ep->wq→ 等待回调唤醒 - 唤醒后遍历就绪链表 →
copy_to_user→ 返回
O(就绪 fd 数),不是 O(全部 fd 数)——这是 epoll 打败 select/poll 的关键。
五、LT vs ET
5.1 LT(Level-Triggered,默认)
fd 有数据可读 → 每次 epoll_wait 都返回。没读完 → 下次还返回。
5.2 ET(Edge-Triggered)
event.events = EPOLLIN | EPOLLET;只在状态变化时通知一次。必须一次读到 EAGAIN。
ET 优势:减少 epoll_wait 返回次数。 ET 陷阱:不读完 → 可能长期不再通知。编程范式见 67 篇。
5.3 内部实现
LT:就绪事件返回后,若 fd 仍有事件 →
重新加入就绪链表(ep_send_events 路径,见 57
篇 §4.5)。 ET:返回后 不
重新加入。
六、EPOLLEXCLUSIVE(4.5+)
event.events = EPOLLIN | EPOLLEXCLUSIVE;多线程/多进程 epoll_wait 同一个 listen socket → 惊群(thundering herd)。
EPOLLEXCLUSIVE:只唤醒一个等待者。Nginx 1.11.3+ 使用。
七、EPOLLONESHOT
event.events = EPOLLIN | EPOLLONESHOT;触发一次后自动禁用 → 需要 epoll_ctl(MOD)
重新启用。
保证每个 fd 同一时刻只被一个线程处理。
八、级联 epoll
epoll fd 本身也是 fd → 可以被另一个 epoll 监控 → 级联 epoll。
用途:分层事件处理(不常用,有复杂性)。
九、epoll vs io_uring
机制对比、syscall 模型与适用场景见专文《epoll 与 io_uring 对比》。
| 特性 | epoll | io_uring |
|---|---|---|
| 通知模型 | 就绪通知 | 完成通知 |
| syscall | epoll_wait + read/write | 可批量化 / 零 syscall |
| 适用 | 网络(成熟) | 文件 + 网络 |
| 复杂度 | 低 | 高 |
| 生态 | 成熟 | 快速成长 |
大多数网络场景 → epoll 足够。文件 I/O + 极致性能 → 评估 io_uring。
十、开放问题
- 纯 TCP 服务何时值得默认 io_uring 而非 epoll——公开结论分裂,见对比文。
EPOLL_BUSY_LOOP与延迟/CPU 占用的 trade-off 仍缺统一工程指南。- 级联 epoll 在 modern 框架中几乎绝迹,是否被
eventfd+ 单 epoll 完全取代——无共识文档。
十一、小结
- epoll = 红黑树(管理 fd)+ 就绪链表(O(1) 事件返回)+ 回调机制
- LT 安全但可能多次返回;ET 高效但必须读完
- EPOLLEXCLUSIVE 解决惊群
- epoll 仍是网络服务器的主力;io_uring 是补充不是全面替代
参考资料
规范 / 手册
man 7 epoll、man 2 epoll_ctl、man 2 epoll_wait
源码
- Linux
fs/eventpoll.c
论文 / 报告
- Davide Libenzi, epoll — scalable I/O event notification mechanism, 2002/2003
站内
工具
strace -e epoll_ctl,epoll_waitss -tlnp(查看 listen socket)bpftrace -e 'kprobe:ep_poll { @[comm] = count(); }'
上一篇:io_uring 内核内部 下一篇:select/poll
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【操作系统百科】Windows 内核与 Linux 的关键差异
NT Executive/Kernel/HAL 分层、Object Manager 与 handle、IRP 驱动栈、IOCP 完成端口 vs epoll/io_uring、EPROCESS/KTHREAD vs task_struct、Registry vs 文件配置,以及 WSL2 为何选择 Hyper-V 轻量 VM。
【操作系统百科】异步 I/O 模型 benchmark
epoll、io_uring、libaio、阻塞线程池——四种异步模型的真实性能对比。本文用统一 workload 量化 echo server、静态文件服务、数据库 I/O 场景下的吞吐、延迟与 CPU 开销。
【操作系统百科】NUMA 内存
多路服务器里内存不再平等——本地访问与跨 socket 远端访问的延迟与带宽不同。本文讲 NUMA 拓扑、mempolicy、AutoNUMA、NUMA balancing、CXL 分级内存,以及如何用 numactl/lscpu/numastat 核对拓扑并做生产调优;不编造本机未测得的纳秒延迟。
【操作系统百科】cgroup v2:资源控制的统一模型
cgroup v2 把 v1 的多 hierarchy 统一成单一树。本文讲 unified hierarchy、controller 清单、cpu.weight/io.weight/memory.max、PSI 压力指标、systemd slice/scope/service 层级、cgroup delegation 与 rootless、以及 cgroup v2 的诊断姿势。