内核编程八:基于printk宏的pr_* 宏

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_debugpr_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),不影响性能。
  • 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 选项控制,适用于动态调试日志。

printkpr_* 宏的关系

  • printk 是内核日志的底层实现,直接用于输出日志信息,需要手动指定 KERN_* 级别。
  • pr_* 宏是 printk 的封装,使代码更简洁、可读性更强,不需要手动添加 KERN_* 级别。
  • pr_debugpr_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 的灵活性和安全性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值