本页目录
epoll 与事件驱动
覆盖: epoll_create1/epoll_ctl/epoll_wait → LT vs ET → timerfd/signalfd/eventfd → 与 select/poll 对比 → epoll 内部实现要点 适用: Linux 2.6+
概述
epoll 是 Linux 上高性能事件驱动编程的标准接口。nginx、HAProxy、Redis 都基于 epoll 构建。与 select/poll 的根本区别在于:epoll 是 O(1) 等待(只返回活跃 fd),而 select/poll 每次都要 O(n) 扫描整个 fd 集合。
epoll API
int epfd = ; // 创建 epoll 实例
struct epoll_event ev = ;
; // 注册 fd
struct epoll_event events;
int nfds = ;
for
epoll_event.events
EPOLLIN: 可读 (数据到达或连接到达)
EPOLLOUT: 可写 (发送缓冲区有空间)
EPOLLERR: 错误 (自动, 不需要设)
EPOLLHUP: 挂断 (对端关闭)
EPOLLRDHUP: 对端关闭写半连接 (4.17+)
EPOLLET: Edge-Triggered (见下)
EPOLLONESHOT: 一次性 (触发后自动移除,直到重新 EPOLL_CTL_MOD)
EPOLLEXCLUSIVE: 排他唤醒 (减少惊群, 4.5+)
LT vs ET: 关键区别
LT (Level-Triggered, 默认):
只要 fd 还处于可读/可写状态 → 每次 epoll_wait 都返回
→ 简单: 可以只读部分数据, 剩余下次再读
→ 风险: 如果没读完, 下次 epoll_wait 还会返回 → 循环
ET (Edge-Triggered):
只在状态变迁时通知 (不可读→可读, 不可写→可写)
→ 高效: 不会重复通知
→ 要求: 必须读/写到 EAGAIN (全部处理完)
→ 必须用 O_NONBLOCK (否则 read 可能阻塞)
ET 模式正确写法
// ET 模式下必须循环读:
while
timerfd / signalfd / eventfd: epoll 伙伴
// 统一事件循环: 把所有异步事件都变成 fd → epoll 统一管理
int epfd = ;
// 1. 网络 socket
;
// 2. timerfd: 定时器
int tfd = ;
;
// 3. signalfd: 信号
sigset_t mask; ; ;
;
int sfd = ;
;
// 4. eventfd: 线程间通知
int efd = ;
;
// 单一线程: epoll_wait → 统一处理所有事件
while
与 select/poll 对比
| select | poll | epoll | |
|---|---|---|---|
| fd 数量 | FD_SETSIZE (1024) | 无上限 | 无上限 |
| 等待 | O(n) scan | O(n) scan | O(1) 活跃数 |
| 状态存储 | 每次传入 (栈) | 每次传入 (栈) | 内核维护 (RB-tree) |
| 边缘触发 | 无 | 无 | 有 (ET) |
| 1万 idle conn | ~100μs | ~100μs | ~1μs |
epoll 内部实现
// fs/eventpoll.c
// epoll 实例包含:
// 1. 红黑树 (rbr): 注册的 fd 列表 (epoll_ctl ADD → 插入)
// 2. 就绪链表 (rdllist): 活跃的 fd (epoll_wait 返回的)
//
// 事件到达 → 驱动/协议栈调 ep_poll_callback:
// → 检查事件匹配? → 加入 rdllist
// → 如果有等待者 (epoll_wait) → 唤醒
参考
- man: epoll(7), epoll_create1(2), epoll_ctl(2), epoll_wait(2)
- 源码:
fs/eventpoll.c - LWN: "The epoll API", "Edge triggered epoll"
关键词: epoll, EPOLLET, EPOLLONESHOT, edge-triggered, timerfd, signalfd, eventfd, event loop