[toc]
Linux 内核崩溃处理核心(kernel/panic.c)全面解析
[kernel/panic.c] [内核 Panic/Oops 终止路径] [在不可恢复错误时完成“停止系统/通知/转储/重启”等收尾流程]
介绍
kernel/panic.c 主要实现内核在致命错误时的统一收尾路径:
panic():不可继续运行时进入的终止流程(通常不返回)。- 与 Oops/BUG/WARN 的策略联动:例如 “Oops 是否升级为 panic”、是否自动重启、是否触发 kdump 等。
- 提供 panic notifier、kmsg dump、停止其它 CPU、控制台输出解锁(bust spinlocks)等机制,尽量在系统已不稳定时仍完成关键动作。
历史与背景
诞生解决的问题
内核遇到不可恢复错误时,继续运行可能导致数据破坏扩大;需要统一路径完成:
- 记录信息(console/printk、dumpers)
- 通知相关子系统(notifier)
- 尝试转储(kdump/kmsg_dump/pstore 等)
- 停止并发扰动(停其它 CPU/禁止中断)
- 最终动作(停机/重启)
重要演进方向(你读文件时可重点关注的“常见结构”)
从“只打印并死循环”演进到“可配置行为”:
- panic 超时自动重启(panic_timeout)
- oops/warn 升级为 panic 的策略(panic_on_oops / panic_on_warn)
- 与 kexec/kdump 的联动(crash_kexec)
- 更可控的输出与 dump(panic_print / kmsg_dump 等)
主流应用情况
- 几乎所有架构与产品都依赖这条路径:服务器依赖 kdump,嵌入式常依赖 watchdog/自动重启与 pstore/ramoops 留痕。
核心原理与设计
核心流程(建议你按源码把每一步打上编号)
通常 panic() 会按以下“阶段化”组织(不同内核版本细节会略有差异):
- 进入不可恢复状态
- 记录当前 CPU/状态,设置全局“已在 panic 中”的标志,避免递归/并发重复进入。
- 关闭抢占/中断或进入更严格的上下文控制,尽量减少系统继续扰动。
- 保证关键输出尽可能可用
- 触发类似
bust_spinlocks的机制:当系统可能死在 console/锁上时,尝试“放宽锁保护”以便把 panic 信息打出去(代价是输出时序/一致性不再严格)。 - 输出 panic 信息、调用栈、寄存器摘要等(其中部分信息来自其它文件,但 panic.c 负责组织终止阶段的打印策略)。
- 通知链(panic notifier)
- 调用 panic notifier 链,让注册者做最后动作(例如:记录额外日志、切换硬件状态、触发特定 dump、点灯/蜂鸣等)。
- 这一步常被产品化用来做“最后的板级动作”。
- dump / 转储
- 触发 kmsg_dump(把日志交给不同 dumper,如 pstore/ramoops 等实现方)。
- 如配置了 kdump:调用 crash_kexec 进入 crash kernel 做内存转储。
- 停止并发(尤其 SMP)
- 尝试停止其它 CPU,避免其它核继续写内存/IO,降低 dump 过程中数据继续变化。
- 最终处置
- 如果设置了
panic_timeout>0:等待超时后重启(或走架构相关重启路径)。 - 否则通常进入死循环/停机等待外部看门狗复位(取决于配置与平台)。
主要优势
- 统一终止语义:所有致命路径最终汇聚到明确的收尾逻辑。
- 可配置策略:是否重启、是否升级 oops/warn、是否触发 kdump 等可由参数控制。
- 可扩展:panic notifier 与 kmsg_dump 让平台/产品能在最后阶段插入必要动作。
局限性与不适用点
- panic 环境不可信:锁、内存分配、调度、IO 都可能不可靠;因此很多代码必须避免睡眠与复杂依赖。
- 输出不保证完整:console 可能卡死或丢日志;bust spinlocks 只是降低“完全打不出”的概率。
- 并发与递归风险:NMI、双重故障、再次触发 panic 时,很多路径只能“尽力而为”。
使用场景
首选场景(它在系统中承担的角色)
- 内核检测到不可恢复错误(内存破坏、严重 BUG、关键子系统不可继续)。
- 产品希望:快速复位 + 留存最小诊断信息(串口日志/pstore/核心转储)。
不推荐“依赖 panic 解决”的场景
- 把 panic 当作“正常错误处理机制”是不合适的:panic 是终止流程,不是恢复机制。
- 如果你只是想在异常时收集信息但继续运行,应考虑更上层的容错(比如局部重启、隔离、降级),而不是依赖 panic。
对比分析
panic vs oops vs BUG vs WARN(从行为与代价角度)
- WARN:提示性告警,通常继续运行;可配置升级为 panic(用于把“疑似内存破坏前兆”变成快速复位策略)。
- BUG:通常意味着逻辑不可接受,常导致 oops 或直接触发更严重路径。
- Oops:发生严重异常但内核可能尝试继续(风险是继续运行导致二次破坏);可配置
panic_on_oops直接升级为 panic。 - panic:明确终止系统运行;更倾向“止损 + 诊断 + 重启/停机”。
对嵌入式产品常见策略:
- 关键设备:
panic_on_oops=1+ 合理panic_timeout+ watchdog,保证快速恢复; - 研发阶段:保留更长日志与更完整 dump(pstore/ramoops/kdump),降低复现难度。
总结
关键特性
panic()组织“打印/通知/dump/停核/重启”的终止流程。- 通过参数控制“是否自动重启、是否把 oops/warn 升级为 panic”等策略。
- panic notifier 与 kmsg_dump 为平台/产品留出最后阶段扩展点。
vpanic / panic:内核进入不可恢复致命路径时的统一收敛点(输出诊断、调用 panic 通知链、可选 kdump/重启/最终停机)
vpanic:核心致命路径(禁止中断/抢占、打印、通知链、dump、可选重启、最终自旋/闪烁)
- 不可睡眠上下文收敛:先关本地中断与抢占,避免在 panic 期间被调度打断或被中断处理再次触发 panic。
- 避免递归放大:
panic_on_warn在当前执行流上被清零,用于防止 panic 路径中再次触发 WARN 导致递归进入 panic。 - 最小依赖打印:使用静态缓冲区
buf[1024],避免 panic 阶段依赖动态内存分配。 - panic 通知链:通过
atomic_notifier_call_chain(&panic_notifier_list, ...)调用已注册的“原子回调”,让关键子系统补充信息或切换到更安全状态(例如你前面看到的 heartbeat 触发器置位停止标志)。 - 不依赖常规定时器的延迟/闪烁:panic 后常规定时器与调度时序不可信,因此重启前等待使用
mdelay()的忙等循环,并通过panic_blink()进行LED闪烁提示。
1 | /* |
panic:可变参数包装,转入 vpanic
1 | /** |
panic_try_start / panic_reset / panic_in_progress / panic_on_this_cpu / panic_on_other_cpu:panic 进入资格与并发态判定
panic_cpu:panic 状态的全局发布点
1 | /** |
panic_try_start:尝试成为“panic 主执行 CPU”
作用与实现原理
该函数使用 atomic_try_cmpxchg() 把 panic_cpu 从 PANIC_CPU_INVALID 原子地改写为当前 CPU 号。
- 成功:表示当前执行流成为“第一个宣布 panic 的 CPU”。
- 失败:表示此前已有 CPU(或同一 CPU 的某个更早路径,例如 NMI/异常路径)宣布进入 panic。
关键技巧是:用 CAS(比较并交换) 实现一次性“资格获取”,从而让 panic 与 crash_kexec 之类路径共享同一互斥条件,避免并行执行导致的相互停顿或资源争用。
1 | /** |
panic_reset:清除 panic 状态(恢复为“未进入 panic”)
作用与实现原理
将 panic_cpu 重置为无效值,用于某些场景下的测试/恢复流程或特殊控制路径。其本质是一次原子写入发布。
1 | /** |
panic_in_progress:判定系统是否处于 panic 状态
作用与实现原理
读取 panic_cpu 是否仍为无效值,从而获得“panic 是否已开始”的全局判定。这里用 unlikely() 仅是性能提示(不改变语义)。
1 | /** |
panic_on_this_cpu:判定“panic 是否发生在当前 CPU”
作用与实现原理
该函数判断 panic_cpu 是否等于当前 CPU 号。其关键前提是:一旦 panic_cpu 被设置,任务迁移在逻辑上不再成立(panic 路径通常会很快禁止抢占/中断并停止其他 CPU),因此可以使用 raw_smp_processor_id() 做判定,而不要求常规的迁移安全约束。
在 单核 上它退化为:panic_cpu == 0 时为 true(只要进入 panic 基本就成立)。
1 | /** |
panic_on_other_cpu:判定“panic 是否发生在远端 CPU”
作用与实现原理
1 | /** |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 wdfk-prog的个人博客!
评论









