sched discussion
[PATCH] sched/core: Convert remaining BUG_ON() instances to WARN_ON_ONCE()
LLM 分析
sched/core:把残留的 BUG_ON() 全部换成 WARN_ON_ONCE()
系列概况
- 标题:[PATCH] sched/core: Convert remaining BUG_ON() instances to WARN_ON_ONCE()
- 作者:Amin Gattout amin.gattout@gmail.com
- 版本:v1,单补丁
- 规模:1 file changed, 10 insertions(+), 10 deletions(-)
- 修改文件:
kernel/sched/core.c - 代码统计:6 处
BUG_ON()->WARN_ON_ONCE(),覆盖 5 个函数站点 - Message-ID:20260827-warn_instead_bug-v1-1-b515c0f74b89@gmail.com
- 完整性:patch + 2 封 reply(两封 reply 都只是纯引用前文,没有实质评论)
补丁目的
此补丁是 commit 09348d75a6ce("sched/all: Change all BUG_ON() instances in the scheduler to WARN_ON_ONCE()")的清尾工作。那次大范围转换遗漏了 kernel/sched/core.c 中的 6 处 BUG_ON(),本补丁把它们全部替换为 WARN_ON_ONCE()。
依据 Documentation/process/deprecated.rst:BUG_ON() 在命中时会直接 panic,把系统打死,导致:
- 报错信息常常来不及刷到 console / syslog;
- 上游开发者很难拿到可读的崩溃现场;
- 用户没有机会主动反馈或 attach dump。
换成 WARN_ON_ONCE() 后:
- 警告刷到 dmesg,便于事后取证;
- 系统继续运行,问题不一定致命;
- 如果用户希望旧行为,可以保留
panic_on_warn=1把它当 panic 处理。
旧流程的问题
旧流程里,一旦以下五个"理论上不可能"的条件命中,内核直接死机:
tick_work_cpu分配失败 -> 整个 tick offload 子系统崩溃;preempt_schedule_irq()在 preemptible / 中断开启上下文被调用 -> 直接 panic;- 把某个 CPU 标记 online / offline 时,它不在 root domain 的 span 掩码里 -> 多 CPU 拓扑假设破灭即死;
sched_init()启动时,调度类优先级顺序乱了 -> 整个调度器体系崩塌。
这些都是"假设不可能"的 invariant,但 BUG_ON() 的代价是:一旦真的发生,机器立刻停摆,调查极困难。
新流程
WARN_ON_ONCE() 的语义是:第一次命中时打印一次栈和警告,然后放行执行后续代码。这相当于把"自爆按钮"换成"红色警报灯 + 一次性记录器"。
- 默认:警告一次,继续运行;
panic_on_warn=1配置下:行为退化为 panic,等价于BUG_ON();- 上游 bug report 因此可以拿到 dmesg 而不是一张写着 "Kernel panic - not syncing" 的死屏幕。
+--------------------------+ cond true +----------------------+
| OLD: BUG_ON(cond) | ------------> | panic() + die() |
+--------------------------+ +----------------------+
|
| panic_on_warn=1
v
+--------------------------+ cond true +----------------------+
| NEW: WARN_ON_ONCE(cond) | ------------> | printk + dump_stack() |
+--------------------------+ +----------------------+
|
| default
v
+------------------------------------------------------+
| continue running, leave dmesg trace for postmortem |
+------------------------------------------------------+
Patch 概览
整条 patch 一共触达 5 个函数、6 处断言:
| 位置 | 函数 | 断言内容 |
|---|---|---|
core.c:5949 | sched_tick_offload_init() | tick_work_cpu 非空 |
core.c:7582 | preempt_schedule_irq() | 不在 preempt 上下文且中断关闭 |
core.c:8619 | sched_set_rq_online() | CPU 在 root domain span 内 |
core.c:8631 | sched_set_rq_offline() | CPU 在 root domain span 内 |
core.c:8954(x4) | sched_init() | 调度类优先级 stop > dl > rt > fair > ext > idle |
sched_init() 里有两段相邻的 BUG_ON() 链,分别校验 [stop, dl, rt, fair, idle] 与 [fair, ext, idle] 的相对顺序,全部一起替换。
关键实现
/* tick offload 初始化:分配 per-CPU 工作结构失败 */
int __init sched_tick_offload_init(void)
{
tick_work_cpu = alloc_percpu(struct tick_work);
- BUG_ON(!tick_work_cpu);
+ WARN_ON_ONCE(!tick_work_cpu);
return 0;
}
/* 抢占调度入口:禁止在可抢占或中断开启时进来 */
asmlinkage __visible void __sched preempt_schedule_irq(void)
{
enum ctx_state prev_state;
prev_state = exception_enter();
- BUG_ON(preempt_count() || !irqs_disabled());
+ WARN_ON_ONCE(preempt_count() || !irqs_disabled());
...
}
/* rq online/offline:CPU 必须属于 root domain span */
static inline void sched_set_rq_online(struct rq *rq, int cpu)
{
- BUG_ON(!cpumask_test_cpu(cpu, rq->rd->span));
+ WARN_ON_ONCE(!cpumask_test_cpu(cpu, rq->rd->span));
}
static inline void sched_set_rq_offline(struct rq *rq, int cpu)
{
- BUG_ON(!cpumask_test_cpu(cpu, rq->rd->span));
+ WARN_ON_ONCE(!cpumask_test_cpu(cpu, rq->rd->span));
}
/* 启动期校验调度类顺序 */
void __init sched_init(void)
{
int i;
/* chain A: stop > dl > rt > fair > idle */
- BUG_ON(!sched_class_above(&stop_sched_class, &dl_sched_class));
- BUG_ON(!sched_class_above(&dl_sched_class, &rt_sched_class));
- BUG_ON(!sched_class_above(&rt_sched_class, &fair_sched_class));
- BUG_ON(!sched_class_above(&fair_sched_class, &idle_sched_class));
+ WARN_ON_ONCE(!sched_class_above(&stop_sched_class, &dl_sched_class));
+ WARN_ON_ONCE(!sched_class_above(&dl_sched_class, &rt_sched_class));
+ WARN_ON_ONCE(!sched_class_above(&rt_sched_class, &fair_sched_class));
+ WARN_ON_ONCE(!sched_class_above(&fair_sched_class, &idle_sched_class));
#ifdef CONFIG_SCHED_CLASS_EXT
/* chain B: fair > ext > idle */
- BUG_ON(!sched_class_above(&fair_sched_class, &ext_sched_class));
- BUG_ON(!sched_class_above(&ext_sched_class, &idle_sched_class));
+ WARN_ON_ONCE(!sched_class_above(&fair_sched_class, &ext_sched_class));
+ WARN_ON_ONCE(!sched_class_above(&ext_sched_class, &idle_sched_class));
#endif
...
}
替换后的执行语义是"断言失败时打一次 backtrace + 继续运行",而不是直接 die()。
sched/core.c assert sites (after patch)
+------------------------+ +------------------------+
| sched_tick_offload_init| | preempt_schedule_irq |
| WARN_ON_ONCE(!tick_w) | | WARN_ON_ONCE(pc|!irqs) |
+------------------------+ +------------------------+
+------------------------+ +------------------------+
| sched_set_rq_online | | sched_set_rq_offline |
| WARN_ON_ONCE(!in span) | | WARN_ON_ONCE(!in span) |
+------------------------+ +------------------------+
+--------------------------------------------------+
| sched_init (boot-time) |
| stop > dl > rt > fair > idle |
| (+CONFIG_SCHED_CLASS_EXT: fair > ext > idle) |
+--------------------------------------------------+
类比
把 BUG_ON() 想象成电闸保险丝:一旦短路,整栋楼立即断电,人完全没了照明去排查哪里烧了。
WARN_ON_ONCE() 则是带记忆的烟雾报警器:只响一次,但会留下时间和地点记录,住户还能正常活动去检查厨房。真正不想冒烟的住户可以在电箱里加一个"报警即断电"开关(panic_on_warn=1),行为就退回到保险丝模式。
调度类顺序的 BUG_ON() 链则像建筑图纸上的承重柱编号:图纸乱了就直接炸楼;改成报警器后,至少工人能看到"这一根编号错了"再去返工,而不是面对废墟。
Highlight:风险与注意点
sched_init()启动期检查:这段断言在系统启动最早阶段触发,WARN_ON_ONCE()之后调度器立刻投入使用,行为并不明朗——一旦顺序真的错了,调度后续会进入不可预测状态。建议保留panic_on_warn默认值在此段生效。preempt_schedule_irq()是中断上下文入口:在 IRQ 路径上换成警告,意味着可能出现"非预期抢占但继续走"的中间态,需要额外留心栈使用和迁移。tick_work_cpu分配失败的WARN_ON_ONCE():offload tick 不能工作的症状是系统时钟事件停滞,影响面广,警告一过即后续无防护。sched_set_rq_online/offline():这两条BUG_ON()在 hotplug 路径上,新写法会让"假设被打破但继续 hotplug",负载均衡可能进入异常分支。- 本线程 reply 信息量:两封回信都只是引用前文,没有 maintainer 实质 Ack 或 Nack,说明这封 patch 还在排队待评审。
- CONFIG 覆盖验证:作者声明在
CONFIG_NO_HZ_FULL+CONFIG_SCHED_CLASS_EXT下 build 测试,确保 6 处站点都能被编译进 image;其它配置组合需要再确认。 - 是否需要拆 patch:6 处断言语义不同,可考虑后续按子系统(tick / hotplug / scheduler-class)拆成多个 patch,便于 review 与 bisect。
一句话总结
把 kernel/sched/core.c 中 09348d75a6ce 漏改的 6 处 BUG_ON() 全部换成 WARN_ON_ONCE(),让"理论上不可能"的 invariant 命中时只报警、留 dmesg,不直接 panic,以提高现场可观测性。