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 消息从调用到控制台输出的完整路径,以及日志级别如何参与过滤:

printk 消息流与日志级别过滤 printk 调用 提取日志级别 有级别? 使用默认级别 使用指定级别 写入内核日志缓冲区 级别 < console_loglevel? 控制台过滤规则: 仅当日志级别数值 小于 console_loglevel 时, 消息才会输出到控制台。

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 动态调整。
  • 合理选择日志级别,既能保证调试信息充分,又能避免生产环境下的日志风暴。