
半夜两点被监控电话叫醒登录服务器一看内核日志里刷了满屏的soft lockupCPU 打满业务掉点反应迟缓。很多运维同行遇到这个告警第一反应就是把watchdog_thresh调大——你以为是给系统“松绑”实际上只是捂住了耳朵假装没听到警报。这个参数背后的内核看门狗机制远比表面上看到的“调个数字”复杂得多这篇文章就把我在实际排障中对 soft lockup 和 watchdog_thresh 的全部理解一次说清楚。1. 先搞清楚soft lockup 到底在报什么1.1 看门狗的本质是一场“调度健康检查”内核看门狗机制的英文全称是soft lockup detector它的核心任务只有一个检查每一个 CPU 上的内核代码路径是不是还“活着”。这里说的“活着”不是看 CPU 有没有通电而是在内核中启动一个长期循环的 watchdog 线程这个线程以实时调度优先级SCHED_FIFO运行它会在每个 CPU 上周期性地“踹一脚”一个计数器。硬件高精度定时器hrtimer在每次中断到来时把计数器加一如果下一次中断发现计数器已经很久没被更新了那么就说明这个 CPU 上的调度机制或者中断执行出了问题——某种死循环、自旋锁或者是不能被抢占的内核路径已经把整个 CPU 卡住了。2016 年之后的内核把watchdog线程与时钟中断解耦改用 per-CPU hrtimer。它的触发流程大致是每个 CPU 上都注册了一个watchdog_timer_fn回调函数这个回调会去更新当前 CPU 的watchdog_touch_ts时间戳并重新唤醒喂狗线程。只要这个回调在watchdog_thresh 的两倍时间内不再执行内核就直接判定发生了 soft lockup。1.2 怎么区分 soft lockup 和 hard lockupsoft lockup的表现是调度器无法在该 CPU 上完成任务切换但中断还能正常响应所以系统整体可能只是局部卡顿。而hard lockup连硬件中断NMI不可屏蔽中断都无法响应整个 CPU 处于完全死寂状态。内核用nmi_watchdog机制来检测 hard lockup利用 NMI 中断触发 perf event 采样如果连续的 NMI 中断都无法中断到一个正在执行的处理器核心就说明这颗 CPU 被彻底冻住了。两者的区分对排障方向至关重要。我实际遇到过不少 case业务都在报“服务器假死”但内核日志只报了 soft lockup排查半天发现是某块网卡驱动在中断上下文里死循环反过来也有只报 hard lockup 的情况最后定位到是 CPU 降频 固件 bug 共同导致的连中断都没法响应。把这两类问题混为一谈很容易把排查方向带偏。1.3 进程级 vs 内核级必须分清“卡在哪一层”soft lockup 的报错信息里通常会有Call Trace但很多初级运维容易忽略一个关键区分soft lockup 永远意味着卡在内核态代码路径上而不是用户态某个进程占用太多 CPU。一个用户态进程就算 100% 占用 CPU它迟早会被内核调度器切换出去给 watchdog 线程让路只有永远不被切换出去的代码才会触发 soft lockup——比如遍历一个无限链表、持锁自旋、在关中断的代码块里忙等、或者 CPU 因为虚拟化原因被长时间抢占。理解了这个层级就能明白单纯看ps aux决定排查方向是错误的因为你看不到内核态的拉锯。后文讲到的所有排查手段本质都是在回答一个问题这颗 CPU 到底在内核态里跑了多久又是被哪段代码拖住的2. 为什么调大 watchdog_thresh 只是在“掩盖”2.1 参数的真实含义它只改告警灵敏度不改病因watchdog_thresh直译过来是“看门狗阈值”默认值 20单位是秒。它在/proc/sys/kernel/watchdog_thresh下可以实时调整但很多把“调大阈值 解决问题”当作灵丹妙药的人完全没有理解这个参数的真实含义。它的作用机制非常简单且粗暴watchdog线程会去刷新时间戳hrtimer高精度定时器中断每watchdog_thresh秒检查一次时间戳有没有被更新。如果超过了预期更新时间就报告。换句话说它就是一个定时器闹钟——把闹钟间隔从 20 秒调到 60 秒闹钟确实不会响了但 CPU 卡住的事实依然存在你的服务依旧在掉线、请求依旧在超时。CPU 无法调度用户进程的问题根本没有被解决你把阈值调成 600 秒系统就要忍受整整 10 分钟的业务拉胯状态这在生产环境里是致命的。下表列出了常见参数和它们的实际作用范围参数默认值作用调节效果kernel.watchdog_thresh20soft lockup 检测间隔只影响多久报一次警不解决卡死kernel.softlockup_panic0触发 soft lockup 后强制 panic快速失败避免长时间带病运行kernel.hardlockup_panic0触发 hard lockup 后强制 panic同上kernel.nmi_watchdog1NMI 中断式检测 hard lockup关闭后无法检测完全死锁的 CPUkernel.softlockup_all_cpu_backtrace0报错时打印所有 CPU 栈辅助排查多核并发问题2.2 掩盖的代价你失去了最宝贵的“黄金排障期”真正让调大 watchdog_thresh 显得得不偿失的原因是它吞噬了故障现场。soft lockup 刚发生时Call Trace里往往精确记录着当前 CPU 正在执行的函数——哪段驱动代码、哪个内核模块、哪个地址空间的锁状态这些信息对定位问题具有决定性价值。内核在报警的同时会把detected soft lockup on cpu后面的栈打印到dmesg里如果你第一时间抓取就可以直接锁定嫌疑代码。但如果你提前调大了阈值等于主动放弃了这段关键证据。我曾经排过一个存储节点的 soft lockup 问题刚开始也是被建议“把 watchdog_thresh 调到 60 先压住”。但调大之后再出现问题时dmesg里的调用栈已经面目全非抓了三个晚上都没有定位到异常函数。后来把参数恢复默认用 ftrace 和 kprobe 挂在驱动关键路径上才在第三次触发瞬间抓到现场——原来是 NVMe 驱动在异常掉盘后的重试循环里忘了释放自旋锁导致的一处逻辑死循环。如果没有当时的栈信息这类问题就是大海捞针。2.3 “调度饥饿”不是看门狗能解决的还有一类极其常见的误判是把 CPU 过载导致的“伪 soft lockup”当成真 lockup 来处理。比如宿主机上跑满了虚拟机某个 vCPU 因为宿主机侧资源争抢而长时间得不到调度guest 内核的 hrtimer 计算时间戳时发现已经超过 20 秒没有触发就会报 soft lockup。这类问题的根源是虚拟化层的 vCPU 饥饿你在 guest 里调大 watchdog_thresh 毫无意义——下个周期 CPU 照样跑不动告警照样会来。正确做法是调整宿主机侧 CPU overcommit、绑定 vCPU 到物理核心、开启 CPU steal time 监控等等。换个生活化的类比看门狗是报警器不是灭火器。你嫌报警器吵把它的灵敏度调低但火还在烧。只有搞清楚是什么在烧内核路径长时间占着 CPU 不让并把源头扑灭才是真正的处理方式。3. 正确的排查姿势从现象到根因3.1 第一步确认是不是“伪 lockup”拿到告警先别急着开 perf而是先回答一个前置问题这个 soft lockup 是真实的内核死循环还是虚拟化/固件环境导致的假报我在公有云环境里遇到过一个十分典型的 case客户自建的 KVM 虚拟机中总有间歇性 soft lockup排查到最后发现是宿主机 CPU 超配严重加上宿主机开启了 C-states导致 vCPU 在低负载时被调度到不同的物理核心上guest 的 hrtimer 遭遇了巨大的时钟跳变。这种场景下guest 内核的时钟源和实际物理时间发生较大偏移看门狗会误判。快速验证手段有三类查看 cpu steal timempstat -P ALL 1输出里的%steal如果持续偏高10%优先怀疑宿主机 CPU 争抢。查看内核日志里有没有大量clocksource相关字样的条目这类提示通常说明时钟源切换或偏移。对比触发时间点与业务高峰期、宿主机其他虚拟机负载的关系。如果是虚拟机还需要检查 CPU 型号是否跨代迁移例如在线迁移到不同 CPU 型号但未配置cpu-modehost-passthrough这也会造成时间跳变干扰看门狗。真实的生产环境里这类“假 lockup”至少占我处理过的 soft lockup 告警的三到四成先把这条排除干净后续工作才不白做。3.2 第二步抓住触发瞬间的调用栈确认不是伪 lockup 后接下来最重要的动作是捕捉现场。这个动作只有在触发瞬间才有意义错过了就失去第一手证据。推荐的操作流程是保持kernel.watchdog_thresh为默认值 20不要修改。在告警频发间隔开启内核的软锁全局栈转储sysctl -w kernel.softlockup_all_cpu_backtrace1调低kernel.watchdog_thresh到 10注意是调低不是调大目的是让系统更早地报告问题、更频繁地保留调用现场。设置一个后台脚本在 dmesg 出现soft lockup关键词时自动抓取当时每个 CPU 的运行队列和中断信息#!/bin/bash dmesg -w | while read line; do echo $line | grep -q soft lockup { date /var/log/lockup_$(date %Y%m%d).log cat /proc/softirqs /var/log/lockup_$(date %Y%m%d).log ps -eLo pid,tid,psr,pcpu,stat,comm /var/log/lockup_$(date %Y%m%d).log } done这套组合拳的核心思路是在误差尽可能小的时间窗口内同时拿到内核栈、软中断分布、进程 CPU 分布三份证据。很多公司的排障只保留内核Call Trace但 soft lockup 有可能是软中断计数异常、或者某个进程占住了 CPU 频繁触发软中断导致的光看内核栈定位面太窄。3.3 第三步用 perf 抓热点函数拿到调用栈之后perf可以进一步帮你量化热点函数到底占了多少样本。perf top适合快速看一眼perf record适合做正式的现场取证。# 实时查看内核热点 perf top -C 3 -g -k 1 # 抓取指定 CPU 上 30 秒的性能数据 perf record -C 3 -g -- sleep 30 perf report -n --stdio其中-C 3是触发 lockup 的那个 CPU 编号日志里会明确给出soft lockup on cpu3这样的提示。-g表示记录调用链。拿到perf report后重点看self%最高的内核函数以及它的调用链来源。如果是驱动函数占大头比如e1000e_clean_tx_irq、mlx5e_poll_rx_cq就锁定驱动逻辑慢慢捋如果是自旋锁相关的函数比如_raw_spin_lock、queued_spin_lock_slowpath大量出现在热点里则说明高概率存在锁竞争或死锁。perf在内核 5.x 上配合kprobe还能做针尖对麦芒式的精确定位perf probe ksoftirqd_running perf probe queued_spin_lock_slowpath0 perf record -e probe:ksoftirqd_running -a -- sleep 30实测中这种探针方式能精确定位到是哪个进程路径在不断触发软中断处理对定位网络软中断挤压和驱动锁竞争特别有效。3.4 第四步检查中断与软中断分布soft lockup 最常见的元凶之一是中断和软中断在某个 CPU 上堆积不均导致调度饥饿。尤其多队列网卡如果没配好 RPS/RFS很容易出现一个 CPU 扛中断、其他 CPU 吃瓜的场景。此时中断处理函数太长不断抢占正在运行的任务加上中断线程的实时优先级高于 watchdog就可能在 watchdog 下次检查之前一直霸占 CPU。快速定位方式# 查看硬中断分布 cat /proc/interrupts # 查看软中断计数 cat /proc/softirqs观察触发 lockup 的那个 CPU 的NET_RX和IRQ_POLL计数是不是异常飙升。我曾经处理过一个非常典型的 case某台网关服务器的单队列网卡驱动程序不支持多队列分发所有数据包中断都落在 CPU0 上NET_RX的软中断计数远高于其他核soft lockup 每隔几分钟就报一次。调整RPSReceive Packet Steering后把软中断分散到多个 CPU问题迎刃而解。# 开启 RPS将 8 核心主机上 CPU0 的接收软中断分散到其他核 echo ff /sys/class/net/eth0/queues/rx-0/rps_cpus echo 4096 /sys/class/net/eth0/queues/rx-0/rps_flow_cnt这里ff代表 8 个 CPU 都在允许集合具体值按你核数换算。当然这只是例举真实生产还需要结合 NUMA topo 分配流量避免跨 NUMA 存取导致缓存颠簸反而把性能拉垮。3.5 第五步判断调度器自身的问题如果中断、软中断都正常perf 里也没有明显热点那就要怀疑调度器本身了。重点看r队列运行队列长度、context switch频率和RT任务的抢占行为。vmstat 1 # 如果持续出现 r 列 核数 * 10 的极端情况 # 且 cs上下文切换飙升这种情况常见于 D 状态进程堆积、kworker风暴、或者rcu自旋。另一个非常隐蔽的坑在RCURead-Copy-Update机制上。内核文档明确建议rcu_nocbs把受 RCU 保护的关键路径从工作队列中独立出去否则如果某个 CPU 上积累了过多的rcu_preempt自旋在CONFIG_PREEMPT_RCU内核中会直接卡死调度路径报 soft lockup。实践中当你看到日志中有rcu_sched self-detected stall字样就要警惕了——RCU stall 与 soft lockup 经常伴生出现。此时可以用# 查看当前 RCU 状态 cat /proc/rcu/rcu_pending如果rcu_pending的计数长时间没有变化说明 RCU 回调线程可能被饿死需要检查rcuc线程是否被优先级更高的实时任务抢占。4. 三起真实的 soft lockup 排障实录4.1 案例一网卡中断挤压的“一人扛所有”现象最早出现在一台 8 核 16 线程的压测机上运行的是 Nginx 网关业务高峰时频繁报soft lockup on cpu0。监控面板上 CPU0 的使用率明显高于其他核而且中断数高得离谱。我上机后第一时间抓了cat /proc/interrupts发现网卡 eth0 的所有队列中断全部绑定在 CPU0 上其他核心只有零星的中断计数。再看cat /proc/softirqsNET_RX一项在 CPU0 上达到了每秒十几万次其他核几乎为零。排查思路就是先确认是不是网卡硬件多队列没生效再判断要不要开 RPS。这里硬件本身只有单队列只能靠软件分发RPS 拉起来后把rps_cpus设置为所有在线 CPU并把rps_flow_cnt设为 4096跟 flow 表大小相关再次压测CPU0 的软中断计数明显下降soft lockup 不再出现。这是个比较典型的“中断不均衡导致看门狗线程饿死”的 case调 watchdog_thresh 只会让告警晚来不能阻止 CPU0 被打爆。4.2 案例二驱动自旋锁的“死等”另一个 case 出现在某台数据仓库节点上内核日志每次报soft lockup on cpu7时Call Trace都指向同一个函数家族——某品牌 NVMe 驱动里的nvme_queue_rq及相关路径。perf 抓热点时queued_spin_lock_slowpath占的采样比例高得反常。这里就比较麻烦因为queued_spin_lock_slowpath是通用自旋锁实现只要锁竞争激烈就会采样很高不一定就是死锁需要进一步定位是哪把锁、谁持有不放。我的做法是开启内核的lockdep锁调试功能。虽然它对性能有影响但可以在带病节点上临时开一下抓证据后立即关掉。开启方式是给内核启动参数加上lockdep1或者动态设置kernel.lockdep1。重新触发 lockup 后dmesg里打印出了锁的持有链终于看清了问题本质驱动在某个错误恢复路径上把ctrl-lock自旋锁在中断上下文里持有同时在进程上下文里又去获取同一把锁形成 AB-BA 死锁。这个 bug 在后续驱动版本里被修复。这个案例给我印象最深的地方在于如果是靠调大 watchdog_thresh 把告警压下去那这个锁死问题会在所有并发激增场景下反复爆发最终一定是以更惨烈的方式暴露。4.3 案例三虚拟化 vCPU 饥饿的“假报警”还有一次是在客户的一套私有云上现象非常诡秘多个虚拟机在不同时间段分别报 soft lockup每个虚拟机里运行的都是完全不同的业务有数据库、有消息队列、有 Web 应用。排除通用驱动问题后把嫌疑锁定在宿主机侧。登录宿主机查看 CPU 调度情况发现宿主机上同时跑了几十台虚拟机且cpu_overcommit比例超过了 1:4部分物理核上的 vCPU 线程频繁被其他虚拟机抢占。同时mpstat里%steal高到 20% 以上。guest 内的 CPU 时间远远达不到期望watchdog线程也无法获得调度。这个 case 最终是用宿主机侧给关键虚拟机开启 CPU pinning、关闭 C-states、给 vCPU 线程设置了SCHED_FIFO优先级来解决的。guest 内核的看门狗参数从头到尾就没动过。如果你在虚拟化环境遇到 soft lockup第一反应应该是去看宿主机的 steal 指标而不是调watchdog_thresh。4.4 从案例提炼出的排障优先级以上三个案例处理手段完全不同但共性都是找到真正卡住 CPU 的路径。我建议的排障优先级是先看是不是虚拟化环境的原因快速排除 vCPU 饥饿。再看硬中断和软中断有没有集中在某个核上。抓 perf 热点和调用栈区分驱动逻辑 vs 锁问题。结合dmesg和内嵌的Call Trace、RCU 状态综合判断。这个顺序能让你在最短时间内筛掉“环境问题”把精力聚焦在真正的内核态代码路径上。5. 避坑指南这些“经验”全是副作用5.1 调大阈值可以临时用但必须静默期后追查根因我不是强烈反对调整watchdog_thresh的一切场景。比如大促期间你明知道某个系统会在高峰时期遭遇特定类型的驱动超时但换驱动需要重启窗口而业务不能中断——这时候临时把 20 秒调到 45 秒可以为你争取一个热修复的实施窗口。但带病运行的时间必须明确设一个上限并且每次调大都要记录变更单必须在 24 小时内完成根因分析并恢复默认。一个常见的作弊技巧是不用一次性调大很多而是慢慢从 20 调到 30再调到 40给内核报错之间的间隔拉开方便在下一个告警来临前抓现场。这比直接调到 60 或 100 要安全得多。5.2 不要误关 softlockup_panic很多人为了不让 lockup 告警打扰自己直接把kernel.softlockup_panic设为 0 来“安抚”系统等于完全关闭保护。在核心数据库节点上这种操作等于放弃最底层的兜底保障。如果一个 CPU 已经 soft lockup你让它继续运行可能导致数据不一致、文件系统元数据损坏、复制链路脑裂等更恶劣的结果。宁愿让它快速 panic、快速重启让高可用机制接管也不要让一个已经卡死的系统继续带病写入关键数据。有人担心 panic 会导致业务中断时间太长但这种想法是错的——soft lockup 发生后该节点本来就无法正常提供服务了排查还需要大量时间与其让它在“假活”状态消耗排障精力不如早点重启让系统恢复可用。当然这需要在架构层面提前做好 HA 切换、故障隔离预案。5.3 别把虚拟化环境的锅算到内核头上我在公有云排障时见过太多因为 guest 内核 soft lockup厂商和客户来回甩锅扯皮的案例。从经验上看在共享型云主机里soft lockup 告警有相当大的概率来自宿主机 CPU 超配和 steal time不一定是内核 bug。遇到这种情况正确姿势是先在宿主机侧确认 vCPU 线程的调度状态检查 CPU 超配比、C-states、NUMA 绑定必要时给关键业务 vCPU 做 pinning。如果不从这个角度排查只盯着 guest 内核栈看很容易得出错误结论甚至因此误换内核、误关 watchdog项目返工不说问题本身也不会消失。5.4 别忽视内核版本升级这个“偏方”有些看似“无法定位”的 soft lockup最后真相是内核特定版本的驱动 bug。比如某些内核版本里的i40e驱动在 RSS 重配置时存在异常路径触发软锁某些e1000e驱动在链路协商失败后重试循环没有退出条件。这种情况下升级内核或者驱动版代表的是“修复根因”而不是“掩盖问题”和调大 watchdog_thresh 有本质区别。升级前注意在生产环境做好兼容性测试至少要在灰度节点压测一周以上再全量滚动更新。同时要保持一个可快速回滚的版本槽位避免新内核引入 regressions。6. 参数速查与长期监控方案6.1 内核参数与配置文件汇总排查 soft lockup 时常与watchdog_thresh一起配合使用的参数我整理了一份速查表内核参数推荐值使用场景kernel.watchdog_thresh20生产默认检测间隔不建议随便调大kernel.softlockup_panic1核心业务节点建议开启宁可快速失败kernel.nmi_watchdog1保留 hard lockup 检测能力kernel.hardlockup_panic1核心节点建议开启kernel.softlockup_all_cpu_backtrace1排障时开启正常关闭减少日志量kernel.rcu_cpu_stall_timeout60调整 RCU stall 检测周期与 lockup 配合观察把这些参数固化在/etc/sysctl.d/99-lockup-tuning.conf中并在变更管理流程里做好审计记录避免临时改动后忘记还原。6.2 建立分层监控而不是只看 dmesg单纯的dmesg | grep lockup是不够的因为触发瞬间错过了就没了。更好的方案是建立一套两层监控体系底层主动抓取内核事件监控脚本监听dmesg关键字段结合perf快照和sysrq现场转储秒级留存证据。上层拉取业务 TP99 延迟、CPU 软中断分布、steal time 等指标到监控平台通过联动分析判断 lockup 的真实业务影响。我在生产实践里通常会写一个 systemd timer 每分钟检查一次dmesg中的soft lockup关键字并把触发前后 30 秒的perf采样自动存盘。这个机制帮我们抓到过多次驱动 bug 的原始现场比裸看告警消息有用得多。6.3 分享一个偷懒但有效的操作习惯最后分享一个我个人的操作习惯面对 soft lockup我永远先打开/proc/softirqs和perf top两个窗口再看任何其他东西。因为 soft lockup 的直接触发者大概率不是“某段代码逻辑核心”而是“某段代码路径长时间不让出 CPU”。而softirqs能一眼看出中断负载是不是不均衡perf top能看出当前的热点函数分布。这两个窗口同时打开的情况下80% 的 soft lockup 都能在 10 分钟内定位到方向。如果这两个窗口看不出异常我才会进入锁分析、RCU 分析之类的深水区。有一次凌晨 3 点一个客户节点报 soft lockup我远程上去后没有翻日志先开了这两个窗口perf top里赫然显示某个 CPU 上某个驱动的napi_poll占比 90%而softirqs里对应 CPU 的NET_RX计数异常增长方向锁定极快。整个过程不到 5 分钟客户甚至还没来得及把现场抓下来我们已经给出了初步结论。这种操作习惯本质上就是把排查路径压缩到最短用最少的动作拿到最高价值的信息。如果你正被 soft lockup 反复折腾先停下手里的sysctl -w kernel.watchdog_thresh60去把现场和栈信息完整记录下来再按上面的顺序逐一排查。你会发现真正解决问题的感觉远比“压住告警”踏实得多。