2. printk基础:函数原型、日志级别与控制台配置
printk 是 Linux 内核中最核心的日志输出函数,其地位等同于用户空间的 printf。驱动开发者通过 printk 输出调试信息、状态变更、错误告警等关键数据。理解 printk 的函数原型、日志级别以及控制台日志级别的配置机制,是进行内核日志分析的第一步。
2.1 printk 函数原型
printk 定义在 <linux/printk.h> 中,其函数原型与 printf 高度相似,但增加了日志级别参数。基本原型如下:
int printk(const char *fmt, ...);
参数说明:
- fmt:格式化字符串,支持与 printf 相同的格式说明符(%d, %s, %x, %p 等)。
- ...:可变参数,对应格式化字符串中的占位符。
- 返回值:成功时返回输出的字符数(不包括结尾的 '\0');失败时返回负的错误码。
关键区别:printk 与 printf 最大的不同在于 printk 的第一个参数通常通过宏拼接的方式嵌入日志级别,例如
printk(KERN_INFO "Hello, world!\n");。日志级别宏与格式化字符串之间没有逗号,这是 C 语言字符串字面量拼接的语法特性。
2.2 日志级别(Log Levels)
Linux 内核定义了 8 个日志级别,从最高优先级 KERN_EMERG 到最低优先级 KERN_DEBUG。每个级别对应一个数字值(0~7),数字越小,优先级越高。日志级别决定了消息是否被输出到控制台,以及如何被 syslog 等用户空间工具处理。
| 宏名称 | 数值 | 含义 | 典型使用场景 |
|---|---|---|---|
KERN_EMERG |
0 | 紧急事件,系统不可用 | 致命硬件故障、内核 panic 前的最后消息 |
KERN_ALERT |
1 | 必须立即处理 | 严重内存错误、关键数据损坏 |
KERN_CRIT |
2 | 临界条件 | 驱动初始化失败、DMA 传输错误 |
KERN_ERR |
3 | 错误条件 | 设备 I/O 错误、资源分配失败 |
KERN_WARNING |
4 | 警告条件 | 设备降级运行、配置不匹配 |
KERN_NOTICE |
5 | 正常但重要的消息 | 驱动加载/卸载、设备热插拔事件 |
KERN_INFO |
6 | 信息性消息 | 驱动版本信息、设备探测结果 |
KERN_DEBUG |
7 | 调试消息 | 函数入口/出口、变量值跟踪 |
注意:如果 printk 调用中未指定日志级别(即直接以
printk("some message\n"); 形式调用),内核会使用默认级别 DEFAULT_MESSAGE_LOGLEVEL(通常为 4,即 KERN_WARNING)。这可能导致调试信息被意外抑制或关键信息被淹没,因此强烈建议始终显式指定日志级别。
2.3 控制台日志级别配置
控制台日志级别(console log level)决定了哪些级别的 printk 消息能够被输出到当前控制台(串口、VGA 终端等)。只有日志级别数值 小于或等于 当前控制台日志级别的消息才会被显示。例如,若控制台日志级别为 4(KERN_WARNING),则只有 KERN_EMERG(0)到 KERN_WARNING(4)的消息会出现在控制台上,而 KERN_NOTICE(5)及更低优先级的消息将被过滤。
控制台日志级别可以通过以下方式查看和修改:
- 查看当前控制台日志级别:
该文件输出 4 个数字,依次为:控制台日志级别、默认消息日志级别、最小控制台日志级别、默认控制台日志级别。cat /proc/sys/kernel/printk - 临时修改控制台日志级别:
将控制台日志级别设为 7,允许所有级别的消息(包括echo "7" > /proc/sys/kernel/printkKERN_DEBUG)输出到控制台。这在调试驱动时非常有用。 - 通过内核启动参数设置:
在 bootloader(如 U-Boot、GRUB)的内核命令行中添加该参数,可在系统启动时直接设置控制台日志级别。loglevel=7 - 使用 dmesg 命令:
设置控制台日志级别,效果等同于写入dmesg -n 7/proc/sys/kernel/printk。
调试实践:在嵌入式系统开发中,通常将控制台日志级别设为 7(
KERN_DEBUG),以便在串口终端上实时看到所有驱动调试输出。但需注意,大量调试消息会拖慢系统响应,生产环境中应恢复为默认级别(通常为 4 或 3)。
2.4 printk 日志处理流程
下图展示了 printk 消息从驱动调用到最终输出的完整路径,包括日志级别过滤、环形缓冲区存储以及控制台输出决策。
2.5 常用调试技巧与注意事项
- 使用 pr_* 宏简化调用:内核提供了
pr_emerg、pr_alert、pr_crit、pr_err、pr_warn、pr_notice、pr_info、pr_debug等宏,自动拼接对应的日志级别。例如pr_info("Device initialized\n");等价于printk(KERN_INFO "Device initialized\n");。 - pr_debug 的特殊性:
pr_debug在未定义DEBUG宏时会被编译为空,不会产生任何代码。在驱动开发中,可以通过在源文件顶部添加#define DEBUG或通过内核配置CONFIG_DYNAMIC_DEBUG来动态启用。 - 避免在中断上下文使用过长格式:printk 在中断上下文(如中断处理函数、tasklet)中调用时,应避免输出过长的消息,因为 printk 可能引起调度延迟。建议仅输出关键错误码或状态标志。
- 使用 %p 扩展格式:内核 printk 支持多种 %p 扩展格式,如
%p(指针)、%pK(受限指针)、%pI4(IPv4 地址)、%pM(MAC 地址)等,可简化常见数据类型的输出。
生产环境建议:在产品发布前,应将所有
pr_debug 和低优先级的 printk 移除或条件编译掉,避免日志输出影响系统性能。同时,通过内核启动参数 quiet 可抑制大部分控制台输出,仅保留 KERN_EMERG 级别的消息。