0/3 已展开

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

补丁要解决:

  1. debugfs 入口改用 cpu_active(),把窗口期的写入直接返回 -EBUSY
  2. dl_server_apply_params() 内部加 cpu_active(cpu) 检查,兜底覆盖"通过检查后又被抢占走、CPU 在中途被 active 摘除"的二次窗口。
  3. 保留 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 */
}

两层防御:

  1. debugfs 入口:看到 cpu_active() 为 false,直接 -EBUSY,请求根本不会进入带宽更新路径。
  2. 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_activecpu_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 的窗口。