☰
Linux内核挂死实战排查:从现场取证到根因定位
2026/10/20 5:20:04 网站建设 项目流程

干运维和内核调试这些年,接到“linux 内核挂死”的工单次数不少。最直观的体验就是:机房灯还亮着,风扇还在转,可系统像被点了穴一样——网络不通、控制台无响应、业务连不进去,ssh 也毫无反应。很多朋友第一反应是赶紧重启,但只要你按下复位键,最关键的现场就没了。内核挂死不是普通崩溃,它往往没有一条完整的报错日志等着你。这篇文章会把内核挂死的常见形态、前期该做的准备、现场怎么取证、根因怎么定位,按我自己的排查习惯一步步讲清楚。不管是刚接触内核问题的新手,还是被“卡死”折磨过的资深运维,都能拿这套思路去复现排查流程。

1. 先搞明白“内核挂死”到底是哪种死法

1.1 内核挂死不是普通崩溃,别急着按重启

内核挂死(kernel hang)和内核崩溃(kernel panic / oops)是两类完全不同的问题。panic 发生时,内核会主动打印寄存器、栈回溯、异常模块等一大段信息,还会根据 panic 参数决定是否立即重启;而 hang 往往是“再也没动静了”,不会主动输出有效日志,也不会自动恢复。用生活里的例子说,panic 是“人突然大喊‘我出事了你快看’”,hang 则是“握着拳头僵在原地,意识还在但说不出话”。

挂死现场最常见的错误动作,是业务同事在等待超时后直接断电重启。我理解这种压力,但请记住一个重要原则:如果能扛住业务压力,先别重启。因为像进程状态、锁等待关系、中断状态、CPU 栈回溯这些关键证据,重启后一切归零。你真想定位根因,至少要先保证现场被“固定”下来——把控制台照片拍下来、把串口日志保存、把非易失性存储中的日志数据导出。慌归慌,证据不能丢。

1.2 四种常见挂死形态:软锁、硬锁、死锁、静默卡死

我习惯把内核挂死按“内核能不能自己报告异常”分成四种:

软锁(soft lockup)。某个 CPU 上发生长时间不调度,内核的软锁检测机制会报警,打印消息类似watchdog: BUG: soft lockup - CPU#0 stuck for 22s!。它不是系统 100% 不可用,只是这颗 CPU 卡死,其它核可能还能还魂。如果能收到这种输出,已经是挂死问题里最“友好”的一种了。

硬锁(hard lockup)。CPU 进入中断被长期关闭的状态,连 NMI watchdog 都无法打断,内核可能只输出极简的警告,甚至完全无输出。这类现场大多要靠外部带外控制和串口日志来辅助还原。

死锁(deadlock)。两个或多个任务各自持有一把锁,在等对方手里的资源,进入谁也动不了的循环。死锁的第一步通常锁在内核态线程或者驱动回调上,表现为整个系统或某个核心进程永久挂起。

静默卡死(silent hang)。这是实战里最头疼的一类。任务集体进入不可中断睡眠(D 状态),等待某个 IO 或设备操作永远不返回,系统没有 lockup 警告,也没有 panic。往往是存储盘阵掉线、网络存储超时、或者某块设备驱动无法完成请求造成的。

区分这几种形态,直接决定你下一步要看什么:软锁优先查 CPU 栈和自旋锁;硬锁优先查关中断路径和平台时钟;死锁要看任务栈与锁依赖;静默卡死则要把重点放到 IO 等待链路上。

1.3 哪些场景更容易中招

根据我见过的现场,内核挂死高发场景集中在这么几类:虚拟化平台上的 CPU 时钟与 tick 配置异常;新装驱动或升级内核后没经过充分压测;存储设备链路抖动引发文件系统长期等待;数据库、备份、批量计算这类高并发高 IO 业务把某些内核路径推到极限。内核调度、文件系统、设备驱动的代码固然复杂,但绝大多数挂死问题并非无迹可寻,只是你需要一套系统的取证流程。

2. 排查前的准备动作:把探针提前埋好

内核挂死排查最尴尬的情况,是问题发生后你才发现系统什么日志都没留。所以真正有效的排查,从“平时就把环境配好”开始。以下是我维护关键机器时的标准配置清单。

2.1 内核参数:让系统具备自爆能力

内核有些参数是“事故预防”用的,建议在 /etc/sysctl.conf 或内核命令行里明确设置:

kernel.panic = 60 kernel.panic_on_oops = 1 kernel.softlockup_panic = 1 kernel.hardlockup_panic = 1 kernel.panic_on_io_nmi = 1

