0/3 已展开

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-ID20260827-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.rstBUG_ON() 在命中时会直接 panic,把系统打死,导致:

  1. 报错信息常常来不及刷到 console / syslog;
  2. 上游开发者很难拿到可读的崩溃现场;
  3. 用户没有机会主动反馈或 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:5949sched_tick_offload_init()tick_work_cpu 非空
core.c:7582preempt_schedule_irq()不在 preempt 上下文且中断关闭
core.c:8619sched_set_rq_online()CPU 在 root domain span 内
core.c:8631sched_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.c09348d75a6ce 漏改的 6 处 BUG_ON() 全部换成 WARN_ON_ONCE(),让"理论上不可能"的 invariant 命中时只报警、留 dmesg,不直接 panic,以提高现场可观测性。