3. 日志级别详解:KERN_EMERG 到 KERN_DEBUG 的 8 个级别
printk 日志级别是驱动调试中最基础、最核心的概念。每个 printk 调用都携带一个日志级别,用于标识消息的严重程度。Linux 内核定义了 8 个日志级别,从最高紧急度 KERN_EMERG 到最低调试信息 KERN_DEBUG。理解这些级别及其对应的宏定义,是精准控制调试信息输出的前提。
3.1 8 个日志级别定义
日志级别定义在头文件 <linux/kern_levels.h> 中。每个级别对应一个数字,数字越小,优先级越高。下表列出了完整的 8 个级别及其典型使用场景:
| 级别宏 | 数字值 | 含义 | 典型使用场景 |
|---|---|---|---|
KERN_EMERG |
0 | 紧急事件 | 系统崩溃、不可恢复的硬件故障 |
KERN_ALERT |
1 | 必须立即处理 | 严重错误,如内存耗尽、关键设备丢失 |
KERN_CRIT |
2 | 临界条件 | 驱动初始化失败、DMA 传输错误 |
KERN_ERR |
3 | 错误条件 | I/O 操作失败、寄存器读写异常 |
KERN_WARNING |
4 | 警告条件 | 设备降级运行、参数超出推荐范围 |
KERN_NOTICE |
5 | 正常但重要 | 驱动加载/卸载、设备热插拔事件 |
KERN_INFO |
6 | 信息性消息 | 硬件版本信息、驱动配置参数 |
KERN_DEBUG |
7 | 调试消息 | 函数入口/出口、变量值跟踪、状态机跳转 |
调试实践建议:在驱动开发阶段,将大部分调试信息设为
KERN_DEBUG 级别;对于可能影响系统稳定性的错误,使用 KERN_ERR 或更高优先级。这样可以在生产环境中通过调整控制台日志级别,只显示关键错误,而隐藏大量调试信息。
3.2 级别宏定义与使用方式
日志级别宏本质上是字符串常量,在编译时与消息文本拼接。宏定义如下:
/* <linux/kern_levels.h> */
#define KERN_EMERG "<0>" /* 系统不可用 */
#define KERN_ALERT "<1>" /* 必须立即采取行动 */
#define KERN_CRIT "<2>" /* 临界条件 */
#define KERN_ERR "<3>" /* 错误条件 */
#define KERN_WARNING "<4>" /* 警告条件 */
#define KERN_NOTICE "<5>" /* 正常但重要 */
#define KERN_INFO "<6>" /* 信息性 */
#define KERN_DEBUG "<7>" /* 调试级别 */
使用示例:
printk(KERN_ERR "i2c_transfer failed, errno = %d\n", ret);
printk(KERN_DEBUG "reg_val = 0x%08x, irq_status = 0x%08x\n", reg, irq);
printk(KERN_INFO "Driver version %s loaded successfully\n", DRV_VERSION);
常见错误:宏与消息之间没有逗号,它们是字符串字面量的拼接。如果写成
printk(KERN_ERR, "msg");(带逗号),会导致编译错误或日志级别丢失。
3.3 默认控制台日志级别(console_loglevel)
内核通过 console_loglevel 变量控制哪些级别的消息能够输出到控制台。只有日志级别数值小于等于 console_loglevel 的消息才会被打印。默认值通常为 7(即所有级别都输出),但在生产系统中常被设为 4(只显示 KERN_WARNING 及以上)。
关键变量关系:
- console_loglevel:控制台日志级别,决定哪些消息显示在终端。
- default_message_loglevel:当 printk 未显式指定级别时使用的默认级别,通常为
4(KERN_WARNING)。 - minimum_console_loglevel:控制台可设置的最低级别,通常为
1(KERN_ALERT)。 - default_console_loglevel:系统启动时的默认控制台级别,通常为
7(KERN_DEBUG)。
运行时查看和修改控制台日志级别:
# 查看当前控制台日志级别
cat /proc/sys/kernel/printk
# 输出示例:7 4 1 7
# ^ ^ ^ ^
# | | | └─ default_console_loglevel
# | | └─ minimum_console_loglevel
# | └─ default_message_loglevel
# └─ console_loglevel
# 临时修改为只显示 KERN_WARNING 及以上(级别 4)
echo 4 > /proc/sys/kernel/printk
# 或使用 sysctl
sysctl -w kernel.printk="4 4 1 7"
调试技巧:在驱动调试时,将
console_loglevel 设为 8(大于 KERN_DEBUG 的 7),可以确保所有调试信息都输出到控制台。但注意,8 不是标准级别,它只是利用比较逻辑让所有消息通过。
3.4 日志级别过滤流程图
下图展示了 printk 消息从产生到控制台输出的完整过滤路径:
3.5 日志级别选择策略
在实际驱动开发中,合理选择日志级别可以大幅提升调试效率。以下是一套推荐的分级策略:
| 调试阶段 | 推荐 console_loglevel | printk 级别使用建议 |
|---|---|---|
| 初始开发 | 8(全部输出) | 所有调试信息使用 KERN_DEBUG,关键路径使用 KERN_INFO |
| 功能验证 | 7(默认) | 保留 KERN_DEBUG 信息,逐步移除冗余日志 |
| 集成测试 | 4(警告及以上) | 仅保留 KERN_ERR 和 KERN_WARNING,调试信息通过 dmesg 查看 |
| 生产发布 | 3(错误及以上) | 只保留 KERN_ERR 和 KERN_CRIT,必要时使用动态调试(dynamic debug) |
内核日志缓冲区查看命令:
dmesg — 查看所有内核日志(不受 console_loglevel 影响)dmesg -n 4 — 设置控制台日志级别为 4(等同于 echo 4 > /proc/sys/kernel/printk)dmesg -l err — 只显示错误级别(KERN_ERR)的日志
3.6 常见问题与避坑指南
- 日志级别丢失:如果 printk 调用中忘记加级别宏,内核会使用
default_message_loglevel(通常为 4),导致调试信息被过滤。始终显式指定级别。 - 级别与数值混淆:记住数值越小优先级越高。
KERN_EMERG(0) 是最紧急的,KERN_DEBUG(7) 是最不紧急的。这与直觉相反,容易出错。 - 控制台级别持久化:通过
/proc/sys/kernel/printk的修改在重启后失效。如需持久化,在启动参数中添加loglevel=N,或在/etc/sysctl.conf中配置kernel.printk。 - 性能影响:大量
KERN_DEBUG级别的 printk 会显著降低系统性能,尤其是在中断上下文或高频调用路径中。调试完成后及时移除或使用pr_debug/dev_dbg等可编译关闭的宏。