Linux内核Lockup检测机制深度实战:从Soft Lockup到Hard Lockup
在Linux系统的生产环境中,"系统卡死"是最令人头疼的故障之一。无论是内核线程陷入死循环、硬件故障导致中断丢失,还是进程长时间处于不可中断的等待状态,都会严重影响系统可用性。Linux内核提供了一套完善的Lockup检测机制——包括Soft Lockup检测、Hard Lockup检测(基于NMI Watchdog)以及Hung Task检测,它们构成了系统健康的"三道防线"。本文将深入剖析这三大机制的原理、实现细节和实战调试方法。
一、Lockup故障分类体系
Linux内核将"系统无响应"分为三个层次:
| 类型 | 别名 | 触发条件 | 检测机制 |
|---|---|---|---|
| Soft Lockup | 软死锁 | 内核线程长时间占用CPU,不释放调度 | hrtimer中断检测 |
| Hard Lockup | 硬死锁 | CPU完全无法响应中断(包括NMI) | NMI Watchdog检测 |
| Hung Task | 任务卡死 | 用户态进程长时间处于D(TASK_UNINTERRUPTIBLE)状态 | 高分辨率定时器 |
三者的严重程度依次递增:Soft Lockup可以恢复,Hard Lockup意味着硬件级故障,而Hung Task则处于两者之间。
二、Soft Lockup深度解析
2.1 核心原理
Soft Lockup的核心思想非常简单:如果某个CPU上的内核代码执行了过长时间(默认20秒)而没有发生时钟中断触发的调度检查,则判定为Soft Lockup。
内核使用高分辨率定时器(hrtimer)来实现检测。每个CPU上都有一个softlockup检测定时器,该定时器在时钟中断的底半部(tick_softirq)中重新装载。如果当前CPU的"心跳计数器"在连续两次检测周期内没有变化,说明该CPU上的代码在执行期间没有给调度器运行的机会。
// kernel/watchdog.c
static void soft_timer_fn(struct timer_list *unused)
{
int cpu = smp_processor_id();
if (unlikely(!watchdog_cpu_started(cpu)))
return;
/* 如果自上次检测后心跳计数器未更新 */
if (softlockup_panic_enabled && !touch_ts) {
/* 心跳停滞,记录并报告Soft Lockup */
softlockup_touch_watchdog();
}
/* 重新装载定时器,每4秒检测一次 */
__mod_timer(...)
}
2.2 关键参数与配置
通过sysctl可调整Soft Lockup行为:
# 查看当前阈值(默认20秒)
sysctl kernel.softlockup_thresh
# kernel.softlockup_thresh = 20
# 启用panic模式(检测到即触发kdump)
sysctl kernel.softlockup_panic=1
# 注:需要同时开启kernel.panic和kernel.panic_on_oops
# 查看Soft Lockup检测是否开启
cat /proc/sys/kernel/softlockup_cpu_threshold
# 输出检测开启的CPU数量
2.3 触发条件分析
典型的Soft Lockup场景包括:
- 内核模块死循环:驱动代码while循环未调用cond_resched()
- 禁用抢占或中断过长:spinlock持有时间过长、local_irq_disable()后未及时恢复
- 密集内存操作:大规模内存拷贝未分段释放CPU
实际案例中,最危险的是那种"看似正确但实际有问题"的代码模式:
/* 危险模式:长时间持有spinlock */
spin_lock(&my_lock);
for (i = 0; i < 1000000; i++) {
/* 每次迭代耗时微妙,总体超过20秒 */
do_something_cpu_intensive(&data[i]);
}
spin_unlock(&my_lock);
三、Hard Lockup深度解析
3.1 核心原理
Hard Lockup比Soft Lockup严重得多。当CPU的中断被完全禁用(包括不可屏蔽中断NMI)时,Soft Lockup的检测机制本身就失效了。为此,Linux使用Performance Monitoring Unit (PMU) 计数器产生的NMI来检测Hard Lockup。
实现架构如下:
Performance Counter (每周期递增)
|
v
达到阈值时触发NMI
|
v
NMI Handler (perf_event_nmi_handler)
|
v
更新hardlockup_touch_ts[cpu]
|
v
如果连续2个周期未更新 → 触发Hard Lockup告警
核心检测逻辑:
// arch/x86/kernel/nmi.c (以x86为例)
static int hw_nmi(unsigned int cmd, struct pt_regs *regs)
{
/* 仅处理PMU计数器溢出导致的NMI */
if (in_nmi()) {
/* 来自PMU的NMI:说明CPU能响应中断,刷新计数器 */
hardlockup_touch_watchdog();
return;
}
/* 处理其他NMI源... */
}
/* 周期性检查 */
static void hardlockup_detector_timer_fn(struct timer_list *unused)
{
int cpu = smp_processor_id();
/* 检查上次心跳时间 */
if (hardlockup_touch_ts[cpu] < (jiffies - HARDLOCKUP_CHECK_PEROID)) {
/* CPU未能在规定时间内刷新NMI心跳 */
hardlockup_panic(cpu);
}
}
3.2 配置与启用
# 查看Hard Lockup检测状态
sysctl kernel.nmi_watchdog
# kernel.nmi_watchdog = 1 (0=禁用, 1=启用)
# 查看当前使用的Watchdog类型
cat /proc/sys/kernel/watchdog
# 0=禁用, 1=启用(使用NMI/NMI-Watchdog)
# 对于使用HPET的系统,需确认HPET已启用
dmesg | grep -i hpet
3.3 Hard Lockup触发场景
- 关中断时间过长:__disable_irq()后死循环,PMU中断也无法响应
- 硬件故障:内存总线故障导致CPU完全挂起
- BIOS/ACPI固件bug:错误的SMI(系统管理中断)处理占用CPU时间过长
在实际排查中,Hard Lockup的日志输出极为有限,因为如果PMU不能产生NMI,连栈回溯都无法输出。此时需要借助串口/IPMI等带外手段获取崩溃信息。
四、Hung Task检测机制
4.1 核心原理
Hung Task检测器监控所有处于TASK_UNINTERRUPTIBLE(D状态)的进程。当某个进程在D状态停留超过timeout秒(默认120秒),内核就会打印告警并输出该进程的调用栈。
D状态通常由I/O等待引起:
用户态调用read()/write()系统调用
|
v
进入内核,发起块设备I/O请求
|
v
进程设为TASK_UNINTERRUPTIBLE(无法被信号打断)
|
v
等待I/O完成中断唤醒
|
v
中断到达:唤醒进程 → TASK_RUNNING
4.2 检测实现
// kernel/hung_task.c
static int hung_task_panic=0;
static void check_hung_task(struct task_struct *t, unsigned long timeout)
{
unsigned long switch_count = t->nvcsw + t->nivcsw;
if (switch_count != t->last_switch_count) {
/* 进程状态有变化,重置计数器 */
t->last_switch_count = switch_count;
t->last_switch_time = jiffies;
return;
}
/* 处于D状态且超过timeout */
if (time_is_after_jiffies(t->last_switch_time + timeout)) {
/* 首次打印告警(仅打印一次) */
if (!sysctl_hung_task_warnings ||
(sysctl_hung_task_warnings-- > 0)) {
pr_emerg("INFO: task %s:%d blocked for more than %ld seconds.\n",
t->comm, t->pid, timeout / HZ);
sched_show_task(t); /* 打印调用栈 */
if (sysctl_hung_task_panic)
panic("hung task");
}
}
}
检测器周期由kernel.hung_task_check_interval_secs控制(默认值由编译时配置决定)。
4.3 关键参数
# 查看超时阈值(默认120秒)
sysctl kernel.hung_task_timeout_secs
# kernel.hung_task_timeout_secs = 120
# 设置告警次数限制(-1表示无限制)
sysctl kernel.hung_task_warnings=10
# 启用检测到hung task后panic
sysctl kernel.hung_task_panic=1
# 查看是否允许检测所有阻塞任务
sysctl kernel.hung_task_all_cpu_backtrace=1
五、三大Lockup机制协同工作
5.1 架构总览
┌─────────────────────────────────────────────────────────────────┐ │ 系统运行时间轴 │ │ │ │ ┌──────────────┐ ┌─────────────────┐ ┌──────────────────┐ │ │ │ Soft Lockup │ │ Hard Lockup │ │ Hung Task │ │ │ │ (hrtimer) │ │ (NMI Watchdog) │ │ (高分辨率定时器)│ │ │ │ │ │ │ │ │ │ │ │ 每4秒检测 │ │ 每10秒检测 │ │ 每120秒超时 │ │ │ │ 阈值: 20秒 │ │ 阈值: ~10秒 │ │ (可配置) │ │ │ └──────────────┘ └─────────────────┘ └──────────────────┘ │ │ │ │ ▲ 严重程度递增 │ │ │ 恢复可能性递减 │ └─────────────────────────────────────────────────────────────────┘
5.2 参数调优建议
生产环境的推荐配置:
# === /etc/sysctl.d/99-watchdog.conf ===
# Soft Lockup: 20秒阈值,开启panic(配合kdump保留现场)
kernel.softlockup_panic = 1
# Hard Lockup: 启用NMI Watchdog,开启panic
kernel.nmi_watchdog = 1
# Hung Task: 120秒超时,限制告警次数避免风暴
kernel.hung_task_timeout_secs = 120
kernel.hung_task_warnings = 5
# 高负载系统建议增大阈值避免误报
# kernel.softlockup_thresh = 30
对于某些特定场景需做调整:
- HPC/实时计算场景:可暂时禁用NMI Watchdog以避免干扰(
kernel.nmi_watchdog=0),自行承担风险 - 嵌入式系统:内存有限时,建议关闭panic模式,仅记录日志
- 虚拟化环境:KVM/QEMU客户机的NMI Watchdog可能不稳定,建议依赖宿主机监控
六、Lockup故障实战排查
6.1 排查工具箱
| 工具 | 用途 | 命令示例 |
|---|---|---|
dmesg -w |
实时查看内核日志 | dmesg -Tw \| grep -i 'lockup\|hung' |
ps aux |
查看D状态进程 | ps -eo pid,stat,wchan:20,comm \| grep ' D ' |
perf top |
实时CPU热点分析 | perf top -C 0 |
| crash | 分析vmdump | crash /usr/lib/debug/lib/modules/$(uname -r)/vmlinux /var/crash/vmcore |
| IPMI SOL | 带外查看崩溃瞬间日志 | ipmitool sol activate |
6.2 分析流程
系统卡死/无响应
|
v
检查是否有Soft Lockup日志 ─── 有 ──► 分析栈回溯,定位长时间持锁代码
|
无
|
v
检查是否有Hard Lockup日志 ─── 有 ──► 检查PMU状态,排查硬件/BIOS问题
|
无
|
v
检查是否有Hung Task日志 ─── 有 ──► 查看D状态进程,排查I/O/网络/存储问题
|
无
|
v
需要带外手段(串口/IPMI/BMC)收集更底层信息
6.3 经典案例:iSCSI存储间歇性雪崩
某生产系统出现周期性无响应,dmesg显示:
[482391.545753] INFO: task kswapd0:123 blocked for more than 120 seconds.
[482391.545788] Tainted: G W OE E 5.4.0-65-generic
[482391.545790] "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
[482391.545793] kswapd0 D 0 123 2 0x00000000
[482391.545803] Call Trace:
[482391.545806] __schedule+0x24c/0x650
[482391.545808] schedule+0x43/0xd0
[482391.545810] io_schedule+0x12/0x40
[482391.545812] blk_mq_get_tag+0x13e/0x270
分析:kswapd0内存回收需要写脏页到iSCSI存储,但存储暂时不可达,导致请求队列满,kswapd0在D状态等待标记(tag)释放。这是典型的存储I/O阻塞引发的连锁卡死。
排查路径:
1. 存储网络延迟检测(ping存储Target)
2. 检查mpt3sas/megaraid驱动日志
3. 分析/proc/diskstats和/sys/block/sdX/stat查看disk io延迟
4. 调整存储多路径策略和超时参数
# 增加块设备超时时间
echo 180 > /sys/block/sda/device_timeout
# 检查SCSI EH状态
cat /sys/class/scsi_device/0\:0\:0\:0/device/state
七、内核源码深入分析
7.1 Watchdog线程初始化
Linux 5.x内核中,watchdog的检测线程由watchdog/提供:
// kernel/watchdog.c
static DEFINE_PER_CPU(unsigned long, watchdog_touch_ts);
static DEFINE_PER_CPU(struct hrtimer, softlockup_hrtimer);
static int __init watchdog_init(void)
{
int cpu, ret;
for_each_cpu(cpu, cpu_online_mask) {
/* 初始化每个CPU的心跳时间戳 */
per_cpu(watchdog_touch_ts[cpu), cpu) = jiffies;
/* 创建hrtimer用于Soft Lockup检测 */
hrtimer_init(...);
hrtimer_start(...);
}
return 0;
}
7.2 NMI Watchdog注册
以x86架构为例,NMI Watchdog使用性能计数器产生NMI:
// arch/x86/events/core.c
static void watchdog_overflow_callback(struct perf_event *event,
struct perf_sample_data *data,
struct pt_regs *regs)
{
/* 每个采样周期触发,NMI中更新touch_ts */
hardlockup_touch_watchdog();
/* 重新编程计数器... */
}
/* 注册perf事件,溢出时产生NMI */
event = perf_event_kernel_counter_create(
PERF_TYPE_HARDWARE, PERF_COUNT_CPU_CYCLES,
watchdog_overflow_callback, NULL, smp_processor_id());
7.3 Hung Task超时检测
// kernel/hung_task.c
static int __init hung_task_init(void)
{
/* 创建hung_task检测工作队列 */
atomic_notifier_chain_register(...)
/* 创建高精度定时器 */
timer_setup(&hung_task_timer, check_hung_tasks_cb, 0);
/* 启动检测循环 */
schedule_delayed_work(&hung_task_work,
sysctl_hung_task_check_interval_secs * HZ);
}
八、Lockup检测在容器与云环境中的特殊考量
8.1 容器中的Watchdog行为
在容器化部署Lockup检测时需注意:
- NMI Watchdog不工作:容器无法访问PMU硬件,Hard Lockup检测不可用
- Soft Lockup检测受限:容器中的hrtimer可能已被宿主内核接管
- Hung Task检测仍有效:D状态检测完全在软件层面工作
建议容器环境主要依赖: - 宿主机的NMI Watchdog检测底层Kernel Lockup - 容器健康检查(liveness probe) - 应用层超时和重试
8.2 虚拟化环境注意事项
KVM/QEMU虚拟机中NMI Watchdog的行为:
# 查看虚拟机PMU支持
grep -e 'pmu' /proc/cpuinfo
# 在QEMU命令行中确保pass-through PMU
-cpu host,+pmu
# 如果PMU不可用,Hard Lockup检测会失效
dmesg | grep -i "nmi watchdog"
# "NMI watchdog: Perf event create on CPU 0 failed with -2"
# 表示PMU无法访问,需要配置宿主机直通
九、总结与最佳实践
9.1 核心参数速查表
| 参数 | 路径 | 默认值 | 建议值 | 说明 |
|---|---|---|---|---|
| Soft阈值 | /proc/sys/kernel/softlockup_thresh |
20(s) | 20-30 | 内核代码执行超时阈值 |
| Soft Panic | /proc/sys/kernel/softlockup_panic |
0 | 1 | 检测到是否宕机保留现场 |
| NMI Watchdog | /proc/sys/kernel/nmi_watchdog |
1 | 1 | 启用/禁用Hard Lockup检测 |
| Hung超时 | /proc/sys/kernel/hung_task_timeout_secs |
120(s) | 60-120 | D状态超时阈值 |
| Hung告警次数 | /proc/sys/kernel/hung_task_warnings |
10 | 5-10 | 防日志风暴 |
9.2 防御性系统建设
- 启用kdump:配置
kernel.softlockup_panic=1和kernel.hung_task_panic=1,确保lockup时自动触发核心转储 - 配置智能告警:使用
kernel.hung_task_warnings限制告警风暴 - 部署带外监控:通过IPMI/BMC串口日志捕获锁死瞬间信息
- 定期压力测试:使用
stress-ng模拟高负载场景,验证watchdog行为 - 保持内核更新:watchdog子系统持续改进,新内核修复了大量误报和漏报
十、参考资料
- Linux内核源码:
kernel/watchdog.c、kernel/hung_task.c Documentation/admin-guide/kernel-parameters.txt中watchdog相关参数Documentation/admin-guide/lockup-watchdog.rstRed Hat Enterprise Linux Troubleshooting Guide第11章"Debugging System Hangs"
运维的本质是让系统在失败前暴露问题。Lockup检测机制就是这样一扇"窗户"——它无法避免问题,但能确保我们在下一次故障中更快地找到根因。做好watchdog配置,就是给系统买一份"意外险"。
本文基于Linux 5.4/5.15 LTS内核源码分析,适用于主流发行版的生产环境配置。

发表评论 取消回复