简单解释一下每个参数的意义:

  • kernel.panic = 60:内核 panic 后 60 秒自动重启。生产环境不可能一直停在那里,给 60 秒是为了留出时间让带外管理或串口把最后的屏障打出去。
  • kernel.panic_on_oops = 1:普通 oops 也触发 panic。oops 本身就是内核走到错误路径的信号,与其让系统带伤继续跑下去,不如直接让它定格,方便取证。
  • kernel.softlockup_panic / hardlockup_panic:检测到 CPU 锁死时立即 panic。注意这会有个权衡——系统会自动重启,你可能会丢失一部分现场,但你会得到一张明确的“诊断书”。愿意监控而不是自动重启的环境,可以保留默认值,用 watchdog 日志作为告警依据。
  • kernel.panic_on_io_nmi:遇到 IO 相关的 NMI 错误时 panic,用于捕获硬件和 IO 路径异常。

提示:这些参数不是所有发行版都默认开启,尤其是 panic_on_oops 和 lockup_panic,不少系统默认是 0。没有开启时,遇到软锁可能只 printk 不动作,日志稍纵即逝。

内核启动参数里还要考虑串口叠加。比如在某发行版常见的 GRUB 配置:

GRUB_CMDLINE_LINUX="console=tty0 console=ttyS0,115200n8"

console=ttyS0的意义在于把内核日志导到串口。哪怕系统网络完全挂了,只要带外串口还通,你就还有眼睛。注意调试时最好去掉quiet参数,不然大量 printk 会被抑制,关键信息可能直接被吞掉。

2.2 kdump 和 crash 工具:出事以后有“尸体”可验

只靠串口日志,拿到的是文字线索;想进一步分析内存现场,就要靠 kdump。kdump 在内核崩溃时启动一个捕获内核,把崩溃内存转储成 vmcore 文件,之后可以用 crash 工具离线分析。

基础安装大体是这几个组件:kdump、crash、kernel-debuginfo(不同发行版包名略有差异)。配置上确保启动参数里有crashkernel=auto或指定预留内存,比如:

crashkernel=512M

预留多大要看你机器的内存规格,通常 512M 到 1G 足够。装好之后可以用 sysrq 触发一次测试性崩溃来验证 dump 流程:

echo c > /proc/sysrq-trigger

这会故意制造一次内核 panic。在测试环境做,别在生产上玩。如果 vmcore 能正常落盘,说明你的救援通道是通的。

实战心得:很多公司机器上 kdump 装了但从来没验证过,出了事才发现 dump 路径不通,比如预留内存不足、存储挂载没就绪、或者磁盘空间不够。建议每次内核升级、每次存储变更后,都在变更窗口主动测一次 dump。

2.3 现场采集清单:动作要快,步骤要有顺序

真的发生挂死,时间窗口可能只有几十秒到几分钟。我的顺序是:

  1. 打开带外管理界面,截屏或录像,记录控制台最后输出。
  2. 确认串口日志是否在实时记录中。如果已经接入串口,马上把日志文件副本取出来。
  3. 如果机器还响应 SysRq 键,按顺序执行Alt+SysRq+w打印所有任务栈、Alt+SysRq+t打印任务列表、Alt+SysRq+l打印 CPU 栈回溯。
  4. 手动触发 kdump(如果配置了echo c > /proc/sysrq-trigger),或者等待自动 panic 转储。
  5. 记录环境信息:内核版本(uname -r)、发行版版本、最近做过的变更、硬件型号、BIOS 版本、驱动版本。

第 5 步常常被忽略,但它对根因定位非常关键。“内核挂死”往往不是无缘无故的,升级内核、替换硬件、上线驱动,这些时间点的记录就是排查的锚点。

3. 现场分析:从日志到任务栈的逐步推理

假设你已经提前把串口开着、kdump 也通了,机器挂死时你手上有一批线索。接下来要做的是把线索串成证据链。

3.1 内核日志:watchdog 消息的潜台词

开机之后第一步看 dmesg:

dmesg -T | tail -n 500

如果系统已经异常,或者在串口日志里,先找时间点附近的输出。典型软锁消息长这样:

watchdog: BUG: soft lockup - CPU#3 stuck for 22s! [kworker/u16:1:17629] Modules linked in: ... CPU: 3 PID: 17629 Comm: kworker/u16:1 Not tainted ... Call Trace: schedule_timeout ...

很多新手只看“CPU#3 stuck”,但其实更重要的几个信息是:

  • Comm 字段:卡住的进程/线程名,明确是内核线程、软中断,还是某个用户态进程通过某系统调用陷入内核。
  • Call Trace:栈回溯里的最后几个函数,锁定卡在哪条路径。
  • Modules linked in:看看加载的哪些模块可能与问题相关。

