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 消息从产生到控制台输出的完整过滤路径:

printk 日志级别过滤流程 printk(LEVEL, "msg") 提取日志级别 L L <= console_loglevel ? (数值越小优先级越高) 否 消息丢弃,写入日志缓冲区 是 输出到控制台设备 终端 / 串口 / 日志文件 说明:无论是否输出到控制台,所有 printk 消息都会写入内核日志缓冲区(dmesg 可查看) 控制台过滤仅影响实时终端显示,不影响日志记录

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 等可编译关闭的宏。