• 正文
  • 相关推荐
申请入驻 产业图谱

搞懂 printk:内核打印的工作流程、级别控制与常见坑

09/20 09:03
144
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

Linux驱动调试系列 · 第 01 篇

驱动调试第一课,从 printk 开始。这是内核中最基础、最常用的打印函数,几乎所有驱动工程师的调试之旅都从它起步。本文从工作原理、日志级别、实战命令到常见踩坑,帮你把 printk 用到位。

感兴趣 Linux 底层驱动可以私信我:瑞芯微 Linux 驱动一对一答疑辅导。

一、printk 是什么?

printk 是 Linux 内核提供的日志打印函数,类似于用户空间的 printf。它是 Kernel Log 机制的基础 API,几乎所有内核调试都从它开始。

与 printf 不同的是,printk 运行在内核态,可以在中断上下文、进程上下文中自由使用。它不依赖 glibc 等用户态运行环境,也不需要用户态内存分配,是驱动开发中最基础也最重要的调试手段。

1.1 为什么不用 printf?

对比项 printf printk
运行空间 用户态 内核态
可调用上下文 进程上下文 中断/进程上下文均可
输出目标 stdout 内核 Log Buffer / Console
是否阻塞 可能 写入缓冲区是原子的,输出到 Console 时可能阻塞
格式化能力 完整 类似,但 %s 等指针需内核地址

注意:printk 与 printf 的格式化不完全等价。printk 不支持浮点数,指针打印用 %p(默认会哈希,如需原始地址用 %px,但生产环境慎用)。

二、printk 工作流程

printk 将日志输出到 Log Buffer(循环缓冲区),然后根据日志级别决定是否输出到 Console(控制台)。dmesg 命令读取的就是这个 Log Buffer,而控制台输出还要受 console_loglevel 控制。

# 查看当前 Log Buffer 中所有级别的消息
$ dmesg

# 实时跟踪新日志(类似 tail -f)
$ dmesg -w

# 只显示 err 及以上级别的日志
$ dmesg -l err,crit,alert,emerg

小技巧:调试驱动时通常先 dmesg -w 再加载/操作设备,这样能第一时间看到 printk 输出。

三、8 个日志等级

printk 支持 8 个日志等级,数值越小级别越高(越紧急)。只有数值严格小于控制台级别(console_loglevel)的消息才会输出到控制台;等于或大于的只写入 Log Buffer,需用 dmesg 查看。

等级 宏定义 数值 说明
紧急 KERN_EMERG 0 紧急事件,系统崩溃前的提示
警报 KERN_ALERT 1 必须立即采取行动
临界 KERN_CRIT 2 临界状态
错误 KERN_ERR 3 错误状态,常用于硬件错误
警告 KERN_WARNING 4 警告信息
注意 KERN_NOTICE 5 正常但重要的消息
信息 KERN_INFO 6 一般信息
调试 KERN_DEBUG 7 调试信息

3.1 推荐写法:pr_* 宏

直接写 printk(KERN_ERR ...) 虽然可行,但容易漏写级别或拼错宏。更规范的做法是使用内核封装好的 pr_* 系列宏:

pr_emerg("系统不可用n");
pr_alert("需要立即处理n");
pr_crit("严重错误n");
pr_err("操作失败: %dn", ret);
pr_warn("发生异常但可继续n");
pr_notice("重要状态n");
pr_info("普通信息n");
pr_debug("调试信息n");   // 默认编译时不输出

pr_debug() 默认不会输出,只有开启 CONFIG_DYNAMIC_DEBUG 或 DEBUG 宏时才生效,下一篇会细讲。

四、控制台日志级别

通过 /proc/sys/kernel/printk 可以查看和修改控制台日志级别,这个文件有 4 个值:

$ cat /proc/sys/kernel/printk
4       4       1       7
位置 含义 默认值
第1个 控制台日志级别(数值小于它的消息才会打印到控制台) 4
第2个 默认消息日志级别(printk 未指定级别时使用) 4
第3个 最低的控制台日志级别 1
第4个 启动时默认的控制台日志级别 7

修改控制台级别

