☰
dmesg命令详解:从内核环形缓冲区到Linux故障排查实战
2026/10/13 10:45:51 网站建设 项目流程

做 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: hostbyte=DID_OK driverbyte=DRIVER_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: hostbyte=DID_OK driverbyte=DRIVER_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 -2

probe 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_mask=0x100cca, order=0, oom_score_adj=0 [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:2048kB

invoked 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把关键进程的分数调低,例如改成-1000(OOM_DISABLE),但这也意味着它仍然会消耗内存,只是系统宁愿杀别的进程。第二,合理配置 cgroup 的内存上限并单独设置memory.oom_control策略,让内存压力被限制在特定服务内部而不是拖垮整机。第三,谨慎使用 swap:vm.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, idVendor=1a86, idProduct=7523 [Mon Jan 13 17:00:06 2025] usb 1-1: New USB device strings: Mfr=0, Product=2, SerialNumber=0 [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 计算,在笔记本休眠唤醒或系统时间跳变后可能失真。把这一点记在心里,排查移动工作站或虚拟机时能少走很多弯路。

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

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

立即咨询