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