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读取并分发。
环形缓冲区 vs 线性缓冲区:
- 线性缓冲区:写满后停止记录,丢失后续日志。适合调试阶段,但无法应对长时间运行的系统。
- 环形缓冲区:始终保留最新 N 条日志,适合生产环境。代价是旧日志会被覆盖,需及时通过
dmesg或syslog持久化。
2.4 日志读取流程
从 printk 调用到用户态看到日志,经历以下步骤:
- 内核写入:驱动调用
printk,格式化消息后原子写入环形缓冲区。 - 控制台输出:若消息级别 ≤
console_loglevel,立即输出到串口或虚拟终端。 - 用户态读取:
dmesg通过/dev/kmsg或syslog()系统调用读取缓冲区。 - 持久化存储:
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 即可快速定位驱动加载是否成功、耗时多少。