epoll 是 Linux 内核的 I/O 多路复用 机制,也是 Android Looper 底层实现的核心。它解决了传统 select/poll 的性能瓶颈,实现了 O(1) 的事件通知效率。

一、为什么需要 epoll?(select/poll 的问题)
| 机制 | 问题 |
|---|---|
| select | 1024 FD 上限,每次调用都要拷贝整个 FD 集合 到内核,内核遍历所有 FD 检查状态,再拷贝回用户态 |
| poll | 用链表突破了 1024 限制,但仍然是 O(n) 遍历 |
| epoll | O(1) 事件驱动,注册一次,内核主动通知 |
核心痛点:
- select/poll 每次调用都要扫描所有 FD(即使大部分没有事件)
- 连接数上万时(C10K 问题),CPU 大量消耗在遍历上
二、epoll 的核心数据结构(内核)
c
// Linux Kernel: fs/eventpoll.c
// (1) eventpoll - epoll 实例
struct eventpoll {
spinlock_t lock; // 自旋锁
struct mutex mtx; // 互斥锁
wait_queue_head_t wq; // epoll_wait() 的等待队列
struct list_head rdllist; // (2) 就绪链表 - 存储已就绪的 FD
struct rb_root_cached rbr; // (3) 红黑树 - 存储所有被监控的 FD
struct epitem *ovflist; // 溢出链表
};
// (4) epitem - 每个被监控的 FD 对应一个
struct epitem {
struct rb_node rbn; // 红黑树节点
struct list_head rdllink; // 就绪链表节点
struct eventpoll *ep; // 所属的 eventpoll
struct epoll_event event; // 事件掩码(EPOLLIN 等)
struct file *file; // 被监控的文件
};
双数据结构设计:
- 红黑树 (rbr):存储所有被监控的 FD,O(log n) 插入/删除/查找
- 就绪链表 (rdllist):存储已就绪的 FD,epoll_wait() 直接返回,O(1)
三、epoll 的三个核心 API
c
// 1. 创建 epoll 实例
int epfd = epoll_create1(EPOLL_CLOEXEC);
// 2. 注册/修改/删除 FD
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event); // 添加
epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &event); // 修改
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL); // 删除
// 3. 等待事件(阻塞/非阻塞)
int nfds = epoll_wait(epfd, events, maxevents, timeout);
四、核心机制:事件回调(The Magic)
epoll 不是轮询,而是事件驱动:
c
// fs/eventpoll.c - ep_poll_callback
// 当被监控的 FD 状态变化时,内核自动调用这个回调
static int ep_poll_callback(wait_queue_entry_t *wait, unsigned mode,
int sync, void *key) {
struct epitem *epi = ep_item_from_wait(wait);
struct eventpoll *ep = epi->ep;
spin_lock(&ep->lock);
// (1) 将 FD 加入就绪链表
if (!ep_is_linked(&epi->rdllink)) {
list_add_tail(&epi->rdllink, &ep->rdllist);
}
// (2) 唤醒在 epoll_wait() 中睡眠的进程
if (waitqueue_active(&ep->wq)) {
wake_up_locked(&ep->wq);
}
spin_unlock(&ep->lock);
return 1;
}
关键洞察:
- 当网络数据到达、pipe 有数据、eventfd 被写入时,内核的网络栈/文件系统会自动调用 ep_poll_callback
- 不需要用户态轮询,内核主动通知
五、epoll_wait() 的工作流程
c
// fs/eventpoll.c - ep_poll
static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events,
int maxevents, long timeout) {
// (1) 快速路径:检查就绪链表
if (!list_empty(&ep->rdllist)) {
goto send_events; // 已有就绪事件,直接返回!
}
// (2) 没有就绪事件,需要睡眠
for (;;) {
set_current_state(TASK_INTERRUPTIBLE); // 标记为可中断睡眠
// (3) 双检锁:再检查一次(避免竞态)
if (!list_empty(&ep->rdllist)) {
set_current_state(TASK_RUNNING);
break;
}
// (4) 真正的阻塞:让出 CPU
schedule_timeout(timeout); // 线程在这里睡眠!
}
send_events:
// (5) 将就绪事件拷贝到用户空间
return ep_send_events(ep, events, maxevents);
}
六、水平触发(LT)vs 边缘触发(ET)
| 模式 | 行为 | 特点 |
|---|---|---|
| LT(Level Triggered) | 只要 FD 就绪,epoll_wait 持续返回 |
默认模式,安全,可能重复通知 |
| ET(Edge Triggered) | 只在状态变化时通知一次 | 高效,必须一次性处理完所有数据 |
类比:
- LT 像快递员小王:你不下楼,他一直在楼下喊
- ET 像快递员小赵:喊一次你不下来,他就走了
七、与 Android Looper 的关联

Android 的 Looper 本质上是对 epoll 的封装:
- Looper 创建一个 epoll 实例
- 将 eventfd(或旧版的 pipe)注册到 epoll
- nativePollOnce() 调用 epoll_wait() 睡眠
- nativeWake() 写入 eventfd,触发 epoll_wait() 返回
八、总结
epoll 是 Linux 内核的高性能 I/O 多路复用机制。它通过「红黑树」管理所有被监控的文件描述符,通过「就绪链表」记录已就绪的事件,通过「事件回调」机制让内核主动通知(而非轮询)。epoll_wait() 在没有事件时让线程睡眠,有事件时立即唤醒,实现了 O(1) 的事件处理效率,与连接数无关。