引言

在网络编程领域,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)操作。核心流程:

  1. 查红黑树避免重复添加(O(log n))
  2. 获取底层文件的 poll 权,调用 vfs_poll() 获取当前就绪状态
  3. 创建 epitem 并插入红黑树
  4. 注册回调 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(                        
                    
点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部