2. 内核异常分类:Oops、Panic、Kernel Bug、Soft Lockup、Hard Lockup、Deadlock

Linux 内核在运行过程中可能遭遇多种异常状态。准确识别异常类型是定位根因的第一步。本章系统梳理六类核心内核异常,从触发机制、典型现象到诊断切入点,为后续 ftrace 与栈回溯联合分析奠定基础。

2.1 异常分类总览

下表从触发条件、系统行为、可恢复性三个维度对比六类异常,帮助建立全局认知。

异常类型 触发条件 系统行为 可恢复性 典型日志特征
Oops 内核访问非法地址、空指针解引用 仅杀死当前进程,系统继续运行 部分可恢复 Unable to handle kernel NULL pointer dereference
Panic 致命错误(如内核堆栈溢出、关键数据结构损坏) 系统立即停止,打印调用栈后挂起或重启 不可恢复 Kernel panic - not syncing
Kernel Bug 内核代码逻辑断言失败 打印 BUG 信息,可能触发 Panic 或 Oops 视上下文而定 ------------[ cut here ]------------
Soft Lockup CPU 在软中断或 spinlock 上长时间自旋(>20s) 内核触发软锁检测,打印 CPU 栈回溯 可恢复(若锁释放) BUG: soft lockup - CPU#X stuck for 22s!
Hard Lockup CPU 在关中断状态下死循环(NMI 无法被响应) NMI 看门狗触发,打印寄存器与栈信息 不可恢复 NMI watchdog: BUG: hard lockup on CPU#X
Deadlock 两个或多个任务互相等待对方持有的锁 任务永久阻塞,系统吞吐下降,可能触发 Lockdep 告警 可恢复(若外部干预) ============================================
WARNING: possible recursive locking detected
诊断优先级提示: 在异常定位中,Oops 和 Panic 是最常见的两类直接崩溃异常,通常伴随完整的栈回溯信息,是 ftrace 联合分析的首选场景。Soft/Hard Lockup 和 Deadlock 属于“活锁”类异常,系统未立即崩溃但行为异常,需要借助 ftrace 的 function_graph 和 lock 事件进行时序分析。

2.2 Oops 与 Panic:内核崩溃的两级响应

2.2.1 Oops 机制详解

Oops 是内核从异常中尝试恢复的第一道防线。当内核检测到非致命错误(如访问 NULL 指针、使用已释放的内存)时,会执行以下流程:

  1. 异常触发:CPU 产生缺页异常或保护异常,进入内核异常处理路径。
  2. 信息收集:内核打印错误描述、CPU 寄存器值、当前进程的栈回溯。
  3. 进程隔离:向当前进程发送 SIGKILL 信号,仅终止该进程。
  4. 系统继续:内核调度器选择下一个进程运行,系统整体不受影响。
关键诊断信息: Oops 日志中的 RIP(指令指针寄存器)和 Call Trace 是定位故障函数的直接线索。结合 ftrace 的 function_graph 可以还原故障发生前的函数调用序列。

以下是一个典型的 Oops 日志片段:

