sched discussion
[PATCH] sched/deadline: Fix DL server divide-by-zero for inactive CPUs
LLM 分析
sched/deadline:修复 DL server 在 inactive CPU 上的除零问题
系列概况
- 标题: [PATCH] sched/deadline: Fix DL server divide-by-zero for inactive CPUs
- 作者: Hui Su sh_def@163.com
- 版本: 单封 PATCH(不是 patch series,无 vN 演进)
- 规模: 2 个文件改动,5 行新增、3 行删除
- 修改文件:
- kernel/sched/deadline.c
- kernel/sched/debug.c
- 代码统计: deadline.c +4/-2,debug.c +1/-1
- Message-ID: 20260812123252.2355986-3-sh_def@163.com
- 完整性: 完整 patch,含 commit message、diff、Fixes/Link/Cc/Signed-off-by;附 reproducer 状态与 Oops 调用栈;后续有 Juri Lelli 的 Acked-by 与作者对 Peter/Ingo 的 ping 回复
补丁目的
CPU hot-unplug 流程中,目标 CPU 会先从 cpu_active_mask 摘除,再被标为 offline。两步之间存在一段窗口:cpu_online() 仍为 true、cpu_active() 已经为 false。如果用户在这段窗口里通过 debugfs 写 fair_server 参数,旧检查 cpu_online() 不会拦下,写入会一路走到 dl_server_apply_params()。对于 root domain span 仅含自身、且已被隔离的 CPU,dl_bw_cpus() 此时返回 0;__dl_sub() / __dl_add() 用这个 CPU 数做除数,立即触发 Oops: divide error。
补丁要解决:
- debugfs 入口改用
cpu_active(),把窗口期的写入直接返回-EBUSY。 dl_server_apply_params()内部加cpu_active(cpu)检查,兜底覆盖"通过检查后又被抢占走、CPU 在中途被 active 摘除"的二次窗口。- 保留 runqueue 局部的
dl_rq_change_utilization不受cpu_active()影响,让本机带宽状态在参数更新期间保持一致。
旧流程的问题
hot-unplug teardown path
+----------------------------------+
| 1. clear cpu_active_mask | cpu_active() -> false
| 2. clear cpu_online_mask | cpu_online() -> true (still)
| 3. cleanup root-domain bandwidth |
+----------------------------------+
|
| window
v
debugfs write -> cpu_online() passes -> dl_bw_cpus() == 0
-> __dl_sub/__dl_add(cpus=0) -> #DE (divide error)
旧提交 4043f5498416 只看 cpu_online(),错过了 cpu_active_mask 先于 cpu_online_mask 清除这段窗口。sched_server_write_common() 的拦截只覆盖 offline 状态,覆盖不到"已 inactive 但仍 online"的中间态。
新流程
/* kernel/sched/debug.c sched_server_write_common() */
- if (!cpu_online(cpu_of(rq)))
+ if (!cpu_active(cpu_of(rq)))
return -EBUSY;
/* kernel/sched/deadline.c dl_server_apply_params() */
if (dl_se->dl_bw_attached) {
- __dl_sub(dl_b, dl_se->dl_bw, cpus);
- __dl_add(dl_b, new_bw, cpus);
+ if (cpu_active(cpu)) {
+ __dl_sub(dl_b, dl_se->dl_bw, cpus);
+ __dl_add(dl_b, new_bw, cpus);
+ }
dl_se->dl_bw_attached = 1;
} else if (dl_se->dl_bw_attached) {
dl_rq_change_utilization(rq, dl_se, new_bw); /* unaffected by active check */
}
两层防御:
- debugfs 入口:看到
cpu_active()为 false,直接-EBUSY,请求根本不会进入带宽更新路径。 dl_server_apply_params()内部:再做一次cpu_active(cpu),兜底覆盖"通过检查后被调度走、CPU 又被 active 摘除"的二次窗口。
关键实现
__dl_sub / __dl_add 内部假设 cpus > 0,以 0 做除数立即 #DE:
/* pseudocode */
static inline void __dl_sub(struct dl_bw *dl_b, u64 dl_bw, int cpus)
{
dl_b->bw -= dl_bw;
dl_b->total_bw -= dl_bw * BW_UNIT / cpus; /* cpus=0 -> #DE */
}
补丁没有改 __dl_sub / __dl_add 的语义,而是把"传入 0"的路径在调用方切断,属于最小侵入修复。复现条件依赖 isolcpus=domain,1 让 root domain span 收缩成 1,dl_bw_cpus() 才能反映成 0;其他隔离策略下 span 包含多个 CPU,dl_bw_cpus() 不会归零,问题被掩盖。
root-domain view (hot-unplug window)
+-----------------------------------+
| span = 1 (isolated) |
| active CPUs = 0 (teardown) |
| dl_bw_cpus() -> 0 |
+-----------------------------------+
|
v
__dl_sub(bw, cpus=0)
|
v
total_bw -= bw / cpus <-- divide error
类比
把酒店前台想成调度器,CPU 想成一间房:
- 客人先交还房卡(
cpu_active_mask清除),前台不再为他提供服务。 - 系统随后才把这间房标为"空房"(
cpu_online_mask清除)。 - 在这两步之间,前台仍把房间视作"在线",允许别人登记入住并修改账单(debugfs 写入);账单系统已经清掉了这位客人的计数,
dl_bw_cpus()返回 0,账单一除以 0 就崩。 - 补丁相当于:第一步刚完成,前台就拒绝任何房间操作;并且账单系统在算除法前自己再核对一次"这位客人还在线吗"。
Highlight:风险与注意点
cpu_active与cpu_online的语义差容易踩坑:active 是 scheduler 视角("还能调度任务"),online 是 CPU 视角("CPU 还在跑")。所有跟调度带宽相关的路径都应该用 active 而不是 online。- 仅在 debugfs 入口加判定不够:
sched_server_write_common()返回前,进程仍可能被抢占或调度走,dl_server_apply_params()内部必须再做一次cpu_active(cpu)。 dl_rq_change_utilization不带cpu_active检查:是有意保留,让 runqueue 局部状态在更新期间保持一致;这条路径如果也加 active 判断,可能引入新的不一致。- 复现路径依赖
isolcpus=domain,1让 root-domain span 收缩为 1;非 domain 隔离或 housekeeping 配置下,dl_bw_cpus()不会归零,问题被掩盖。 - 已得到 Juri Lelli 的
Acked-by,Hui Su 在 8 月 27 日 ping Peter Zijlstra 与 Ingo Molnar 等待主分支合入;本 patch 同时Cc: stable@vger.kernel.org,建议回溯到引入 DL server 接口的d741f297bcea。
一句话总结
把 debugfs 入口与 dl_server_apply_params() 内部的 root-domain 带宽更新都换成 cpu_active() 判定,堵住 CPU hot-unplug 期间 dl_bw_cpus 被清零导致 #DE 的窗口。