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内核提供的一套“紧急后台指令”,即使系统大部分功能已经瘫痪,只要内核中断处理还基本正常,就可以通过它强制执行一些关键操作。在物理机上需要按Alt+SysRq+功能键,在串口控制台或某些虚拟化环境里,更稳定的做法是直接往/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_watchdog=1 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,理由是简单可靠:内核启动参数里加上console=ttyS0,115200systemd的启动日志也配置console=ttyS0转发这样就算网络的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="... crashkernel=512M"安装并启动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 CPU,B 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_to+0x... nfs_wait_on_request+0x... nfs_page_async_flush+0x... nfs_flush_inode+0x... nfs_release_page+0x...这个栈非常典型:进程卡在nfs_wait_on_request上等待NFS请求完成,但这个请求因为存储切换时的连接已被重置,RPC层没有正确处理重连,请求在超时时钟内“永久悬挂”。更糟的是根目录挂在NFS上,几乎所有路径解析和配置读取都要经过NFS文件系统,这就形成了所有进程排队等锁的雪崩。处理措施分两步,紧急恢复是重启服务器并在启动参数里临时加上rootflags=hard,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_oops=1 sysctl -w kernel.hung_task_timeout_secs=300 sysctl -w kernel.softlockup_panic=1 sysctl -w kernel.hardlockup_panic=1这几个参数的含义是:遇到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输出的习惯。内核版本、发行版补丁级别、编译时间,这些信息看起来不起眼,但有时候决定两个看似完全相同的问题是否是同一种原因。另一个是养成“先备份内核日志,再重启系统”的肌肉记忆。我在经历了几次“重启之后日志全丢”的痛苦后,开始强制自己进入排障流程的第一步永远是拉日志,哪怕只是丢到本地临时文件里,也比什么都留不下强得多。
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内核提供的一套“紧急后台指令”,即使系统大部分功能已经瘫痪,只要内核中断处理还基本正常,就可以通过它强制执行一些关键操作。在物理机上需要按Alt+SysRq+功能键,在串口控制台或某些虚拟化环境里,更稳定的做法是直接往/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_watchdog=1 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,理由是简单可靠:内核启动参数里加上console=ttyS0,115200systemd的启动日志也配置console=ttyS0转发这样就算网络的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="... crashkernel=512M"安装并启动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 CPU,B 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_to+0x... nfs_wait_on_request+0x... nfs_page_async_flush+0x... nfs_flush_inode+0x... nfs_release_page+0x...这个栈非常典型:进程卡在nfs_wait_on_request上等待NFS请求完成,但这个请求因为存储切换时的连接已被重置,RPC层没有正确处理重连,请求在超时时钟内“永久悬挂”。更糟的是根目录挂在NFS上,几乎所有路径解析和配置读取都要经过NFS文件系统,这就形成了所有进程排队等锁的雪崩。处理措施分两步,紧急恢复是重启服务器并在启动参数里临时加上rootflags=hard,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_oops=1 sysctl -w kernel.hung_task_timeout_secs=300 sysctl -w kernel.softlockup_panic=1 sysctl -w kernel.hardlockup_panic=1这几个参数的含义是:遇到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输出的习惯。内核版本、发行版补丁级别、编译时间,这些信息看起来不起眼,但有时候决定两个看似完全相同的问题是否是同一种原因。另一个是养成“先备份内核日志,再重启系统”的肌肉记忆。我在经历了几次“重启之后日志全丢”的痛苦后,开始强制自己进入排障流程的第一步永远是拉日志,哪怕只是丢到本地临时文件里,也比什么都留不下强得多。