2. printk 入门:函数原型、日志级别与控制台设置
在 Linux 驱动开发中,printk 是最基础、最直接的调试手段。它类似于用户空间的 printf,但运行在内核上下文,能够将调试信息输出到内核日志缓冲区(kernel log buffer)或控制台。掌握 printk 的用法、日志级别机制以及控制台输出控制,是驱动调试的第一步。
2.1 printk 函数原型
printk 定义在 <linux/printk.h> 中,其函数原型与 printf 高度相似:
int printk(const char *fmt, ...);
- 返回值:成功时返回输出的字符数(不含结尾的 '\0');失败时返回负的错误码。
- 格式化字符串:支持
%d、%x、%s、%p(内核指针)等标准格式符。 - 特殊格式符:
%p可配合扩展格式(如%pI4打印 IPv4 地址),%px打印未混淆的指针地址(需开启CONFIG_DEBUG_KERNEL)。
提示: 与用户空间的
printf 不同,printk 默认不会立即刷新到控制台,而是先写入内核日志缓冲区。控制台输出由日志级别和 console_loglevel 共同决定。
2.2 日志级别(Log Levels)
每个 printk 调用都可以附带一个日志级别宏,用于标识消息的严重程度。日志级别定义在 <linux/kern_levels.h> 中,共 8 个级别,从最高优先级(KERN_EMERG)到最低(KERN_DEBUG)。
| 宏名称 | 数值 | 含义 | 典型使用场景 |
|---|---|---|---|
KERN_EMERG |
0 | 紧急事件,系统不可用 | 致命硬件故障、内核 panic 前 |
KERN_ALERT |
1 | 必须立即处理 | 严重错误,需管理员介入 |
KERN_CRIT |
2 | 临界条件 | 驱动初始化失败、关键资源耗尽 |
KERN_ERR |
3 | 错误条件 | 设备 I/O 错误、内存分配失败 |
KERN_WARNING |
4 | 警告条件 | 不推荐配置、性能降级 |
KERN_NOTICE |
5 | 正常但重要 | 驱动加载/卸载、设备热插拔 |
KERN_INFO |
6 | 信息性消息 | 版本信息、设备探测结果 |
KERN_DEBUG |
7 | 调试消息 | 函数入口/出口、变量值跟踪 |
使用示例:
printk(KERN_ERR "my_driver: DMA transfer failed, status=%d\n", status);
printk(KERN_INFO "my_driver: device probed at 0x%lx\n", addr);
printk(KERN_DEBUG "my_driver: entering ioctl, cmd=%u\n", cmd);
注意: 日志级别宏必须放在格式化字符串之前,且两者之间没有逗号。宏展开后是一个字符串前缀,与后面的格式串在编译时拼接。
2.3 控制台日志级别设置
内核通过三个关键变量控制哪些 printk 消息能够输出到控制台:
- console_loglevel:当前控制台日志级别。只有日志级别数值 小于 此值的消息才会被打印到控制台。
- default_message_loglevel:当
printk未显式指定日志级别时,默认使用的级别(通常为KERN_WARNING,即 4)。 - minimum_console_loglevel:控制台日志级别可设置的最小值(通常为 1,即
KERN_ALERT)。 - default_console_loglevel:系统默认的控制台日志级别(通常为 7,即
KERN_DEBUG,表示所有消息都输出)。
查看当前控制台日志级别:
cat /proc/sys/kernel/printk
# 输出示例:7 4 1 7
# 分别对应:console_loglevel, default_message_loglevel, minimum_console_loglevel, default_console_loglevel
动态修改控制台日志级别(需要 root 权限):
# 只显示 KERN_ERR 及以上(数值 ≤ 3)
echo "3" > /proc/sys/kernel/printk
# 恢复为显示所有消息(数值 = 7)
echo "7" > /proc/sys/kernel/printk
调试技巧: 在驱动开发初期,建议将
console_loglevel 设为 8(或 7),确保所有 printk 消息(包括 KERN_DEBUG)都能在控制台看到。发布前再调高日志级别,只保留 KERN_ERR 及以上的输出。
2.4 printk 消息流与日志级别过滤
下图展示了 printk 消息从调用到控制台输出的完整路径,以及日志级别如何参与过滤:
2.5 常用调试场景与日志级别选择
在实际驱动开发中,合理选择日志级别既能保证调试效率,又能避免生产环境下的日志泛滥。以下是一些推荐实践:
| 调试阶段 | 推荐级别 | 说明 |
|---|---|---|
| 开发初期,跟踪函数调用 | KERN_DEBUG |
打印入口/出口、关键变量值 |
| 验证硬件寄存器读写 | KERN_INFO |
打印寄存器地址、读写值 |
| 处理设备 I/O 错误 | KERN_ERR |
打印错误码、状态寄存器 |
| 驱动加载/卸载流程 | KERN_NOTICE |
打印模块名、设备号、资源信息 |
| 生产环境,仅保留关键告警 | KERN_WARNING 及以上 |
设置 console_loglevel=4 |
进阶提示: 在调试过程中,如果
printk 输出过多导致控制台刷屏,可以临时将 console_loglevel 调低,或使用 dmesg -n 3 命令只显示 KERN_ERR 及以上级别的消息。所有 printk 消息仍会写入内核日志缓冲区,可通过 dmesg 命令随时查看。
2.6 本章小结
printk是内核空间的格式化输出函数,原型与printf一致,但运行在中断上下文或进程上下文。- 日志级别从
KERN_EMERG(0)到KERN_DEBUG(7),数值越小优先级越高。 - 控制台日志级别
console_loglevel决定哪些消息能输出到控制台,可通过/proc/sys/kernel/printk动态调整。 - 合理选择日志级别,既能保证调试信息充分,又能避免生产环境下的日志风暴。