三个字节之后:就绪、通知与 deadline¶
管道里写入 ABC,epoll 报可读,程序只取走 A,随后再次等待。还有 BC 留在管道里,为什么 ET 模式下可能等不到下一次事件?从这个反例可以推到事件循环的公平性、通知合并和超时设计。
需要先理解fd 的引用与复用、非阻塞 IO 的进度。以下代码在源码包的 labs/mechanisms.c:epoll_lab(),运行方式见环境说明。
先走完一次 ET 时序¶
ET(edge-triggered,边沿触发)围绕状态变化报告事件。该实验注册非阻塞管道的读端,监听 EPOLLIN | EPOLLET:
| 顺序 | 操作 | 管道里剩余 | 实验结果 |
|---|---|---|---|
| 1 | 写入 ABC | ABC | 从空变为有数据 |
| 2 | epoll_wait |
ABC | 返回一次可读 |
| 3 | 读取 1 字节 | BC | 得到 A |
| 4 | 零超时 epoll_wait |
BC | 没有新的事件 |
| 5 | 再次 read | 空 | 得到 BC |
| 6 | 再次 read | 空 | 返回 EAGAIN |
第 4 步返回 0 不等于第 5 步没有数据。就绪信息说明尝试 IO 可以有进展,应用仍要实施 IO;上一次事件也不会自动替程序读完。Linux epoll 文档用类似的管道反例解释 ET,建议非阻塞处理直到 EAGAIN 后再等待新事件。epoll 手册
关键代码对照如下,完整实现还负责创建和关闭描述符:
assert(write(p[1], "ABC", 3) == 3);
assert(epoll_wait(ep, &ev, 1, 100) == 1);
assert(read(p[0], &c, 1) == 1 && c == 'A');
assert(epoll_wait(ep, &ev, 1, 0) == 0);
assert(read(p[0], b, sizeof(b)) == 2 && !memcmp(b, "BC", 2));
assert(read(p[0], b, sizeof(b)) == -1 && errno == EAGAIN);
这个受控实验观察到的时序,不能扩大为任意情况下 ET 都只报告一次;其他状态变化或并发活动仍可能产生事件。可靠设计以自己的未完成工作和 IO 结果为依据。
LT 的预算,与 ET 的 ready 集合¶
LT(level-triggered,电平触发)在就绪条件保持时可以再次报告。实验切为 EPOLLIN,写入 DEF,读走 D 后,下一次等待仍报告可读。
这使 LT 容易采用每轮处理预算:从热点源读取少量数据后让出机会,控制事件与定时事件仍能得到处理。labs/extra.c:fairness() 同时放入热点数据和 STOP,对热点最多处理四字节,并核对控制数据得到处理。
ET 也可以预算化,但预算耗尽且尚未到 EAGAIN 时,必须把源保存到自己的 ready 集合;下一轮先继续已有工作,不能只等内核的新边沿。ready 项还需要有效身份,旧 batch 的 fd 数字复用问题见fd 生命周期。
eventfd 保存计数,队列保存业务¶
本实验创建普通 eventfd,连续写两次 1,再读一次得到 2,之后非阻塞读取返回 EAGAIN。未启用 EFD_SEMAPHORE 时,读取取出当前计数并清零;它不携带两条请求的内容。eventfd 手册
SDK 的使用关系是:
不能用“一次唤醒处理一条请求”替代检查队列。多次唤醒可能合并,唤醒发生时队列也可能已被其他逻辑处理。若 owner 每轮限制处理数量,预算后还需安排继续处理;队列中仍有工作时直接睡眠,会把公平策略变成遗漏工作。
可写监听与时间到期分别管理¶
socket 经常处于可写状态,持续注册 EPOLLOUT 会使循环不断返回。platform/runtime/linux/runtime.c:watch_output() 仅在输出队列非空时启用它,发送到 EAGAIN 时保留队列,排空后移除监听。
定时等待则要围绕绝对 deadline:假设请求在单调时间 100 ms 提交、期限 50 ms,deadline 是 150 ms。110 ms 被其他事件唤醒后,仅剩 40 ms;每次醒来都重等完整 50 ms,会无限推迟完成义务。
本实验用 CLOCK_MONOTONIC 创建 timerfd,读取其到期计数;SDK 的 core 还用逻辑时间向量核对到期前、到期时和到期后的转换。真实毫秒调度受 OS 影响,不能拿一次近似 50 ms 的测量证明严格边界。signalfd 示例先屏蔽 SIGUSR1,再以 fd 读取该信号。
运行与预期¶
在容器内源码根目录:
make -C labs all
./build/labs/mechanisms epoll
./build/labs/mechanisms extra
make -C platform linux-test
机制输出中查找以下 PASS 名称:
L3-ET-partial-read-counterexample-drain
L3-LT-budget-HUP-EOF
L3-eventfd-notification-coalesces
L3-timerfd-monotonic
L3-signalfd-mask
L3-LT-hot-source-control-budget
L3-ready-batch-generation-fd-reuse
epoll 运行三字节、LT、eventfd、timerfd 与 signalfd;extra 包含热点公平性与旧 batch 身份,也会运行 stdio/权限补充实验。HUP 实验关闭写端后核对事件及 EOF,提醒事件循环把关闭状态与剩余数据分别处理。以上是受控输入的通过条件,并非吞吐或实时延迟保证。全部机制与构建回归为 make -C labs test。
从三字节反例最终得到的是 owner 的职责:保留未完成工作、按预算安排处理、让停止与时间检查得到机会,并在对应对象仍有效时执行 IO。并发与所有权继续把这些调度规则与资源寿命连接;停止案例解释停止时为什么必须处理剩余通知义务。
进一步分析控制时延¶
事件循环调度与背压将单源就绪问题扩展到多源调度,核算预算、同步回调与未完成工作的进展。