dmesg命令详解:从内核环形缓冲区到Linux故障排查实战
做 Linux 系统运维这些年我诊断问题的第一站往往不是业务日志而是一条 dmesg 命令。dmesg 作为内核环形缓冲区的读取工具能直接输出内核在启动和运行期间产生的日志对诊断Linux系统问题的价值远被很多人低估了。有一次半夜值班某台机器突然文件系统只读、业务写入失败我敲了 dmesg -T | tail -50两分钟内就从底层日志里锁定了磁盘 I/O 超时的根因。这篇文章就把 dmesg 的基本用法和几个我能直接复现的排查案例整理出来给需要用它救火的同行做个参考。1. 先搞懂 dmesg 的输出从哪来内核环形缓冲区的工作机制1.1 为什么内核日志会放在一个会覆盖的缓冲区里内核在运行过程中会产生大量日志从硬件探测、驱动初始化到文件系统挂载、网络协议栈事件全都会记录下来。但这些日志并不是无限制写入磁盘的内核为了性能考虑默认把它们写进一个固定大小的内存缓冲区也就是内核环形缓冲区kernel ring buffer。环形这两个字是理解 dmesg 行为的关键。缓冲区被设计成首尾相连写满之后新日志会覆盖最老的日志。这意味着你看到的内容永远是最近一段时间的内核消息而不是全部历史。这一点在日常诊断中特别重要系统运行越久、日志越密集早期信息就越可能已经被冲掉。所以遇到问题后的第一件事永远是先把当前缓冲区内容拍下来保存到文件而不是继续在屏幕上翻。环形缓冲区的存在还有第二层意义即使根文件系统损坏、磁盘完全无法写入内核依然能把事件记录在内存里。这也是为什么裸机故障、内核 panic 场景下dmesg 往往是唯一还能读取的日志来源。1.2 一条典型输出的字段到底在说什么先看一段真实风格的 dmesg 输出[ 123.456789] ata1.00: exception Emask 0x0 SAct 0x0 SErr 0x0 action 0x6 frozen [ 123.457012] ata1.00: irq_stat 0x40000008 [ 123.457201] sd 0:0:0:0: [sda] tag#12 FAILED Result: hostbyteDID_OK driverbyteDRIVER_OK [ 123.457390] sd 0:0:0:0: [sda] tag#12 Sense Key: Medium Error [ 123.457588] sd 0:0:0:0: [sda] tag#12 Add. Sense: Unrecovered read error方括号里的123.456789是内核启动之后的秒数不是墙上时钟时间。如果你不加-T参数看到的永远是这种相对时间需要自己换算成几点几分发生的。后面跟着的是打印这条日志的内核子系统或驱动名称再往后是具体事件描述。ata1.00表示第一个 ATA 控制器上的第一个设备sd 0:0:0:0是 SCSI 子系统对磁盘的枚举路径[sda]是我们在 /dev 下看到的块设备名。Sense Key: Medium Error和Unrecovered read error是 SCSI 协议层的错误描述直白翻译就是盘片上有物理坏道读不出来。这些字段看起来很零散但它们组合在一起能准确告诉你问题发生在协议栈的哪一层是磁盘介质本身、传输线缆还是控制器。我见过不少人一看到I/O error就急着换硬盘结果换完问题依旧最后发现是数据线松动。所以单独看一条信息不够要学会把相邻的多行日志当成一个整体来读。2. 基本用法速览我日常用得最多的几组参数2.1 按级别过滤只看错误和警告dmesg 的原始输出是逐行流水账正常系统启动下来少说几百行全量看根本不可能。我日常第一条命令永远是按级别过滤dmesg -T -l err dmesg -T -l emerg -l alert -l crit -l err -l warn内核日志有严格的优先级体系从高到低分别是 emerg、alert、crit、err、warn、notice、info、debug。用-l参数可以只显示指定级别的行。-l err只显示错误级-l warn只显示警告级。实际排障时我经常把 warn 也带上因为很多驱动对硬件异常只会发警告但不会报错比如某块网卡的链路降速警告级日志里就能看到。这里有一个容易被忽略的细节-l过滤的是日志本身的级别而不是日志内容的严重程度。有些驱动习惯把所有状态信息都打在 warn 级别有些则只把真正的错误放在 err。所以只看 err 反而可能漏线索。我的习惯是先看-l err -l warn两级如果没结果再扩大范围看 notice。2.2 按关键字过滤用 grep 精准定位子系统级别过滤能筛掉大部分噪音但碰到具体问题时按子系统关键字搜索更直接。比如怀疑磁盘问题我会这么做dmesg -T | grep -i -E sda|sd [0-9]|ata|scsi|nvme dmesg -T | grep -i -E usb|input|hid # 外设问题 dmesg -T | grep -i -E igb|e1000|mlx|eth # 网卡问题 dmesg -T | grep -i -E out of memory|oom-killer # 内存问题-i参数让 grep 忽略大小写因为内核日志里的设备名有时是全小写有时是混着大写。需要说明的是dmesg 本身没有按模块过滤的独立参数唯一可靠的入口就是通过关键字 grep这也是为什么前面要先理解了各个子系统在日志里的命名规律。实际排障时我会把grep -E的匹配词收集在一个变量里反复用避免每次重敲一长串。2.3 时间戳-T 和 -e 的区别默认的[123.456789]格式对脚本处理友好但人眼阅读很费劲。dmesg -T会把它换算成类似[Mon Jan 13 14:22:31 2025]的墙上时钟时间换算逻辑很简单当前系统时间减去 uptime。这带来一个小坑如果系统休眠过、或者内核时钟源不稳定-T显示的绝对时间可能偏几分钟但在大多数服务器场景下精度完全够用。如果只想看最近发生的事件距现在多久用-e可能更直观dmesg -e -l err它会显示每条日志距离当前时刻的秒数差比如[ -5.123]表示五秒之前发生的。这个参数在实时跟踪事件时特别顺手。配合-w持续输出相当于一个内核级的事件实时监视器dmesg -w -T拔插一根 USB 线、移除一块网卡都能立刻看到对应事件。这个组合我在硬件验收、驱动调试时几乎每次都用。3. 案例一文件系统只读与磁盘 I/O 超时的定位3.1 现象业务写入失败挂载点变成只读某天凌晨某台数据库服务器报警。应用日志里大量出现Read-only file system错误mysqld 进程试图写入数据文件却完全失败。因为文件系统已被内核强制切换为只读应用层怎么重试都没有意义。遇到这种情况我的第一判断是底层块设备出了问题而不是文件系统本身损坏。因为 ext4、xfs 这些文件系统如果没做特殊配置在内核检测到大量 I/O 错误后出于保护数据一致性的考虑会把挂载点重新挂载为只读阻止后续写入可能带来的二次破坏。这时候直接看业务日志是浪费时间。内核在做这个决定之前一定已经把原因写进了环形缓冲区。3.2 dmesg 里的关键线索执行dmesg -T -l err | tail -30输出如下做了脱敏整理[Mon Jan 13 03:12:47 2025] sd 0:0:0:0: [sda] tag#12 FAILED Result: hostbyteDID_OK driverbyteDRIVER_OK [Mon Jan 13 03:12:47 2025] sd 0:0:0:0: [sda] tag#12 Sense Key: Medium Error [Mon Jan 13 03:12:47 2025] sd 0:0:0:0: [sda] tag#12 Add. Sense: Unrecovered read error [Mon Jan 13 03:12:47 2025] sd 0:0:0:0: [sda] tag#12 CDB: Read(10) 28 00 00 2f 34 56 00 00 08 00 [Mon Jan 13 03:12:47 2025] blk_update_request: I/O error, dev sda, sector 0x2f3456 op 0x0:(READ) flags 0x0 phys_seg 1 prio class 0 [Mon Jan 13 03:12:48 2025] EXT4-fs error (device sda1): ext4_do_update_inode: IO failure这里最重要的几处信息Sense Key: Medium Error明确把问题指向存储介质sector 0x2f3456给出了具体出错的逻辑块地址EXT4-fs error说明文件系统层确实因为底层 I/O 失败而产生了错误。tag#12是请求标签用来关联同一批下发到设备的命令。这几行连在一起基本可以断定是物理坏道或盘片介质退化而不是线缆松动或控制器异常。3.3 排查链路从几行日志到根因判断整个判断链路我按下面几步走。第一步确认DID_OK与DRIVER_OK说明主机总线适配器和驱动层都认为命令正常发出去了只有设备层给出了错误应答所以问题大概率在磁盘自身。第二步确认Medium Error与Unrecovered read error这是设备明确表示读不到数据属于介质级故障。第三步看sector 0x2f3456是否集中在同一个区域。如果出错扇区分散得很开可能是盘片整体退化如果集中在某一段地址更像局部坏道。第四步检查EXT4-fs error是否伴随大量同类日志这决定了文件系统是否已经把设备降级。如果同一事件在短时间内反复出现我会立即做备份迁移并同步用smartctl -a /dev/sda查看Current_Pending_Sector和Reallocated_Sector_Ct两个计数。这两个属性直接反映盘片上的重映射扇区数量。在我经手的案例里Current_Pending_Sector持续增长往往意味着磁盘正处于快速劣化状态拖得越久风险越高。3.4 处理思路与验证这类问题的处理不是简单换盘。我的标准流程是先备份数据再通过 S.M.A.R.T. 自检确认坏道范围然后用dd或badblocks做只读扫描。如果坏道是刚出现的局部问题可以尝试重新分区绕过坏扇区如果数量较多或者盘片老化明显直接更换磁盘。更换完新盘后我会用下面的命令确认事件不再发生dmesg -T | grep -c Medium Error记录一个基准数隔半小时再查一次如果计数不再增长基本可以判断问题已经隔离。还有个小技巧更换线缆、接口后想看是否真的恢复了链路稳定性可以观察dmesg -T | grep -i link up对应的物理速率符号而不只是看业务是否恢复。4. 案例二驱动加载失败新网卡不出接口4.1 现象lspci 能看到设备但 ip link 没有对应接口某台物理服务器加装了一款网卡系统里执行lspci | grep -i ethernet能看到设备但ip link里始终没有出现新的网络接口。这种硬件识别到了驱动没起来的情况很典型。我见过有人反复换 PCIe 插槽、甚至怀疑卡是坏的其实问题往往出在驱动或固件缺失上。dmesg 在驱动加载阶段的输出会直接告诉你卡和内核之间发生了什么。4.2 dmesg 里的报错特征执行过滤查看驱动相关日志dmesg -T | grep -i -E igb|e1000e|i40e|failed|firmware能看到类似这样的输出[Mon Jan 13 10:02:11 2025] igb: probe of 0000:03:00.0 failed with error -2 [Mon Jan 13 10:02:11 2025] e1000e: The NVM image is corrupted. [Mon Jan 13 10:02:12 2025] igb: Direct firmware load for igb-3.0.0.bin failed with error -2probe of 0000:03:00.0 failed是驱动在探测 PCIe 设备时失败了。后面的error -2对应的内核 errno 是-ENOENT表示没有找到需要的文件在这里通常指固件文件缺失。Direct firmware load ... failed with error -2则直接点名了具体缺哪个二进制固件。这些信息组合起来问题根因已经清楚了内核里驱动模块本身还在但驱动运行所需的固件数据没有安装到/lib/firmware目录。4.3 对照设备 ID、驱动模块与固件三者的关系理解这类问题需要把三个东西串起来PCI 设备 ID、内核驱动模块、固件文件。网卡芯片出厂时会带一组 vendor ID 和 device ID内核靠这个 ID 匹配对应的驱动模块。驱动模块启动后很多网卡还需要从文件系统加载一段专有的微码、管理固件到卡上这段数据存放在/lib/firmware中。任何一个环节缺失设备都无法正常工作。检查顺序我很固定先通过lspci -nn拿设备 ID再modinfo igb看驱动支持的设备列表最后去/lib/firmware下找对应固件文件是否存在。如果是精简安装的 Linux 发行版很多固件包没有默认装上这是生产环境加装新硬件最常见的坑之一。还需要注意同一款网卡芯片在不同版本下可能使用不同型号的固件文件不能想当然地认为装上驱动就能用。4.4 解决路径与验证最直接的修复方法是安装固件包并重新加载模块# 以 Debian/Ubuntu 为例 apt install linux-firmware # 或 CentOS/RHEL 系 yum install linux-firmware # 重新加载驱动模块让探测过程再走一遍 modprobe -r igb modprobe igb重新加载模块后再用dmesg -T | tail -20看输出。如果看到类似igb 0000:03:00.0: added PHY、igb 0000:03:00.0: Intel(R) Gigabit Ethernet Network Connection、最后出现一句Link is up基本就成功了。如果重新加载后固件还是加载失败我建议先检查内核版本与固件版本的匹配度老内核自带的驱动可能不认识新固件此时更新系统内核比一个一个找固件包更省事。这类驱动加载问题dmesg 的价值就是帮你在十分钟内把硬件坏了和驱动没配好区分开。5. 案例三进程突然消失——OOM Killer 的日志解读5.1 现象数据库进程被系统杀掉应用日志里没有报错有次某应用集群内存吃紧一台跑 Java 服务的节点上进程毫无征兆地不见了。应用日志最后几行没有任何异常堆栈systemd 服务状态显示 failed但原因模棱两可。我第一反应是查 dmesg因为如果是内存耗尽触发了内核 OOM Killer它杀死进程时一定会留下记录。很多时候进程被杀应用自己根本来不及写任何日志只有内核态能告诉我们发生了什么。5.2 dmesg 里 OOM 相关输出的关键字段执行dmesg -T | grep -i -E out of memory|oom-killer|killed process典型输出长这样关键字段脱敏[Mon Jan 13 14:00:01 2025] mysqld invoked oom-killer: gfp_mask0x100cca, order0, oom_score_adj0 [Mon Jan 13 14:00:01 2025] [ pid ] uid tgid total_vm rss pgtables_bytes swapents oom_score_adj name [Mon Jan 13 14:00:01 2025] [ 2015] 999 2015 12671234 10801234 5123456 120 0 mysqld [Mon Jan 13 14:00:02 2025] Out of memory: Killed process 2015 (mysqld) total-vm:12671234kB, anon-rss:10801234kB, file-rss:2048kBinvoked oom-killer表示某个进程触发了内核的内存耗尽处理逻辑注意触发的进程不一定是被杀的那个它只是恰好申请内存失败唤醒了内核的回收和杀进程机制。total_vm是进程映射的虚拟内存总量rss是实际驻留物理内存anon-rss是匿名内存file-rss是文件映射部分。oom_score_adj表示内核对该进程可被杀优先级的调节值越大越容易被选中。最后一行Killed process就是真正的判决结果。5.3 结合 free、进程内存统计确认根因看到 OOM 日志后我还会确认系统到底是怎么被耗尽的。先看free -h里的available是否接近 0再看/proc/meminfo里的CommitLimit和Committed_AS判断是不是超量提交导致。更关键的是找出谁吃掉了内存ps aux --sort-rss | head能列出物理内存占用最高的进程。很多时候 OOM 不是某个进程突然暴涨而是多个进程都增长日积月累把内存吃光了也有可能是 page cache 里积压了大量可回收页但回收速度跟不上分配速度。这里有个经常被忽略的点dmesg 里的 OOM 日志只反映系统级别内存耗尽的那一下但如果你的进程运行在 cgroup 限制内内存达到 cgroup 上限被杀时并不一定触发全系统 OOM Killer消息可能只出现在对应的 cgroup 日志里。所以看到 dmesg 里没有 OOM 记录不一定代表没有内存相关的强制结束行为这时要回头检查服务自身的 cgroup 配置。5.4 避免误杀的方法如果确认是误杀高优先级的数据库/中间件进程我一般从三个方向处理。第一通过调整/proc/pid/oom_score_adj把关键进程的分数调低例如改成-1000OOM_DISABLE但这也意味着它仍然会消耗内存只是系统宁愿杀别的进程。第二合理配置 cgroup 的内存上限并单独设置memory.oom_control策略让内存压力被限制在特定服务内部而不是拖垮整机。第三谨慎使用 swapvm.swappiness太高会频繁换页太低又可能在突发内存压力时直接触发 OOM需要根据工作负载权衡。任何调整后我都建议用dmesg -T -w观察系统空闲时段确认没有新的 OOM 触发。6. 案例四外设热插拔失效——把 dmesg 当事件追踪器用6.1 现象USB 设备用一段时间后无法识别某条产线上的一台工控机外接一个 USB 转串口适配器用于和测试仪器通信。运行几小时后适配器突然失效业务端报无法打开串口。设备本身没有物理损坏迹象重新插拔一次又能恢复但只能撑几个小时。这种间歇性失效问题最让运维头疼因为问题不在应用层而在 USB 总线链路本身。幸好 dmesg 对这种热插拔事件的记录非常完整几乎是逐条打印的。6.2 用 dmesg -T 还原时间线拿到机器后我立即执行dmesg -T | grep -i -E usb|tty|option|pl2303|ch341 | tail -40输出还原了整个事件链[Mon Jan 13 16:30:22 2025] usb 1-1: USB disconnect, device number 3 [Mon Jan 13 16:30:22 2025] option1 ttyUSB0: GSM modem (1-port) converter now disconnected from ttyUSB0 [Mon Jan 13 16:30:22 2025] usb 1-1: failed to re-enumerate - device disconnected [Mon Jan 13 17:00:05 2025] usb 1-1: new high-speed USB device number 4 using xhci_hcd [Mon Jan 13 17:00:06 2025] usb 1-1: New USB device found, idVendor1a86, idProduct7523 [Mon Jan 13 17:00:06 2025] usb 1-1: New USB device strings: Mfr0, Product2, SerialNumber0 [Mon Jan 13 17:00:06 2025] option 1-1:1.0: GSM modem (1-port) converter detected [Mon Jan 13 17:00:06 2025] usb 1-1: GSM modem (1-port) converter now attached to ttyUSB0从日志可以看出16:30 设备断开系统尝试重新枚举但失败17:00 人工重新插拔后才恢复正常。这里的USB disconnect说明物理链路在某刻断开failed to re-enumerate说明设备当时没有能再次枚举成功。结合产线工人没有动过线的描述我判断不是人为因素而是供电或线缆接触问题。随后用外接带屏蔽的 USB 延长线并改用独立供电 Hub问题就不再复现了。6.3 与 journalctl -k 配合确认更早的事件环形缓冲区容量有限如果失效事件发生在很久以前可能已经被覆盖。好在这类事件对时间敏感我经常用 journald 的内核日志做交叉验证。systemd 环境下 journald 会采集内核消息执行journalctl -k -b -1可以查看上一次启动的内核日志journalctl -k --since yesterday可以回溯到今天之前的内核消息。有些细节值得注意dmesg 显示的是环形缓冲区当前内容而 journald 如果一直运行则包含了更长期的历史两者相互印证能还原出完整的故障时间线。当 dmesg 里反复出现failed to re-enumerate、reset SuperSpeed USB device、not running at full speed这类日志时优先级从高到低考虑供电不足、线缆过长、屏蔽不良、设备自身老化。USB 链路对电气质量非常敏感很多时好时坏的设备问题日志早就告诉你了只是没有把相关行拼成一条完整的故事线。7. 用好 dmesg 的三个习惯与两个常见误解7.1 习惯一出问题先保存现场环形缓冲区会覆盖这是 dmesg 排障里最大的敌人。系统跑得越久早期线索消失得越快。我的习惯是把保存 dmesg 现场做成肌肉记忆dmesg -T /tmp/dmesg_$(date %F_%H%M%S).log保存完再开始分析。尤其在接到生产环境告警时哪怕当时还没确定是不是内核问题先把快照留好后面排查到最后发现是驱动问题还能回到第一现场重新读日志。这个动作成本太低收益却很大。7.2 习惯二用 -w 实时跟踪硬件事件验收设备、更换硬件、调试驱动时我会开一个终端专门跑dmesg -w -T让日志实时滚动。插入 U 盘、挂载网络存储、调整网卡速率所有内核事件都会实时显示。这样做有几个好处第一能准确看到硬件上电到驱动加载完成的每一个阶段第二能对比不同硬件在同样操作下的行为差异第三设备偶发失效时可以及时捕获现场而不是事后翻已经覆盖的日志。7.3 习惯三分清 dmesg 与 /var/log/dmesg、journald 的关系很多人把/var/log/dmesg当成实时日志其实它通常是系统启动早期内核日志的快照代表的是开机那一刻的状态不是当前时刻。当前时刻的动态内核日志要看 dmesg 命令输出或journalctl -k。如果用 systemd 的系统dmesg 命令一般也是从内核接口读取而 journald 则额外维护了一份可持久化的内核日志流。理解这三者的差异能避免我看 /var/log/dmesg 怎么没有报错这类误导。7.4 误解一dmesg 只能看启动阶段信息这是个很容易被名字误导的地方。dmesg 虽然常用来查启动问题但内核在运行期间持续向环形缓冲区写入各种驱动事件。前面几个案例里的运行期 I/O 错误、USB 断连、OOM 事件全都不是启动阶段产生的。把 dmesg 局限在开机排查里等于放弃了一个持续更新的实时事件源。正确的心态是把它当成一台永远在记录内核事件的示波器。7.5 误解二dmesg 必须用 root 才能看不少 Linux 发行版默认情况下普通用户也能执行 dmesg但部分安全加固的系统里/proc/sys/kernel/dmesg_restrict会被设为 1只有 root 或有CAP_SYSLOG权限的账号能读取完整信息。如果执行 dmesg 报Operation not permitted先检查这个参数cat /proc/sys/kernel/dmesg_restrict如果是 1可以临时用 sudo但更合理的做法是在系统审计策略里允许特定运维账号读取毕竟排障不应该依赖临时提权。这个细节在刚接手一批新机器时特别容易踩权限不够被挡在门口还以为是内核日志没了。我在实际使用中还有个习惯内核升级前后分别做一次 dmesg 快照存档用来对比驱动行为的变化。内核从旧版本升级到新版本后某些固件加载路径、错误码含义可能会有细微调整没有基线根本看不出差异。最后再分享一个实用技巧如果发现 dmesg 时间戳和系统当前时间对不上先确认你有没有用-T如果用了还是对不上再看系统时间本身是否被 NTP 校准过——dmesg 的原始时间戳按内核启动后的 tick 计算在笔记本休眠唤醒或系统时间跳变后可能失真。把这一点记在心里排查移动工作站或虚拟机时能少走很多弯路。