0/4 已展开

LLM 分析

sched/fair:6.18.y 的 EEVDF 饥饿回退请求与前置依赖争议

系列概况

  • 标题Please backport 101f3498b4bd to 6.18.y
  • 作者:Janne Huttunen(Nokia)发起;Sasha Levin 参与 stable 评审
  • 版本:没有正式的 v1/v2 标记;线程中提交了一份针对 6.18.y 的手工 backport
  • 规模:4 封邮件,包含 backport 请求、stable 维护者反馈、回移 diff 和安全性追问
  • 修改文件:给定 diff 中可见 kernel/sched/fair.c
  • 代码统计:diff 在输入中被截断,无法可靠统计完整增删行数
  • Message-IDPAWPR07MB100715EA5D8FCEB34B9987DF69AC02@PAWPR07MB10071.eurprd07.prod.outlook.com
  • 完整性:不完整。第 1、3 封邮件正文均在中途截断,而且输入没有包含 Peter Zijlstra 对最终安全性问题的回复

补丁目的

该线程请求把上游提交 101f3498b4bd 回移到 Linux 6.18.y。这个提交会撤销:

6d71a9c61604 sched/fair: Fix EEVDF entity placement bug causing scheduling lag

请求者在升级到 6.18.y 后观察到严重的调度饥饿:

  • 服务器存在专用、隔离且启用 nohz_full 的 CPU;
  • CPU 平时主要运行 system.slice 中一个 SCHED_OTHER、nice -10 的线程;
  • 偶尔会有一个位于 user.slice、只能在该 CPU 上运行的普通线程;
  • 该线程有时会连续数秒、数分钟乃至数小时得不到运行时间;
  • 即使发送 SIGKILL,任务也必须先获得 CPU 才能退出,因此会表现为迟迟无法被杀死。

请求者怀疑,6d71a9c61604 引入的 EEVDF 实体放置或 lag 处理会把 user.slice 对应的调度实体放到异常靠后的虚拟时间位置,从而造成长期不可运行。

这是一个 stable 分支调度正确性修复请求,但是否能安全地只回退一个提交仍有争议。

旧流程的问题

在该场景中,两个任务不只是以普通线程身份直接竞争;它们分别属于不同的 cgroup,因此 CFS/EEVDF 还需要先调度 system.sliceuser.slice 对应的组调度实体。

来信中的未完全证明理论可以概括为:

  1. system.slice 的实体长期拥有可运行负载;
  2. user.slice 偶尔进入竞争;
  3. user.slicevruntime 或 lag 在实体放置过程中被推到异常位置;
  4. 如果其 vruntime 远超平均虚拟运行时间,它可能呈现负 lag,暂时不满足 EEVDF 的 eligibility 条件;
  5. 这种状态在特定权重、分组和低干扰 CPU 环境中可能持续很久。
isolated CPU
     |
     v
root cfs_rq
     |
     +-- system.slice entity --> nice -10 task --> almost always runnable
     |
     +-- user.slice entity   --> pinned task   --> occasionally runnable
                                      |
                                      v
                              bad virtual placement?
                                      |
                                      v
                           no runtime for a long time
                                      |
                                      v
                           SIGKILL remains pending

这里的重点不是 SIGKILL 失效,而是被杀任务始终没有被调度执行,因而没有机会完成退出路径。

新流程

提议中的处理流程是:

  1. 在 6.18.y 上撤销 6d71a9c61604
  2. 恢复其修改前的 lag、权重变化和虚拟运行时间缩放逻辑;
  3. 使用针对 6.18.y 上下文适配过的 diff;
  4. 依靠请求者报告的数周生产测试验证饥饿是否消失。

但是 stable 维护者没有立即接受该方案,因为上游撤销 6d71a9c61604 之前,已经先合入:

4823725d9d1d sched/fair: Increase weight bits for avg_vruntime

按照 Sasha Levin 的说明,真正消除底层舍入误差的是 4823725d9d1d6d71a9c61604 只是绕过该误差。6.18.y 没有这项精度改造,因此单独撤销 workaround 可能让旧的舍入问题重新出现。

Upstream:
6d71a9c61604 workaround for rounding artifact
                 |
                 v
4823725d9d1d increase avg_vruntime weight precision
                 |
                 v
101f3498b4bd revert the earlier workaround

Linux 6.18.y:
6d71a9c61604 present
                 |
                 +-- 4823725d9d1d absent
                 |
                 v
standalone revert requested -- safety not yet confirmed

因此线程结束时的实际状态是:backport 已提供且做过生产测试,但暂缓进入 stable 队列,等待原作者确认。

