3. panic 流程深度解析:panic 函数调用链、panic_smp_self_stop 与 crash_kexec
当内核检测到不可恢复的致命错误时,会调用 panic() 函数。这是内核故障处理的最终手段,其执行流程高度结构化,涉及多核同步、信息转储与可选的 kdump 捕获。理解这一调用链,是分析内核崩溃转储文件(vmcore)和定位根因的前提。
3.1 panic 函数的核心调用链
panic() 函数定义于 kernel/panic.c,其执行路径可划分为四个阶段。下表总结了各阶段的关键动作与输出:
| 阶段 | 关键函数 / 宏 | 主要动作 | 典型输出 |
|---|---|---|---|
| 1. 初始化与原子性保证 | panic() 入口 |
获取 panic_lock,设置 panic_cpu,禁用本地中断 |
Kernel panic - not syncing: ... |
| 2. 信息转储 | dump_stack(), show_regs() |
打印当前 CPU 的调用栈与寄存器 | 调用栈回溯、寄存器值 |
| 3. 用户通知与回调 | panic_notifier_list |
执行注册的 panic 通知链(如 netconsole、kdump 注册) | 各通知模块的输出 |
| 4. 多核处理与最终状态 | crash_kexec(), panic_smp_self_stop() |
尝试触发 kdump,停止其他 CPU,最后进入死循环 | kdump 加载信息或 Rebooting in N seconds |
关键点:
panic() 函数是 单核串行化 执行的。通过 panic_cpu 原子变量确保只有一个 CPU 能执行完整的 panic 流程,其他 CPU 在 panic_smp_self_stop() 中被停止或等待。
以下 SVG 展示了 panic() 函数的主调用链与关键分支:
3.2 panic_smp_self_stop:多核同步的最后防线
在 crash_kexec() 执行失败或未配置 kdump 的情况下,内核需要确保其他 CPU 不再干扰当前 CPU 的 panic 输出。此时 panic_smp_self_stop() 被调用。
- 功能: 向所有其他 CPU 发送 IPI(核间中断),使其执行
stop_this_cpu()函数。 - 行为: 其他 CPU 进入 halt / wfi 状态,不再响应调度或中断。
- 关键代码:
void panic_smp_self_stop(void)
{
// 向其他 CPU 发送停止 IPI
smp_send_stop();
// 当前 CPU 进入死循环,等待其他 CPU 停止
while (1)
cpu_relax();
}
常见陷阱: 如果某个 CPU 因为中断被屏蔽或处于不可中断状态(如自旋锁持有者),
panic_smp_self_stop() 可能无法立即停止该 CPU。这会导致 panic 输出被其他 CPU 的后续输出污染,增加分析难度。此时需要检查 nmi_watchdog 或 hardlockup_panic 是否触发。
3.3 crash_kexec:从崩溃到捕获的桥梁
crash_kexec() 是 panic 流程中最重要的分支之一,它负责加载并跳转到 kdump 内核,从而保存当前内核的内存快照(vmcore)。
3.3.1 执行条件
| 条件 | 说明 |
|---|---|
kexec_crash_image 已加载 |
系统启动时或手动通过 kexec -p 加载了捕获内核 |
panic() 未被嵌套调用 |
防止递归 panic 导致死锁 |
| 当前 CPU 是 panic CPU | 只有触发 panic 的 CPU 才能执行 kexec 跳转 |
3.3.2 执行流程
- 保存寄存器: 将当前 CPU 的寄存器状态保存到
crash_notes中,供捕获内核解析。 - 关闭设备: 调用
machine_crash_shutdown()关闭外设(如磁盘、网络),防止 DMA 破坏内存。 - 跳转到捕获内核: 通过
kexec_crash_image->entry跳转到捕获内核的入口点。 - 捕获内核启动: 捕获内核启动后,将原始内核的内存内容写入磁盘或网络。
性能影响:
crash_kexec() 的执行时间通常在 100ms ~ 1s 之间,主要取决于设备关闭和内存拷贝速度。对于生产环境,建议配置 panic_timeout=0 以避免自动重启,确保 kdump 有足够时间完成。
以下 SVG 展示了 crash_kexec() 的详细执行流程:
3.4 实战:从 panic 日志反推调用链
以下是一个典型的 panic 日志片段,我们通过它来反推内核的执行路径:
[ 123.456789] Kernel panic - not syncing: Fatal exception
[ 123.456790] CPU: 2 PID: 1234 Comm: kworker/2:1 Tainted: G W
[ 123.456791] Hardware name: VMware Virtual Platform
[ 123.456792] Call Trace:
[ 123.456793] <NMI>
[ 123.456794] dump_stack+0x12/0x20
[ 123.456795] panic+0x1a0/0x300
[ 123.456796] oops_end+0x80/0x90
[ 123.456797] do_page_fault+0x2a0/0x400
[ 123.456798] page_fault+0x30/0x40
[ 123.456799] </NMI>
[ 123.456800] <...>
[ 123.456801] <0xffffffff81000000>
[ 123.456802] Rebooting in 10 seconds..
分析要点:
- 调用栈方向: 从下往上读,
page_fault→do_page_fault→oops_end→panic。 - NMI 标记: 表示 panic 是在 NMI 上下文中触发的,通常与硬件错误或 NMI watchdog 有关。
- Rebooting 信息: 表明
crash_kexec()未执行(或执行失败),系统进入panic_timeout倒计时重启。
3.5 关键配置与调试建议
| 配置项 | 推荐值 | 说明 |
|---|---|---|
CONFIG_KEXEC |
y | 启用 kexec 系统调用 |
CONFIG_CRASH_DUMP |
y | 启用 crash dump 支持 |
panic_timeout |
0 | 禁止自动重启,等待手动干预或 kdump 完成 |
nmi_watchdog |
1 | 启用 NMI watchdog,检测硬锁死 |
生产环境建议: 在部署 kdump 的生产环境中,务必设置
panic_timeout=0 并配置 crashkernel=auto 预留内存。同时,通过 kexec -p 加载捕获内核后,使用 echo c > /proc/sysrq-trigger 手动触发一次 panic,验证 kdump 是否正常工作。