资讯动态

内核挂死排查实战:软锁死、硬锁死、任务卡死与kdump取证

发布时间:2026/10/21 4:56:56 来源:尧图企业网站定制
1. 先判断这是哪种“死”软锁、硬锁还是任务卡死搞内核维护和系统排障的人迟早会碰上一次服务器“怎么敲都没反应”的场面。我最早遇到这类问题是在某次生产环境大版本升级后的第二天早上监控发现一台机器失联等机房同事到现场看整个控制台已经冻住键盘灯都不闪。当时第一反应是“网络不通吧”但接上串口以后才发现内核其实还在打印一部分日志只是系统已经彻底进入不可服务状态。从那之后我养成的第一个习惯就是别急着重启先通过症状判断眼前的“死”到底是哪一种。内核挂死hang、lockup在Linux世界里并不是单指某一类故障它大致分为三种完全不同的表现形态软锁死soft lockup、硬锁死hard lockup和任务卡死hung task。三者的成因、取证方式和修复路径都不一样如果不分青红皂白直接一键重启那么下一次出现同样问题的时候你依然拿不到任何有效线索。1.1 软锁死soft lockup的典型特征软锁死的定义是某个CPU上长时间没有发生调度器tick切换内核的软锁检测线程在固定的时间窗口内没得到执行机会。最直观的表现是系统整体极慢但又不是完全静止。控制台偶尔还能响应鼠标或键盘可能有延迟几秒甚至几十秒的响应SSH连接会断断续续但进程列表大概率还能拉出来。这种情况下dmesg里通常会留下类似这样的记录watchdog: BUG: soft lockup - CPU#3 stuck for 22s! [kworker/u:1:123]这条信息说明编号为3的CPU已经有22秒没正常调度了当前停在名为kworker/u:1:123的内核工作线程上。看到这类日志的时候问题多数出在中断上下文或持锁时间过长上比如某个驱动在关中断后做了大量耗时操作或者自旋锁被长时间占住导致该CPU上的普通内核线程没法推进。还有一个很容易被忽略的情况某些实时性调优把内核线程绑核、提高优先级之后配合不合理的核心隔离配置也会让watchdog误报软锁。我遇到过不止一次“系统明明没死只是某个业务线程把CPU占满了”结果被误判为内核故障。软锁的判定条件是CPU长时间不调度但有时候一个高优先级实时线程死循环同样能让调度器没法正常切换。所以看到软锁日志先别急着把锅扣在驱动头上需要用perf或ftrace确认到底是谁霸占了执行流。1.2 硬锁死hard lockup的特征硬锁死比软锁死严重得多。它意味着某个CPU连中断都处理不了了NMI非屏蔽中断都没法把它从当前的执行路径里拉出来。系统表现是彻底“死透”——网络不通、键盘无响应、串口也可能什么都打不出来只能靠NMI watchdog在另一个CPU上记录下这只“病CPU”的寄存器快照。NMI本身是优先级最高的中断类型它能在CPU关中断的状态下仍然介入。硬锁死通常意味着问题已经深入到非常基础的中断/异常处理层面可能是中断向量表被破坏可能是某个代码路径错误地关闭了所有中断并长时间死循环也可能跟CPU微码或硬件信号有关。实际排障中硬锁死的取证极度依赖硬件层面的辅助比如IPMI/BMC的NMI按钮、串口控制台、或者kdump的崩溃转储机制。如果这些都没有提前配置好硬锁死之后的系统基本上就是一块废铁只能重启。这也是为什么我一直强调所有跑核心业务的服务器一定要提前配置好crash dump机制不要等出事了才想起来装工具。1.3 任务卡死hung task与不可中断睡眠第三种挂死形态最迷惑人整个系统看起来还活着CPU使用率也不高网络也通但某些关键进程永远卡在D状态不可中断睡眠里怎么也杀不掉。D状态本质是进程在内核态等待某种资源比如磁盘I/O、锁、内存映射页的交换。正常情况下这种等待会很快结束但如果对应的驱动或文件系统出了大问题等待就变成永久性的了。内核的hung task检测机制会在hung_task_timeout_secs默认120秒内检测到一直没有得到调度的任务并打印类似下面的日志INFO: task java:12345 blocked for more than 120 seconds. Not tainted 5.10.0-xxx #1 echo 0 /proc/sys/kernel/hung_task_timeout_secs disables this message.这条日志后面通常会附带一大段调用栈。如果你发现日志里的栈停在__lock_page、buffer_io_error、或者某个驱动特有的等待函数上那基本可以断定是底层I/O或锁竞争出问题了。比起软锁、硬锁任务卡死通常不导致整机完全不可用但会造成服务假死、连接积压、崩溃雪崩等更隐蔽的灾难。三种挂死形态的差异我整理成了一张常见对比表排障时可以先按表对照形态系统表现日志特征常见根因方向软锁死极慢但仍可响应soft lockup - CPU#x stuck for Ns!驱动关中断过久、自旋锁持锁过长、实时线程霸占CPU硬锁死完全无响应NMI watchdog触发可能只有寄存器快照中断处理异常、CPU微码问题、底层硬件故障任务卡死服务假死D状态进程堆积hung task blocked for more than N seconds磁盘I/O卡死、NFS/网络文件系统超时、内存回收死锁2. 从现象倒推根因内核僵住的几条典型因果链知道是哪种“死”只是第一步接下来要琢磨的是“为什么会死”。内核挂死的原因千奇百怪但其核心机制其实跳不出几条路。我有一次排查一个内存压力下的卡死问题耗了整整三天最后发现是内存回收路径里执行了一个超大循环的调试函数导致其他进程全都拿不到内存锁。这类问题如果一开始就往驱动方向找方向就错了。2.1 自旋锁与中断上下文的不当竞争自旋锁是内核里最基础的同步设施。它的特点是持有期间不可睡眠如果拿不到锁调用者会一直自旋等待。这种设计在单CPU下还无所谓在多核环境下就是一把双刃剑只要有一个CPU在关中断状态下持有自旋锁超过一定时间其他CPU就会集体卡在同一个锁上形成连锁堵塞。典型的翻车场景是这样的某个中断处理函数里对设备寄存器做轮询判断数据是否就绪默认情况下几次就退出了。但如果寄存器状态因为固件缺陷永远不满足条件这个轮询就是死循环。循环里如果还开着自旋锁那受害的就不只是本CPU而是所有尝试访问同一设备的CPU。调试这类问题时特别要注意一个细节打印日志本身可能改变竞争时序。很多“加了printk之后问题就消失了”的诡异现象往往就是因为日志增加了延迟恰好绕过了锁竞争窗口。这时候我会选择用ftrace的lock功能或者/proc/lock_stat去抓锁的静态数据而不是一上来就想着加日志。2.2 内存子系统从碎片化到静默损坏内存引发内核挂死的路径往往更隐蔽。最常见的是内存碎片化严重时内核在尝试分配高阶内存页的过程中长时间遍历页表又或者是kswapd内核线程在高内存压力下陷入与某进程的写回循环等待两边互相等对方的锁最后整个内存回收子系统卡死。更麻烦的是内存静默损坏。当某些内存页因为硬件问题频繁报错时内核会尝试隔离这些页但这套机制并非万能。在一些老旧BIOS和特定内存控制器组合下损坏的内存页可能导致页表内容被篡改进而让CPU跳转到非法地址执行表现为随机panic或硬锁死。这种问题靠软件日志往往看不出真实原因最终都得靠mcelog、EDAC驱动上报的硬件错误记录来定位。内存相关的挂死有一个值得提醒的点不要盲目搜索panic调用栈中最后一行。因为内存损坏触发的位置是随机探测到的真正损坏的地址可能早就被改写最后的栈帧只是“第一现场的受害者”不是“凶手”。要结合/var/log/mcelog或rasdaemon记录的硬件错误甚至要配合内存压力测试工具刷一遍内存才能坐实根因。2.3 驱动和文件系统阻塞一步步拖死内核线程不少内核挂死其实是“某个资源等不到最后拖垮全局”的故事。举一个我实际遇到过的案例某台服务器的根文件系统挂载在网络存储上存储端因为网络波断开了连接内核里访问该文件系统的进程全都进入D状态等待。正常情况下网络恢复后这些进程会重新调度但那次恰好存储端的锁没有正常释放结果所有后续的系统调用都卡在解析路径上最终连systemd都调度不了整机呈现假死状态。这种“连锁堵塞”的内核挂死本质上是依赖链上某一环断裂。链路可能是这样的后端存储故障 → 文件系统I/O悬挂 → 持有文件系统锁的内核线程不退出 → 访问该路径的所有进程阻塞 → 系统服务联动卡死 → 整体不可用。排查这类问题第一步永远不是看代码逻辑而是确认底层物理资源是否健康。很多新手拿到hung task日志就去查内核源码结果查了半天发现NFS服务器早就挂了。先检查存储、网络、硬件状态再往上层追代码路径能省掉大量无效工作。2.4 误把资源耗尽当内核问题的“干扰项”有一类情况特别容易混淆视听明明内核没坏但表现看起来像内核挂死。典型的比如进程数爆满、句柄耗尽、内存不足触发OOM后系统进入不可控状态。这些外因有时候会在短时间内制造出极其类似软锁死的症状导致排障人员拿着watchdog日志查半天驱动最后发现只是某个业务进程疯狂fork把pid_max打满了。区分的关键在于看证据链是否闭合。如果日志里同时出现“fork失败Cannot allocate memory”“page allocation failure”“Out of memory: Kill process”等信息那大概率是资源耗尽引发的次生灾害内核本身未必有实质bug。反向来说如果你看到的是某个固定内核路径反复出现在栈里且资源数据一切正常那才是真正需要深挖的内核问题。3. 第一现场取证能用的工具先排一遍内核故障有一个很反直觉的特点在最危急的时候你平时配置的工具决定了你能拿到多少证据。我见过不少事故报告写到最后就俩字——“重启”原因就是现场啥都没准备。这里把我认为最实用的取证工具和操作顺序梳理一遍。3.1 物理机与虚拟机的取证差异先说环境差异。物理机上串口控制台是最可靠的信息来源。它不依赖网卡、不依赖显卡驱动就算内核在很早的启动阶段崩掉串口日志也照样能传出来。虚拟机的处境稍有不同你觉得虚拟机卡死很多时候是宿主机层面的问题虚拟机内部看不到任何异常日志。我在虚拟化环境中排查内核问题时通常会先去宿主机上看/var/log/libvirt/qemu或者云平台的监控数据确认到底是客户机挂死还是宿主机异常。另外虚拟机的NMI watchdog有时不可靠。部分虚拟化层的时钟漂移会让软锁检测产生误报所以虚拟机上看到软锁日志先对比宿主机时间同步状态再决定是否继续深挖否则容易在本来没问题的系统上浪费时间。3.2 SysRq 魔术键串的实操用法SysRq是Linux内核提供的一套“紧急后台指令”即使系统大部分功能已经瘫痪只要内核中断处理还基本正常就可以通过它强制执行一些关键操作。在物理机上需要按AltSysRq功能键在串口控制台或某些虚拟化环境里更稳定的做法是直接往/proc/sysrq-trigger写字符。我最常用的是一串取证的组合操作echo t /proc/sysrq-trigger # 把所有进程的任务状态打印到内核日志 echo w /proc/sysrq-trigger # 打印所有处于不可中断睡眠状态的任务 echo m /proc/sysrq-trigger # 打印内存信息 echo l /proc/sysrq-trigger # 打印所有在线CPU的栈回溯 echo p /proc/sysrq-trigger # 打印当前寄存器信息让系统正常的情况下先跑一遍echo t你往往能看到上千行输出。所以实际操作中我更推荐先写小写字母仅打印跟任务状态相关的内容。日志会通过内核对printk的处理写到/var/log/messages或dmesg前提是日志服务还没挂。如果日志系统已经不可用了那就得靠串口或者kdump把输出带出来。SysRq里还有一个反直觉的小技巧echo b /proc/sysrq-trigger会立即重启系统这个键我们在“保留现场”和“快速恢复”之间做抉择时可能会用到。但除非你确定不需要保留任何现场信息否则别乱按。3.3 NMI watchdog让CPU“硬起”一把NMI watchdog是处理软锁和硬锁时的王牌机制。它通过性能计数器在每个CPU上触发NMI周期性中断如果在一个给定的时间窗口内没有收到CPU的调度tick响应NMI就会强制中断并打印相关信息。开启它的方式在不同架构上略有差异x86上通常配置如下sysctl kernel.nmi_watchdog1 echo 1 /proc/sys/kernel/nmi_watchdog配置完之后可以用下面的方式模拟一个死循环验证watchdog是否真的能触发# 谨慎这会真的让系统卡死 echo 1 /proc/sys/kernel/watchdog_thresh echo c /proc/sysrq-trigger注意echo c是触发内核主动panic的测试手段只建议在测试机上做。在真实故障里NMI watchdog的价值在于它能把“某个CPU卡住”的事实变成一条日志方便你事后用crash工具解析。它本身不会修复问题但绝对是取证链路里的关键一环。3.4 控制台串口日志往往比图形界面更可靠排障时的一个常见误区是图形界面还能显示就觉得“至少还能抢救一下”。实际上图形栈Xorg或Wayland本身就是用户态程序它还能显示只能说明用户态还没完全崩跟内核状态没有必然关系。真实的内核日志最终都要走printk而printk在不同console机制下的表现差异非常大。生产服务器上我最推荐配置串口console理由是简单可靠内核启动参数里加上consolettyS0,115200systemd的启动日志也配置consolettyS0转发这样就算网络的SSH断了、显卡驱动崩溃了串口上依然能看到内核的最后输出。实际排障中我见过太多因为“日志没抓到”而无法定位的故障装上串口console之后后续排查顺利了不少。4. 把现场搬回实验室kdump/crash 解读 vmcore取证工具再强如果只能拿到活体的状态很多深层问题依然看不见。尤其是随机性的内核panic或挂死你需要的不是“当前的日志”而是一份崩溃瞬间的内存转储。这就是kdump这套机制存在的意义。4.1 崩溃转储机制的选择与配置kdump的基本思路是在系统崩溃瞬间用一个专门预留的干净内核接管把崩溃前的内存镜像保存成vmcore文件。这个文件记录的是故障发生时所有CPU寄存器、内核栈、内存页的完整快照几乎可以相当于一次“法医尸检”。配置kdump最关键的参数是crashkernel它决定了系统启动时预留多少内存给捕获内核。内存太大会白白占用业务可用内存太小又可能导致捕获内核起不来。一般x86_64环境给256MB-512MB即可视系统实际规格而定# 修改 /etc/default/grub 里的 GRUB_CMDLINE_LINUX GRUB_CMDLINE_LINUX... crashkernel512M安装并启动kdump服务yum install kexec-tools crash systemctl enable --now kdump.service配置完成后做一个测试echo c /proc/sysrq-trigger。如果配置正确几秒后系统会被捕获内核接管并生成/var/crash/下的vmcore。这个动作在测试机上一定要做否则可能直到生产故障那天才发现kdump根本没启动或者crashkernel没预留成功。4.2 用 crash 工具定位挂起线程拿到vmcore之后crash工具就是最核心的分析手段。它的用法和gdb有点类似但针对内核数据结构做了大量优化。打开vmcore的第一步是确认基本信息crash /usr/lib/debug/lib/modules/$(uname -r)/vmlinux /var/crash/127.0.0.1-2025-xx-xx/vmcore进入交互界面后bt命令可以打印所有CPU当时执行的内核栈ps命令可以列出崩溃瞬间存活的任务log命令可以查看完整的内核printk缓冲。我常用的定位思路是这样的先用log扫一遍最后的错误关键词比如BUG、Oops、panic、Call Trace。再用bt -a查看每个CPU的栈回溯找到行为异常的CPU。如果栈回溯停在锁等待函数如__mutex_lock_slowpath就用lock命令查看锁的状态判断是持锁者没释放还是锁本身被破坏。最后结合dev、files、mount等命令确认崩溃时对应的设备或文件系统状态。这套流程不能保证每次都能直接定位根因但它能帮你把“哪里出问题”精确到某个函数、某个设备再返回去查对应的驱动代码或上游补丁效率会高很多。4.3 调取返回地址堆栈还原路径有些内核挂死并没有打印出明确的Oops尤其那些以软锁或硬锁形态出现的故障崩溃转储里的栈信息往往只有几个地址。这时候需要用crash的地址符号化功能把原始栈帧数字化。常见的伪代码如下crash bt 1 PID: 1 TASK: ffff9a0f0a2b8000 CPU: 2 COMMAND: systemd #0 [ffff9a0f0bfffd50] __native_flush_tlb at ffffffff81070fb0 #1 [ffff9a0f0bfffd68] flush_tlb_mm_range at ffffffff81071d69 #2 [ffff9a0f0bfffd88] flush_tlb_page at ffffffff81071e93看到这样的栈说明进程在执行页表相关的TLB刷新操作。这类操作通常短暂且频繁如果它卡住了大概率是页表本身被并发修改或者硬件翻页机制出了问题。下一步就可以顺着flush_tlb_page的调用者往上追看谁在异常地操作页表。有一个经验之谈永远结合多个CPU的栈一起看不要只看一个CPU的栈。很多死锁是A CPU持有锁等待B CPUB CPU又持有另一个锁等待A CPU释放单独看任何一边都是“正常等锁”两边放一起才看得出环形等待。5. 复盘两个典型故障一次软锁一次 D 状态大堵车理论讲得再多不如把实际排障过程复述一遍来得直观。下面这两个案例分别对应了前面提到的软锁死和任务卡死都是经过改造脱敏后的真实经历。5.1 案例一某存储驱动的软锁背景一台承载批量计算任务的工作站跑着某个老版本的分发存储驱动。故障表现是每隔一段时间系统会突然变得极慢鼠标能动但基本点不动任何东西SSH能连上但执行命令要等十几秒。第一次看到dmesg时日志显示watchdog: BUG: soft lockup - CPU#5 stuck for 22s!CPU5卡在驱动的queuecommand相关路径上。我当时的第一反应是驱动跟新内核不兼容打算直接升级驱动。但升级前先做了两步取证一是打开/proc/lock_stat观察锁的等待排序数据二是用ftrace跟踪queuecommand的耗时。结果发现问题并不是驱动代码本身“卡住”而是驱动在每次处理I/O请求前都要读取一个状态寄存器而这个寄存器在某些存储固件版本下访问延迟能达到秒级。CPU5在中断上下文里反复读取这个慢寄存器导致隔离了其它任务最终触发软锁。修复方案也很简单给驱动加上寄存器读取超时超时后放弃等待并上报错误同时升级存储端的固件。这个案例让我很受触动软锁的根因可能在硬件设备上而不是纯内核逻辑软件层面的监控指标和硬件健康状态之间有时是强耦合的。5.2 案例二NFS 根目录 D 状态雪崩背景某开发团队的构建服务器系统的根目录直接挂载在NFS上本地只有少量临时目录。某次网络存储设备例行切换构建机上的执行任务集体卡住表现为大量java和git进程进入D状态ps aux输出能看到一大片D标记。抓到的hung task日志如下INFO: task java:2331 blocked for more than 120 seconds. echo 0 /proc/sys/kernel/hung_task_timeout_secs disables this message. Call Trace: __switch_to0x... nfs_wait_on_request0x... nfs_page_async_flush0x... nfs_flush_inode0x... nfs_release_page0x...这个栈非常典型进程卡在nfs_wait_on_request上等待NFS请求完成但这个请求因为存储切换时的连接已被重置RPC层没有正确处理重连请求在超时时钟内“永久悬挂”。更糟的是根目录挂在NFS上几乎所有路径解析和配置读取都要经过NFS文件系统这就形成了所有进程排队等锁的雪崩。处理措施分两步紧急恢复是重启服务器并在启动参数里临时加上rootflagshard,bg,intr之类的NFS选项让挂载在超时后能重新尝试事后根治则是调整NFS的timeo和retrans参数同时在NFS服务器端开启更健全的NOTIFY_DEVICEID4_MOVE等协议支持减少类似切换导致的连接悬挂。这次事故让我深刻认识到当关键系统路径依赖远程资源时必须提前规划失效模式否则一个小切换就会拖垮全站。5.3 从复盘里提炼的共性模式把这两个案例放在一起能看到几条共通的规律内核挂死往往不是“某一行业务代码”的问题而是系统里某个资源或设备路径变慢/失效后被放大。真正有用的证据绝大多数需要在故障前就配置好的机制来捕获临时抓抓不到。快速“重启恢复”不等于“问题解决”不把根因挖出来故障大概率会换个时间点再回来。如果非要给一条最核心的建议那就是把内核相关的监控、日志、转储机制当成基础设施的一部分而不是出了事故再补的临时工具。6. 这几条经验让你避开后续九十步弯路内核挂死的排查说到底是“在正确的时间点拿到正确的证据”的游戏。这几年踩过的坑很多有几条体会我觉得值得单独写出来。它们不是那种“要备份、要监控”的泛泛建议而是非常具体的操作习惯。6.1 设置一些“预防性”参数Linux内核其实有很多可以提前打开或调整的“保险丝”。比如kernel.panic_on_oops设为1让系统在Oops后自动触发panic从而生成vmcore再比如kernel.hung_task_timeout_secs调整到300避免业务上暂时卡顿就直接被杀进程。还有fs.suid_dumpable和kernel.core_pattern让用户态的core dump统一收集到集中目录避免把日志散落在各个业务目录里。我建议至少在生产环境执行下面这套预调整思路sysctl -w kernel.panic_on_oops1 sysctl -w kernel.hung_task_timeout_secs300 sysctl -w kernel.softlockup_panic1 sysctl -w kernel.hardlockup_panic1这几个参数的含义是遇到Oops或软硬锁时直接panic并交给转储机制而不是让系统在坏状态下继续硬撑。很多人对panic有恐惧但其实在半死不活的状态里运行反而比及时崩溃更伤数据。提前让系统“该崩就崩、崩了有记录”是内核排障界公认的稳妥做法。6.2 保留现场 vs 快速恢复的取舍故障发生时运维团队往往面临一个矛盾保存现场需要额外时间每多一分钟系统不可用业务损失就多一分。我的观点是如果故障是可复现的或刚刚发生过一次优先尝试保留现场如果是无法复现且暂不影响关键路径的偶发故障可以先尽快恢复但也要在恢复前想办法抓一把日志或网卡状态。实际操作上保留现场有些“低成本高收益”的抓手系统还能响应时执行echo t /proc/sysrq-trigger并保存dmesg能进紧急维护模式时直接记录/proc/slabinfo、/proc/meminfo、/proc/vmstat有外接管理口时用BMC/IPMI截取屏幕输出最后才考虑用sysrq-b快速重启。很多新手一看到系统卡死就想关电源这其实是损失最大的选项。哪怕只是多等几秒多拿一份日志后续分析的价值都不可估量。6.3 回退的常见误区修复阶段最容易犯的错是在没有完整证据时直接“回退版本”。比如有人看到软锁栈里出现某驱动函数就断定是驱动升级引入的回归直接回退驱动版本。但有时候问题的真正触发点是硬件固件、BIOS配置或者存储固件的变化驱动只是“受害者”。我有一次就遇到过这样的情况某显卡驱动更新后出现随机挂死所有人默认认为是驱动bug纷纷指导回退。结果回退了依然挂死。最后是通过crash分析发现崩溃点在内存页的DMA映射路径上再追查发现是主板上DMA重映射IOMMU相关的BIOS开关被另一个固件升级重置了。驱动升级只是让原本脆弱的IOMMU路径更容易触发而已。所以回退动作之前务必问自己三个问题是否有日志能证明故障在回退之后不会再触发硬件/固件层面是否也有过变更根因分析结论是否指向驱动本身而不是其它层的间接结果这三个问题全都确认清楚了再动手回退也不迟。最后再分享一个小经验遇到任何内核层面的诡异问题养成顺手收集uname -a输出的习惯。内核版本、发行版补丁级别、编译时间这些信息看起来不起眼但有时候决定两个看似完全相同的问题是否是同一种原因。另一个是养成“先备份内核日志再重启系统”的肌肉记忆。我在经历了几次“重启之后日志全丢”的痛苦后开始强制自己进入排障流程的第一步永远是拉日志哪怕只是丢到本地临时文件里也比什么都留不下强得多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