Patch 概览

  1. Janne Huttunen:提出 backport 请求

    • 报告 6.18.y 上特定隔离 CPU 的长期调度饥饿;
    • 请求回移 101f3498b4bd
    • 给出与 cgroup、CPU affinity、nohz_fullvruntime 有关的初步理论。
  2. Sasha Levin:要求提供可应用的补丁

    • 上游提交无法直接干净地应用到 6.18.y;
    • 要求请求者发送经过测试的 backport。
  3. Janne Huttunen:发送手工 backport

    • 修改 kernel/sched/fair.c
    • 可见部分涉及 lag 计算和实体权重缩放;
    • 据后续邮件引用,和原始提交的主要差异是使用 div_s64 而不是 div64_long,外加少量上下文调整。
  4. Sasha Levin:暂缓合入

    • 肯定了数周生产测试;
    • 指出上游撤销提交依赖更高精度的 avg_vruntime 基础;
    • 请求 Peter 判断在缺少 4823725d9d1d 时,独立回退是否安全。

关键实现

给定 diff 的可见部分首先把 lag 计算拆成一个可以显式传入平均虚拟运行时间的辅助函数:

vlag = avruntime - se->vruntime;
return clamp(vlag, -limit, limit);

se->vlag = entity_lag(cfs_rq, se, avg_vruntime(cfs_rq));

其意义是:

  • avruntime 表示作为参照的平均虚拟运行时间;
  • se->vruntime 表示实体已经消耗的归一化虚拟时间;
  • 两者之差形成实体的 lag;
  • 正 lag 通常意味着实体落后、仍被欠服务;
  • 负 lag 通常意味着实体已经领先;
  • clamp 防止异常大的历史差值无限影响后续调度。

与原来只能在 update_entity_lag() 内部读取当前 avg_vruntime() 相比,独立的 entity_lag() 允许其他路径在确定的参考点上计算 lag。

diff 还恢复或加入了 rescale_entity()。可见注释表明,它用于处理实体在非零 lag 点改变权重时的虚拟运行时间调整。直观上,改变权重会改变“实际运行时间换算为虚拟时间”的比例;如果只替换权重而不调整虚拟位置,就可能凭空增加或抹去实体已经积累的服务债务。

不过输入在 rescale_entity() 的数学证明注释中截断,因此不能从给定材料确认:

  • 完整缩放公式;
  • 所有调用点;
  • place_entity() 的最终变化;
  • backport 的完整增删范围;
  • div_s64div64_long 的具体使用位置。

类比

可以把每个 cgroup 看成共用一个收银台的顾客队伍,vruntime 是队伍手中的“已服务刻度”,lag 则表示它相对于全体平均刻度是落后还是领先。

6d71a9c61604 像是在刻度尺存在舍入误差时加了一块临时垫片,避免某支队伍被摆到错误位置;4823725d9d1d 则像是直接换成精度更高的刻度尺。上游先换尺子,再移除垫片是合理的;而 6.18.y 如果没有换尺子就直接移除垫片,虽然可能解决当前队伍被卡住的问题,却也可能让原来的测量误差重新出现。

生产测试说明这块垫片在请求者的机器上确实可能造成严重副作用,但不能单独证明旧刻度尺在其他负载下不会再次出错。

Highlight:风险与注意点

  • 修复与回归之间存在直接冲突:独立回退可能解决数小时饥饿,却重新暴露 avg_vruntime 的舍入 artifact。
  • 上游提交顺序很重要:不能因为 101f3498b4bd 在上游安全,就假定它在缺少 4823725d9d1d 的 6.18.y 上同样安全。
  • 生产测试很有价值但覆盖有限:数周无问题支持回移请求,却主要覆盖 Nokia 的隔离 CPU、cgroup 和固定 affinity 场景。
  • 这是分组调度问题:两个线程处于不同 slice,竞争过程包含 cgroup 调度实体,不能简单按两个普通 CFS 任务的 nice 值解释。
  • 不要误解 SIGKILL 现象:信号可以已经 pending;真正的问题是任务没有获得运行机会来进入退出路径。
  • 手工 backport 需要算术审查div_s64div64_long 的除数类型、符号、截断和溢出边界需要确认,而不能只看编译通过。
  • 证据不完整:原始故障理论和 backport diff 都在输入中被截断,无法审查全部公式和调用路径。
  • 维护状态尚未闭环:Sasha 明确选择等待 Peter 的 ack;给定线程中没有最终接受、拒绝或测试矩阵结论。

版本变化

本线程没有正式的 v1、v2 版本迭代,但涉及三项有顺序依赖的上游变化:

提交作用
6d71a9c61604为 EEVDF 实体放置问题提供 workaround
4823725d9d1d增加 avg_vruntime 的权重精度,修复底层舍入 artifact
101f3498b4bd在精度问题解决后撤销前述 workaround

4823725d9d1d 属于较大的 v7.1 avg_vruntime 重构。Sasha 认为整套重构不适合回移到 6.18.y,而这正是 standalone revert 难以直接判定安全的原因。

线程中唯一可称为“版本变化”的 backport 适配,是请求者报告将原补丁中的 div64_long 换为 div_s64,并调整少量 patch context;完整差异无法从截断输入核验。

一句话总结

这个 backport 有望修复 6.18.y 上极其严重的 EEVDF/cgroup 饥饿,但由于该分支缺少上游先行的 avg_vruntime 精度修复,单独回退是否会重现旧的舍入问题仍需调度维护者确认。