3. panic流程深度解析

当内核检测到不可恢复的致命错误时,会调用 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() 函数的主调用链与关键分支:

panic() 函数主调用链 panic("Fatal Error") panic_lock / panic_cpu dump_stack() + show_regs() crash_kexec() 尝试 kdump 加载 vmcore panic_smp_self_stop() while(1) 死循环 成功 失败 / 未配置

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 执行流程

  1. 保存寄存器: 将当前 CPU 的寄存器状态保存到 crash_notes 中,供捕获内核解析。
  2. 关闭设备: 调用 machine_crash_shutdown() 关闭外设(如磁盘、网络),防止 DMA 破坏内存。
  3. 跳转到捕获内核: 通过 kexec_crash_image->entry 跳转到捕获内核的入口点。
  4. 捕获内核启动: 捕获内核启动后,将原始内核的内存内容写入磁盘或网络。
性能影响: crash_kexec() 的执行时间通常在 100ms ~ 1s 之间,主要取决于设备关闭和内存拷贝速度。对于生产环境,建议配置 panic_timeout=0 以避免自动重启,确保 kdump 有足够时间完成。

以下 SVG 展示了 crash_kexec() 的详细执行流程:

crash_kexec() 执行流程 crash_kexec() 入口 检查 kexec_crash_image 保存 crash_notes machine_crash_shutdown() 跳转到捕获内核入口 捕获内核启动 & 转储 返回 panic() 主流程 未加载

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 是否正常工作。