当前位置: 首页 > article >正文

Linux Kernel调试:强大的printk(二)

前言

如果你对printk的基本用法还不熟悉,请先阅读:

Linux Kernel调试:强大的printk(一)

上一篇Linux Kernel调试:强大的printk(一)我们介绍了printk的基础知识和基本用法,了解了printk的一些特性,比如支持输出级别等,不过这是最基础的用法,在实际项目中,一般不会直接这样用,本篇就来讲一下printk的进阶用法。

如果对pr_xxx已经熟悉了,可以直接阅读后面的内容:

Linux Kernel调试:强大的printk(三):dev_xxx相关的内容,以及限制打印速率

pr_xxx

使用printk需要指定日志级别,虽然也可以不指定级别(使用默认级别),但是在实际使用中还是推荐每次调用都最好指定级别,不过这样一来就很麻烦,且不够清晰,所以内核在include/linux/printk.h中提供了如下形式的定义:

#define pr_emerg(fmt, ...) \printk(KERN_EMERG pr_fmt(fmt), ##__VA_ARGS__)#define pr_alert(fmt, ...) \printk(KERN_ALERT pr_fmt(fmt), ##__VA_ARGS__)#define pr_crit(fmt, ...) \printk(KERN_CRIT pr_fmt(fmt), ##__VA_ARGS__)#define pr_err(fmt, ...) \printk(KERN_ERR pr_fmt(fmt), ##__VA_ARGS__)#define pr_warn(fmt, ...) \printk(KERN_WARNING pr_fmt(fmt), ##__VA_ARGS__)#define pr_notice(fmt, ...) \printk(KERN_NOTICE pr_fmt(fmt), ##__VA_ARGS__)#define pr_info(fmt, ...) \printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__)#define pr_cont(fmt, ...) \printk(KERN_CONT fmt, ##__VA_ARGS__)// 这里是从源码中精简出来的
#define pr_debug(fmt, ...) \printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)

由这些定义也可以看出,pr_xxx对应于printk的不同级别,例如pr_emerg就等价于KERN_EMERG级别的printk,这样我们在内核编程时,或者写内核模块时,就可以直接使用pr_xxx来输出log了,也是推荐使用这种方式。

我们把上一篇Linux Kernel调试:强大的printk(一)的示例代码替换成pr_xxx即可进行实验,效果和上一篇一样,这里不在赘述。

这里着重讲一下上面代码中的pr_fmt,这是一个宏,默认定义也位于include/linux/printk.h

/*** pr_fmt - used by the pr_*() macros to generate the printk format string* @fmt: format string passed from a pr_*() macro** This macro can be used to generate a unified format string for pr_*()* macros. A common use is to prefix all pr_*() messages in a file with a common* string. For example, defining this at the top of a source file:**        #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt** would prefix all pr_info, pr_emerg... messages in the file with the module* name.*/
#ifndef pr_fmt
#define pr_fmt(fmt) fmt
#endif

这个宏的作用就是为我们要打印的信息生成统一的格式字符串。从上面的默认定义也可以看出,如果之前没有定义pr_fmt那就将pr_fmt(fmt)定义为fmt,所以如果我们要使用pr_fmt来统一打印的格式,就需要在包含include/linux/printk.h之前定义pr_fmt,下面我们通过示例代码来演示pr_fmt宏的作用,可到这里获取https://gitee.com/coolloser/linux-kerenl-debug

