Linux 高级 IO:I/O 多路转接之 epoll 接口与原理详解
一、前置知识
- 要讲解 epoll,离不开 poll。虽然 poll 解决了 select 的两个硬伤(fd 上限、重置问题),但效率仍有瓶颈。
- poll 需要用户维护关心 fd 的数组,进行大量遍历。例如在分派器判断就绪 fd 时需要遍历数组,在内核检测 fd 就绪也需要遍历文件描述符表。
- 若关心的 fd 数量巨大(如 1 亿个),多次遍历将严重影响效率。
- 因此使用 epoll 优化 poll 的效率问题。
二、epoll 接口
epoll 有三个接口:epoll_create、epoll_wait、epoll_ctl。
epoll_create
用于创建 epoll 模型,返回对应的文件描述符 epfd。参数 size 已被忽略,可传入任意 int 值。返回值 >= 0 表示成功,否则为 -1。 Linux 下一切皆文件,epoll 模型与文件打开对象关联,进而与进程的文件描述符表关联。epfd 即为该下标。
epoll_wait
一次等待多个文件描述符。epoll_event 结构体包含 events(位图标志)和 data(用户自定义数据)。 第三个参数 maxevents 表示本次调用处理的最大事件就绪 fd 个数。 第四个参数 timeout 为超时时间(毫秒)。0 为非阻塞,负数为阻塞。 返回值为就绪事件的个数。
常用事件标志:
- EPOLLIN:可读或对端关闭连接。
- EPOLLOUT:可写。
- EPOLLPRI:有紧急数据可读。
- EPOLLERR:发生错误。
- EPOLLHUP:被挂断。
- EPOLLET:边缘触发模式(默认 LT 为水平触发)。
- EPOLLONESHOT:只监听一次。
epoll_ctl
用于向 epoll 模型中添加、修改、删除文件描述符及对应事件。 参数 op 包括:EPOLL_CTL_ADD、EPOLL_CTL_MOD、EPOLL_CTL_DEL。 成功返回 0,失败返回 -1 并设置错误码。
三、原理讲解
- 红黑树机制:内核建立红黑树维护和管理 fd,无需用户层建立第三方数组。
- 就绪队列机制:检测到 fd 事件就绪后,将节点连接到双链表形式的就绪队列中。
- 网卡驱动回调机制:网卡检测到数据产生硬件中断,驱动调用回调函数读取数据至 TCP 接收队列。
- 操作系统根据 fd 查找红黑树中是否关心读事件,若已就绪则链接到就绪队列。上层只需从就绪队列获取即可,无需遍历。
内核层面创建 struct eventpoll 结构体描述 epoll 模型,包含红黑树根节点 rbr 和就绪队列 rdlist。该结构体与文件对象 struct file 关联,通过 epfd 访问。
struct eventpoll {
/* 红黑树的根节点,这颗树中存储着所有添加到 epoll 中的需要监控的事件 */
struct rb_root rbr;
/* 双链表中则存放着将要通过 epoll_wait 返回给用户的满足条件的事件 */
struct list_head rdlist;
};
四、epoll 的优点
- 获取就绪 fd 的时间复杂度为 O(N),其中 N 不超过 maxevents。
- fd 和 event 没有上限,只需向红黑树添加。
- 检测是否有 fd 事件就绪的时间复杂度为 O(1),只需判断就绪队列是否为空。


