4. printk 基础用法:日志级别与格式化输出
在嵌入式 Linux 内核调试中,printk 是最直接、最可靠的输出手段。与用户空间的 printf 不同,printk 需要显式指定日志级别,并支持针对设备树节点的专用格式化符号 %pOF。本章将系统讲解这两项核心技能,为后续跟踪 of_ 函数调用路径打下基础。
调试原则: 在设备树解析路径中,优先使用
printk 而非 dev_dbg 或 pr_debug,因为后者可能被动态调试机制关闭,而 printk 在大多数内核配置下始终可用。
4.1 日志级别:控制消息的“优先级”
每个 printk 调用都带有一个日志级别宏,它决定了消息是否显示在控制台以及写入系统日志的紧急程度。内核使用 前缀来标记级别,级别数字越小,优先级越高。
| 日志级别宏 | 数值 | 含义 | 典型使用场景 |
|---|---|---|---|
KERN_EMERG |
0 | 紧急事件,系统不可用 | 致命硬件错误、内核崩溃前 |
KERN_ALERT |
1 | 必须立即处理 | 严重设备故障、内存耗尽 |
KERN_CRIT |
2 | 临界条件 | 驱动初始化失败、DMA 错误 |
KERN_ERR |
3 | 错误条件 | 设备树解析失败、of 函数返回 NULL |
KERN_WARNING |
4 | 警告,可能有问题 | 设备树属性缺失但可降级运行 |
KERN_NOTICE |
5 | 正常但重要的消息 | 设备树节点匹配成功、驱动绑定 |
KERN_INFO |
6 | 信息性消息 | 打印设备树节点名称、寄存器地址 |
KERN_DEBUG |
7 | 调试消息 | 跟踪 of 函数内部路径、变量中间值 |
设备树调试中的级别选择:
KERN_ERR:用于of_函数返回NULL、of_property_read_*失败等不可恢复的错误。KERN_DEBUG:用于跟踪of_find_node_by_name的遍历路径、of_get_property的返回值等调试细节。KERN_INFO:用于打印成功解析到的节点全路径(配合%pOF)。
4.2 格式化输出:从基础到设备树专用
printk 的格式化语法与 printf 基本一致,但在内核调试中,有几个格式符需要特别掌握,尤其是针对设备树的 %pOF。
4.2.1 基础格式符回顾
| 格式符 | 类型 | 示例 | 输出 |
|---|---|---|---|
%s |
字符串 | printk(KERN_INFO "name = %s", np->name); |
name = uart0 |
%d / %i |
有符号十进制整数 | printk(KERN_DEBUG "irq = %d", irq); |
irq = 42 |
%u |
无符号十进制整数 | printk(KERN_INFO "size = %u", size); |
size = 4096 |
%x / %X |
十六进制整数 | printk(KERN_ERR "reg = 0x%x", reg); |
reg = 0xffe0 |
%p |
指针地址 | printk(KERN_DEBUG "np = %p", np); |
np = 00000000c1234567 |
%s + of_node_full_name() |
节点全路径(旧方式) | printk(KERN_INFO "%s", of_node_full_name(np)); |
/soc/uart@ffe00000 |
4.2.2 设备树专用格式符:%pOF
%pOF 是内核为设备树节点提供的专用格式化扩展,它可以直接接收一个 struct device_node * 指针,自动输出节点的全路径。相比手动调用 of_node_full_name(),%pOF 更简洁、更安全,且在内核 4.x 之后广泛支持。
推荐用法:
struct device_node *np;
np = of_find_node_by_name(NULL, "uart0");
if (np) {
printk(KERN_INFO "找到节点: %pOF\n", np); // 输出: /soc/uart@ffe00000
printk(KERN_ERR "节点 %pOF 解析失败\n", np); // 错误时也打印路径
of_node_put(np);
}
%pOF 的额外优势:
- 自动处理 NULL 指针:如果传入
NULL,输出(null),不会崩溃。 - 与日志级别无缝配合:可以在
KERN_ERR、KERN_DEBUG等任何级别中使用。 - 可读性强:直接输出类似
/soc/i2c@ffd00000/rtc@51的完整路径,便于定位。
避坑提醒: 不要对
%pOF 传入 of_node_get 后的临时指针而不释放,否则会导致节点引用计数泄漏。始终在打印后配对 of_node_put。
4.3 实战组合:在 of 函数路径中使用 printk
将日志级别与格式化输出结合,可以构建出清晰的调试信息。以下是一个在设备树节点查找函数中插入 printk 的典型模式:
struct device_node *np;
int ret;
np = of_find_node_by_name(NULL, "uart0");
if (!np) {
printk(KERN_ERR "of_find_node_by_name: 未找到节点 uart0\n");
return -ENODEV;
}
printk(KERN_INFO "成功找到节点: %pOF\n", np);
/* 读取属性 */
ret = of_property_read_u32(np, "reg", ®_val);
if (ret) {
printk(KERN_ERR "节点 %pOF 读取 reg 属性失败, ret=%d\n", np, ret);
of_node_put(np);
return -EINVAL;
}
printk(KERN_DEBUG "节点 %pOF reg = 0x%x\n", np, reg_val);
of_node_put(np);
调试技巧: 在开发阶段,可以将
KERN_DEBUG 消息临时改为 KERN_INFO 或 KERN_NOTICE,以便在默认日志级别下直接看到调试输出。调试完成后改回 KERN_DEBUG 并提交。
4.4 日志级别控制:让调试消息可见
即使代码中使用了 KERN_DEBUG,如果当前控制台日志级别低于 7,消息也不会显示在终端上。以下是常用的控制方法:
| 方法 | 命令 | 说明 |
|---|---|---|
| 查看当前级别 | cat /proc/sys/kernel/printk |
输出 4 个数字:控制台级别、默认级别、最低级别、启动时级别 |
| 临时提升级别 | echo 8 > /proc/sys/kernel/printk |
使所有级别(包括 KERN_DEBUG)都输出到控制台 |
| 启动参数设置 | loglevel=8 |
在 bootargs 中添加,开机即生效 |
| 忽略级别输出 | echo "file drivers/of/* +p" > /sys/kernel/debug/dynamic_debug/control |
动态调试,仅适用于 pr_debug/dev_dbg |
生产环境注意: 不要在生产内核中长时间开启
loglevel=8,大量 KERN_DEBUG 消息会拖慢系统并填满日志缓冲区。调试完成后应恢复默认级别(通常为 4 或 7)。