引言
在网络编程领域,C10K 问题催生了多种 I/O 多路复用方案。从传统的 select/poll 到 Linux 2.6 引入的 epoll,再到 Linux 5.1 横空出世的 io_uring,内核事件通知机制经历了三次范式跃迁。本文将从源码级别深入剖析 epoll 的设计哲学,并将其置于 Reactor 模式、io_uring 演进的宏观框架中审视,帮助读者构建完整的知识体系。
一、从 select 到 epoll:O(n) 到 O(1) 的范式跃迁
1.1 select 的三大缺陷
select 诞生于 1983 年的 BSD 4.2,其接口设计存在根本性局限:
- fd 数量受限:fd_set 使用位图实现,默认上限 FD_SETSIZE=1024,无法扩展
- 线性扫描开销:内核必须遍历全部 fd_set 才能确定就绪集合,复杂度 O(n)
- 不可重用性:每次调用后 fd_set 被修改,必须重置后才能再次使用
1.2 poll 的改进与局限
poll 用 pollfd 数组替代位图,解决了 fd 数量限制,但仍然需要线性扫描整个数组,O(n) 的复杂度并未改善。
1.3 epoll 的核心创新
epoll(Linux 2.6 )引入了三个核心概念:
- epoll instance(红黑树):内核维护一个红黑树管理所有被监听的 fd,插入/删除复杂度 O(log n)
- 就绪队列(rdllist):就绪的 fd 自动加入双向链表,epoll_wait 只需检查队列是否非空
- 回调机制:每个 fd 注册 ep_poll_callback,当 fd 就绪时回调自动将自身加入就绪队列
二、epoll 的三个系统调用
2.1 epoll_create/epoll_create1
创建 epoll 实例,内核返回一个文件描述符。关键数据结构是 eventpoll:
struct eventpoll {
struct mutex mtx; // 保护并发访问
wait_queue_head_t wq; // epoll_wait 的等待队列
struct list_head rdllist; // 就绪双向链表
struct rb_root rbr; // 红黑树根节点
struct epitem *ovflist; // 溢出链表(ET模式)
};
2.2 epoll_ctl
对红黑树进行添加(EPOLL_CTL_ADD)、修改(EPOLL_CTL_MOD)、删除(EPOLL_CTL_DEL)操作。核心流程:
- 查红黑树避免重复添加(O(log n))
- 获取底层文件的 poll 权,调用
vfs_poll()获取当前就绪状态 - 创建 epitem 并插入红黑树
- 注册回调
ep_poll_callback到文件的等待队列
2.3 epoll_wait
最简单的路径:检查 rdllist 是否有就绪事件,若无则阻塞等待。核心流程:
epoll_wait(epfd, events, maxevents, timeout):
1. 检查 rdllist 是否非空 → 直接返回
2. 非空:ep_send_events() → copy_to_user
3. 空:init_waitqueue_entry(

发表评论 取消回复