Linux Kernel Watchdog 硬锁死与软锁死检测深度实战
生产环境中,内核死锁、中断风暴或长时间关中断是系统管理员最头疼的故障之一。传统的
sysrq和kdump虽然能捕获事后现场,但无法在故障发生的毫秒级时间窗内给出实时告警。Linux 内核内置的 Watchdog 子系统(softlockup+hardlockup)提供了无需额外硬件的实时锁死检测能力——它是系统可靠性的最后一道软件防线。
一、为什么需要内核 Watchdog?
操作系统内核本质上是一个高并发的运行时:中断处理、软中断、工作队列、RCU 回调、调度器 tick 都在争抢 CPU。当一个 bug 导致长时间关中断(cli)、自旋锁死循环、或软中断风暴时,用户态进程看似无法感知,但整个系统实质上已经"卡死"。
传统的看门狗需要外部硬件(如 iLO/iDRAC 板卡),而 Linux 内核从 2.6 时代引入了纯软件实现的 Watchdog 机制:
- 软锁死(Soft Lockup):某个 CPU 被一个可抢占的上下文(如内核线程、软中断)长时间占据,调度器 tick 仍然工作,但普通进程长时间得不到执行。
- 硬锁死(Hard Lockup):某个 CPU 完全停止响应,本地APIC定时器中断(或NMI)都未能触发——意味着关中断(local_irq_disable)后未恢复,或发生了死循环而不响应中断。
两者的核心差异在于:软锁死时中断仍然工作,硬锁死时中断已被关闭。这一差异直接决定了检测机制的设计思路。
二、软锁死检测:hrtimer 驱动的调度监视器
2.1 核心数据结构
软锁死的检测基于高精度定时器(hrtimer),每个 CPU 维护一个独立的检测窗口:
// include/linux/sched.h
struct task_struct {
// ...
#ifdef CONFIG_SOFTLOCKUP_DETECTOR
/* 软锁死检测时间戳 */
unsigned long softlockup_timestamp; // jiffies 时刻
int softlockup_count; // 连续未调度次数
#endif
};
// kernel/watchdog.c
static DEFINE_PER_CPU(struct hrtimer, softlockup_hrtimer);
static DEFINE_PER_CPU(unsigned long, softlockup_timestamp);
static DEFINE_PER_CPU(unsigned long, watchdog_touch_ts);
2.2 检测原理
每个 watchdog_thresh(默认 10 秒,即 2 * HZ * watchdog_thresh 中的半周期检查),hrtimer 回调函数 softlockup_fn 执行以下判断链:
┌──────────────────────────────────────────────────────────────┐
│ hrtimer 到期回调 (每 watchdog_thresh/2 秒) │
├──────────────────────────────────────────────────────────────┤
│ 1. 读取当前 jiffies → now │
│ 2. 计算 delta = now - per_cpu(softlockup_timestamp[cpu]) │
│ 3. 若 delta > 2 * watchdog_thresh → 触发软锁死告警 │
│ 4. 否则:重新启动 hrtimer │
│ │
│ 每个时钟 tick 中: │
│ - 调度器更新当前任务的运行时间戳 │
│ - touch_softlockup_watchdog() 刷新 timestamp │
└──────────────────────────────────────────────────────────────┘
关键实现逻辑 (kernel/watchdog.c):
static enum hrtimer_restart softlockup_fn(struct hrtimer *hrtimer)
{
unsigned long now = jiffies;
unsigned long delta = now - __this_cpu_read(softlockup_timestamp);
if (delta < 2 * watchdog_thresh) {
/* 正常:重新设置定时器 */
hrtimer_forward_now(hrtimer,
ms_to_ktime(watchdog_thresh / 2 * 1000));
return HRTIMER_RESTART;
}
/* 软锁死!当前 CPU 超过 2*watchdog_thresh 未被调度切换 */
softlockup_hrtimer, now, delta);
/* 打印全栈回溯并可选触发 panic */
if (softlockup_panic)
panic("softlockup: hung tasks");
return HRTIMER_RESTART;
}
2.3 刷新机制:为什么关中断不会误报?
每当进程被调度器切换时,scheduler_tick() → watchdog_tick() → touch_softlockup_watchdog() 刷新当前 CPU 的时间戳。这意味着:
- 只要调度器还在运行(软锁死的前提),时间戳就会被刷新
- 如果一个内核线程死循环但不主动让出 CPU,调度器最终会在时间片耗尽时 tick,若此时仍无法抢占(如
preempt_disable()),则softlockup_count累加
- 仅当连续错过 2 个完整检测周期(默认 20 秒)才上报软锁死
三、硬锁死检测:NMI Watchdog 与本地 APIC
硬锁死的检测比软锁死困难得多:既然中断已被关闭,常规手段无法执行。Linux 利用 NMI(不可屏蔽中断) 来穿透关中断状态。
3.1 NMI Watchdog 的硬件基础
现代 x86 CPU 通过本地 APIC(Advanced Programmable Interrupt Controller)提供 NMI 能力:
┌─────────────────────────────────────────────────┐
│ Intel APIC NMI 路径 │
├─────────────────────────────────────────────────┤
│ Local APIC Timer │
│ ├─ 模式:Periodic(周期性)或 TSC-Deadline │
│ ├─ 向量:NMI_VECTOR (=2) │
│ ├─ 目标:当前 CPU(Self-IPI) │
│ └─ 行为:即使 IF=0(关中断)也能触发 │
├─────────────────────────────────────────────────┤
│ NMI 不可屏蔽特性: │
│ - 不受 EFLAGS.IF 影响 │
│ - 独立于普通中断向量表 │
│ - 典型延迟 < 1μs(等同本地中断延迟) │
└─────────────────────────────────────────────────┘
3.2 内核 NMI Watchdog 实现
NMI Watchdog 在 kernel/watchdog_hld.c 中实现,核心机制:
// kernel/nmi.c(简化)
static DEFINE_PER_CPU(unsigned long, hrtimer_interrupts);
static DEFINE_PER_CPU(unsigned long, hardlockup_timestamp);
static DEFINE_PER_CPU(bool, hardlockup_happened);
/*
* NMI handler: 由 APIC 定时器周期性触发
* 关键:即使关中断,NMI 也能进入此 handler
*/
static noirq void hardlockup_handler(struct pt_regs *regs)
{
u64 now = local_clock();
u64 delta = now - __this_cpu_read(hardlockup_timestamp);
/* 检查距离上次 hrtimer tick 的时间 */
if (delta < hardlockup_thresh) {
/* 正常:hrtimer 仍在运行 */
__this_cpu_write(hardlockup_timestamp, now);
return;
}
/*
* 硬锁死!hrtimer 已停止超过阈值。
* 这意味着 CPU 关中断后未恢复,或进入了死循环
* 不响应中断处理。
*/
__this_cpu_write(hardlockup_happened, true);
/* 触发硬锁死告警 */
hardlockup_detected(regs, delta);
}
arch/x86/kernel/apic/apic.c 中的定时器配置:
static void setup_nmi_watchdog(void)
{
if (!nmi_watchdog_enabled)
return;
/* 设置 APIC LVT Timer 为 NMI 模式 */
apic_write(APIC_LVT_TIMER, APIC_LVT_DM_NMI | APIC_LVT_TIMER_PERIODIC);
/* 设置分频和计数 */
apic_write(APIC_TMICT, watchdog_clk_div * watchdog_thresh_cycles);
/* 启用定时器 */
apic_write(APIC_LVTT, APIC_LVT_MASKED & ~1); // unmask
}
3.3 检测时序图
时间轴(ms): 0 2.5 5 7.5 10 12.5 15 17.5 20
| | | | | | | | |
hrtimer: TICK TICK TICK TICK TICK TICK TICK TICK ← 软锁死正常时运行
↓
local_irq_disable()
↓
[CPU 关中断,循环死锁]
↓
NMI(2.5s): NMI NMI NMI NMI NMI NMI NMI NMI NMI
↓ ↓
检查htimer_interrupts 发现中断计数
发现无新 tick 停止增加 → 硬锁死!
核心判断逻辑:NMI handler 检查 hrtimer(或本地 APIC 周期性中断)是否在合理窗口内触发过。如果 NMI 触发间隔远超预期(说明普通定时器中断已停止),则判定为硬锁死。
3.4 perf NMI Watchdog 替代方案
从 Linux 3.10+ 开始,perf 事件机制也可以作为 NMI Watchdog:
# 使用 perf 事件作为 NMI watchdog(更高效,避免双重 NMI)
echo 1 > /proc/sys/kernel/nmi_watchdog
# 或
sysctl kernel.nmi_watchdog=1
# 查看当前 watchdog 状态
cat /proc/sys/kernel/nmi_watchdog # 0=禁用 1=启用
cat /proc/sys/kernel/watchdog # 软锁死全局开关
cat /proc/sys/kernel/watchdog_thresh # 阈值(默认10秒)
perf NMI Watchdog 的优势在于复用 perf 基础设施,不需要单独的 APIC 定时器配置,且能更精确地追踪性能事件。
四、内核参数与生产配置
4.1 关键 sysctl 参数
| 参数 | 默认值 | 说明 |
|---|---|---|
kernel.nmi_watchdog |
1 | 启用/禁用硬锁死检测(NMI watchdog) |
kernel.soft_watchdog |
1 | 启用/禁用软锁死检测 |
kernel.watchdog |
1 | 全局 watchdog 总开关 |
kernel.watchdog_thresh |
10 | 软锁死检测阈值(秒) |
kernel.hardlockup_panic |
0 | 硬锁死时是否 panic |
kernel.softlockup_panic |
0 | 软锁死时是否 panic |
kernel.hardlockup_all_cpu_backtrace |
0 | 硬锁死时是否打印所有 CPU 回溯 |
4.2 Linux 5.x+ 的重要变化
Linux 5.0 引入了 cgroup v2 集成,使得 watchdog 可以按 cgroup 分组管理:
// 新特性:per-cgroup 的 softlockup 权限控制
// 允许对不受信任的 cgroup 关闭软锁死检测(避免容器内误报)
echo 0 > /sys/fs/cgroup/mycontainer/memory.watchdog_thresh
Linux 6.x 进一步增加了 BPF 跟踪集成:
# 使用 BPF 追踪 watchdog 事件
bpftrace -e 'tracepoint:watchdog:softlockup_detected {
printf("softlockup on cpu %d\n", args->cpu);
}'
4.3 生产环境推荐配置
# /etc/sysctl.d/99-watchdog.conf
kernel.watchdog_thresh = 15 # 云环境适当放宽到15秒
kernel.hardlockup_panic = 1 # 硬锁死立即 panic 重启
kernel.softlockup_panic = 0 # 软锁死先告警,不立即 panic
kernel.hardlockup_all_cpu_backtrace = 1 # 硬锁死时打印所有CPU栈
kernel.nmi_watchdog = 1 # 启用 NMI watchdog
五、实战:模拟锁死与检测验证
5.1 模拟硬锁死(关中断死循环)
// 注意:这会导致实例无响应,仅用于测试
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/sched.h>
static int hardlockup_thread(void *data)
{
pr_info("Starting hardlockup simulation...\n");
/* 关中断,进入死循环 → 若干秒后 NMI 检测硬锁死 */
local_irq_disable();
while (true)
cpu_relax(); // PAUSE 指令,降低功耗
return 0;
}
static int __init test_init(void)
{
kthread_run(hardlockup_thread, NULL, "hardlockup_sim");
return 0;
}
预期输出:
[ 123.456] watchdog: BUG: soft lockup - CPU#2 stuck for 23s! [hardlockup_sim:1234]
[ 133.789] NMI watchdog: BUG: hard lockup - CPU#2 stuck for 24s! [hardlockup_sim:1234]
[ 133.790] Modules linked in: test_lockup(O)
[ 133.791] CPU: 2 PID: 1234 Comm: hardlockup_sim Tainted: G O
[ 133.792] Hardware name: Dell Inc. PowerEdge R740/0KMX1P, BIOS 2.3.10
[ 133.793] Call Trace:
[ 133.794] hardlockup_thread+0x23/0x30 [test_lockup]
5.2 模拟软锁死(关抢占死循环)
/* 关抢占但保持中断 → 软锁死(中断仍能响应,调度器被禁用)*/
preempt_disable();
while (true)
cpu_relax();
5.3 使用 QEMU/KVM 调试
# 启动 QEMU 并启用 NMI watchdog
qemu-system-x86_64 \
-kernel vmlinuz \
-append "nmi_watchdog=1 watchdog_thresh=5 console=ttyS0" \
-enable-kvm \
-nographic \
-m 2G
# 触发 NMI(从宿主机)
echo 'm' | sudo tee /proc/sysrq-trigger # 手动触发 NMI
六、高级调试:当 Watchdog 触发时我们能得到什么
6.1 硬锁死时的全 CPU 回溯
当某个 CPU 发生硬锁死时,内核会向所有其他 CPU 发送 NMI IPI,强制它们打印调用栈:
// kernel/watchdog_hld.c
static void hardlockup_detected(struct pt_regs *regs, u64 delta)
{
/* 打印当前(卡死)CPU 的栈 */
pr_emerg("BUG: hard lockup - CPU#%d stuck for %llus!\n",
smp_processor_id(), delta / NSEC_PER_SEC);
show_regs(regs);
dump_stack();
/* 触发其他 CPU 打印回溯 */
if (hardlockup_all_cpu_backtrace) {
trigger_all_cpu_backtrace();
/*
* 通过 NMI IPI 发送,其他 CPU 的 NMI handler
* 会调用 dump_cpu_task() 打印各自栈
*/
}
}
6.2 Hung Task Detector 的互补
Watchdog 检测的是 CPU 级别的卡住,而 Hung Task Detector 检测的是进程级别的不响应:
# 启用 hung task 检测(默认启用)
cat /proc/sys/kernel/hung_task_timeout_secs # 默认 120 秒
cat /proc/sys/kernel/hung_task_panic # 超时是否 panic
cat /proc/sys/kernel/hung_task_check_count # 最大检测进程数
两者的协作关系:
- Hung Task:每个进程一个超时(可调度但不响应调度)
- Soft Lockup:CPU 粒度(关抢占或长循环)
- Hard Lockup:CPU 粒度(关中断)
┌─────────────────────────────────────────────────────┐
│ Linux 锁死检测三重防线 │
├──────────┬──────────┬──────────┬───────────────────┤
│ 机制 │ 粒度 │ 前提 │ 硬件需求 │
├──────────┼──────────┼──────────┼───────────────────┤
│Hung Task │ 进程 │ 调度器活 │ 无 │
│Soft │ CPU │ 中断正常 │ hrtimer (Timer) │
│Hard │ CPU │ 中断关闭 │ NMI / APIC │
└──────────┴──────────┴──────────┴───────────────────┘
6.3 ftrace 追踪 watchdog 行为
# 追踪 softlockup detector 的 hrtimer 触发
cd /sys/kernel/debug/tracing
echo 'softlockup' > set_event
echo 'sched_switch' >> set_event
echo 'hrtimer_expire_entry' >> set_event
echo 1 > tracing_on
# 触发软锁死场景后查看轨迹
cat trace
七、Watchdog 的实现细节与边界条件
7.1 为什么 NMI 处理需要注意 re-entrancy?
NMI handler 不能安全地调用大部分内核 API(无锁语义、无阻塞),因此:
/* NMI handler 限制清单 */
❌ 不可调用可能导致睡眠的函数 (kmalloc(GFP_KERNEL), mutex_lock...)
❌ 不可调用 printk_ratelimit() (可能触发调度)
✓ 使用 printk_deferred() (延迟打印,避免 NMI 死锁)
✓ 使用原子操作 (atomic_inc, atomic_cmpxchg)
✓ 使用 per-cpu 变量避免锁竞争
7.2 Watchdog 与 CPU Hotplug 的交互
CPU 热插拔时需要暂停/恢复 watchdog:
// kernel/watchdog.c
static int watchdog_cpu_dying(unsigned int cpu)
{
/* CPU 下线:停止其 hrtimer,避免迁移时误报 */
hrtimer_cancel(&per_cpu(softlockup_hrtimer, cpu));
/* 标记 CPU 为离线,跳过检测 */
cpumask_clear_cpu(cpu, &watchdog_cpus);
return 0;
}
static int watchdog_cpu_online(unsigned int cpu)
{
/* CPU 上线:重新初始化并启动 watchdog */
watchdog_enable(cpu);
return 0;
}
7.3 Watchdog 在虚拟化环境中的行为
- KVM Guest:需要
kvm.nested=1+kvm.ignore_msrs=0才能让 Guest 使用 NMI watchdog
- AWS/GCP 云环境:部分实例类型不支持嵌套 APIC 虚拟化,硬锁死检测会降级为软锁死模式
- 检测降级:
dmesg中会出现NMI watchdog: Perf event create on CPU 0 failed with -2提示
八、总结
Linux Kernel Watchdog 系统是操作系统可靠性的关键底层机制:
- 软锁死检测:基于 hrtimer 超时,依赖调度器仍在运行,检测"可抢占上下文长时间不释放"
- 硬锁死检测:基于 NMI/APIC 的不可屏蔽中断能力,是检测"关中断死锁"的唯一手段
- 三重防线:Hung Task + Soft Lockup + Hard Lockup 覆盖从进程到 CPU 的完整层级
对于生产系统,建议始终启用 watchdog 并根据 SLA 调整阈值。关键系统应配置 hardlockup_panic=1 让故障节点自动重启迁移,而非挂起等待人工介入。理解 Watchdog 的内部机制,也是内核开发者定位复杂死锁问题的必备功底。
参考资源
Documentation/sysctl/kernel.rst— watchdog 参数官方文档
kernel/watchdog.c— 软锁死检测实现
kernel/watchdog_hld.c— 硬锁死检测实现(x86 APIC 相关在arch/x86/kernel/apic/apic.c)
- Intel SDM Vol. 3A Chapter 10 — APIC 与 NMI 硬件规范
tools/perf/Documentation/perf-record.txt— perf NMI watchdog 用法

发表评论 取消回复