内核报错unable to handle kernel paging request:内存还是驱动?排查全攻略

📅 发布时间:2026/10/11 10:10:47
内核报错unable to handle kernel paging request:内存还是驱动?排查全攻略
后台一报“unable to handle kernel paging request”群里往往紧接着就有人喊“内存坏了赶紧换”。这个反应我太熟悉了因为自己刚入行时也这么喊过结果折腾半天换了三根内存条问题纹丝不动最后发现是一块网卡驱动的锅。反过来也有同事一口咬定是驱动问题查了一周代码最后内存条拿下来一测坏得明明白白。这种报错就像服务器内核在崩溃前留下的一段“乱码口供”看着吓人但里面藏着足够多的线索。它确实是内存故障的头号嫌疑却远不是唯一原因驱动与内核模块的指针错误、设备透传时的地址映射异常、固件给内核挖的坑都可能打出同一行日志。本文就把我从现场摸出来的经验写清楚这个报错到底在说什么怎么用最快路径判断是内存坏了还是驱动背锅以及遇到之后第一步该做什么。1. 先把这个报错翻译成人话1.1 内核到底在向你汇报什么现代 CPU 访问内存并不是直接拿物理地址去读而是通过页表做一层地址转换虚拟地址先查页目录再查页表最终找到物理页帧。这个过程如果某一级的页表项是空的、非法的、或者对应的物理地址根本不存在CPU 就会触发一个缺页异常把控制权交回内核。内核在处理这个异常时发现你访问的虚拟地址不在任何合法映射里也根本不是一次正常的缺页加载比如按需换页、写时复制只能判定这是一次非法访问。于是打印出 “unable to handle kernel paging request at 0xffff…” 这一行然后根据配置决定是杀掉当前进程、触发 Oops还是直接 panic 重启。你可以把它理解成一份按图索骥的地图内核拿着地址簿去找一块内存发现地址簿指向一片根本不存在的地皮自然要停下来报警。报警信息里的关键字段就是这份“口供”的核心部分。1.2 Oops 信息里每一行都是线索内核在报错时会附带一整套现场记录很多新手只看到开头的红色 panic 就慌了其实后面每一行都不能跳过“BUG: unable to handle kernel paging request at ffffc900...”出问题的虚拟地址“RIP: 0010:...”出错的指令位置0010 表示内核态代码段“CR2: ...”异常发生时 CPU 里保存的出错地址和报错行地址一致“Call Trace”从当前函数一路回溯的调用栈“Code: ...”出错点附近的机器码反汇编后能看到具体指令我第一次遇到这类报错时只看了“unable to handle”就去拆机器了后来才明白RIP 和 Call Trace 才是破案的入口。RIP 如果指向某个驱动模块的代码段问题大概率出在驱动或模块与内核的配合上如果指向内核自身某个与内存管理强相关的函数比如释放页面的路径那硬件故障、尤其是内存颗粒问题的可能性就迅速上升。1.3 三种常见出场时机先对号入座同样的报错在不同阶段出现指向的原因天差地别启动阶段内核刚解压、驱动刚初始化就崩常见的嫌疑是 initramfs 包含的模块与当前内核不匹配、某个硬件固件返回了非法资源、或者内核参数指定的内存范围有问题。运行一段时间后随机出现这种最让人头疼内存坏、驱动 bug、固件定时器出错都会以这种形式出现。内存压力高峰或进程反复被杀时出现要警惕是不是有模块在回收页面的路径里使用了已经释放的页或驱动把 DMA 地址写到了错误的地方。先把自己遇到的情况归个类能省掉后面一大半无用功。比如我经历过一台机器只在凌晨备份任务跑起来后崩溃一开始所有人盯着内存和磁盘最后发现是备份软件用的某内核模块在特定缓冲区分配路径上存在地址计算错误。2. 内存坏了还是驱动问题先看特征再动手2.1 内存故障更偏向这些信号内存颗粒损坏时内核会访问到一个物理上存在、但内容已经不可靠的页帧。这种故障在日志上的表现往往很有“随机感”崩溃位置不固定这次挂在数据库进程上下次可能挂在 SSH 守护进程上同一台机器重现困难负载轻的时候几天不出问题负载一高就频繁触发报错地址散落有时 CR2 接近、有时毫不相干伴随 Machine Check Exception 提示、EDAC 或 MCE 日志出现更关键的一点是如果机器是 ECC 内存故障往往会在真正崩溃之前留下一连串“Corrected Error”记录。很多人看到 EDAC 里报“1 CE”就以为没事其实这相当于内存颗粒已经在持续出错纠错机制只是把错误盖住了。累积到无法纠正时就是 UE、MCE panic 或者这种 paging request 报错。非 ECC 内存则没有预报直接用 memtest86 这类工具离线扫描是更靠谱的手段。我的经验是至少跑三到四遍完整测试并且在测试时加压、升温坏颗粒往往在高负载下才现形。2.2 驱动问题的明显线索驱动或内核模块导致的 paging request逻辑上通常是模块内部指针没初始化、使用已释放的内存、或者设备 DMA 把数据写到了非法地址。它有一些非常典型的“指纹”Call Trace 里有明确的模块名比如某存储驱动、某网卡驱动、某 GPU 驱动的符号崩溃和特定功能强相关比如网卡一收大包就崩、GPU 一跑特定任务就崩、磁盘一执行某种队列命令就崩模块版本与当前内核明显不匹配通过 modinfo 能看到模块的 vermagic 和内核版本对不上报错地址往往落在模块自身分配的内存区域而不是随机物理地址更换驱动版本后相同负载下问题消失或转移我在排查时有一个习惯先把 call trace 里出现的第一个非内核符号记下来用 lsmod 查它属于哪个模块再通过 objdump 或 gdb 反汇编 RIP 附近代码。如果 RIP 落在模块代码段硬件故障的概率就大幅下降应该优先查驱动。2.3 别忘了还有其他“嫌疑犯”内存和驱动是头号嫌疑但并非全部。实际排障中我还遇到过固件与 ACPI 表问题某个设备向内核报告了一个非法的内存资源内核访问时就触发 paging requestPCIe 设备透传与 IOMMU 配置错误虚拟化场景里设备直通给虚拟机后 DMA 映射出错宿主机日志里会出现这种报错内核自身 bug某个路径上对页表做了错误的引用计数文件系统或存储栈 bug块设备驱动把 bio 指向已释放页面所以更稳妥的思路不是“二选一”而是把日志特征当作证据链交叉验证。我用下面这张表快速归类。嫌疑对象典型特征首要排查手段物理内存崩溃随机、负载相关、EDAC/MCE 报错、memtest 可复现离线内存检测、单条替换、查 SEL 日志驱动/内核模块Call Trace 指向模块、功能相关性、版本不匹配反汇编 RIP、卸载或升级模块、检查 vermagic固件/ACPI启动早期或特定设备初始化时出现升级固件、对比同一机型其他机器IOMMU/透传虚拟化环境、直通设备操作时出现检查 DMAR 日志、关闭直通验证3. 排查实操从固定现场到硬件验证3.1 第一步永远是固定现场别急着重启很多人一看到内核 panic 就下意识重启结果机器是起来了线索全没了。正确做法是先把现场“钉死”如果机器还没完全死透立刻执行 dmesg 或 journalctl -k 保存内核日志确认是否配置了 kdump有 vmcore 的话保留 /var/crash 目录从带外管理界面IPMI/BMC的 SEL 日志中捞硬件事件记录这条很多人会漏记录内核版本、模块列表、BIOS 版本、内核启动参数有条件的话保留 /proc/modules 和 dmesg 的完整全量输出这块操作我通常写成一份固定脚本因为越紧急越容易手忙脚乱# 保存当前内核日志 dmesg /root/dmesg_$(date %Y%m%d_%H%M%S).log journalctl -k -b -1 /root/kernel_log_previous_boot.log # 保存模块与系统信息 lsmod /root/lsmod_$(date %Y%m%d_%H%M%S).txt cat /proc/modules /root/lsmod_$(date %Y%m%d_%H%M%S).txt uname -a /root/uname_$(date %Y%m%d_%H%M%S).txt dmidecode -t memory /root/dmidecode_memory_$(date %Y%m%d_%H%M%S).txt这些命令不值钱但关键时刻能救命。尤其 dmidecode 这条很多人直到换内存条时才后悔当初没记下原始槽位信息。3.2 从 Oops 报道里读出有效地址拿到日志后第一件事是看报错那几行的地址。如果内核带了 kallsyms或者有 System.map 文件可以用下面方式判断地址归属先看 CR2 和报错行地址这个地址就是非法访问的虚拟地址再看 RIP 指向哪里如果 RIP 地址落在某个内核模块区间就能锁定模块用 /proc/modules 里的模块加载地址区间做比对或用 gdb 加载 vmlinux 和模块符号判断逻辑可以简化成一句话报错地址在哪个代码的“地盘”谁的锅就最大。举个例子如果日志里出现 “RIP: 0010:...[xxx_module]”, 不要犹豫立刻查这个模块的版本和加载参数# 查看模块详细信息重点是 vermagic 和依赖 modinfo xxx_module # 查看模块是否与内核版本匹配 cat /proc/modules | grep xxx_module如果 RIP 指向的是内核自身的函数比如 __free_pages 或 get_page 这类路径再结合是否有 EDAC/MCE 日志才能判断是不是内存硬件问题。很多实战案例里RIP 指向驱动模块代码但内存检测也报错这是最容易扯皮的情况我的原则是先把模块升级或移除再看硬件测试结果谁的问题都能独立验证不要互相覆盖。3.3 两条路线怎么选先查驱动还是先测内存判断顺序其实有通用套路。我把它写成一个简单决策流程如果 Call Trace 出现明确的模块名或崩溃与某个特定设备/功能强相关先走驱动路线如果报错地址在 CR2 与 RIP 之间没有明显模块归属且伴随 MCE/EDAC 记录先走内存路线如果两边都没有直接证据先做离线内存检测成本最低能排除一大半风险驱动路线的具体操作是保留当前内核日志后尝试把嫌疑模块卸载或用内核参数禁用观察同负载下是否复现如果无法禁用则换一个与当前内核版本严格匹配的驱动版本或重新编译模块。内存路线的具体操作是非 ECC 机器用 memtest86 离线跑至少两轮完整测试ECC 机器优先看 EDAC 和 mcelog检查有没有持续增长的 Corrected Error有条件就做单条内存插槽替换这是最笨但最可靠的定位法。我踩过一个坑某台机器在 memtest 下跑了一轮全绿我以为是驱动问题结果第二天崩溃更频繁后来发现是 memtest 默认测试模式没有覆盖到损坏的区域。从那以后我坚持至少跑两轮且开启全部测试模式。3.4 虚拟化与透传场景别忘了 IOMMU如果你面对的是虚拟化宿主机且虚拟机配置了 PCIe 设备直通paging request 还可能来自 IOMMU 映射异常。DMA 重映射失败、设备尝试访问一段未映射的宿主物理地址都会被内核记录为地址访问异常。这类问题的排查重点在 dmesg 里搜索 IOMMU、DMAR 相关关键字dmesg | grep -i -E iommu|dmar journalctl -k | grep -i -E iommu|dmar如果日志里出现 “DMAR: DRHD” 之后的错误或直通设备在虚拟机启动时频繁报 fault先把设备的直通关系拆掉恢复成使用虚拟网卡或虚拟磁盘再观察。很多时候问题并不在真实内存条而是硬件设备把 DMA 地址写飞了。4. 两次排障实录复盘4.1 案例一折腾完内存发现是驱动版本太老某数据中心一台存储节点内核用的某个长期支持版本跑着一个开源存储软件网卡是某厂商的万兆卡。故障表现很规律运行三到五天在某次大流量数据同步时内核 panic报的正是 unable to handle kernel paging request。第一次接到告警团队第一反应是内存因为机器已经服役超过三年。于是申请停机窗口用 memtest86 跑了两轮全绿又把 ECC 的 EDAC 日志翻了一遍没有 Corrected Error 记录。内存嫌疑暂时排除。后来仔细看 call trace发现最上面一行指向一个网卡驱动相关的符号。用 modinfo 查了一下模块版本竟然比内核源码里携带的版本旧了一大截而且从调用链看这个模块用的 API 和当前内核已经不兼容。卸载旧模块改用驱动厂商针对该内核版本发布的新版后连续观察一个季度再没出现该报错。这个案例给我的启发是内存测试没问题不代表驱动没问题但驱动问题也不能用“内存测试过了”来排除两边必须独立验证。另外很多长期运行的服务器驱动版本从部署那天起就没更新过内核补丁升级后模块 API 悄悄变了问题就会潜伏下来直到特定负载出现才爆发。4.2 案例二ECC 报错被无视内存条默默扛了半年另一台服务器是某个办公系统的数据库主机不算新但配置了 ECC 内存。现象是近一个月内多次出现进程突然被杀应用日志里报 OOM但系统内存明明还有富余。后来某天直接 panic日志里就是 unable to handle kernel paging request。翻 dmesg 时我注意到一个被忽略的细节从一个月前开始每隔一段时间就有一条 EDAC 报错内容大致是某个内存控制器报告了 1 个 Corrected Error。这种“可纠正错误”很容易被看成小问题但它的频率其实在逐步上升从几天一条变成几小时一条。这说明内存颗粒已经在劣化纠错机制只是暂时兜住了。之后我们做了两件事一是用工具把所有 ECC 错误记录导出来缕清规律二是趁维护窗口做单条替换测试把内存按槽位分组逐一压测。最终定位到某一根内存条换掉之后 EDAC 日志归零系统恢复平稳。这台机器让我彻底改变了“ECC 报错就没事”的认知现在但凡看到 CE 错误数量在增长我就直接安排隔离和替换。4.3 两个案例放在一起看这两个案例正好代表了两种极端一个驱动问题伪装得像内存故障一个内存故障却被当成常规噪声。真实排障中两者往往还会同时出现比如驱动 bug 触发了内存控制器的异常记录或者损坏的内存条恰好导致某个驱动的 DMA 缓冲区出错。所以我的建议是不要纠结“到底是谁”而要尽快列出证据把每条证据的指向度打个分。Call Trace 指向模块加 N 分EDAC 连续报错加 N 分memtest 复现加 N 分。分数最高的路线先走但另一条路也不能完全不管。5. 我现在的做法把它变成流程而不是玄学5.1 给所有重要机器配齐“考古装备”经过几次通宵排障之后我把固定现场这件事从“事后补救”变成了“事前配置”。每台上线的重要服务器都提前做好这几项启用 kdump让内核 panic 时自动保存 vmcore把内核日志通过 systemd-journal 持久化防止重启丢失配置硬件事件日志采集定期拉取 BMC 的 SEL 记录记录内核版本、驱动版本、固件版本的基线设置 panic_on_oops 为合适值避免机器挂起后无人知晓这些配置一次性成本很低但能让后续排障时间从几天压缩到半小时。没有 vmcore 的时候很多问题只能靠猜有 vmcore 之后用 crash 工具打开转储直接看 CPU 寄存器、页表状态和调用栈结论会清晰得多。5.2 日常监控把风险提前暴露我更相信监控而不是救火。针对这类报错我现在会在日常巡检里覆盖四层硬件层通过 EDAC/MCE 监控所有可纠正错误一旦发现 CE 数量曲线上升立刻安排内存检测驱动层维护一份服务器上的驱动版本与内核版本的对照表内核升级前先验证模块兼容性日志层设置对 “unable to handle kernel paging request” 关键字的自动告警并在告警时自动归档前后一小时的内核日志硬件巡检对关键业务机器每年至少做一次离线内存检测和磁盘健康检查不要小看最后一条。很多故障机器在正式崩溃前内存已经在持续小规模出错只是被缓存和纠错机制掩盖了。定期离线检测就像给服务器做体检能在问题放大之前发现病灶。5.3 真遇到这种报错时的快速处置清单最后分享一份我打印出来贴工位上的清单每条都来自实战不要立刻重启先把 dmesg、journalctl、SEL 日志、模块信息全部固定下来看 CR2 地址和 Call Trace判断 RIP 属于内核还是某个模块如果属于模块查模块版本、加载参数、与内核的兼容性同时查 EDAC/MCE看有没有持续增长的硬件错误记录根据指向明确度选择先替换驱动还是先测内存内存检测必须覆盖完整测试模式至少两轮定位后不要急着把机器放回生产先小负载验证再逐步加压我一般会在控制台敲的检查顺序也放这里方便直接复制使用dmesg | grep -i unable to handle journalctl -k -b -1 | grep -i unable to handle dmesg | grep -i -E EDAC|MCE|Hardware Error lsmod | grep -E xxx_driver|yyy_module mcelog --client 2/dev/null || cat /var/log/mcelog这套流程执行下来大部分 paging request 报错都能在三十分钟内给出一个高置信度的方向。即使最终仍需要硬件替换或驱动重装也不会再出现“拆了一下午机器最后发现改个参数就好”的尴尬。说到底unable to handle kernel paging request 不是一句定罪的判决书而是一份等待解读的现场笔录。内核已经尽量把关键信息打在脸上了你要做的不是凭经验猜而是顺着地址、调用栈和硬件日志这条线把证据链补完整。内存可能是凶手驱动也可能是有时候它俩还会合谋。但只要你一步步走真相通常就藏在最开始那几行日志里。