如果日志里是BUG: hard lockup,一般伴随IP和RIP指向 CPU 当前执行位置。由于硬锁时中断被屏蔽,输出通常更短,有时只有一条函数名和寄存器片段。这种时候我多半会转向外部证据:串口、带外、硬件监控日志。

3.2 sysrq:把现场抠出来的急救工具

先确保 sysrq 可用:

cat /proc/sys/kernel/sysrq

通常 1 表示启用全部功能;如果不满足,内核命令行加sysrq=1后重启。

挂死时如果网络已经不通,但有键盘或带外控制台,最经典的几个键是:

组合键作用什么时候用
Alt+SysRq+w显示所有任务的内核栈回溯判断是否存在死锁或卡死路径
Alt+SysRq+t显示所有任务的状态和 CPU 列表判断是否有大量 D 状态进程
Alt+SysRq+l显示当前 CPU 的栈回溯定位当前执行位置
Alt+SysRq+c触发 panic 并转储kdump 测试或主动收尾

远程命令行触发:

echo w > /proc/sysrq-trigger echo t > /proc/sysrq-trigger

输出会打印到内核日志,串口日志和 dmesg 都能读到。一次挂死现场,我通常按 w、t、l 的顺序来,先看全貌再看局部。

3.3 一个典型现场的逐步推理

假设某次挂死的日志长这样:

INFO: task mysqld: 1234 blocked for more than 120 seconds. "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message. task:mysqld state:D stack: 0 pid: 1234 ppid: 1 ... Call Trace: __switch_to submit_bio_wait blk_execute_rq ...

看到blocked for more than 120 seconds,说明这是典型 D 状态卡死。第一步是看它在等什么:栈回溯显示submit_bio_wait,意味着进程在向底层块设备 IO 链路发起了同步等待。此时我会并行看几样东西:

  1. 系统里所有 D 状态任务是否都集中在同一设备。执行ps -eo pid,stat,wchan:30,cmd | grep D。
  2. 查看/proc/diskstats中对应设备是否有 IO 持续增长,确认是设备完全无响应还是链路抖动。
  3. 查看 dmesg 有没有设备错误、SCSI 错误、文件系统错误的相关记录。

如果所有 D 状态任务都挂在同一个 sdxx 或 nvme 设备上,优先级最高的排查方向就是设备与驱动;如果 D 状态任务分散,那要回到锁分析和 CPU 栈上。这种“同设备聚集”的特征,我在存储挂死现场见过太多次。

4. 根因定位的常见方向与实用方法

把现场看完,接下来要回答“为什么会挂死”。以下是我排查根因时最常走的四个方向。

4.1 驱动与内核模块:上线变更的高危区

驱动是内核挂死的头号嫌疑。尤其是网络、存储、GPU 这类复杂设备驱动,只要中断处理、自旋锁、IO 完成路径里出现一个笔误,就能让系统僵住。

常见的问题模式有:

  • 在中断上下文里调用了可能睡眠的函数,导致中断路径阻塞。
  • 自旋锁(spinlock)使用不当,没有在最坏情况下释放,或者同一个锁被重复获取。
  • 驱动的定时器/工作队列与卸载路径竞争,导致 use-after-free。
  • 固件交互超时后,驱动没有正确恢复设备状态,设备进入假死。

排查时我通常会先查驱动版本和内核版本是否匹配,然后看最近一次变更记录:有没有升级内核、换过驱动包、改过设备参数。如果是在新驱动上线后开始挂死,回退驱动或者用旧内核验证是成本最低的止损方式。

查看当前驱动信息和关联的设备:

lsmod | grep -E 'nvme|scsi|ixgbe|bond|virtio' ethtool -i eth0 # 网卡驱动与固件信息 lspci -vvv # 设备能力与状态

4.2 存储与 IO 路径:静默卡死的第一现场

存储链路故障造成的挂死,我在生产环境里见得非常多。现象上常常是数据库或大规模 IO 任务先卡住,然后系统资源被耗尽,最终演变成整体无响应。

初步判断思路:

  1. 看任务状态:大量 D 状态进程。
  2. 看是否集中在同一块设备上:cat /proc/diskstats,对比 io_ms 等字段。
  3. 看文件系统层:dmesg 是否出现 “INFO: task X blocked” 或有 “EXT4-fs error”、“XFS ... I/O error” 之类的日志。
  4. 看底层设备:如果是远程盘、分布式存储、SAN 存储,还要联系存储侧看链路丢包与延迟。

