2. dmesg 基础:命令详解、日志级别与环形缓冲区原理

在嵌入式 Linux 驱动开发中,dmesg 是内核向用户态输出诊断信息的核心通道。无论是驱动加载失败、中断异常还是内存越界,第一手线索几乎都来自 dmesg。本章从命令用法、日志级别和底层环形缓冲区三个维度,建立对 dmesg 的系统认知。

2.1 dmesg 命令详解

dmesg 用于读取内核环形缓冲区中的消息,默认输出所有未读日志。常用选项如下:

选项 功能说明 典型场景
-C 清空环形缓冲区 测试前清除旧日志,避免干扰
-c 打印后清空缓冲区 连续监控时获取增量日志
-n <level> 设置控制台日志级别 减少串口打印,提升系统响应
-l <level> 按级别过滤输出 只查看错误或警告信息
-f <facility> 按设施类型过滤 仅显示内核或用户态日志
-T 显示人类可读时间戳 关联驱动加载与硬件时序
-w 实时跟踪新日志 配合 tail -f 调试热插拔
实战建议:在 RK3568 开发板上调试显示驱动时,建议先执行 dmesg -C 清空缓冲区,再加载驱动模块,最后用 dmesg -T -l err,warn 过滤出错误和警告日志,避免被大量调试信息淹没。

2.2 日志级别:KERN_EMERG 到 KERN_DEBUG

内核日志级别定义了消息的严重程度,共 8 级(0~7)。数字越小,优先级越高。驱动开发者通过 printk 宏指定级别:

// 示例:在 RK3568 显示驱动中使用不同级别
printk(KERN_ERR "hdmi: failed to read EDID\n");
printk(KERN_WARNING "hdmi: unsupported resolution\n");
printk(KERN_INFO "hdmi: connected to monitor\n");
printk(KERN_DEBUG "hdmi: ddc clock = %d\n", clk_rate);
宏名称 数值 含义 典型输出场景
KERN_EMERG 0 紧急事件,系统不可用 内核 panic、硬件致命错误
KERN_ALERT 1 必须立即处理 严重硬件故障、内存耗尽
KERN_CRIT 2 临界条件 驱动 probe 失败、DMA 超时
KERN_ERR 3 错误条件 寄存器读写异常、中断丢失
KERN_WARNING 4 警告,可能存在问题 分辨率降级、时钟频率偏差
KERN_NOTICE 5 正常但重要的事件 驱动加载成功、设备枚举完成
KERN_INFO 6 信息性消息 版本号、配置参数、状态切换
KERN_DEBUG 7 调试信息 寄存器值、函数调用流程
注意:控制台日志级别(console_loglevel)默认只输出优先级 ≤ 4(KERN_WARNING)的消息。若需在串口终端看到 KERN_DEBUG 信息,必须通过 dmesg -n 7 或 echo 7 > /proc/sys/kernel/printk 临时提升级别。调试结束后务必恢复,否则大量调试日志会拖慢系统。

2.3 环形缓冲区原理

内核日志系统基于固定大小的环形缓冲区实现。其核心设计要点如下:

  • 固定容量:默认大小通常为 16KB~128KB(可通过 CONFIG_LOG_BUF_SHIFT 配置)。
  • 循环覆盖:当写指针到达缓冲区末尾时,自动回绕到起始位置,覆盖最旧的日志。
  • 原子写入:在中断上下文或自旋锁保护区域,printk 仍可安全写入,不会引起死锁。
  • 延迟输出:日志先写入缓冲区,再由内核线程 klogd 或用户态 syslogd 读取并分发。
内核环形缓冲区工作原理 内核环形缓冲区(Ring Buffer)工作原理 槽位 0 [旧日志] 槽位 1 [旧日志] 槽位 2 [旧日志] 槽位 3 [当前写] 槽位 4 [最新] 槽位 5 [新日志] 槽位 6 [新日志] 槽位 7 [即将覆盖] 写指针 (write_idx) 读指针 (read_idx) 覆盖方向 关键特性 固定容量 · 循环覆盖 · 原子写入 · 延迟输出
环形缓冲区 vs 线性缓冲区:
  • 线性缓冲区:写满后停止记录,丢失后续日志。适合调试阶段,但无法应对长时间运行的系统。
  • 环形缓冲区:始终保留最新 N 条日志,适合生产环境。代价是旧日志会被覆盖,需及时通过 dmesg 或 syslog 持久化。

2.4 日志读取流程

从 printk 调用到用户态看到日志,经历以下步骤:

  1. 内核写入:驱动调用 printk,格式化消息后原子写入环形缓冲区。
  2. 控制台输出:若消息级别 ≤ console_loglevel,立即输出到串口或虚拟终端。
  3. 用户态读取:dmesg 通过 /dev/kmsg 或 syslog() 系统调用读取缓冲区。
  4. 持久化存储:syslogd 或 journald 将日志写入 /var/log/ 目录。
RK3568 调试技巧:在显示驱动 probe 函数入口和出口分别添加 printk(KERN_INFO "xxx_probe: enter\n") 和 printk(KERN_INFO "xxx_probe: exit, ret=%d\n", ret)。通过 dmesg -T | grep xxx_probe 即可快速定位驱动加载是否成功、耗时多少。