内容简介:内核中定义了一系列的trace point,这些trace point在特定的内核函数中被触发调用时被记录,而对应到systemtap中就是换言之,通过systemtap能够对这些已经静态注册的内核调用记录点进行监控、跟踪。以下来解释trace point在内核的实现以及与systemtap相关的内容。
概述
内核中定义了一系列的trace point,这些trace point在特定的内核函数中被触发调用时被记录,而对应到systemtap中就是 kernel.trace
类型的probe事件,可以使用命令来查看系统所有的trace point:
$ sudo stap -L 'kernel.trace("*")' | more kernel.trace("9p:9p_client_req") $clnt:struct p9_client* $type:int8_t $tag:int kernel.trace("9p:9p_client_res") $clnt:struct p9_client* $type:int8_t $tag:int $err:int kernel.trace("9p:9p_protocol_dump") $clnt:struct p9_client* $pdu:struct p9_fcall*
换言之,通过systemtap能够对这些已经静态注册的内核调用记录点进行监控、跟踪。
以下来解释trace point在内核的实现以及与systemtap相关的内容。
数据结构
内核通过 DECLARE_TRACE
来声明一个trace point:
DECLARE_TRACE(subsys_eventname, TP_PROTO(int firstarg, struct task_struct *p), TP_ARGS(firstarg, p));
在这里:
-
subsys_eventname是定义trace事件的唯一字符串,又能拆解成两部分:subsys就是子系统的名称,而eventname是事件名称。比如下面将作为实例的
softirq_entry
,就定义了一个在softirq
子系统中的entry
事件。 - TP_PROTO(int firstarg, struct task_struct *p):定义了传入trace函数的参数原型。
- TP_ARGS(firstarg, p):定义了参数名称,其类型与TP_PROTO中的类型一一对应。
这个宏的定义如下:
// include/linux/tracepoint.h #define DECLARE_TRACE(name, proto, args) \ __DECLARE_TRACE(name, PARAMS(proto), PARAMS(args), \ cpu_online(raw_smp_processor_id()), \ PARAMS(void *__data, proto), \ PARAMS(__data, args))
其中的宏 __DECLARE_TRACE
定义如下:
#define __DECLARE_TRACE(name, proto, args, cond, data_proto, data_args) \ extern struct tracepoint __tracepoint_##name; \ static inline void trace_##name(proto) \ { \ if (static_key_false(&__tracepoint_##name.key)) \ __DO_TRACE(&__tracepoint_##name, \ TP_PROTO(data_proto), \ TP_ARGS(data_args), \ TP_CONDITION(cond), 0); \ if (IS_ENABLED(CONFIG_LOCKDEP) && (cond)) { \ rcu_read_lock_sched_notrace(); \ rcu_dereference_sched(__tracepoint_##name.funcs);\ rcu_read_unlock_sched_notrace(); \ } \ } \ __DECLARE_TRACE_RCU(name, PARAMS(proto), PARAMS(args), \ PARAMS(cond), PARAMS(data_proto), PARAMS(data_args)) \ static inline int \ register_trace_##name(void (*probe)(data_proto), void *data) \ { \ return tracepoint_probe_register(&__tracepoint_##name, \ (void *)probe, data); \ } \ static inline int \ register_trace_prio_##name(void (*probe)(data_proto), void *data,\ int prio) \ { \ return tracepoint_probe_register_prio(&__tracepoint_##name, \ (void *)probe, data, prio); \ } \ static inline int \ unregister_trace_##name(void (*probe)(data_proto), void *data) \ { \ return tracepoint_probe_unregister(&__tracepoint_##name,\ (void *)probe, data); \ } \ static inline void \ check_trace_callback_type_##name(void (*cb)(data_proto)) \ { \ } \ static inline bool \ trace_##name##_enabled(void) \ { \ return static_key_false(&__tracepoint_##name.key); \ }
可以看到,这个宏做了如下的事情:
-
声明了一个类型为
tracepoint
的结构体变量__tracepoint_##name
。 -
定义了几个相关的函数,分别用于处理trace event、注册、注销等。其中需要重点关注的是宏
trace_##name
,这里定义了对对应的traceevent进行跟踪的函数。
其中,宏里面一个字符串跟着 ##name
表示这个字符串与name的连接形成的字符串。
这里的结构体 tracepoint
定义如下:
// include/linux/tracepoint-defs.h struct tracepoint { const char *name; /* Tracepoint name */ struct static_key key; int (*regfunc)(void); void (*unregfunc)(void); struct tracepoint_func __rcu *funcs; };
该结构体中分别定义了:
- traceevent名称。
- 注册、注销、被触发时的处理函数。
以上只是声明了 tracepoint
结构体变量,而具体定义变量的宏是 DEFINE_TRACE
:
// include/linux/tracepoint.h #define DEFINE_TRACE(name) \ DEFINE_TRACE_FN(name, NULL, NULL); #define DEFINE_TRACE_FN(name, reg, unreg) \ static const char __tpstrtab_##name[] \ __attribute__((section("__tracepoints_strings"))) = #name; \ struct tracepoint __tracepoint_##name \ __attribute__((section("__tracepoints"))) = \ { __tpstrtab_##name, STATIC_KEY_INIT_FALSE, reg, unreg, NULL };\ static struct tracepoint * const __tracepoint_ptr_##name __used \ __attribute__((section("__tracepoints_ptrs"))) = \ &__tracepoint_##name;
因此, DEFINE_TRACE
的作用就是:
-
在
__tracepoints_strings
section中定义了字符串数组变量__tpstrtab_##name
,其值为name。 -
在
__tracepoints
section中定义了结构体tracepoint变量__tracepoint_##name
。
以上解释了trace point相关的数据结构、宏、变量等,下面以一个实例来展开说明。
实例
这里以软中断被调用时的入口trace event为例,其定义如下:
DEFINE_EVENT(softirq, softirq_entry, TP_PROTO(unsigned int vec_nr), TP_ARGS(vec_nr) );
这里的宏 DEFINE_EVENT
不过是前面 DECLARE_TRACE
宏的一个包装:
#define DEFINE_EVENT(template, name, proto, args) \ DECLARE_TRACE(name, PARAMS(proto), PARAMS(args))
从上面的讨论可以知道,这里声明了一个名为 __tracepoint_softirq_entry
的 tracepoint
类型结构体。而根据我们前面对宏的展开分析, trace_##name
也就是这里展开的 trace_softirq_entry
是对这个trace event进行调用的入口,果然在 __do_softirq
函数中看到了它的身影:
// kernel/softirq.c asmlinkage __visible void __softirq_entry __do_softirq(void) { // ... trace_softirq_entry(vec_nr); // ... }
systemtap相关
这里需要注意的另一个问题是,每个systemtap中的kernel.trace当时可以知道的参数,除了trace event本身的参数之外,还有当时所在嵌入函数内部的变量,比如这里的 softirq_entry
这个probe,在systemtap对应的tapset中是这样的:
probe softirq.entry = kernel.trace("irq_softirq_entry") !, kernel.trace("softirq_entry") ? { # kernels < 2.6.37 h = @choose_defined($h, 0) vec = @choose_defined($vec, 0) action = (@defined($h) ? @cast($h,"softirq_action","kernel<linux/interrupt.h>")->action : 0) # kernels >= 2.6.37 vec_nr = @choose_defined($vec_nr, 0) }
这里可以的变量 h
类型是在内核中的头文件 <linux/interrupt.h>
中定义的 softirq_action
,因为这个变量就是在上面的函数 __do_softirq
中定义的:
// kernel/softirq.c asmlinkage __visible void __softirq_entry __do_softirq(void) { // ... struct softirq_action *h; // ... trace_softirq_entry(vec_nr); // ... }
所以,要看一个systemtap的kernel.trace能引用哪些变量,除了看其自身,还包括看其所嵌入函数的上下文中的变量,最好直接到对应的tapset的说明,因为-L只能打印出这个kernel.trace自身定义的变量:
$ sudo stap -L 'kernel.trace("softirq_entry")' kernel.trace("irq:softirq_entry") $vec_nr:unsigned int
总结:
-
内核中的trace事件以
trace_*
来命名。 -
看到systemtap中的
'kernel.trace("xx")'
,其对应的内核代码可以使用trace_xx
来搜索,通过阅读这个trace事件所嵌入的代码也可以或者这个probe事件能打印的变量。
参考资料
- tracepoint.txt:内核自带文档,路径”Documentation/trace/tracepoint.txt”
- ftrace 中 eventtracing 的实现原理
- man tapset:可以查看当前systemtap自带定义的tapset详细定义。
- SystemTap Tapset Reference Manual :tapset手册的线上版本。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Systemtap 中内核 trace 事件的实现
- eBPF内核探测将任意系统调用转换成事件
- [译] eBPF内核探测:如何将任意系统调用转换成事件
- 内核必须懂(六): 使用kgdb调试内核
- 详解JS事件 - 事件模型/事件流/事件代理/事件对象/自定义事件
- Linux内核如何替换内核函数并调用原始函数
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
编程人生(上卷)
[美] Peter Seibel / 图灵社区 / 人民邮电出版社 / 2014-12 / 39.00元
这是一本访谈笔录,记录了当今最具个人魅力的15 位软件先驱的编程生涯。包括Donald Knuth、Jamie Zawinski、Joshua Bloch、Ken Thompson等在内的业界传奇人物,为我们讲述了他们是怎么学习编程的,在编程过程中发现了什么以及他们对未来的看法,并对诸如应该如何设计软件等长久以来一直困扰很多程序员的问题谈了自己的观点。中文版分为上下卷,上卷介绍8位大师。一起来看看 《编程人生(上卷)》 这本书的介绍吧!