pr_*
宏
在之前的文章讲到printk的使用方法,我们发现通过printk宏打各个级别的日志非常繁琐,所以在Linux内核中基于printk宏又新定义了一些宏,例如在 include/linux/printk.h 中的如下定义:
#define pr_emerg(fmt, ...) printk(KERN_EMERG fmt, ##__VA_ARGS__)
#define pr_alert(fmt, ...) printk(KERN_ALERT fmt, ##__VA_ARGS__)
#define pr_crit(fmt, ...) printk(KERN_CRIT fmt, ##__VA_ARGS__)
#define pr_err(fmt, ...) printk(KERN_ERR fmt, ##__VA_ARGS__)
#define pr_warn(fmt, ...) printk(KERN_WARNING fmt, ##__VA_ARGS__)
#define pr_notice(fmt, ...) printk(KERN_NOTICE fmt, ##__VA_ARGS__)
#define pr_info(fmt, ...) printk(KERN_INFO fmt, ##__VA_ARGS__)
例如:
pr_err("Disk error occurred!\n");
等价于:
printk(KERN_ERR "Disk error occurred!\n");
pr_debug
与 pr_devel
-
pr_debug
:#ifdef DEBUG #define pr_debug(fmt, ...) printk(KERN_DEBUG fmt, ##__VA_ARGS__) #else #define pr_debug(fmt, ...) no_printk(fmt, ##__VA_ARGS__) #endif
- 只有在 DEBUG 选项开启 (
#define DEBUG
) 时才会打印日志。 - 否则会被优化为空(
no_printk
),不影响性能。
- 只有在 DEBUG 选项开启 (
-
pr_devel
:#ifdef CONFIG_DYNAMIC_DEBUG
#define pr_devel(fmt, ...) printk(KERN_DEBUG fmt, ##__VA_ARGS__) #else #define pr_devel(fmt, ...) no_printk(fmt, ##__VA_ARGS__) #endif
- 作用类似
pr_debug
,但受CONFIG_DYNAMIC_DEBUG
选项控制,适用于动态调试日志。
- 作用类似
printk
和 pr_*
宏的关系
printk
是内核日志的底层实现,直接用于输出日志信息,需要手动指定KERN_*
级别。pr_*
宏是printk
的封装,使代码更简洁、可读性更强,不需要手动添加KERN_*
级别。pr_debug
和pr_devel
只有在特定编译选项启用时才会生效,适用于调试阶段。
代码示例
static int __init my_init(void)
{
printk(KERN_INFO "This is a test message using printk\n");
pr_info("This is a test message using pr_info\n");
return 0;
}
这两种方式最终的效果是一样的,但 pr_info
更简洁,推荐使用 pr_*
宏。
printk VS pr_*
方法 | 作用 | 需手动加 KERN_* | 是否推荐 |
---|---|---|---|
printk | 直接打印日志 | 是 | 否(代码冗长) |
pr_* | 更简洁的 printk 封装 | 否 | 是 |
pr_debug | 仅在 DEBUG 选项启用时生效 | 否 | 仅用于调试 |
pr_devel | 仅在 CONFIG_DYNAMIC_DEBUG 启用时生效 | 否 | 仅用于动态调试 |
在 Linux 内核开发中,推荐使用 pr_*
宏来替代 printk
,以提高代码可读性和维护性。
宏中的参数详解
在
#define pr_emerg(fmt, ...) printk(KERN_EMERG fmt, ##__VA_ARGS__)
这个宏定义中,参数部分 fmt, ...
以及 ##__VA_ARGS__
的作用如下:
参数部分
#define pr_emerg(fmt, ...) printk(KERN_EMERG fmt, ##__VA_ARGS__)
fmt
:格式化字符串,类似printf
中的格式字符串,例如"System is down!\n"
。...
(可变参数):表示零个或多个额外参数,用于格式化fmt
中的占位符,如%d
、%s
等。
示例:
pr_emerg("System failure at %d!\n", 42);
展开后变成:
printk(KERN_EMERG "System failure at %d!\n", 42);
##__VA_ARGS__
作用
##__VA_ARGS__
是 GCC 预处理器 提供的特殊语法,它的作用是 处理变参为空的情况,避免语法错误。
如果 pr_emerg
被调用时没有可变参数,例如:
pr_emerg("System is down!\n");
直接展开为:
printk(KERN_EMERG "System is down!\n");
由于 __VA_ARGS__
为空,##
会移除前面多余的 ,
,保证不会出现 printk(KERN_EMERG "System is down!\n", );
这种错误。
如果 ##__VA_ARGS__
不存在,调用 pr_emerg("Hello")
可能会展开成:
printk(KERN_EMERG "Hello", );
这样会导致编译错误。
##__VA_ARGS__
适用场景
-
有变参 时,正常展开:
pr_emerg("Error code: %d\n", 404);
展开为:
printk(KERN_EMERG "Error code: %d\n", 404);
-
无变参 时,去掉多余逗号:
pr_emerg("Critical failure!\n");
展开为:
printk(KERN_EMERG "Critical failure!\n");
代码示例
#define pr_emerg(fmt, ...) printk(KERN_EMERG fmt, ##__VA_ARGS__)
void test() {
pr_emerg("System halted\n"); // 无变参
pr_emerg("Error code: %d\n", 500); // 有变参
}
展开后:
void test() {
printk(KERN_EMERG "System halted\n");
printk(KERN_EMERG "Error code: %d\n", 500);
}
小结
语法 | 作用 |
---|---|
fmt | 必须参数,格式字符串 |
... | 可选参数,支持多个变参 |
__VA_ARGS__ | 代表所有传递的变参 |
##__VA_ARGS__ | 处理无变参时自动去掉前面多余的 , ,防止编译错误 |
这使 pr_emerg
兼容 带参数 和 无参数 两种情况,提高了 printk
的灵活性和安全性。