前言
使用printk的打印方式只能通过设置输出等级来进行控制,具备一定的局限性。在实际系统运行过程中,我们更希望能选择性地打开某些子系统或者模块的输出,为此内核提供了动态调试技术。内核中包括pr_debug、dev_dbg接口都使用了动态调试技术。
动态调试配置与使用
配置内核选项
要使用动态调试,需要在内核编译时打开动态调试开关,配置选项为CONFIG_DYNAMIC_DEBUG以及CONFIG_DEBUG_FS:
挂载debugfs文件系统
在Linux系统启动后,手动执行命令挂载debugfs文件系统:
mount -t debugfs debugfs /sys/kernel/debug
执行完成后,查看/sys/kernel/debug目录,可以发现目录下有一个dynamic_debug的文件夹,里面有个control文件节点,这是动态调试提供的配置入口:
使用动态调试
控制动态调试信息打印的方法如下:
// 打开指定文件中所有的动态调试信息
echo 'file 文件名 +p' > /sys/kernel/debug/dynamic_debug/control
// 打开指定模块中所有的动态调试信息
echo 'module 模块名 +p' > /sys/kernel/debug/dynamic_debug/control
// 打开指定函数中所有的动态调试信息
echo 'func 函数名 +p' > /sys/kernel/debug/dynamic_debug/control
// 打开系统中所有的动态调试信息
echo -n '+p' > /sys/kernel/debug/dynamic_debug/control
// 关闭指定文件中所有的动态调试信息
echo 'file 文件名 -p' > /sys/kernel/debug/dynamic_debug/control
举例说明:
除了p
选项外,动态调试也支持其它的选项,用于输出一些额外信息,如函数名、行号、模块名字以及线程ID等:
- p:打开动态调试打印;
- f:输出函数名;
- l:输出行号;
- m:输出模块名字;
- t:输出线程ID。
动态调试基本原理
当配置了CONFIG_DYNAMIC_DEBUG选项,内核会在编译阶段把所有动态调试的使用信息记录下来,包括文件名路径、模块名、函数、输出所在行号以及要输出的打印等,这些信息我们可以通过/sys/kernel/debug/dynamic_debug/control文件节点查询出来:
通过查询control文件节点,我们获取到系统中包含的所有动态调试信息,通过配置指定字段,可以选择我们需要打印的信息。
pr_debug接口实现
这里通过pr_debug接口说明动态调试的实现,代码如下:
#if defined(CONFIG_DYNAMIC_DEBUG) || \
(defined(CONFIG_DYNAMIC_DEBUG_CORE) && defined(DYNAMIC_DEBUG_MODULE))
#include <linux/dynamic_debug.h>
#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
可以看到当配置了动态调试之后,pr_debug会调用dynamic_pr_debug接口来控制打印的输出:
#define dynamic_pr_debug(fmt, ...) \
_dynamic_func_call(fmt, __dynamic_pr_debug, \
pr_fmt(fmt), ##__VA_ARGS__)
#define _dynamic_func_call(fmt, func, ...) \
__dynamic_func_call(__UNIQUE_ID(ddebug), fmt, func, ##__VA_ARGS__)
#define __dynamic_func_call(id, fmt, func, ...) do { \
DEFINE_DYNAMIC_DEBUG_METADATA(id, fmt); // 根据打印方式和信息生成标识信息,与control节点的某一打印对应 \
if (DYNAMIC_DEBUG_BRANCH(id)) \
func(&id, ##__VA_ARGS__); \
} while (0)
#define DYNAMIC_DEBUG_BRANCH(descriptor) \
unlikely(descriptor.flags & _DPRINTK_FLAGS_PRINT) // 检查标志位,标志位通过debugfs可以进行设置
dynamic_pr_debug接口调用到最后会根据descriptor的标志位_DPRINTK_FLAGS_PRINT的设置情况,来判断是否进行输出。
相关参考
- 《奔跑吧,Linux内核》
- https://cloud.tencent.com/developer/article/1819284