// 定义模块的格式化字符串
#define pr_fmt(fmt) "%s:%s():%d: " fmt, KBUILD_MODNAME, __func__, __LINE__#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>// 定义模块加载函数
static int __init pr_fmt_init(void)
{pr_info("====printk模块加载成功!====\n");pr_debug("这是一个DEBUG级别的信息\n");pr_info("这是一个INFO级别的信息\n");pr_warn("这是一个WARNING级别的信息\n");pr_err("这是一个ERROR级别的信息\n");pr_crit("这是一个CRITICAL级别的信息\n");pr_alert("这是一个ALERT级别的信息\n");pr_emerg("这是一个EMERGENCY级别的信息\n");// 使用KERN_CONT继续上一条日志消息pr_info("这是一条需要继续的信息...");pr_cont("...这是继续的部分\n");return 0; // 返回0表示模块加载成功
}// 定义模块卸载函数
static void __exit pr_fmt_exit(void)
{pr_info("====printk模块卸载成功!====\n");
}// 注册模块加载和卸载函数
module_init(pr_fmt_init);
module_exit(pr_fmt_exit);// 模块信息
MODULE_LICENSE("GPL");
MODULE_AUTHOR("your_name");
MODULE_DESCRIPTION("pr_fmt内核模块示例");
MODULE_VERSION("0.1");
# 定义模块名称
MODULE_NAME := pr_fmt# 定义内核构建目录,替换成你自己的路径
KERNEL_BUILD_DIR := /home/leo/debug_kernel/linux-6.12.28# 定义目标文件
obj-m += $(MODULE_NAME).o# 默认目标
all:@echo "Building the $(MODULE_NAME) kernel module..."$(MAKE) -C $(KERNEL_BUILD_DIR) M=$(PWD) modules# 清理目标
clean:@echo "Cleaning up the build environment..."$(MAKE) -C $(KERNEL_BUILD_DIR) M=$(PWD) clean

执行make进行编译,然后执行sudo insmod pr_fmt.ko加载模块,之后执行dmesg查看log信息:

可以看到,在每一条log之前都加入了[模块名称]:[函数名]:[行号]

所以使用pr_fmt,一处定义,多处使用,我们就可以很方便的添加我们需要的信息了,这里还是要再提醒一下,定义pr_fmt一定要在包含include/linux/printk.h之前,或者在模块源码文件的最开始定义。

pr_debug和pr_devel

在内核源码中,pr_debug和pr_devel的定义和其他几个pr_xxx不同:

pr_debug的定义如下:

/* If you are writing a driver, please use dev_dbg instead */
#if defined(CONFIG_DYNAMIC_DEBUG) || \(defined(CONFIG_DYNAMIC_DEBUG_CORE) && defined(DYNAMIC_DEBUG_MODULE))
#include <linux/dynamic_debug.h>/*** pr_debug - Print a debug-level message conditionally* @fmt: format string* @...: arguments for the format string** This macro expands to dynamic_pr_debug() if CONFIG_DYNAMIC_DEBUG is* set. Otherwise, if DEBUG is defined, it's equivalent to a printk with* KERN_DEBUG loglevel. If DEBUG is not defined it does nothing.** It uses pr_fmt() to generate the format string (dynamic_pr_debug() uses* pr_fmt() internally).*/
#define pr_debug(fmt, ...)                        \dynamic_pr_debug(fmt, ##__VA_ARGS__)
#elif defined(DEBUG)
#define pr_debug(fmt, ...) \printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
#else
#define pr_debug(fmt, ...) \no_printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
#endif

可以看到,如果配置了动态debug则pr_debug定义为了dynamic_pr_debug(关于动态打印以后会单独介绍),如果定义了DEBUG则pr_debug定义为了KERN_DEBUG级别的printk,否则pr_debug就是no_printk

pr_devel定义如下:

/*** pr_devel - Print a debug-level message conditionally* @fmt: format string* @...: arguments for the format string** This macro expands to a printk with KERN_DEBUG loglevel if DEBUG is* defined. Otherwise it does nothing.** It uses pr_fmt() to generate the format string.*/
#ifdef DEBUG
#define pr_devel(fmt, ...) \printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
#else
#define pr_devel(fmt, ...) \no_printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
#endif

可以看到如果定义了DEBUG,pr_devel和pr_debug一样也是KERN_DEBUG级别的printk,否则也是no_printk

