[Linux Kernel] Trace Point 官方文档 & 实际使用

   前面转载了一篇关于 tracepoint 的文章,老实说没怎么看得懂,不如看看官方文档吧。

官方文档:

  • linux-5.4.37
  • Documentation\trace\tracepoints.rst

一、简介

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 放置在代码的关键位置
  • 执行到 tracepoint 时会调用 callback 函数,这函数可以叫探针函数(probe function)
  • 该 callback 是在 caller 的上下文中的,所以会影响 caller 的效率
  • 该 callback 可以在 runtime 是注册
  • 可以控制 tracepoint 的 “on” “off” 开关状态。
  • 如果按 C# 的说法,我觉得 tracepoint 其实就是“委托”

二、用法

tracepoint 的使用需要下面三个条件:

  • #include <linux/tracepoint.h>
  • 定义 tracepoint,应该定义在头文件中。
  • 在 C 的代码中放置 tracepoint。

举例

这里举一个 subsys 的例子。

  • 在 include/trace/events/subsys.h 中:
使用 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 <linux/tracepoint.h>

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 <trace/define_trace.h>
  • 在 subsys/file.c 中:
somefct() 函数中添加了先前定义的 tracepoint

#include <trace/events/subsys.h>

#define CREATE_TRACE_POINTS
DEFINE_TRACE(subsys_eventname);

void somefct(void)
{
	...
	trace_subsys_eventname(arg, task);
	...
}

其中关键点:

  • subsys_eventname 是你给这个事件(tracepoint)取的标识符名称
  • `TP_PROTO(int firstarg, struct task_struct *p)` 是 tracepoint 的 callback 的函数原型
  • `TP_ARGS(firstarg, p)` 是参数的名字,可以发现和函数原型中的一致
  • 如果头文件被多个源文件使用,#define CREATE_TRACE_POINTS 只应该在一个源文件中出现

1. 注册 callback

  • 使用 register_trace_subsys_eventname() 函数来注册 callback(探针函数)。
  • 使用 unregister_trace_subsys_eventname() 函数来移除注册的 callback。

   tracepoint_synchronize_unregister() 必须在 module_exit 函数结束前调用,确保没有 caller 使用 probe function。这一点,以及在 调用 probe callback 周围禁用抢占的事实,确保 probe callback 的移除和模块的卸载时安全的。

2. 其他注意点

   跟踪点机制支持插入同一跟踪点的多个实例,但必须在所有内核上对给定的跟踪点名称进行单个定义,以确保不会发生类型冲突。跟踪点的名称管理是使用原型来完成的,以确保键入的内容是正确的。编译器在注册站点验证探针类型的正确性。跟踪点可以放在内联函数、内联静态函数、展开循环以及常规函数中。

   这里建议使用命名方案“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_<tracepoint>() 调用都有对应的 trace_<tracepoint>enabled() 函数(当 tracepoint 是 “on” 时返回 true,否则返回 false)。trace<tracepoint>() 应该始终位于 if (trace_<tracepoint>_enabled()) 的块中,以防止 tracepoint 被启用与检查之间的竞争。

   使用 trace_<tracepoint>_enabled() 的优点是它使用 tracepoint 的 static_key ,允许使用跳转标签(jump labels)来实现 if 语句,并避免条件分支。

注:方便的宏 TRACE_EVENT() 提供了另一种方法来定义 tracepoint,访问 Using the TRACE_EVENT() macro (Part 1)(Part 2)(Part 3) 来了解更多细节。

三、实际使用案例

   从上面看起来还比较简单,实际使用的时候还折腾了一下,可能是因为我太菜了吧。

1. module_entry.c

这里要注意 callback 的第一个参数一定是 (void*) 类型!!不得不说,万恶之源宏定义。

#include <linux/init.h>
#include <linux/module.h>
#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");

2. module_tracepoint.h

#ifndef _MODULE_TRACEPOINT_
#define _MODULE_TRACEPOINT_

#if !defined(_TRACE_SUBSYS_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_SUBSYS_H

#include <linux/tracepoint.h>

DECLARE_TRACE(tp_test,
    TP_PROTO(int num),
    TP_ARGS(num));

#endif /* _TRACE_SUBSYS_H */
    
/* This part must be outside protection */
#include <trace/define_trace.h>
#endif // _MODULE_TRACEPOINT_

3. Makefile

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

4. result

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.

5. summary

   其实是很简单的东西,提供了一种很好的调试方式,可以随时使用或是不使用,甚至可以搞一些骚操作。简单来说感觉可以理解为 C# 的多播委托,和普通的函数指针 callback 还是有些区别的。
   还学到的东西,那可能就是 C 语言“有趣”的宏定义了吧,感觉以后又可以写一些骚操作了呢,宏定义实现泛型应该是所有语言最让人看不懂的了吧。。。可能还是因为自己菜。

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值