0/3 已展开

LLM 分析

sched/deadline:修复 boot 时 offline 的 CPU 的 DL server 初始化

系列概况

  • 标题:sched/deadline: Fix DL server initialization for initially offline CPUs
  • 作者:Hui Su <sh_def@163.com>
  • 版本:单版本 PATCH(无 vN 重发)
  • 规模:单补丁,4 封邮件(1 patch + 3 回复)
  • 修改文件kernel/sched/deadline.c
  • 代码统计:24 行变更(16 insertions, 8 deletions)
  • Message-ID20260811100612.1408592-2-sh_def@163.com
  • 完整性:线程完整;Juri Lelli 已给 Acked-by;作者2026-08-27 ping Peter/Ingo 等待合入

补丁目的

修复 commit 9f239df55546 ("sched/deadline: Initialize dl_servers after SMP") 引入的回归:

该 commit 把 DL server(rq->fair_serverrq->ext_server)的初始化挪到 sched_init_smp(),默认假设 SMP 初始化完成后所有 CPU 都已 online。但启动参数带 maxcpus= 等场景会故意让部分 CPU 保持 offline;sched_init_dl_servers() 又只遍历 for_each_online_cpu(),所以这些 CPU 的默认 runtime/period 永远没被配置,后续 hotplug 上线也不会重跑,参数长期为 0。

后果:fair server runtime 为 0 时无法被激活,后上线 CPU 的 CFS 任务在 DL/RT 任务霸占 CPU 时失去 fair server 的防饥饿保护,用户态负载可能 stall。

# echo 1 > /sys/devices/system/cpu/cpu1/online
# cat /sys/kernel/debug/sched/fair_server/cpu1/runtime
0

旧流程的问题

boot (maxcpus=1)
  -> sched_init_smp()
 -> sched_init_dl_servers()
 for_each_online_cpu(): 只看 CPU0
              set runtime/period
 setup_new_dl_entity()
            CPU1 (offline) 完全跳过 -> runtime=0, period=0
  ->之后 echo 1 > cpu1/online
       cpuhp 路径不重跑 sched_init_dl_servers()
       => CPU1 fair/ext server 永久残废

两层根因:遍历集合错(应取 possible 而非 online快照);hotplug 上线没有"补初始化"钩子。

新流程

sched_init_dl_servers()
  for_each_possible_cpu(cpu):
    online = cpu_online(cpu)
    |-- online == true  --> 保持原行为
    |     update_rq_clock(rq)
    |     set runtime/period
    |     setup_new_dl_entity()           (建立当前 CBS 周期)
    |     dl_server_apply_params(init=1)
    |       overflow 检查 + __dl_add(root_domain)
    |-- online == false --> 只做"静态"部分
         跳过 update_rq_clock()
          set runtime/period               (配置量照写)
          跳过 setup_new_dl_entity()       (动态量留给首次 CBS wakeup)
          dl_server_apply_params(init=1)
            跳过 overflow 检查 (cpu 非 active)
            __add_rq_bw() 本地 rq 记账
            跳过 __dl_add() 不进 root domain

... 之后 echo 1 > cpu1/online ...
 cpu 变 active -> root domain rebuild -> 本地 rq 带宽被发布到 root domain -> fair server 可激活

修好后再跑同一测试:fair_server/cpu1/runtime读到 50000000period 读到 1000000000

关键实现

1. 遍历 possible CPU,区分 online / offline

