sched discussion
Please backport 101f3498b4bd to 6.18.y
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-ID:
PAWPR07MB100715EA5D8FCEB34B9987DF69AC02@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.slice 和 user.slice 对应的组调度实体。
来信中的未完全证明理论可以概括为:
system.slice的实体长期拥有可运行负载;user.slice偶尔进入竞争;user.slice的vruntime或 lag 在实体放置过程中被推到异常位置;- 如果其
vruntime远超平均虚拟运行时间,它可能呈现负 lag,暂时不满足 EEVDF 的 eligibility 条件; - 这种状态在特定权重、分组和低干扰 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 失效,而是被杀任务始终没有被调度执行,因而没有机会完成退出路径。
新流程
提议中的处理流程是:
- 在 6.18.y 上撤销
6d71a9c61604; - 恢复其修改前的 lag、权重变化和虚拟运行时间缩放逻辑;
- 使用针对 6.18.y 上下文适配过的 diff;
- 依靠请求者报告的数周生产测试验证饥饿是否消失。
但是 stable 维护者没有立即接受该方案,因为上游撤销 6d71a9c61604 之前,已经先合入:
4823725d9d1d sched/fair: Increase weight bits for avg_vruntime
按照 Sasha Levin 的说明,真正消除底层舍入误差的是 4823725d9d1d;6d71a9c61604 只是绕过该误差。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 概览
-
Janne Huttunen:提出 backport 请求
- 报告 6.18.y 上特定隔离 CPU 的长期调度饥饿;
- 请求回移
101f3498b4bd; - 给出与 cgroup、CPU affinity、
nohz_full和vruntime有关的初步理论。
-
Sasha Levin:要求提供可应用的补丁
- 上游提交无法直接干净地应用到 6.18.y;
- 要求请求者发送经过测试的 backport。
-
Janne Huttunen:发送手工 backport
- 修改
kernel/sched/fair.c; - 可见部分涉及 lag 计算和实体权重缩放;
- 据后续邮件引用,和原始提交的主要差异是使用
div_s64而不是div64_long,外加少量上下文调整。
- 修改
-
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_s64与div64_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_s64与div64_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 精度修复,单独回退是否会重现旧的舍入问题仍需调度维护者确认。