ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Linux内核恐慌(Kernel Panic)排查与预防实战指南

Linux内核恐慌(Kernel Panic)排查与预防实战指南 凌晨两点十七分手机监控推送把我和周公强行拆散线上数据库主库失联机房远程管理卡里印着一行冰冷的字符——Kernel Panic - not syncing: Fatal exception。这台机器过去半年一直稳如老狗怎么突然就崩了退回到两年前我第一次遇见内核恐慌第一反应是联系机房重装系统后来才知道那是最笨的处理方式。如果你也在 Linux 服务器或工作站上遇到过这种全屏滚字母、键盘鼠标全无响应的场面大概率第一反应是拍张照片然后重启。但照片里那一大堆看不懂的英文其实暗藏玄机。这篇指南我会从内核恐慌的本质讲起一步步拆解日志采集、崩溃转储分析、高频案例定位链路最后给出一套日常预防的配置清单。整个过程基于实操经验不是教科书式罗列适合所有被内核崩溃折磨过的运维、开发和对 Linux 底层感兴趣的爱好者。1. Kernel Panic 的本质——它不是死机这么简单很多朋友把内核恐慌和普通死机混为一谈其实两者有本质区别。普通应用卡死、系统无响应通常是用户态进程在闹情绪内核还能正常调度而 Kernel Panic 是内核主动选择了自杀式保护。1.1 内核态与用户态为什么内核要壮士断腕现代操作系统把 CPU 的运行等级划分为内核态和用户态。用户态的程序比如 Nginx、MySQL、你的 shell运行在受限的指令集内它们只能通过系统调用syscall向内核申请资源。内核则运行在高特权级别负责管理内存、进程调度、驱动硬件。这里的关键在于内核态出错的后果比用户态严重得多。用户态程序崩溃最多就是那个进程消失内核对其他进程的保护机制还在而内核一旦越界访问了非法内存、陷入死锁或者执行了非法指令整个系统的内存管理和进程调度就处于不可信状态。这时候内核要是硬撑着继续运行反而可能写坏磁盘数据、破坏文件系统造成比宕机更严重的损失。所以内核选择了那条最保守的路径打印一段错误信息停止一切活动。这就是 Kernel Panic 的底层逻辑——它不是故障本身而是内核在检测到不可恢复错误后的一种保护性响应。1.2 oops 和 panic 的区别级别不同处理方式不同在日常运维中你还会遇到一个词叫oops。从字面上看oops 像是内核哎呀一声但它的严重程度低于 panic。用代码审计类比oops 是内核捕获到一个异常比如空指针解引用它会打印调用栈杀掉触发的进程然后尝试继续运行。但如果这个异常发生在中断上下文或者内核自身的关键路径上内核发现我已经没法继续玩了就会升级为 panic。判别方法也很直接如果日志信息中包含Kernel panic - not syncing那就不用抱侥幸心理了系统已经彻底停摆如果只看到Oops:开头的一段栈信息系统可能还活着但那个触发问题的进程已经没了。1.3 触发内核恐慌的常见诱因根据我这些年和内核崩溃打交道的经验诱因大致可以分成四类硬件层面的不稳定内存条故障、CPU 过热、电源供电波动、PCIe 设备异常这类问题最隐蔽因为硬件看似正常工作只是偶尔抽风。驱动缺陷第三方驱动或内核模块尤其是网卡、显卡、存储控制器驱动的 bug可能在特定流量或特定操作下踩中内核的敏感路径。内核自身的 bug不常见但存在尤其是使用了某些新特性、新文件系统或者打了不兼容的补丁。文件系统或存储固件问题SSD 固件 bug 导致返回损坏数据ext4/XFS 在特定操作序列下触发断言失败。记住这个分类很重要后面排查的时候你会不停地在硬件还是软件内核还是驱动自家代码还是上游 bug这几个维度之间做排除。2. 从看见恐慌到抓住证据日志采集是第一道防线内核恐慌那一刻屏幕上滚动的字符转瞬即逝。如果你没有做任何准备重启之后就两手空空只能靠猜来定位问题。所以我在所有生产环境都会提前配置好三层证据采集体系。2.1 第一层串口和控制台日志很多服务器故障时显示器上根本看不到画面远程管理卡黑屏、无头服务器靠拍屏幕不现实。建议在 GRUB 内核引导参数里加上串口 console 输出# 编辑 /etc/default/grub GRUB_CMDLINE_LINUXconsoletty0 consolettyS0,115200n8然后执行grub2-mkconfig -o /boot/grub2/grub.cfg不同发行版路径有差异重启生效。这样内核启动信息和 panic 日志会同时输出到串口连上串口线或通过 IPMI SOLSerial-over-LAN就能完整录到崩溃现场。如果机器上没有串口设备netconsole是另一个不错的方案——把内核日志通过网络发到另一台日志服务器。配置方式不复杂但要注意它依赖网络驱动正常工作如果是网卡驱动本身导致崩溃netconsole 也可能失效。所以我的原则是串口优先netconsole 作为辅助。2.2 第二层kdump 与 crashkernel 内存预留串口日志能记录下内核打印的信息但信息量有限——内核打印的最后几十行只能看到表象看不到崩溃那一刻的内存全貌和完整调用栈。要想真正把案发现场保留下来必须用 kdump。kdump 的核心思想是在系统启动时预留一小段内存crashkernel当内核 panic 时正在运行的内核让位给这个预留的急救内核由它把崩溃内核的内存镜像vmcore保存到磁盘。配置步骤大概是这样的# 1. 在 /etc/default/grub 里添加 crashkernel 参数 GRUB_CMDLINE_LINUXcrashkernel512M # 2. 更新 grub 配置并安装 kexec-tools grub2-mkconfig -o /boot/grub2/grub.cfg yum install kexec-tools crash # Debian/Ubuntu 用 apt install kdump-tools # 3. 修改 /etc/kdump.conf 指定转储路径 # 例如path /var/crash 表示保存到本地磁盘 path /var/crash core_collector makedumpfile -c -d 31 # 4. 启动 kdump 服务并验证 systemctl enable kdump systemctl start kdump systemctl status kdump关于crashkernel512M这个值怎么定我的经验是小内存机器4G 以下预留 256M 也够用但生产环境内存通常 16G 起步预留 512M 比较稳妥。预留过大会浪费系统可用内存预留过小会导致急救内核启动失败vmcore 根本存不下来。判断是否生效可以看/sys/kernel/kexec_crash_loaded的内容是否为 1。验证 kdump 是否真正可用我习惯在测试环境主动触发一次 panicecho c /proc/sysrq-trigger执行完这条命令系统会立刻崩溃然后自动重启。重启后检查/var/crash下是否有 vmcore 文件。这项演练建议在新机器部署时就做别等到故障发生才发现 kdump 配了一堆参数但根本没法用。2.3 第三层持久化日志与监控告警前面两层解决的是崩溃现场这一层解决崩溃前的蛛丝马迹。很多内核问题在真正 panic 之前已经在dmesg或/var/log/messages里留下过异常的征兆。比如内存 ECC 报错、磁盘 I/O 超时、ext4 文件系统警告、驱动固件告警等。我配置服务器时都会把rsyslog或systemd-journald的日志发送到集中的日志平台并对 Hardware Error, blocked for more than 120 seconds, BUG:, Oops:, panic 这些关键词设置告警。有时候你会在日志里看到一周前就出现过的内存报错而 panic 只是那个错误积累到临界点的总爆发。3. 崩溃转储的解读——vmlinux、vmcore 与 crash 工具的配合当你的系统真正产生了 vmcore接下来最关键的一步就是把它打开看看内核到底死在了哪里。这里我用的是crash工具搭配有符号表的vmlinux内核文件。很多新手卡在这一步因为不清楚这些文件之间的关系。3.1 搞清楚 vmlinux 和 vmlinuz 的区别/boot下通常能看到vmlinuz-xxx这个文件它是压缩过的内核镜像构建调试用的vmlinux是未压缩、带完整调试符号的版本一般几百 MB 到 1GB 大小生产环境默认不会安装。crash工具分析 vmcore 时必须依赖 vmlinux 里的符号信息否则它只能显示一串串内存地址完全无法还原成函数名。不同发行版提供的途径不一样CentOS/RHEL 可以通过debuginfo-install kernel-debuginfo安装对应的 debuginfo 包Ubuntu 需要开启 ddebs 源后安装linux-image-$(uname -r)-dbgsym。安装好后用下面的命令进入 crash 交互环境crash /usr/lib/debug/lib/modules/$(uname -r)/vmlinux /var/crash/127.0.0.1-2024-01-15-03:22:11/vmcore3.2 crash 的基本操作从一堆十六进制到人话进入 crash 后我第一个命令永远是btbacktrace它会打印崩溃时所有 CPU 的调用栈。举个例子一段典型的输出长这样PID: 1427 TASK: ffff9a4d81a90000 CPU: 3 COMMAND: kworker/u8:2 #0 [ffff9a4d82813c40] machine_kexec at ffffffff8105b4be #1 [ffff9a4d82813ca0] __crash_kexec at ffffffff8111bc2a #2 [ffff9a4d82813d70] panic at ffffffff810a000f #3 [ffff9a4d82813dd0] oops_end at ffffffff8102bdf6 #4 [ffff9a4d82813de0] no_context at ffffffff8104b558 #5 [ffff9a4d82813e40] __bad_area_nosemaphore at ffffffff8104b8a1 #6 [ffff9a4d82813e90] bad_area_nosemaphore at ffffffff8104b989 #7 [ffff9a4d82813ea0] do_page_fault at ffffffff8104b8f6 #8 [ffff9a4d82813f30] page_fault at ffffffff81a2b1a5这段栈的读法是从下往上最开始是page_fault也就是 CPU 触发了一个缺页异常紧接着do_page_fault处理这个异常时发现地址非法进入bad_area_nosemaphore内核判断这个错误无法恢复调用oops_end最终升级为panic。如果栈里出现某个驱动模块的函数名比如e1000_clean_rx_irq或者nvme_queue_rq那嫌疑就非常聚焦了。bt之外我常用的命令还有log查看崩溃时内核环形缓冲区里的完整日志相当于 dmesg 输出。它比串口日志信息更全能看到 panic 之前的内核消息。ps查看崩溃时系统中还有哪些进程在运行能够还原当时的进程上下文。files查看进程打开的文件排查是否有异常的文件描述符。dev查看设备信息配合驱动模块判断问题是否出在设备 I/O 上。mod -s 模块名加载模块符号表如果模块不在默认路径下需要手动指定模块目录mod -s /path/to/modules/4.18.0-xxx.el8.x86_64/3.3 阅读内核最后的遗言日志里那些高频关键词当你执行log看到内核最后的输出会碰到下面这些高频词BUG: unable to handle kernel NULL pointer dereference at ...最常见的一种某个内核函数对一个空指针做了解引用。排查方向这个地址是 0 还是接近 0如果是 0 基本就是未检查的分配失败或错误的对象指针。general protection fault通常是访问了已经释放的内存或者对象头被破坏。和内存越界、UAFUse-After-Free密切相关。Kernel panic - not syncing: Out of memory and no killable processes内核内存耗尽但连一个可杀进程都不剩这个通常在 cgroup 限制或内存泄漏时出现。Kernel panic - not syncing: Fatal exception in interrupt中断上下文中的异常基本都是驱动相关的问题。Watchdog detected hard LOCKUP on cpu XCPU 锁死通常是驱动在关中断状态下死循环。理解这些关键词能帮你快速判断问题归属哪一类。接下来我会结合案例讲具体的定位链路。4. 实战案例拆解三类高频内核恐慌的定位链路理论说太多容易飘我直接拆三个我实际处理过的案例。每个案例的过程都还原了症状→怀疑→验证→根因的完整链路你可以直接把这个套路套用在自己的故障里。4.1 案例一内存损坏——最像灵异事件的随机崩溃症状一台基于 CentOS 7 的应用服务器运行两三个月后随机 panic。崩溃时间毫无规律白天晚上都有压力大时频繁一点。vmcore 里bt显示的调用栈五花八门今天是ext4_do_update_inode明天是tcp_sendmsg后天又变成某个驱动函数。单看任何一次栈都觉得是那个模块的 bug但合在一起就没有共同点了。定位链路这种情况我第一个想到的就是内存。怎么验证先查内核日志里有没有 MCEMachine Check Exception记录journalctl -k | grep -i Hardware Error mcelog --daemon # 或者 dmesg | grep -i mce如果看到EDAC MC0: UE row 1, channel 0这类错误那基本可以锁定内存条的物理位置。紧接着用dmidecode -t memory查看内存插槽对应的物理槽位联系机房更换对应内存条。但还有一种更阴的情况ECC 内存报错可能被 BIOS 静默吞掉日志里完全看不到 MCE。这时候最笨也最有效的方法是做一轮 memtest86 的压力测试或者干脆把内存条一根根拔下来交叉测试。我的心得遇到随机分布在内核各个子系统的 panic优先怀疑内存这是性价比最高的排查方向。很多运维一看到不同模块的调用栈就陷进某个驱动 bug的坑里回头发现只是内存条坏了白白浪费好几天。4.2 案例二驱动缺陷——调用栈直指凶手症状一台运行 Docker 的宿主机某个网卡在流量高峰期触发 panic。vmcore 里bt的栈底指向bnx2x_poll这是 Broadcom 网卡驱动的一个收包函数。日志里还有大量bnx2x: mcp timeout的警告。定位链路这个案例相对好定位因为调用栈非常明确。但难点在于区分驱动 bug和硬件问题——网卡坏了也可能导致驱动函数收包异常。我当时的做法是检查网卡温度、光模块光功率、链路错误计数ethtool -S看rx_errors,tx_errors,rx_crc_errors更新网卡固件到官网最新版本收集驱动版本信息ethtool -i对比厂商 release notes在测试环境用同样的流量重新压测观察是否复现。最终确认是驱动在某个特定的 RSS 队列重分配路径上存在锁问题升级驱动后问题消失。我的心得驱动类 panic 定位相对容易难点在于背锅侠判断。我见过不少因为驱动 bug 反复 panic 的机器也见过硬件故障却被驱动背锅的情况。两手都要查不要只盯着调用栈里的函数名。4.3 案例三文件系统与存储固件——数据损坏引发的连锁崩溃症状一台运行 MySQL 的服务器某天突然出现Kernel panic - not syncing: EXT4-fs (device sdb1): panic forced after error。日志前面能看到EXT4-fs error (device sdb1): ext4_lookup: deleted inode referenced。定位链路这个案例的关键在于EXT4 文件系统在内核配置了errorspanic的情况下只要检测到文件系统内部不一致就会主动触发 panic——它宁可停机也不愿意继续读写损坏的数据。所以根因不在 panic 本身而在为什么会出现文件系统错误。排查顺序先看存储设备本身是否健康smartctl -a /dev/sdb检查Reallocated_Sector_Ct,Current_Pending_Sector,UDMA_CRC_Error_Count这些关键属性检查 RAID 控制器日志和 BBU电池备份单元状态把这一层磁盘的固件升级到厂商修复版本文件系统层用e2fsck离线状态下做一次全面检查。最后定位到 SSD 固件在极端写入压力下会返回错误数据导致文件系统元数据损坏。固件升级后重挂文件系统并恢复备份问题解决。我的心得存储类的 panic第一反应别急着跑fsck先查硬盘健康状态。在硬件还在持续写坏数据的时候跑fsck可能把问题扩大化。正确顺序是先确认硬件稳定再做文件系统修复。4.4 排查方法论三个关键问题把上面三个案例抽象一下每次内核 panic 的定位本质上都是在回答三个问题调用栈指向哪个模块或函数如果不同 panic 的调用栈各不相同优先怀疑硬件如果调用栈高度一致优先怀疑对应的驱动或内核模块。panic 之前发生了什么看日志里有没有内存报错、I/O 超时、温度告警、驱动 timeout 警告。这个系统和平时有什么不同最近是否更新过内核、安装过新驱动、调整过 BIOS 或固件5. 那些误导人的表象与排查误区——踩坑启发技术方案讲完我想专门写一段反套路的内容。内核恐慌的排查里有四个我亲眼见过或者说自己踩过的坑希望能帮你少走弯路。5.1 误区一panic 后立刻重启现场全丢这是最可惜的一种操作。很多新入行的运维一看内核崩溃就慌第一时间重启服务确保业务恢复。结果 vmcore 没落地、串口日志没录后续想追查只能靠着一句复制下来的错误信息瞎猜。我的建议是如果服务允许先花五分钟把/proc/kmsg或串口屏上的最后几百行日志完整记录下来确认 kdump 已经保存完 vmcore 再重启。多花五分钟能省后面好几天。尤其是在治标和治本之间你总得留下能治本的证据。5.2 误区二mono 性认为内核版本越新越安全很多朋友遇到 panic 后的第一个想法是升级内核到最新版。这个方向不一定错但要注意如果旧版本内核是你当前业务环境验证过的稳定版本贸然升到新版本可能引入新的兼容性问题。某些硬件厂商特别是存储、网卡对内核版本有严格限制升级内核必须同步升级驱动。正确的做法是先根据调用栈和日志定位问题属于驱动、硬件还是内核本身再决定是升级内核、升级驱动、升级固件还是更换硬件。盲目升级可能只是把当前的 panic 变成另一种 panic。5.3 误区三忽视dmesg里的历史警告panic 是最后的一根稻草在那之前往往已经有很多前兆。比如我在上面案例三里提到的EXT4-fs error它其实在 panic 发生前几小时就已经在日志里出现过了。再比如网络驱动的mcp timeout、内存的 ECC 纠错记录、磁盘的 I/O 超时都是早期信号。所以搭建一个日志聚合和告警系统非常关键。等 panic 发生了才去翻日志你只能看到冰山一角如果日志系统一直在记录和告警很多问题在 panic 之前就已经被定位和处理了。5.4 误区四只分析一个 vmcore不看统计趋势单次 vmcore 的分析只能告诉你这次死在了哪里却很难告诉你为什么是这次。我处理过一个案例同一个机器的 vmcore 生成了二十几个每个调用栈都指向不同的地方。如果只看第一个和最后一个你会认为是完全无关的两个问题但把所有 vmcore 放在一起看才发现它们共同的背后是内存 ECC 错误在逐步恶化。所以我建议运维同学给每台机器建立一个内核事件档案记录每次 panic 的时间、调用栈摘要、硬件变化、内核版本、驱动版本。时间久了很多规律会自然浮现出来。6. 让系统不恐慌预防配置与日常巡检的实用清单排查和善后固然重要但更值钱的其实是不让它发生或发生了能快速恢复。下面这套配置和巡检思路是我在几十台服务器上实践后的精简版直接抄作业就行。6.1 内核参数里的自动重启开关在生产环境有些服务可以接受秒级或分钟级的中断但无法接受长时间宕机。这种情况下让内核 panic 后自动重启就很有必要# /etc/sysctl.conf kernel.panic 10 kernel.panic_on_oops 1kernel.panic 10表示 panic 后 10 秒自动重启单位是秒。kernel.panic_on_oops 1表示一旦发生 oops 就升级为 panic避免系统带病继续运行造成数据损坏。但要注意自动重启是把双刃剑。如果没有配置 kdump重启后现场全丢而且如果是硬件层面的不稳定重启后可能反复 panic陷入崩溃-重启-崩溃的循环。所以务必要和 kdump 配合使用。6.2 kdump 配置检查清单我在部署任何 Linux 服务器时都会走一遍下面的自查流程检查项预期结果验证命令crashkernel 参数是否存在内核启动参数包含 crashkernelcat /proc/cmdlinecrashkernel 内存是否预留成功文件内容为 1/sys/kernel/kexec_crash_loadedkdump 服务状态active (running)systemctl status kdump转储目标路径可写目录存在且剩余空间充足df -h /var/crash本地触发 panic 演练重启后生成 vmcoreecho c /proc/sysrq-trigger关于 crashkernel 内存大小补充一点经验如果机器内存 64Gcrashkernel512M够用如果业务内存很满vmcore 转储时会用 makedumpfile 进行压缩但压缩也需要时间这段时间内 IO 压力会比较大。所以大内存机器建议预留 1G小内存机器可以配crashkernel256M。6.3 巡检清单把问题消灭在 panic 之前日常巡检不需要天天看一周一次足够。下面是我的一套轻量巡检命令# 1. 检查内核日志中的错误关键词 journalctl -k --since 7 days ago | grep -Ei error|fail|warn|bug|oops|panic | head -50 # 2. 检查硬件健康状态重点看内存和磁盘 dmesg | grep -i Hardware Error mcelog --client 2/dev/null smartctl -a /dev/sda | grep -E Reallocated|Pending|CRC_Error # 3. 检查文件系统错误 dmesg | grep -Ei ext4| xfs |btrfs | grep -i error # 4. 检查内存使用和 SWAP 周转 free -h cat /proc/meminfo | grep -E CommitLimit|Committed_AS # 5. 检查 CPU/主板温度如果有 lm_sensors sensors 2/dev/null之所以巡检要覆盖这几个点是因为我在实际工作中发现绝大多数 panic 前 7 天内日志里已经出现过至少一次相关的硬件或驱动告警。只是这些告警被淹没在滚动日志里没人注意到。6.4 模拟演练别让预案停留在文档里最后给一个容易被忽略但很有价值的建议定期做内核崩溃模拟演练。我在新服务器上线时会主动触发一次 panic确认 kdump 能生成 vmcore、crash 工具能正常打开、团队有人能在 30 分钟内给出初步定位结论。这种做法在生产环境也许很久用不上但真正遇上 panic 时熟练度会帮你节省大量抢救时间。演练的流程很简单先在测试机上完成上面 6.2 小节的配置检查然后执行echo c /proc/sysrq-trigger观察重启后 vmcore 是否落地再用 crash 工具完整走一遍bt、log、ps的流程。跑过一次之后你就知道从容应对是什么感觉了。写在最后一份来自实战的内核稳定经验说实话写这篇指南的时候我心里最大的感悟是内核恐慌不是洪水猛兽它更像是一位老实人最后的呐喊——内核知道自己的状态已经不可信了与其继续带病运行不如停下来把现场留给你。关键是你有没有准备好接收它的遗言。我的个人习惯是三点所有生产环境必须配置 kdump 和串口日志内存和存储的硬件告警一个都不能放过每次 panic 之后必须形成一份分析报告归档。坚持做到这三点之后我再遇到内核恐慌时心态已经从完了又宕机了变成了让我看看这次是怎么回事。如果你手头有一台刚 panic 完的机器不要急着重装系统先按这篇指南的步骤把日志、vmcore、调用栈收集起来你会发现百分之八十的 panic 都是可以追根溯源并被彻底修复的。希望这份指南能让你在下一次面对内核恐慌时多一分从容少一分慌张。
返回列表