-	for_each_online_cpu(cpu) {
+	for_each_possible_cpu(cpu) {
+		bool online = cpu_online(cpu);
-		update_rq_clock(rq);
+		if (online)
+			update_rq_clock(rq);

update_rq_clock() 依赖运行中的 rq 时钟,对 offline CPU 无意义。

2. fair / ext server 的 CBS 周期只对 online CPU 建立

-		setup_new_dl_entity(dl_se);
+		if (online)
+			setup_new_dl_entity(dl_se);

changelog 的取舍:配置量(50ms/1s)boot 时写入;动态量(剩余预算、绝对 deadline)推迟到首次 CBS wakeup 再算,避免给 offline CPU 算出基于陈旧时钟的过期 deadline。

3. 带宽记账:本地记,root domain 缓记

-	cap = dl_bw_capacity(cpu);
-	if (__dl_overflow(dl_b, cap, old_bw, new_bw))
-		return -EBUSY;
+	if (!init || cpu_active(cpu)) {
+		cap = dl_bw_capacity(cpu);
+		if (__dl_overflow(dl_b, cap, old_bw, new_bw))
+			return -EBUSY;
+	}
-	__dl_add(dl_b, new_bw, cpus);
+	if (cpu_active(cpu))
+		__dl_add(dl_b, new_bw, cpus);
  • overflow 检查:dl_bw_capacity() 对非 active CPU容量不可信(甚至0),会被误判 -EBUSY,所以仅 init 路径且 cpu_active 时才检查;init == 0 的用户改参路径照旧。
  • root domain 记账:__dl_add() 把带宽加进全局 DL 账本,offline CPU 还不属于有效 root domain,此时记账会污染全局账本并造成后续 rebuild 重复计数;推迟到 active 后由 rebuild 统一发布。

不变的部分:__add_rq_bw(new_bw, &rq->dl) 是 per-rq 本地状态,无论 online 与否都正确设置,保证 CPU 上线后本地状态自洽。

状态机

             configured  local_rq_bw  root_domain_bw   CBS_period
online CPU   [ boot ]   [   set   ]  [ set at boot ]  [ set at boot ]
offline CPU  [ boot  ]   [   set   ]  [ deferred   ]  [ on 1st wakeup ]
 |
 cpu becomes active
                                            v root domain rebuild -> published

类比

连锁餐厅的总部预算:每家分店(CPU)都有"每小时至少留 5 分钟服务普通客人"的承诺(fair server 50ms/1s)。旧做法下总部只在开业日给已开门的分店发承诺书——装修中的店(maxcpus=)被漏掉,后来开门也不会补发,于是普通客人可能被 VIP(DL/RT)无限期挤在门外。

新做法:给所有已规划的分店都发承诺书。已开门的:承诺书 + 立刻计时(setup_new_dl_entity)+ 立刻计入总部总预算(__dl_add)。还没开门的:承诺书先放进抽屉(本地 rq 带宽),不计入总部总预算——总部当前账目里没有这家店,硬加会让账目对不上(overflow 误判);等它开门(cpu_active),总部做一次预算重算(root domain rebuild)自然生效。计时器不提前启动:装修期开始计"服务时长"是荒谬的,等第一位客人上门(首次 CBS wakeup)再开表。

Highlight:风险与注意点

  1. overflow 跳过是有条件放宽if (!init || cpu_active(cpu)) 只放行 boot 初始化路径,用户改参(debugfs、init == 0)仍走检查——容易被误读为"削弱 DL 准入控制"。
  2. 依赖 root domain rebuild 发布带宽:补丁正确性建立在 "CPU active 时一定触发 rebuild 并把 dl_bw_attached 的 server 带宽汇总进去" 上。若某条 hotplug 路径漏掉 rebuild 或 rebuild 不汇总,会出现账本与实际预留不一致——最值得单独验证的点。
  3. dl_bw_attached 语义:offline CPU 在 boot 时被置 dl_bw_attached = 1,但其带宽未进 root domain。若有人提前改参触发 else if (dl_se->dl_bw_attached) __dl_sub(...),会去减"没加过的带宽"。需要确认 debugfs 在 CPU inactive 时不可达。
  4. ext_server 同受影响CONFIG_SCHED_CLASS_EXTrq->ext_server 走同一段代码。changelog 说明 boot 时无 BPF 调度器,ext_server 无带宽;但 late-online CPU 上加载 BPF 调度器的行为应一并测试。
  5. 首次 CBS wakeup 路径必须可靠:把初始化拆成静态/动态后,任何假设 "DL server 的 deadline/runtime 在 boot 后立即有效" 的路径都可能读到零值。
  6. stable 回合:带 Fixes: + Cc: stable,旧分支上 ext_server 可能不存在,回合冲突需小心。

版本变化

线程中未见 vN 标记,无 vN→vN+1 演进;单版本 PATCH 一次到位。

一句话总结

sched_init_dl_servers()for_each_online_cpu() 改为 for_each_possible_cpu(),对 offline CPU 只写入静态 runtime/period 与本地 rq 带宽、把 root domain 记账和 CBS 周期建立推迟到 CPU真正 active 之后,从而修复 maxcpus= 场景下后上线 CPU 的 fair server 参数为 0、CFS 任务可能被饿死的回归。