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

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(三)

相关文章:

  • 两个mysql的maven依赖要用哪个?
  • 高级特性实战:死信队列、延迟队列与优先级队列(一)
  • 基于MATLAB编程针对NCV检测数据去漂移任务的完整解决方案
  • [特殊字符] Function Calling 技术详解与 Qwen 模型实践指南
  • 软考 系统架构设计师系列知识点之杂项集萃(72)
  • Oracle控制文件损坏恢复方案
  • RabbitMQ 可靠性保障:消息确认与持久化机制(一)
  • Android应用中设置非系统默认语言(使用Kotlin)
  • ChatGPT+知网,AI如何辅助真实科研写作流程?
  • JavaEE 网络编程套接字详解与实战示例
  • 永磁同步电机控制算法--IP调节器
  • 文章代码|皮层/表皮特异性转录因子 bZIP89 的自然变异决定了玉米侧根发育和抗旱能力
  • 【监控】Node Exporter 介绍及应用
  • QListWidgetItem的函数介绍
  • webpack面试问题
  • Maven基础篇
  • 使用Vue3制作一款个性化上传组件
  • 【LangChain全栈开发指南】从LLM应用到企业级AI助手构建
  • 理解计算机系统_线程(八):并行
  • 塑料杯子什么材质最好,用起来是不是安全?
  • 专业网站设计公司价格/重庆今天刚刚发生的重大新闻
  • 开发流程图/免费seo软件推荐
  • 网站制作 服务/青岛seo网站建设公司
  • 做网站连带责任/文大侠seo博客
  • 做交通分析的网站/世界500强企业
  • 51制作工厂网站在线观看无需选择/十大室内设计网站