2. printk 日志级别:从 KERN_EMERG 到 KERN_DEBUG

printk 是内核中最基础的日志输出接口,其核心设计围绕 日志级别 (Log Level) 展开。每个 printk 调用都必须携带一个级别宏,用于标识消息的严重程度。内核根据这些级别决定是否将消息输出到控制台,以及是否写入系统日志缓冲区。

2.1 八级定义与数值含义

Linux 内核在 <linux/kern_levels.h> 中定义了 8 个日志级别宏,数值从 0 到 7,数值越小,优先级越高。下表列出了每个级别的宏名、数值、对应的字符串前缀以及典型使用场景:

宏名 数值 字符串前缀 含义 典型场景
KERN_EMERG 0 "<0>" 紧急事件 系统即将崩溃或已崩溃,如致命硬件错误、内核 panic
KERN_ALERT 1 "<1>" 必须立即处理 严重系统故障,如磁盘损坏、内存耗尽
KERN_CRIT 2 "<2>" 临界条件 关键子系统故障,如文件系统损坏、驱动加载失败
KERN_ERR 3 "<3>" 错误条件 功能异常但系统可继续运行,如 I/O 错误、设备超时
KERN_WARNING 4 "<4>" 警告条件 潜在问题,如资源使用率过高、配置不推荐
KERN_NOTICE 5 "<5>" 正常但重要 系统启动信息、模块加载/卸载、安全事件
KERN_INFO 6 "<6>" 信息性消息 驱动探测、硬件配置、版本信息
KERN_DEBUG 7 "<7>" 调试消息 开发阶段的详细调试输出,生产环境通常关闭
关键记忆点:数值 0 是最高优先级(EMERG),数值 7 是最低优先级(DEBUG)。内核使用 console_loglevel 变量作为阈值,只有级别数值 小于 该阈值的消息才会被打印到控制台。

2.2 级别在 printk 中的使用规范

在代码中使用 printk 时,级别宏必须作为 第一个参数,且与格式字符串之间不加逗号。这是内核编码风格中的硬性规定。

// 正确用法:级别宏紧贴格式字符串
printk(KERN_ERR "DMA transfer failed on channel %d\n", channel);

// 错误用法:级别宏与格式字符串之间加了逗号
printk(KERN_ERR, "DMA transfer failed on channel %d\n", channel);  // 编译警告

// 错误用法:忘记加级别宏(默认使用 KERN_WARNING,但不推荐)
printk("DMA transfer failed on channel %d\n", channel);
常见陷阱:printk 的级别宏在预处理阶段会被替换为字符串前缀(如 "<3>"),因此 printk(KERN_ERR "msg") 在编译后等价于 printk("<3>" "msg"),C 编译器会自动拼接相邻字符串。如果误加逗号,级别宏会变成单独参数,导致编译警告或运行时行为异常。

2.3 级别选择原则与避坑指南

选择正确的日志级别是内核调试的基本功。以下原则可以帮助你做出合理判断:

  • KERN_EMERG / KERN_ALERT:仅在系统级灾难时使用。滥用会导致真正的紧急消息被淹没。
  • KERN_CRIT:用于关键子系统失效,如文件系统损坏、内存分配失败导致 OOM。
  • KERN_ERR:最常用的错误级别。驱动或模块遇到可恢复的错误时使用,例如设备未响应、校验和错误。
  • KERN_WARNING:用于非致命但值得关注的情况,如降级模式、不支持的配置。
  • KERN_NOTICE:正常但重要的状态变更,如网络接口 up/down、模块加载。
  • KERN_INFO:常规信息,如驱动版本、硬件探测结果。
  • KERN_DEBUG:仅在开发调试阶段使用。生产内核通常通过 dynamic_debug 或编译选项关闭此级别输出。
实战建议:在驱动开发初期,可以将所有调试信息先用 KERN_INFO 输出,待功能稳定后,再根据消息的重要程度降级为 KERN_DEBUG 或直接删除。这样既能保证开发阶段的可见性,又能避免污染生产环境的日志。

2.4 日志级别与控制台输出的关系

内核通过三个关键变量控制日志输出行为:

  • console_loglevel:控制台输出阈值。只有级别数值小于此值的消息才会被打印。
  • default_message_loglevel:未指定级别的 printk 默认使用的级别(通常为 4,即 KERN_WARNING)。
  • minimum_console_loglevel:控制台允许设置的最小级别(通常为 1,即 KERN_ALERT)。

运行时可以通过 /proc/sys/kernel/printk 文件查看和修改这些值:

# 查看当前日志级别设置
cat /proc/sys/kernel/printk
# 输出示例:7       4       1       7
# 分别对应:console_loglevel, default_message_loglevel, minimum_console_loglevel, default_console_loglevel

# 将控制台日志级别设为 4(只显示 KERN_WARNING 及以上)
echo "4 4 1 7" > /proc/sys/kernel/printk
调试技巧:在串口调试时,可以将 console_loglevel 设为 8(或 15),这样所有级别的消息(包括 KERN_DEBUG)都会输出到控制台。但注意,这会产生大量日志,可能影响系统性能。

2.5 日志级别与 dmesg 的交互

所有 printk 消息(无论是否输出到控制台)都会被写入内核的 环形缓冲区 (ring buffer),可以通过 dmesg 命令查看。dmesg 默认显示所有级别的消息,但可以通过 -l 参数按级别过滤:

# 只显示 KERN_ERR 及以上级别的消息
dmesg -l err

# 显示 KERN_WARNING 和 KERN_ERR 级别的消息
dmesg -l warn,err

# 显示所有级别的消息(默认行为)
dmesg -l emerg,alert,crit,err,warn,notice,info,debug

下图展示了 printk 消息从产生到输出的完整路径:

printk 消息流与日志级别过滤 printk(KERN_xxx) 解析级别数值 console_loglevel 过滤 环形缓冲区 (dmesg) 控制台输出 dmesg / syslog 说明:只有通过 console_loglevel 过滤的消息才会输出到控制台;所有消息都会进入环形缓冲区,可通过 dmesg 查看

2.6 级别使用规范总结

下表总结了不同开发阶段和场景下的级别选择策略:

开发阶段 推荐级别 说明
原型验证 KERN_INFO / KERN_DEBUG 尽可能多的输出,快速定位问题
功能调试 KERN_ERR / KERN_WARNING 关注错误路径和异常分支
代码审查/提交 按严重程度分级 删除或降级调试信息,保留必要的错误和警告
生产环境 KERN_ERR 及以上 仅保留关键错误,避免日志洪泛
核心原则:日志级别不仅是技术选择,更是工程规范。一个良好的内核模块应该让运维人员仅通过 dmesg -l err 就能快速定位问题,而不需要从海量调试信息中筛选。