4. printk 实战:在驱动模块中添加 printk、查看 dmesg 日志、使用 tail -f 实时监控
printk 是 Linux 内核中最基础、最直接的调试手段,也是驱动开发者的“第一把调试扳手”。本章通过一个完整的驱动模块实例,演示如何插入 printk、控制日志级别、查看 dmesg 缓冲区以及使用 tail -f 实现实时监控。
4.1 printk 基础与日志级别
printk 与用户空间的 printf 类似,但增加了日志级别(loglevel)机制,用于控制消息的紧急程度和输出目标。内核通过 /proc/sys/kernel/printk 文件定义四个控制值:
| 字段 | 含义 | 典型默认值 |
|---|---|---|
| console_loglevel | 允许输出到控制台的最低优先级 | 7(DEBUG) |
| default_message_loglevel | 未指定级别时的默认优先级 | 4(KERN_WARNING) |
| minimum_console_loglevel | 控制台可设置的最低优先级 | 1(KERN_ALERT) |
| default_console_loglevel | 系统启动时的控制台级别 | 7(DEBUG) |
printk 的日志级别宏定义在 <linux/kern_levels.h> 中,常用级别如下:
| 宏 | 数值 | 适用场景 |
|---|---|---|
| KERN_EMERG | 0 | 系统不可用,必须立即处理 |
| KERN_ALERT | 1 | 需要立即采取行动 |
| KERN_CRIT | 2 | 严重错误条件 |
| KERN_ERR | 3 | 驱动或硬件错误 |
| KERN_WARNING | 4 | 警告,可能存在问题 |
| KERN_NOTICE | 5 | 正常但重要的信息 |
| KERN_INFO | 6 | 信息性消息 |
| KERN_DEBUG | 7 | 调试信息,仅用于开发 |
最佳实践:开发阶段使用 KERN_DEBUG 或 KERN_INFO;发布驱动时,将常规日志降级为 KERN_WARNING 或 KERN_NOTICE,避免污染生产环境的内核日志缓冲区。
4.2 实战驱动模块:带 printk 的 hello_debug
下面编写一个包含多级别 printk 的简单驱动模块,演示模块加载和卸载时的日志输出。
// hello_debug.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
static int __init hello_debug_init(void)
{
printk(KERN_EMERG "[HELLO] EMERG: 系统即将崩溃?\n");
printk(KERN_ALERT "[HELLO] ALERT: 立即处理!\n");
printk(KERN_CRIT "[HELLO] CRIT: 严重错误\n");
printk(KERN_ERR "[HELLO] ERR: 驱动错误\n");
printk(KERN_WARNING "[HELLO] WARNING: 注意\n");
printk(KERN_NOTICE "[HELLO] NOTICE: 正常但重要\n");
printk(KERN_INFO "[HELLO] INFO: 模块加载成功\n");
printk(KERN_DEBUG "[HELLO] DEBUG: 调试信息\n");
return 0;
}
static void __exit hello_debug_exit(void)
{
printk(KERN_INFO "[HELLO] 模块卸载,再见!\n");
}
module_init(hello_debug_init);
module_exit(hello_debug_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Driver Expert");
MODULE_DESCRIPTION("printk 级别演示模块");
编译后使用 insmod 和 rmmod 加载/卸载模块,观察不同级别的日志输出行为。
4.3 查看 dmesg 日志
dmesg 是读取内核环形缓冲区(ring buffer)的标准工具。常用操作如下:
- 查看所有日志:
dmesg - 查看最后 N 条:
dmesg -n 10或dmesg | tail -n 20 - 清空缓冲区:
dmesg -c(清空后仅新日志可见) - 按级别过滤:
dmesg -l err,warning(只显示错误和警告) - 人类可读时间戳:
dmesg -T(将内核时间戳转为可读时间) - 跟踪新日志:
dmesg -w(类似 tail -f,等待新日志)
示例:加载模块后执行
dmesg -l info,debug | grep HELLO,可快速过滤出本模块的调试信息。
4.4 使用 tail -f 实时监控内核日志
在驱动调试过程中,实时观察日志输出至关重要。tail -f 配合 /var/log/kern.log 或 dmesg -w 可实现类似效果:
# 方法一:监控内核日志文件(Ubuntu/Debian)
tail -f /var/log/kern.log
# 方法二:使用 dmesg 的跟踪模式(所有发行版)
dmesg -w
# 方法三:结合 grep 过滤特定模块
dmesg -w | grep HELLO
当执行 insmod hello_debug.ko 时,tail -f 终端会立即显示所有 printk 输出,无需手动刷新。
注意:如果使用
tail -f /var/log/kern.log,需要确保系统日志服务(rsyslog/systemd-journald)正在运行,并且内核日志被正确写入该文件。嵌入式系统中可能没有该文件,此时优先使用 dmesg -w。
4.5 printk 输出流程与环形缓冲区
下图展示了 printk 从驱动调用到控制台/日志文件的完整路径:
4.6 控制台日志级别动态调整
在调试过程中,可能需要临时调整控制台输出级别,避免被大量调试信息淹没:
# 查看当前控制台级别
cat /proc/sys/kernel/printk
# 输出示例:7 4 1 7
# 只允许 KERN_ERR 及以上级别输出到控制台
echo "3 4 1 7" > /proc/sys/kernel/printk
# 恢复默认(所有级别)
echo "7 4 1 7" > /proc/sys/kernel/printk
调试技巧:在开发阶段,可以将 console_loglevel 设为 7,配合
dmesg -w | grep YOUR_DRIVER 实时过滤,既能看到所有调试信息,又不会被无关日志干扰。
4.7 常见问题与避坑指南
| 问题 | 原因 | 解决方案 |
|---|---|---|
| printk 不输出到控制台 | console_loglevel 太低,过滤了低优先级消息 | 检查 /proc/sys/kernel/printk,临时调高 console_loglevel |
| dmesg 看不到最新日志 | 环形缓冲区被旧日志填满,新日志覆盖了旧日志 | 使用 dmesg -c 清空缓冲区,或增大缓冲区大小(内核配置) |
| tail -f 无输出 | syslog 服务未运行,或日志未写入 kern.log | 改用 dmesg -w,或检查 journalctl -kf(systemd 系统) |
| printk 导致系统卡顿 | 高频调用 printk,尤其在中断上下文 | 使用 printk_ratelimit() 或 pr_debug() 条件编译 |