摘要

  • 接收活锁不是机器停摆,而是接收中断耗尽 CPU,使协议处理、发送或应用无法完成;处理活动持续,实际交付可以降到零。
  • Mogul 与 Ramakrishnan 让中断只负责唤醒,以轮转轮询、回调配额、尽早丢包、下游队列反馈和 CPU 周期上限维持进展;没有配额的轮询同样失败。
  • 论文的数字来自单处理器、10 Mbit/s 以太网和一台较慢的 DECstation。每轮五至十个包只是该设备上的实验结果,不是今天的通用参数。

机器很忙,工作却没有抵达

中断给硬件事件一张插队券。流量稀疏时,它避免持续轮询,也降低响应时延。可一旦到包速度超过整条处理链的能力,插队就会变成垄断。

论文考察的 4.2BSD 式结构中,网卡中断先把包搬到 IP 输入队列,协议处理随后在较低优先级运行。新到的包可以再次打断清空队列的工作。队列满后,机器仍花费驱动和内存操作的成本,再把包丢掉;应用收不到,路由器的输出接口也发不走。

这不是死锁。输入降低后,系统会恢复。可在过载持续时,CPU 利用率与中断计数都可能很好看,最终消费者的吞吐却为零。论文因此把吞吐定义在终点:包必须到达应用,或由路由器的输出接口真正发送。

中断优先级是隐藏的调度器

普通调度器几乎没有参与决定。固定的中断优先级已经替它选择了接收工作。协议栈、发送完成、路由维护、管理进程与用户程序只能等待。

合并中断能摊薄开销,提高无损接收上限,但只能推迟崩溃点。更快地收进来,并不等于更快地送出去。

让中断唤醒轮询,而不是占有处理器

改造后的系统在轻载时保留中断。负载升高后,中断处理程序只标记某设备需要服务、唤醒轮询线程,并暂时保持该设备中断关闭。

轮询线程按轮转顺序访问接收与发送事件源,每个回调只能处理规定数量的包。积压清空后,中断才重新启用。一个包只要被接口接纳,内核就尽量把它继续推向协议或输出,而不是把它放进另一条等待低优先级代码处理的队列。

重点不是“中断坏、轮询好”。纯轮询在安静时浪费 CPU,也会增加时延;纯中断在饱和时可能吞掉公平性。混合方式让中断发现不可预测的第一批工作,让有边界的轮询处理已经连续化的负载。

无配额轮询为什么仍然失败

论文最有价值的结果之一,是改造方案自己的失败。取消每次回调的包数配额后,接收回调总能发现下一个包,于是永不返回。轮询循环无法轮到发送完成,输出描述符不能释放,输出队列被塞满,有效吞吐再次接近零。

轮询只是把垄断从中断处理程序搬到了回调。真正创造发送机会的是配额和顺序。测试中每轮五至十个包表现稳定、接近最优;更大批次虽然减少轮询开销,却增加时延与饥饿风险。作者明确说,不同 CPU 和接口可能需要不同数值。

在投入成本前丢弃,并听见下游

过载意味着不可能完成全部输入。若在网卡处丢包,系统几乎没有投入;若经过驱动、协议与多次排队才丢,稀缺周期已被浪费。尽早丢弃回答“何时放弃”,队列反馈则回答“入口何时必须停手”。

在 screend 实验中,下游队列达到 75% 时暂停输入,降到 25% 时恢复,并以约一毫秒定时器防止无限期关闭。论文承认这些阈值是任意选择。可以迁移的不是数字,而是证据回路:下游无法排空时,上游接纳不能继续冒充成功。

另一个机制以十毫秒为窗口统计网络处理消耗的 CPU 周期,达到份额后暂停输入,让用户进程和系统维护运行。机器对本地用户明显更灵敏,但远程交互仍受丢包影响。预留时间改善了公平性,没有创造新容量。

数字必须带着分母

被测路由器是运行 Digital UNIX V3.2 的 DECstation 3000/300,特意选择当时最慢的 Alpha。它连接两段空闲的 10 Mbit/s 以太网。每次试验发送 10,000 个 UDP 包,每包含四字节负载;发送并非严格定速,速率是数秒平均值。

原始内核配合用户态 screend 时,约 2,000 包/秒以上开始恶化,约 6,000 包/秒出现完全活锁;不用 screend 时,转发峰值约为 4,700 包/秒。报告明确反对把这些结果外推到更快的局域网与处理器。它也没有验证真正的 SMP 行为,更没有解决“应当保住哪一种包”。

今天的 Linux NAPI 文档仍可见相似形状:中断调度轮询,轮询期间屏蔽 IRQ,并以包数或时间预算限制一次工作。这个对照说明边界调度仍重要,却不能证明每一种现代过载都是接收活锁。

Google Research 的人物页记录了 Mogul 在 DEC/Compaq WRL、HP Labs 与 Google 网络基础设施的经历。接收活锁论文始终是他与 Ramakrishnan 的共同成果。它留下的最硬标准不是某个参数,而是一条记账规则:没有到达终点的忙碌,不能叫吞吐。

来源