# 将控制台级别改为 8,让所有级别(含 debug)都输出到控制台
$ echo 8 > /proc/sys/kernel/printk

# 等价的另一种写法(dmesg -n 与写 /proc/sys/kernel/printk 等效)
$ dmesg -n 8

改动 /proc/sys/kernel/printk 只在当前运行环境生效,重启后恢复默认值。如需永久生效,可在内核启动参数里加 loglevel=8

五、printk 使用示例

基本用法:

#include <linux/kernel.h>

// 带级别打印
printk(KERN_ERR "这是一条错误信息n");
printk(KERN_INFO "这是一条信息: %dn", value);

// 推荐使用内核封装的宏
pr_err("错误信息n");
pr_warn("警告信息n");
pr_info("普通信息n");
pr_debug("调试信息n");

常用格式化符:

格式化符 类型 说明
%d / %ld int / long 十进制有符号整数
%u / %lu unsigned int / long 十进制无符号整数
%x / %lx unsigned int / long 十六进制小写
%p 指针 内核指针地址(已哈希,安全)
%px 指针 原始地址(生产环境慎用)
%s 字符串 字符串,必须是内核地址
%zu size_t 无符号大小

六、实战建议

先看 dmesg,再看代码:驱动出问题,第一反应不是加打印,而是 dmesg -w 复现。很多硬件错误、段错误、资源冲突已经被内核打印出来。

少而精地打印关键路径:在 probe、remove、中断处理函数、关键寄存器读写处放 pr_info() 或 pr_debug(),追踪执行流即可,不要大面积刷屏。

善用日志级别:错误用 pr_err(),状态变更用 pr_info(),调试细节用 pr_debug()。级别用对了,生产日志不会变得无法阅读。

清理发布版本:发布前用 grep -nE "pr_debug|printk" 检查,移除或降级明显是调试用的打印,避免性能开销和敏感信息泄露。

七、常见踩坑

Log Buffer 被覆盖:默认缓冲区 128KB 或更大,在高频 printk 场景下旧日志会被快速覆盖。调试关键问题时应及时 dmesg > log.txt 保存。

printk 导致时序变化:在竞态条件、中断频繁的地方,printk 的延迟会让 bug 无法复现,必要时改用 trace_printk() 或 ftrace

%s 打印用户态指针可能 oops:printk 的格式化函数运行在内核态,%s 只能打印内核地址。传入用户态地址可能触发缺页甚至 oops(取决于架构与 SMAP/PAN),需先用 copy_from_user 拷贝到内核缓冲区后再打印。

浮点数禁用:printk 不支持 %f,内核态也不建议进行浮点运算。

中文字符输出乱码:如果串口终端编码与内核日志编码不一致,中文会显示为乱码。调试输出尽量使用 ASCII 英文。

八、面试考点

printk 与 printf 的本质区别是什么?

/proc/sys/kernel/printk

四个字段分别是什么含义?为什么推荐使用 pr_err()pr_info()pr_debug() 而不是直接 printk()?为什么 pr_debug() 默认看不到输出?如何开启?在 ISR(中断上下文)里能调用 printk 吗?有什么风险?printk 支持浮点数打印吗?为什么?

九、下篇预告

下一篇介绍 dynamic debug(动态打印),让你在不重新编译、不修改代码的情况下,随时开关某个文件甚至某一行 printk/pr_debug 的输出,是生产环境中排查问题的利器。

驱动调试系列导航

第01篇:printk 内核打印 →当前· 第02篇:dynamic debug 动态打印 · 第03篇:内核调用栈调试 · 第04篇:休眠唤醒 standby 调试 · 第05篇:access_ok 地址检测 · 第06篇:分支预测优化 · 第07篇:procfs 虚拟文件系统 · 第08篇:debugfs 调试文件系统 · 第09篇:devmem 直接读写寄存器 · 第10篇:Linux 性能工具大全 · 第11篇:CPU 使用率详解 · 第12篇:perf 性能优化

相关推荐

本公众号专注于嵌入式技术,包括但不限于C/C++、嵌入式、物联网、Linux等编程学习笔记,同时,公众号内包含大量的学习资源。欢迎关注,一同交流学习,共同进步!