半夜两点被告警叫起来,先说结论:那台跑着数据库的物理机上,网卡驱动在链路抖动时刷了一屏NIC Link is Down,而真正的元凶是下面几行blk_update_request: I/O error。这些线索全都在一个地方——dmesg。dmesg这个命令用起来简单到只有五个字母,但它是 Linux 上唯一一个能让你直接听到内核说话的窗口:硬件上线、驱动加载、磁盘报错、内存耗尽、进程被内核杀掉,全部从这里冒出来。麻烦的是,它默认吐出来的东西又多又密,时间戳还反人类,几千行里真正有用的可能就三行。这篇东西我想把它拆开讲透:dmesg到底在读什么、时间戳为什么总对不上、怎么把噪声过滤到只剩十几行、以及几类典型故障在日志里长什么样。不管你是刚接手服务器的新手,还是已经能背出常见 oops 格式的老兵,应该都能从里面抠出点能直接用的东西。
1. dmesg 读取的到底是什么:环形缓冲区与内核日志的产生路径
1.1 从 printk 到环形缓冲区的完整链路
先把机制讲清楚,不然后面的“日志丢了”“过滤不到”全是玄学。内核代码里想输出一句话,用的是printk(),它和用户态printf最大的区别在于:printk不能阻塞、不能等 I/O、不能睡,因为它可能运行在中断上下文、持有自旋锁、甚至是在崩溃前的最后几行代码里。既然不能等磁盘,那消息往哪放?答案是一块内核启动时就分配好的连续内存,业内叫ring buffer(环形缓冲区),对应源码里的__log_buf。
printk干的事很朴素:把格式化后的字符串加上一个前缀(级别、facility、时间戳、CPU 号、进程名和 PID)写进这块内存的写指针位置,然后移动写指针。写满了怎么办?回到开头覆盖最老的那条。这就是“环形”的含义,也是为什么你dmesg看到的第一行往往不是真正的第一条——开机早期那些消息早就被后来的日志挤掉了。
消息进入缓冲区之后,有两条消费路径:一条是同步推给控制台(物理串口、虚拟终端、ttyS0),你开机时屏幕上滚过去的就是它;另一条是被动等待读取,dmesg走的就是这条。当你敲下dmesg,用户态程序做的是打开/dev/kmsg或者调用syslog(2)系统调用(SYSLOG_ACTION_READ_ALL),把整块缓冲区拷贝出来再格式化。所以dmesg本身没有“查询”的能力,它就是一键导出——这一点决定了它的两个特性:快,以及看到的是快照。
注意:
dmesg输出的内容在拷贝那一刻就固定了,后面的新消息不会自动出现。想要持续观察必须加-w,这个后面单独讲。
1.2 为什么有些日志“凭空消失”了:缓冲区覆盖的真实代价
环形缓冲区的大小是编译期内核时定下的,由CONFIG_LOG_BUF_SHIFT控制,常见默认值是 18,也就是 2 的 18 次方,262144 字节,约 256 KB。听起来不小,但你要考虑到:一台带 4 个万兆网卡、两块 NVMe、一堆 USB 设备的机器,光开机阶段的设备枚举就能吃掉小半。启动完成后如果某个驱动开始疯狂刷错误(比如一个接触不良的 USB 设备反复重连),几分钟就能把缓冲区冲干净,把开机时的关键信息全部覆盖。
我在一台边缘设备上就吃过这个亏:现场反馈“偶尔重启”,我到场时dmesg | head -20看到的全是正常的 USB 枚举日志,以为没事,后来才发现真正的 panic 记录在缓冲区被覆盖前就已经滚出去了。判断方法很简单,看第一行的时间戳:如果dmesg | head -1的秒数不是接近 0,而是几百甚至几千,说明开机日志已经没了。
# 看最早一条日志的时间偏移(单位:秒),大于几十就要警惕 dmesg | head -1 # 数一下缓冲区里现在有多少行,心里有个数 dmesg | wc -l想把窗口开大,有两个办法。一是启动参数加log_buf_len=4M(在 GRUB 的内核行里追加),这个参数从 2.6 时代就有,现代内核仍支持;二是改编译配置重新编内核,生产环境基本不现实。所以实际做法就是前者,或者在 GRUB 里加上之后update-grub重启生效。顺便说一个广为流传的误解:dmesg -s 4194304那个-s不是用来扩大内核缓冲区的,它只是告诉syslog(2)系统调用“我准备了一个多大的用户态缓冲区来接收”。内核那边该多小还是多小,写错方向等于白忙。
1.3 dmesg、kern.log、journalctl -k 三者的分工
刚接触的人经常搞混这三个东西,我用一句话区分:dmesg看的是当下内存里的内核日志,/var/log/kern.log看的是syslog 守护进程落盘的历史,journalctl -k看的是systemd-journald 落盘的内核日志。dmesg最快、最原始、不需要任何服务在跑,但重启即失;后两者能跨重启查询,但依赖服务配置,而且格式经过二次加工。
这个区别在排障时非常要命。如果机器已经重启过了,你dmesg看到的是新一次启动的日志,崩溃前的现场一点不剩。这时候唯一的指望就是journalctl -k -b -1(上一次启动的内核日志),前提是 journald 配置了持久化存储。如果连这个都没有,那就只能翻/var/log/kern.log或者更老的messages,而且日志轮转(logrotate)还会把旧的切走压缩。
| 来源 | 数据在哪 | 跨重启 | 依赖服务 | 典型用途 |
|---|---|---|---|---|
dmesg | 内核 ring buffer(内存) | 否 | 无 | 现场即时排查、复现问题时边操作边看 |
journalctl -k | journald 的二进制日志文件 | 取决于Storage=配置 | systemd-journald | 查上次启动、按时间范围检索 |
/var/log/kern.log | 纯文本文件 | 是(受轮转限制) | rsyslog / syslog-ng | 长期归档、给外部日志系统采集 |
我个人的习惯是:现场第一次登录,先dmesg -T | tail -100抓个快照存到/tmp,然后再做任何可能产生大量日志或者清空缓冲区的操作。这一步三十秒,能省掉后面几小时的扯皮。
2. 时间戳这个坑:-T、-H 与 --time-format 的真实差异
2.1 默认的秒级偏移为什么反人类
dmesg默认输出的时间戳长这样:[12345.678901],含义是“距离系统启动 12345.678901 秒”。内核这么设计有它的道理——启动阶段系统时钟还没校准,根本没有可靠的墙上时间可用;而且单调递增的数值在跨 CPU、跨中断上下文比较顺序时最可靠。但对排障的人极不友好:你要先算出这次启动是什么时候,再加上 12345 秒,才能对应到“昨天下午三点二十”。中间还得考虑时区、夏令时、以及中间重启过几次。
更麻烦的是这串数字的精度。它来自内核的local_clock(),在高负载或者虚拟化环境下,几个 CPU 之间的时间戳可能有微小抖动,所以你会看到相邻两行的秒数偶尔不是严格递增的——这不是日志坏了,是正常的时钟源差异。
2.2 -T 的时区陷阱,以及它为什么会算错
-T(--ctime)的作用是把偏移换算成人类可读的Mon Jan 1 12:00:00 2024格式。它的算法是:拿当前的系统时间减去当前的uptime,得到开机时刻,再加上每条日志的偏移。问题就出在这个“当前”上。
第一层误差来自系统时钟跳变。如果机器开机后 NTP 把时间往前拨了 10 分钟(比如 RTC 电池没电导致开机时间偏了),那么-T算出来的所有历史时间都会整体偏移 10 分钟。第二层误差来自休眠和虚拟机快照恢复:虚拟机从快照恢复后,uptime和内核时间戳的关系被打乱,-T显示的时间可能和宿主机时间对不上几个小时。第三层是时区:-T用的是/etc/localtime,如果容器里没有正确挂载时区文件,你会看到 UTC 时间却以为是本地时间。
所以我的做法是:-T只用来做粗定位(“大概在几点几分”),精确对时间一律用date、应用日志、或者journalctl的真实时间戳交叉验证。如果发现-T的时间和别的日志差了整整一个时区,不用怀疑,就是时区问题。
# 粗定位用这个 dmesg -T | tail -50 # 精确核实启动时间 uptime -s who -b # 如果 -T 出来的时间明显不对,直接对比 date2.3 -H、-d 和 --time-format 各管什么
-H(--human)是我日常最推荐的姿势。它做了三件事:开启分页器、按可读方式显示时间、给输出加上颜色和分组。分页器这一点在日志很长的时候特别救急,因为你终于可以像看less一样上下翻、搜索关键字。-H显示的时间也是基于-T的同一套换算,所以精度问题一样存在,但可读性高得多。
-d(--show-delta)显示的是相邻两条日志的时间差,这个功能被严重低估了。排查“卡了多久”类问题时它几乎是神器:比如系统启动慢,用dmesg -d一看,两条日志之间隔了 47 秒,那这 47 秒就是问题所在,一眼定位。再比如某个驱动反复超时重试,-d会清晰地显示 1 秒、2 秒、4 秒、8 秒的退避节奏,比数行数快多了。
--time-format=是更细粒度的开关,可选值包括ctime、reltime、delta、notime、iso。iso输出 ISO8601 格式(带时区),适合喂给脚本或者做日志对比;notime直接去掉时间戳,适合拿去 diff 两次输出。这几个参数在老版本的 util-linux 上可能不被支持,先用dmesg --help | grep time-format确认一下再写进脚本。
# 看启动过程中哪一步最慢 dmesg -d | sort -t'>' -k2 -rn | head -20 # ISO 格式,方便和其他日志对齐 dmesg --time-format=iso | grep -i error # 去掉时间戳,纯粹看内容 dmesg --time-format=notime | sort | uniq -c | sort -rn | head最后那条命令我很喜欢,uniq -c统计重复行,能瞬间看出哪个错误在刷屏。刷屏的那个不一定是根因,但它通常是根因的伴生现象,顺着它往上翻往往能找到源头。
2.4 -w 跟随与实时观察的正确姿势
-w(--follow)让dmesg像tail -f一样持续输出新消息,这是现场复现问题的标准动作:先开一个终端dmesg -w,然后在另一个终端插拔设备、重启服务、制造负载,观察内核实时反应。这个组合比事后翻日志高效得多,因为你知道每个操作对应哪几行。
但-w有个坑:它会把缓冲区里已有的全部内容先倒一遍,再开始跟随。如果缓冲区里有几千行,你打开就先被淹没了。util-linux 2.30 之后加了--follow-new,只输出从此刻开始的新消息,干净得多。老版本没有这个选项的话,可以用dmesg -w | tail -n 0或者dmesg -wH配合分页器里的G跳到底部来凑合。
另一个细节:-w的退出要用 Ctrl+C,但如果是在ssh里且网络断了,这个会话可能残留。所以长期监控别用dmesg -w,而是用 rsyslog 把日志转发走,或者写个脚本定期采集。
# 推荐:只看新消息 dmesg --follow-new # 老版本兜底 dmesg -w | tail -n 0提醒一句:
dmesg -w输出的是内核的原始流,级别低于控制台阈值的消息(比如 debug 级)可能压根不在这里出现,别指望它能捕获所有东西。
3. 按级别和设施过滤:把几千行缩到十几行
3.1 级别数字背后的含义,以及为什么 debug 日志经常见不到
内核日志有八个级别,数值越小越严重:0emerg、1alert、2crit、3err、4warn、5notice、6info、7debug。驱动里写pr_err()就是 3,pr_info()是 6,pr_debug()是 7。dmesg默认把所有级别的日志都吐给你,但在缓冲区里,级别高低会影响到一些东西。
关键在于/proc/sys/kernel/printk这个文件,它包含四个数字,第一个是console_loglevel。只有级别数值小于console_loglevel 的消息才会被同步推到控制台或者串口。默认常见配置是4 4 1 7,意思是 console_loglevel 为 4,那么warn(4)及以上的消息不会出现在控制台,err(3)及以下才会。这就解释了一个经典现象:某个驱动明明在dmesg里刷满了pr_info的信息,但你看串口终端一片安静——因为级别被压住了。
cat /proc/sys/kernel/printk # 输出示例:4 4 1 7 # 依次是:console_loglevel default_message_loglevel minimum_console_loglevel default_console_loglevel不同内核版本对于“消息是否进入 ring buffer”的判定规则存在差异,早期版本确实存在级别高就不记录的情况。所以如果你dmesg -l debug一条都看不到,先别怀疑命令用错了,很可能是驱动根本没打,或者打的时候级别被编译期的DEBUG宏掐掉了。
3.2 -l 与 -n 的经典混淆,我踩过
新手最容易犯的错是把-n当成“按级别过滤”。dmesg -n 3的实际效果是修改内核的 console_loglevel 为 3,也就是把控制台的输出阈值改了,而且要 root 权限,改完还会一直生效直到重启或再次修改。这命令压根不读日志,它是写操作。
真正用来过滤的是-l或--level:
# 只看严重级别的 dmesg -l err,crit,alert,emerg这里还有个版本坑:较老的 util-linux 对-l的逗号分隔列表支持不完整,可能只认第一个值,导致你以为过滤了四个级别,实际只过滤了一个。稳妥做法是先用dmesg --version看版本,或者干脆用--level=长选项并做一次实测验证(故意构造一条 warn 日志看会不会被过滤掉)。我在一个老旧的嵌入式环境里就中过招,当时排查了半天“为什么 warn 级别的没出现”,最后发现是-l只取了err。
-x(--decode)也是好东西,它把原始的数字前缀翻译成人类能读的kern:err这种形式,配合-l用可以确认自己过滤的到底是什么。
# 原始格式 dmesg | head -1 # [ 0.000000] Linux version 5.15.0-... # 解码后 dmesg -x | head -1 # kern :info : [ 0.000000] Linux version 5.15.0-...3.3 -f facility 过滤与 -k / -u 的边界
facility 是日志的“来源分类”,内核日志里常见的有kern(内核本身)、user(用户态直接写/dev/kmsg的消息)、daemon、auth等。老实说,绝大多数情况下dmesg输出的都是kern,-f用得不多,但在两种场景下有用:一是容器环境中,某些用户态程序会往/dev/kmsg里写消息,混杂在内核日志里;二是你想分离出userfacility 的消息来定位某个应用的行为。
# 只看内核本身的 dmesg -k # 只看用户态写进来的 dmesg -u # 指定 facility dmesg -f kern,daemon-k和-u这对组合还有一个隐藏用途:如果你在dmesg里看到一堆莫名其妙的、格式和内核日志不一样的文本行,而且dmesg -u能过滤出来,那基本可以断定是某个用户态程序在往/dev/kmsg灌东西,顺着 PID 找到它就行。
3.4 用 grep 组合出真正有用的过滤链
说了这么多参数,实际排障时我最常用的还是dmesg加上grep。因为内核日志的格式太规整了,grep的命中率极高。下面这套组合拳是我常年备在便签里的:
# 只看错误和警告,去掉噪声 dmesg -T | grep -iE "error|fail|warn|critical|panic|oops|bug" # 磁盘和文件系统相关 dmesg -T | grep -iE "ata|nvme|scsi|sd[a-z]|ext4|xfs|blk_update" # 内存相关 dmesg -T | grep -iE "oom|out of memory|page allocation failure" # 网络相关 dmesg -T | grep -iE "link is|eth[0-9]|enp|driver|firmware" # 找出重复最多的错误 dmesg --time-format=notime | sort | uniq -c | sort -rn | head -20最后那条是我最推荐的“第一眼”命令。它不是在找根因,而是在找噪声模式。如果一个错误在十分钟内重复了三千次,那它要么就是根因,要么正在把缓冲区冲垮,两种情况都必须先按下去。
有一个心态上的提醒:
grep error抓到的很多东西是无害的。比如ACPI Error: No handler for Region在很多机器上开机就出现,实际不影响任何功能。判断标准是看它是否伴随功能异常,而不是看它字面上有没有“Error”。
4. 四类高频故障在 dmesg 里的真实长相
4.1 内存耗尽:OOM Killer 的日志怎么读
OOM 的日志格式非常固定,认准Out of memory: Killed process这一行就够:
[12345.678901] Out of memory: Killed process 23456 (java) total-vm:8123456kB, anon-rss:4096000kB, file-rss:2048kB, shmem-rss:0kB, UID:1000 pgtables:8192kB oom_score_adj:0关键是后面这几个字段:total-vm是进程申请的虚拟内存总量,anon-rss是实际占用的匿名页(堆和栈),这两个数字能告诉你被杀进程到底占了多少。如果anon-rss很大,说明确实是它吃光了内存,罪有应得;如果anon-rss很小但依然被杀,说明是别的进程撑爆了,它只是被oom_score挑中的倒霉蛋。
新版内核还会多一行结构化的信息:
oom-kill:constraint=CONSTRAINT_NONE,nodemask=(null),cpuset=/,mems_allowed=0,global_oom,task_memcg=/system.slice,app.service,task=java,pid=23456,uid=1000这里的constraint=CONSTRAINT_NONE表示是全局 OOM(整机内存不够),如果是CONSTRAINT_CPUSET或者CONSTRAINT_MEMCG,说明是 cgroup 配额打满导致的局部 OOM,排查方向完全不同——前者要加内存或者优化程序,后者要调 cgroup 限额。这个区分非常重要,很多人一看 OOM 就去加物理内存,结果加了还是 OOM,因为瓶颈在容器限额上。
再往上看几行,通常能找到触发点:page allocation failure、swap使用情况、以及被杀之前系统是否已经进入内存回收的挣扎状态。这些细节能区分“突发的内存暴涨”和“长期的内存泄漏”。
4.2 磁盘与文件系统:I/O error、EXT4-fs error、ATA 总线报错
磁盘问题的日志有三个层次,层次不同处理方式完全不一样。
第一层是块层报错:
[12345.123456] blk_update_request: I/O error, dev sda, sector 4096 op 0x0:(READ) flags 0x0sector 4096这个数字很有用,它是物理扇区号,乘 512 就是字节偏移,可以反推是分区表的哪个位置。如果同一个扇区反复报错,基本可以断定是物理坏道,这时候用badblocks扫一下或者直接换盘。
第二层是文件系统层报错:
[12345.234567] EXT4-fs error (device sda1): ext4_find_entry:1532: inode #2: comm rsyslogd: reading directory lblock 0看到EXT4-fs error要立刻警惕,因为它意味着文件系统元数据可能已经不一致了。这时候的正确动作是:先别急着重启,把dmesg完整存下来,然后确认是否有备份,再考虑fsck。在挂载状态下跑fsck是绝对禁忌,会扩大损坏范围。
第三层是硬件总线报错:
[12345.345678] ata1.00: exception Emask 0x0 SAct 0x0 SErr 0x0 action 0x0 [12345.345679] ata1.00: failed command: READ FPDMA QUEUED [12345.345680] ata1.00: status: { DRDY ERR } [12345.345681] ata1.00: error: { UNC }{ UNC }是“不可纠正错误”,翻译成人话就是硬盘自己都修不了。Emask和SErr这两个值的含义比较专业,但有个快速判断法:如果SErr是0x0而Emask非零,通常是盘的问题;如果SErr有明显的位标志(比如0x10的B位表示链路错误),那要查 SATA 线缆和背板。
NVMe 盘的报错长得不一样,常见的是:
[12345.456789] nvme nvme0: I/O 123 QID 3 timeout, reset controllerreset controller意味着内核已经放弃等待,开始重置控制器了。这种情况要么是盘过热降速,要么是固件 bug,要么是 PCIe 链路有问题。先看smartctl -a /dev/nvme0的温度和介质错误计数,能排除掉一大半。
4.3 USB 与 PCI 热插拔:设备到底认没认出来
USB 问题在dmesg里表现得很规律,插上去的瞬间会有一组日志:
[1234.567890] usb 1-1: new high-speed USB device number 3 using xhci_hcd [1234.678901] usb 1-1: New USB device found, idVendor=1234, idProduct=5678 [1234.678902] usb 1-1: Product: Some Device [1234.789012] usb-storage 1-1:1.0: USB Mass Storage device detected如果中间出现device descriptor read/64, error -71或者device not accepting address,那说明枚举失败。-71是EPROTO,协议错误,八成是线材或者供电问题;-110是超时,通常是设备本身挂了。这类问题我一般先换线,再换口,最后换设备,三步排掉九成。
PCI 设备的问题更隐蔽,因为lspci只能看到成功枚举的设备,失败的在这里:
[1234.890123] pcieport 0000:00:1c.0: AER: Corrected error received: 0000:00:1c.0 [1234.890124] pcieport 0000:00:1c.0: PCIe Bus Error: severity=Corrected, type=Physical LayerCorrected error是可纠正错误,不致命但会持续出现,说明 PCIe 链路质量在下降;Uncorrected就严重了,可能导致设备掉线。AER 报错密集出现时,先查插槽灰尘、金手指氧化,再考虑降速运行(在 BIOS 里把 PCIe 速率从 Gen4 降到 Gen3),这是很实用的一招。
4.4 内核 oops、soft lockup 与 hung task:怎么快速判断严重程度
这三类日志的严重程度递增,但都意味着内核状态出问题了。
soft lockup是这样的:
[12345.678901] watchdog: BUG: soft lockup - CPU#2 stuck for 23s! [kworker/2:1:12345]含义是 CPU 2 上的一个内核线程连续 23 秒没有让出 CPU。常见原因是驱动里的死循环,或者自旋锁争用。注意这里的 “BUG” 是内核的语气词,不代表一定触发了 bug 检测机制。
hung task是这样的:
[12345.789012] INFO: task java:23456 blocked for more than 120 seconds. [12345.789013] Tainted: G OE 5.15.0-91-generic #101-Ubuntu [12345.789014] "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.任务阻塞超过 120 秒(默认阈值),通常是 D 状态卡在 I/O 上。看到这个先别急着调大hung_task_timeout_secs把它关掉——那只是让报警闭嘴,问题还在。真正的方向是查存储或者网络文件系统(NFS 挂了最容易触发)。
oops最严重,长这样:
[12345.890123] BUG: unable to handle page fault for address: 0000000000000018 [12345.890124] #PF: supervisor read access in kernel mode [12345.890125] PGD 0 P4D 0 [12345.890126] Oops: 0000 [#1] SMP PTI [12345.890127] RIP: 0010:my_module_func+0x1c/0x30 [my_module]RIP那一行是核心,它指出了出错指令的地址和所属模块。如果后面跟着[my_module],那责任就在这个第三方模块身上;如果是内核自身符号,那要看看是不是硬件内存故障。Oops: 0000 [#1]里的[#1]表示这是第一次 oops,如果是[#2]、[#3],说明系统已经在崩溃边缘反复挣扎了。
一个实操经验:oops 出现后系统可能还能跑,但状态已经不可信了,任何写入操作都可能造成数据损坏。正确做法是立刻把
dmesg导出到别的机器,然后尽快安排重启。别为了“多收集点信息”硬撑着继续跑业务。
5. 权限、清空风险与生产环境下的使用边界
5.1 dmesg_restrict:为什么普通用户敲下去是空的
在某些发行版上,普通用户执行dmesg会得到dmesg: read kernel buffer failed: Operation not permitted。这不是权限配错了,而是kernel.dmesg_restrict这个内核参数被设成了 1。它的作用是限制非特权用户读取内核日志,主要出于信息安全考虑——内核日志里可能包含内存地址、硬件序列号等信息,对攻击者有侦察价值。
# 查看当前值 sysctl kernel.dmesg_restrict # 临时放开(需要 root,重启后失效) sysctl -w kernel.dmesg_restrict=0 # 持久化 echo "kernel.dmesg_restrict = 0" > /etc/sysctl.d/99-dmesg.conf sysctl --system我的建议是:生产环境保持 1。需要看日志就sudo dmesg,或者把排障脚本用 root 跑。把它关掉换取的那点方便,不值得拿信息暴露去换。如果确实有非 root 的监控程序需要读内核日志,更稳妥的做法是给它单独开一个受限的采集通道(比如让 rsyslog 把kern.*写到一个特定权限的文件里),而不是把整个缓冲区开放。
5.2 -c 和 -C:我亲手抹掉过一次现场
这两个参数看起来无害,实际是dmesg里最危险的操作。-c是“读取后清空”,-C是“只清空不输出”。后者尤其阴险,因为它什么都不打印,你敲完以为没事,实际上缓冲区已经空了。
我犯过一次:某台机器磁盘报错,我ssh上去顺手敲了个dmesg -c | grep error,想着把结果拿走顺便清一下让下次干净点。结果那次 grep 匹配到了几行但没匹配全,等我把输出贴给同事讨论完,回头想再看上下文——缓冲区已经没了,而那台机器因为业务原因不能立刻重启。最后是靠journalctl -k里残留的部分日志拼出来的,但那次的启动早期信息彻底丢了。
从那以后我给自己定了个规矩:任何情况下先dmesg > /tmp/dmesg.$(date +%Y%m%d%H%M%S).log,然后再做操作。如果确实需要清空缓冲区(比如为了让-w输出更干净),也一定是先存后清。
# 正确姿势 dmesg -T > /tmp/dmesg-$(date +%s).log && dmesg -C顺便说,-c在一些更新的 util-linux 版本里已经被标记为不推荐使用,未来的大版本可能会移除。依赖它的脚本迟早要改。
5.3 让内核日志活过重启:持久化的两条路
如果排障需要跨重启查日志,必须提前把持久化配好,事后再配就晚了。
走 journald 这条路,改/etc/systemd/journald.conf:
[Journal] Storage=persistent SystemMaxUse=2GStorage=persistent让日志写到/var/log/journal/而不是内存里的/run/log/journal/。改完systemctl restart systemd-journald,之后就能用journalctl -k -b -1查上一次启动、journalctl -k --since "2024-01-01 10:00"按时间筛。SystemMaxUse是防止日志把磁盘塞满,这个必须设,我见过因为 journal 不限制大小把根分区撑爆的案例。
走 rsyslog 这条路,确认/etc/rsyslog.conf里有:
kern.* /var/log/kern.log并且加载了imklog模块(老版本是$ModLoad imklog,新版本通常默认加载)。这条路的好处是纯文本、好 grep、好给外部采集器(比如各种日志平台)读。缺点是文件会轮转,需要配好logrotate的保留份数和压缩策略,不然要么占满磁盘,要么把关键历史切没了。
两条路可以并存,不冲突。我一般在关键机器上两个都开,journald 负责快速按启动次数检索,kern.log 负责长期归档。
5.4 把缓冲区调大、把控制台调安静
最后说两个调优动作,都是改内核参数的活儿,但要分清哪个是哪个。
想扩大缓冲区,只能靠启动参数log_buf_len=,前面说过了。想控制台安静一点(比如串口终端被pr_info刷屏导致没法输入),改的是console_loglevel:
# 只让 err 及更严重的消息上控制台 dmesg -n 3 # 或者直接写 /proc echo 3 > /proc/sys/kernel/printk这两条命令效果一样,都需要 root,都会立刻生效但重启后失效。要持久化就写到/etc/sysctl.d/里配kernel.printk,注意这个参数的格式是四个数字,只改第一个的话其余三个要原样保留:
kernel.printk = 3 4 1 7还有一个容易被忽略的点:这些调整只影响“是否推到控制台”,不影响dmesg能不能读到。所以如果你发现串口上安静了但dmesg里还有大量 info 日志,那是正常的,不是配置没生效。
我个人在长期使用里形成的一套固定动作是这样的:登上一台出问题的机器,先dmesg -T > /tmp/dmesg-$(date +%s).log存档,然后dmesg --time-format=notime | sort | uniq -c | sort -rn | head -20看噪声模式,接着dmesg -T | grep -iE "error|fail|warn|oom|panic|oops"抓异常行,最后用dmesg -d看时间间隔找“卡在哪一步”。这四步下来,八成的问题已经能定位到方向了。剩下两成需要journalctl -k -b -1翻历史、或者上crash工具分析 vmcore。另外一个小技巧:把常用的几条grep组合写成一个~/.bashrc里的函数,排障时敲两个字母就能跑,比现场想命令快得多。至于-c,我现在连碰都不碰了 —— 缓冲区那点空间,留着比清掉有价值得多。