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场景包括:

  1. 内核模块死循环:驱动代码while循环未调用cond_resched()
  2. 禁用抢占或中断过长:spinlock持有时间过长、local_irq_disable()后未及时恢复
  3. 密集内存操作:大规模内存拷贝未分段释放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触发场景

  1. 关中断时间过长:__disable_irq()后死循环,PMU中断也无法响应
  2. 硬件故障:内存总线故障导致CPU完全挂起
  3. 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检测时需注意:

  1. NMI Watchdog不工作:容器无法访问PMU硬件,Hard Lockup检测不可用
  2. Soft Lockup检测受限:容器中的hrtimer可能已被宿主内核接管
  3. 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 防御性系统建设

  1. 启用kdump:配置kernel.softlockup_panic=1和kernel.hung_task_panic=1,确保lockup时自动触发核心转储
  2. 配置智能告警:使用kernel.hung_task_warnings限制告警风暴
  3. 部署带外监控:通过IPMI/BMC串口日志捕获锁死瞬间信息
  4. 定期压力测试:使用stress-ng模拟高负载场景,验证watchdog行为
  5. 保持内核更新:watchdog子系统持续改进,新内核修复了大量误报和漏报

十、参考资料

  • Linux内核源码:kernel/watchdog.c、kernel/hung_task.c
  • Documentation/admin-guide/kernel-parameters.txt 中watchdog相关参数
  • Documentation/admin-guide/lockup-watchdog.rst
  • Red Hat Enterprise Linux Troubleshooting Guide 第11章"Debugging System Hangs"

运维的本质是让系统在失败前暴露问题。Lockup检测机制就是这样一扇"窗户"——它无法避免问题,但能确保我们在下一次故障中更快地找到根因。做好watchdog配置,就是给系统买一份"意外险"。


本文基于Linux 5.4/5.15 LTS内核源码分析,适用于主流发行版的生产环境配置。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿
网站二维码

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部