土法炼钢兴趣小组的算法知识备份

【操作系统百科】epoll 内部

文章导航

分类入口
os
标签入口
#epoll#event-poll#lt-et#thundering-herd#ready-list

目录

epoll 是 Linux 网络服务器的核心 I/O 多路复用机制。它为什么比 select/poll 快?内部怎么实现的?

一、先看图

flowchart TD
    subgraph eventpoll
        RBTREE[红黑树<br/>管理所有 fd]
        RDLLIST[就绪链表<br/>有事件的 fd]
        WAIT[等待队列<br/>阻塞的 epoll_wait]
    end

    ADD[epoll_ctl ADD] --> RBTREE
    MOD[epoll_ctl MOD] --> RBTREE
    DEL[epoll_ctl DEL] --> RBTREE

    RBTREE -->|回调触发| RDLLIST
    RDLLIST --> EWAIT[epoll_wait<br/>返回就绪 fd]
    EWAIT --> WAIT

    classDef ep fill:#388bfd22,stroke:#388bfd,color:#adbac7;
    classDef op fill:#3fb95022,stroke:#3fb950,color:#adbac7;
    class RBTREE,RDLLIST,WAIT ep
    class ADD,MOD,DEL,EWAIT op

二、核心结构

// fs/eventpoll.c
struct eventpoll {
    struct rb_root_cached rbr;     // 红黑树:所有注册的 fd
    struct list_head rdllist;       // 就绪链表
    wait_queue_head_t wq;          // epoll_wait 等待队列
    // ...
};

struct epitem {
    struct rb_node rbn;             // 红黑树节点
    struct list_head rdllink;       // 就绪链表节点
    struct epoll_filefd ffd;        // fd + file*
    struct epoll_event event;       // 用户注册的事件
    // ...
};

三、epoll_ctl:注册 fd

epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
  1. 分配 epitem
  2. 插入红黑树(O(log n))
  3. 在目标 fd 的等待队列注册回调 ep_poll_callback

当 fd 有事件 → 回调把 epitem 加入就绪链表 → 唤醒 epoll_wait。

四、epoll_wait:收割事件

int n = epoll_wait(epfd, events, maxevents, timeout);
  1. 检查就绪链表 → 非空则直接返回
  2. 空 → 睡在 ep->wq → 等待回调唤醒
  3. 唤醒后遍历就绪链表 → copy_to_user → 返回

O(就绪 fd 数),不是 O(全部 fd 数)——这是 epoll 打败 select/poll 的关键。

五、LT vs ET

5.1 LT(Level-Triggered,默认)

fd 有数据可读 → 每次 epoll_wait 都返回。没读完 → 下次还返回。

5.2 ET(Edge-Triggered)

event.events = EPOLLIN | EPOLLET;

只在状态变化时通知一次。必须一次读到 EAGAIN。

ET 优势:减少 epoll_wait 返回次数。 ET 陷阱:不读完 → 永远不再通知。

5.3 内部实现

LT:就绪事件返回后,如果 fd 仍有事件 → 重新加入就绪链表。 ET:返回后 重新加入。

六、EPOLLEXCLUSIVE(4.5+)

event.events = EPOLLIN | EPOLLEXCLUSIVE;

多线程/多进程 epoll_wait 同一个 listen socket → 惊群(thundering herd)。

EPOLLEXCLUSIVE:只唤醒一个等待者。

Nginx 1.11.3+ 使用。

七、EPOLLONESHOT

event.events = EPOLLIN | EPOLLONESHOT;

触发一次后自动禁用 → 需要 epoll_ctl(MOD) 重新启用。

保证每个 fd 同一时刻只被一个线程处理。

八、级联 epoll

epoll fd 本身也是 fd → 可以被另一个 epoll 监控 → 级联 epoll

用途:分层事件处理(不常用,有复杂性)。

九、epoll vs io_uring

特性 epoll io_uring
通知模型 就绪通知 完成通知
syscall epoll_wait 可零 syscall
适用 网络 文件 + 网络
复杂度
生态 成熟 快速成长

大多数网络场景 → epoll 足够。文件 I/O + 极致性能 → io_uring。

十、小结


参考文献

工具


上一篇io_uring 内核内部 下一篇select/poll

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-05-23 · os

【操作系统百科】异步 I/O 模型 benchmark

epoll、io_uring、libaio、阻塞线程池——四种异步模型的真实性能对比。本文用统一 workload 量化 echo server、静态文件服务、数据库 I/O 场景下的吞吐、延迟与 CPU 开销。

2026-04-27 · os

【操作系统百科】内存回收

Linux 内存回收是 VM 最复杂的子系统之一。本文讲 active/inactive LRU、kswapd 与 direct reclaim、watermark 三线、swappiness 的真实含义、MGLRU 改造、memcg 回收与 PSI。

2026-04-28 · os

【操作系统百科】交换

swap 还值得开吗?本文讲 swap area 基础、swap cache、zram 压缩内存、zswap 前端压缩池、swappiness 的真实含义、容器里的 swap 策略,以及为什么现代 Android 全靠 zram 不靠磁盘。

2026-05-03 · os

【操作系统百科】Slab/SLUB 分配器

buddy 只管页粒度(4K+),内核大多数对象只有几十到几百字节。slab/SLUB 在 buddy 之上做对象级缓存。本文讲 slab 历史、SLUB 接手、SLOB 退场、kmem_cache、per-CPU cache、KASAN 集成。


By .