BUG: unable to handle kernel NULL pointer dereference at 0000000000000010
IP: [<ffffffffa0002000>] my_driver_write+0x20/0x50 [my_module]
PGD 0 P4D 0
Oops: 0002 [#1] SMP
CPU: 2 PID: 1234 Comm: test_app Tainted: P           O
RIP: 0010:my_driver_write+0x20/0x50
Call Trace:
 <IRQ>
  [<ffffffff81234567>] vfs_write+0x123/0x200
  [<ffffffff81234589>] ksys_write+0x45/0x90
  [<ffffffff81001234>] do_syscall_64+0x5c/0x110
 </IRQ>

2.2.2 Panic 机制详解

Panic 是内核的最终异常响应。当内核检测到无法恢复的致命错误时,会调用 panic() 函数,执行以下操作:

  • 停止所有其他 CPU:通过 smp_send_stop() 发送停止信号。
  • 打印 Panic 信息:包含错误原因、当前 CPU 寄存器、完整栈回溯。
  • 触发 kdump(若配置):启动捕获内核,保存崩溃转储文件。
  • 系统挂起或重启:根据 panic_timeout 参数决定行为。
注意: Panic 发生后,系统不再可用。此时 ftrace 的 trace buffer 可能尚未刷新到磁盘。建议在启动参数中配置 ftrace_dump_on_oops=1,使内核在 Panic 时自动将 ftrace 缓冲区内容输出到控制台或串口。

2.3 Kernel Bug:内核自检断言

BUG() 和 BUG_ON() 是内核开发者嵌入代码的显式断言宏。当条件不满足时,内核会打印 ------------[ cut here ]------------ 标记,并输出调用栈。Kernel Bug 的行为取决于内核配置:

  • CONFIG_BUG=y:触发 Oops,系统继续运行。
  • CONFIG_BUG_ON_PANIC=y:直接触发 Panic。

Kernel Bug 通常指示内核代码逻辑错误,而非硬件问题。诊断时重点关注 BUG() 所在函数及其调用路径。

2.4 Soft Lockup 与 Hard Lockup:CPU 无响应检测

2.4.1 检测机制对比

特性 Soft Lockup Hard Lockup
检测方式 高精度定时器(hrtimer)周期性检查 NMI 看门狗(NMI watchdog)
中断状态 中断可响应 中断被关闭
典型原因 spinlock 长时间持有、软中断死循环 关中断后死循环、硬件故障
诊断工具 ftrace function_graph + irqsoff tracer NMI 栈回溯 + ftrace nmi 事件

2.4.2 Soft Lockup 诊断流程

  1. 查看 /var/log/messages 或 dmesg 中的 soft lockup 日志。
  2. 记录 Call Trace 中卡住的函数。
  3. 使用 ftrace 的 irqsoff tracer 测量中断关闭时间。
  4. 使用 function_graph 追踪卡住函数的调用链。

2.5 Deadlock:锁依赖循环

Deadlock 是最隐蔽的内核异常之一。Linux 内核通过 Lockdep(Lock Dependency Validator)在运行时检测潜在的死锁。Lockdep 会在以下情况输出告警:

  • 检测到锁的循环依赖(ABBA 模式)。
  • 检测到锁的递归获取(同一线程重复获取同一锁)。
  • 检测到锁的释放顺序与获取顺序不一致。

Lockdep 告警日志包含完整的锁依赖链和当前任务的栈回溯,是诊断死锁的核心依据。

ftrace 联合诊断技巧: 对于 Deadlock 场景,启用 ftrace 的 lock 事件组(echo 1 > /sys/kernel/debug/tracing/events/lock/enable),可以记录每次锁的获取和释放操作,精确还原锁的时序关系。

2.6 异常分类关系图

下图展示了六类内核异常在系统响应层级上的关系,以及 ftrace 在不同异常类型中的诊断切入点。

内核异常分类与 ftrace 诊断切入点 内核异常触发 可恢复 / 部分可恢复 不可恢复 Oops Soft Lockup Deadlock Panic Hard Lockup Kernel Bug ftrace 诊断切入点 function_graph / irqsoff / wakeup / lock 事件组 / nmi 事件 诊断输出:栈回溯 + 函数调用链 + 锁时序 + 中断延迟

2.7 异常类型与诊断工具匹配表

在实际调试中,根据异常类型选择合适的 ftrace tracer 和事件组,可以大幅提升定位效率。

异常类型 首选 ftrace tracer 辅助事件组 关键分析指标
Oops function_graph kmem(内存分配事件) RIP 所在函数、调用路径、内存操作序列
Panic function_graph + ftrace_dump_on_oops printk 事件 Panic 前最后执行的函数、内核消息序列
Kernel Bug function_graph bug 事件(需内核支持) BUG 触发点、条件判断上下文
Soft Lockup irqsoff / preemptoff timer 事件 中断关闭时长、spinlock 持有时间
Hard Lockup nmi 事件 function_graph(需 NMI 安全) NMI 触发时的指令指针、寄存器状态
Deadlock lock 事件组 function_graph + wakeup 锁获取/释放时序、任务唤醒链
实战建议: 在开发或测试环境中,建议始终开启 CONFIG_LOCKDEP 和 CONFIG_FTRACE,并设置 ftrace_dump_on_oops=1。这样在发生 Oops 或 Panic 时,可以同时获取栈回溯和 ftrace 时序数据,为联合诊断提供完整素材。