所以我们如果要打印debug信息优先使用pr_debug,尽量不要使用pr_devel,因为pr_debug可以使用动态打印,pr_devel则没有使用动态打印,一旦我们不处于debug模式,就没法再查看对应log了。

pr_debug的打印去哪了

不知道你有没有发现,我们上面的示例代码中明明有:

pr_debug("这是一个DEBUG级别的信息\n");

但是在运行结果中,却没有这句打印:

那pr_debug的打印去哪了?其实从pr_debug的定义中我们也能发现问题,因为我们编译内核模块时没有定义DEBUG宏,所以pr_debug现在是no_printk,自然就没有打印出来了,下面我们修改Makefile使编译选项定义DEBUG宏,注意其中的ccflags-y += -DDEBUG

# 定义模块名称
MODULE_NAME := pr_fmt# 定义内核构建目录,替换成你自己的路径
KERNEL_BUILD_DIR := /home/leo/debug_kernel/linux-6.12.28# 定义目标文件
obj-m += $(MODULE_NAME).occflags-y += -DDEBUG# 默认目标
all:@echo "Building the $(MODULE_NAME) kernel module..."$(MAKE) -C $(KERNEL_BUILD_DIR) M=$(PWD) modules# 清理目标
clean:@echo "Cleaning up the build environment..."$(MAKE) -C $(KERNEL_BUILD_DIR) M=$(PWD) clean

现在再编译加载模块,执行dmesg查看:

终于打印出来了^_^

其实,代码中,在包含include/linux/printk.h之前通过#define DEBUG也可以让pr_debug打印出来,这里不再演示,需要你自己动手~

总结

本文重点介绍了pr_xxx的特点以及用法,pr_xxx是对printk的包装,但是其含义更加明确,使用更加方便,所以我们在实际开发中,优先使用pr_xxx进行log输出,还需要特别注意的是pr_debug,这个在我们开发阶段很有用处,且在后面我们介绍的动态打印中也有很大的用处

下一篇我们介绍dev_xxx:

Linux Kernel调试:强大的printk(三)

http://www.lryc.cn/news/2387114.html

相关文章:

  • Kafka Kraft模式集群 + ssl
  • [crxjs]自己创建一个浏览器插件
  • 类的设计模式——单例、工厂以及建造者模式
  • STM32之看门狗(IWDG)
  • PyTorch实现MLP信用评分模型全流程
  • 语音识别——文本转语音
  • 跟着华为去变革 ——读《常变与长青》有感
  • 图像分割技术的实现与比较分析
  • node.js配置变量
  • Ubuntu+Docker+内网穿透:保姆级教程实现安卓开发环境远程部署
  • 为什么需要清除浮动?清除浮动的方式有哪些?
  • 计算机网络学习20250526
  • ArkUI:鸿蒙应用响应式与组件化开发指南(一)
  • YOLOv11改进 | Neck篇 | 双向特征金字塔网络BiFPN助力YOLOv11有效涨点
  • C/C++的OpenCV 进行轮廓提取
  • 计算机网络总结(物理层,链路层)
  • TIGER - 一个轻量高效的语音分离模型,支持人声伴奏分离、音频说话人分离等 支持50系显卡 本地一键整合包下载
  • yolov8,c++案例汇总
  • 无人机降落伞设计要点难点及原理!
  • 20250526给荣品PRO-RK3566的Android13单独编译boot.img
  • vue3项目动态路由的相关配置踩坑记录
  • git子模块--命令--列表版
  • C++(4)
  • 构建版本没mac上传APP方法
  • 如何解决大模型返回的JSON数据前后加上```的情况
  • 本地处理 + GPU 加速 模糊视频秒变 4K/8K 修复视频老旧素材
  • 服务器异常数据问题解决 工具(tcpdump+wireshark+iptables)
  • 综合实现案例 LVS keepalived mysql 等
  • 【QT】对话框dialog类封装
  • 2025/5/26 学习日记 基本/扩展正则表达式 linux三剑客之grep