sched discussion
[PATCH] sched/deadline: Fix DL server initialization for initially offline CPUs
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-ID:
20260811100612.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_server、rq->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读到 50000000,period 读到 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:风险与注意点
- overflow 跳过是有条件放宽:
if (!init || cpu_active(cpu))只放行 boot 初始化路径,用户改参(debugfs、init == 0)仍走检查——容易被误读为"削弱 DL 准入控制"。 - 依赖 root domain rebuild 发布带宽:补丁正确性建立在 "CPU active 时一定触发 rebuild 并把
dl_bw_attached的 server 带宽汇总进去" 上。若某条 hotplug 路径漏掉 rebuild 或 rebuild 不汇总,会出现账本与实际预留不一致——最值得单独验证的点。 dl_bw_attached语义:offline CPU 在 boot 时被置dl_bw_attached = 1,但其带宽未进 root domain。若有人提前改参触发else if (dl_se->dl_bw_attached) __dl_sub(...),会去减"没加过的带宽"。需要确认 debugfs 在 CPU inactive 时不可达。- ext_server 同受影响:
CONFIG_SCHED_CLASS_EXT下rq->ext_server走同一段代码。changelog 说明 boot 时无 BPF 调度器,ext_server 无带宽;但 late-online CPU 上加载 BPF 调度器的行为应一并测试。 - 首次 CBS wakeup 路径必须可靠:把初始化拆成静态/动态后,任何假设 "DL server 的 deadline/runtime 在 boot 后立即有效" 的路径都可能读到零值。
- 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 任务可能被饿死的回归。