如果确认是 IO 路径问题,紧急恢复优先考虑两个方向:一是让受影响的服务快速恢复,比如杀掉相关 D 状态进程并重建,或者切换存储链路;二是保留证据后重启,再对存储系统的固件、多路径配置、超时参数做彻底检查。内核的 IO 超时参数也要看一眼,避免超时时间过长导致任务长时间挂起。

4.3 硬件与虚拟化平台:别忘了看环境信号

有些挂死与业务代码和驱动没关系,根子在硬件或虚拟化平台上。下面几个线索很有价值:

  • 带外日志显示硬件 watchdog 重启,但内核日志却没有任何 panic。
  • dmesg 中反复出现硬件错误事件,如EDAC、PCIe Bus Error、MCE(Machine Check Exception)。
  • 虚拟机上出现时钟跳变、CPU 热插拔、或者底层宿主机负载过高。

对于虚拟化环境,特别值得怀疑的有两类:一类是虚拟 CPU 的时钟源漂移,导致内核 tick 异常,watchdog 误报或者真的挂死;一类是底层宿主机的 IO 排队,让虚拟机的块设备请求迟迟不能完成,表现为 guest 内大量 D 状态进程。

提示:怀疑硬件问题时,跑一遍内存/磁盘自检,观察温度、电源状态,并记录固件版本。硬件问题不一定每次复现,但往往有规律,比如特定负载、特定温度才出现。

4.4 用户态负载与资源耗尽:看似无关的因素

某次排查,内核挂死出现在凌晨定时任务窗口。一开始所有人都在查驱动和内核代码,后来发现是用户态脚本在短时间内创建海量线程,触发了内核任务调度路径上的长时间遍历,进而和 CPU hotplug 产生了竞争。

这类问题虽然是“业务引爆”的,但最终卡死点在内核代码。遇到高并发、资源上限被反复打满的场景,多留个心眼:看 CGroup 限制、线程上限、文件句柄上限、内存和 swap 水位,这些外部条件可能是压垮内核路径的最后一根稻草。

5. 速查表与实践心得

5.1 常见挂死场景速查表

我把这些年排查过的挂死典型场景整理成一张表,便于大家在现场快速对照。

典型症状首要怀疑方向最快取证动作止损手段
网络中断、进程全卡,无日志硬锁/时钟问题/驱动串口日志 + SysRq+wpanic_on_oops 重启或 dump
大量 D 状态进程集中在某设备存储/IO 路径查 diskstats、设备错误日志切链路/重启后验证设备
只有单个 CPU 报警且可恢复软锁/驱动死循环dmesg + 栈回溯升级/回退驱动
内核线程互相等待,崩溃前有锁痕迹死锁crash 分析 vmcore记录调用栈后重启
带外日志出现硬件 watchdog 重启硬件故障检查 MCE/EDAC/PCIe 日志替换硬件/更新固件
升级内核后首次出现回归/兼容性对比新旧内核日志回退内核版本

表格不是万能答案,但它能帮你快速锁定“下一步该往哪看”,节省最宝贵的黄金排查窗口。

5.2 我踩过的坑和现在坚持的习惯

讲几个真实踩过的坑,希望对你有用。

第一个坑是“不验证 kdump 就当它有”。有次业务机器真正挂死,kdump 配置看着齐全,但预留内存被驱动加载后占掉,导致 dump 失败,最后连 vmcore 都没有。后来我在每个变更窗口都坚持做一次echo c > /proc/sysrq-trigger的验证,确认 dump 能正常完成才收工。

第二个坑是“只看 dmesg 不看时间关系”。内核挂死前往往还有一堆普通日志,你必须把日志时间和变更时间对齐。曾经一台机器在升级某网卡驱动后 40 分钟出现挂死,日志里却只有一段看起来无关的“link up”记录,后来发现正是驱动在 link up 事件后触发了错误的队列初始化。找到时间关联,根因就到了眼前。

第三个习惯是“平时就把调试通道打通”。我维护的每台关键机器,都要求满足三个基本条件:串口控制台可访问、kdump 已验证、sysrq 可用。这三条通道平时看着多余,真出事时每一条都可能成为唯一的救命稻草。

虽然内核挂死问题的表现千奇百怪,且每个现场都有自己的特殊性,但只要你能做到平时把三件事准备好——串口、kdump、sysrq,事中按“固定现场、采集日志、分析栈、关联变更”的顺序走,大部分根因都能落到具体模块或设备上。我个人现在对每台关键机器的要求是变更前后各做一次 dump 验证,新驱动的灰度范围不超过 10%。这些习惯看着不起眼,却是我这些年从一次次现场里换来的最实在的结论。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询