前面转载了一篇关于 tracepoint 的文章,老实说没怎么看得懂,不如看看官方文档吧。
官方文档:
A tracepoint placed in code provides a hook to call a function (probe) that you can provide at runtime. A tracepoint can be “on” (a probe is connected to it) or “off” (no probe is attached). When a tracepoint is “off” it has no effect, except for adding a tiny time penalty (checking a condition for a branch) and space penalty (adding a few bytes for the function call at the end of the instrumented function and adds a data structure in a separate section). When a tracepoint is “on”, the function you provide is called each time the tracepoint is executed, in the execution context of the caller. When the function provided ends its execution, it returns to the caller (continuing from the tracepoint site).
一个 tracepoint 可以放置在 code 中,提供 hook (钩子) 的功能,该 hook 功能可以 call 一个 function (probe,探针),这个 function 可以在 runtime 时提供。tracepoint 可以是 “on”(连接了一个 probe探针),或者是 “off”(没有探针连接)。当一个 tracepoint 是 “off” 时,除了会有一点点时间惩罚(检查分支条件)和空间惩罚(在插入指令的函数的末尾为函数调用添加几个字节,并在单独的 section 添加数据结构)外,就没有其它影响了。当 tracepoint 为 “on” 时,每当执行到跟踪点时你提供的 probe function 都会在调用方(caller)的上下文中被调用。所以当 probe function 执行完时,都会回到 caller 继续执行。
You can put tracepoints at important locations in the code. They are lightweight hooks that can pass an arbitrary number of parameters, which prototypes are described in a tracepoint declaration placed in a header file.
您可以在代码中的重要位置放置 tracepoints,它们是轻量级的挂钩(lightweight hooks),可以传递任意数量的参数,原型被描述在头文件中的 tracepoint 声明中。
They can be used for tracing and performance accounting.
它们可以用于跟踪和统计。
tracepoint 的使用需要下面三个条件:
这里举一个 subsys 的例子。
使用 DECLARE_TRACE() 定义了一个 tracepoint
#undef TRACE_SYSTEM
#define TRACE_SYSTEM subsys
#if !defined(_TRACE_SUBSYS_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_SUBSYS_H
#include
DECLARE_TRACE(subsys_eventname,
TP_PROTO(int firstarg, struct task_struct *p),
TP_ARGS(firstarg, p));
#endif /* _TRACE_SUBSYS_H */
/* This part must be outside protection */
#include
在 somefct() 函数中添加了先前定义的 tracepoint
#include
#define CREATE_TRACE_POINTS
DEFINE_TRACE(subsys_eventname);
void somefct(void)
{
...
trace_subsys_eventname(arg, task);
...
}
其中关键点:
tracepoint_synchronize_unregister() 必须在 module_exit 函数结束前调用,确保没有 caller 使用 probe function。这一点,以及在 调用 probe callback 周围禁用抢占的事实,确保 probe callback 的移除和模块的卸载时安全的。
跟踪点机制支持插入同一跟踪点的多个实例,但必须在所有内核上对给定的跟踪点名称进行单个定义,以确保不会发生类型冲突。跟踪点的名称管理是使用原型来完成的,以确保键入的内容是正确的。编译器在注册站点验证探针类型的正确性。跟踪点可以放在内联函数、内联静态函数、展开循环以及常规函数中。
这里建议使用命名方案“subsys_event”作为限制冲突的约定。跟踪点名称是内核的全局名称:无论是在内核映像中还是在模块中,它们都被认为是相同的。
如果 tracepoint 不得不在内核模块中使用,可以使用 EXPORT_TRACEPOINT_SYMBOL_GPL() 或 EXPORT_TRACEPOINT_SYMBOL() 来导出定义的 tracepoint。
如果您需要对 tracepoint 参数做一些工作,并且该工作仅用于 tracepoint,那么该工作可以封装下面的 if 语句中:
if (trace_foo_bar_enabled()) {
int i;
int tot = 0;
for (i = 0; i < count; i++)
tot += calculate_nuggets();
trace_foo_bar(tot);
}
所有的 trace_
使用 trace_
注:方便的宏 TRACE_EVENT() 提供了另一种方法来定义 tracepoint,访问 Using the TRACE_EVENT() macro (Part 1) ,(Part 2) 和 (Part 3) 来了解更多细节。
从上面看起来还比较简单,实际使用的时候还折腾了一下,可能是因为我太菜了吧。
这里要注意 callback 的第一个参数一定是 (void*) 类型!!不得不说,万恶之源宏定义。
#include
#include
#include "module_tracepoint.h"
#define CREATE_TRACE_POINTS
DEFINE_TRACE(tp_test);
static void tp_callback_1(void * data, int num)
{
printk("%s: n = %d\n", __func__, num);
}
static void tp_callback_2(void * data, int num)
{
printk("%s: n = %d\n", __func__, num * 2);
}
static __init int __module_init(void)
{
int i = 0;
printk("Hello, %s.\n", __func__);
trace_tp_test(++i);
register_trace_tp_test(tp_callback_1, NULL);
trace_tp_test(++i);
printk("event: register tp_callback_2\n");
register_trace_tp_test(tp_callback_2, NULL);
trace_tp_test(++i);
return 0;
}
static __exit void __module_exit(void)
{
unregister_trace_tp_test(tp_callback_1, NULL);
unregister_trace_tp_test(tp_callback_2, NULL);
printk("Hello, %s.\n", __func__);
return;
}
module_init(__module_init);
module_exit(__module_exit);
MODULE_LICENSE("Dual BSD/GPL");
#ifndef _MODULE_TRACEPOINT_
#define _MODULE_TRACEPOINT_
#if !defined(_TRACE_SUBSYS_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_SUBSYS_H
#include
DECLARE_TRACE(tp_test,
TP_PROTO(int num),
TP_ARGS(num));
#endif /* _TRACE_SUBSYS_H */
/* This part must be outside protection */
#include
#endif // _MODULE_TRACEPOINT_
CONFIG_MODULE_SIG=n
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
MODULE := xhr
obj-m := $(MODULE).o
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
.PHONY:clean
clean:
rm -f *.o *.ko *.symvers *.order *.mod.c *.mod .*.cmd
$(MODULE)-objs += module_entry.o
root@ubuntu:/home/xhr/study/code/module& insmod xhr.ko
[ 6543.758265] Hello, __module_init.
[ 6543.758631] tp_callback_1: n = 2
[ 6543.758633] event: register tp_callback_2
[ 6543.758634] tp_callback_1: n = 3
[ 6543.758635] tp_callback_2: n = 6
root@ubuntu:/home/xhr/study/code/module& rmmod xhr
[ 6557.501027] Hello, __module_exit.
其实是很简单的东西,提供了一种很好的调试方式,可以随时使用或是不使用,甚至可以搞一些骚操作。简单来说感觉可以理解为 C# 的多播委托,和普通的函数指针 callback 还是有些区别的。
还学到的东西,那可能就是 C 语言“有趣”的宏定义了吧,感觉以后又可以写一些骚操作了呢,宏定义实现泛型应该是所有语言最让人看不懂的了吧。。。可能还是因为自己菜。