使用 Linux 内核跟踪点¶
- 作者:
Mathieu Desnoyers
本文档介绍了 Linux 内核跟踪点及其使用方法。它提供了如何在内核中插入跟踪点、将探测函数连接到跟踪点以及一些探测函数示例。
跟踪点的目的¶
代码中放置的跟踪点提供了一个钩子,用于调用你在运行时提供的函数(探测)。跟踪点可以处于“开”(连接了探测)或“关”(未附加探测)状态。当跟踪点为“关”时,它没有任何影响,除了增加微小的时间开销(检查分支条件)和空间开销(在被插桩函数末尾为函数调用增加几个字节,并在单独的部分中添加数据结构)。当跟踪点为“开”时,你提供的函数会在每次执行跟踪点时被调用,处于调用者的执行上下文中。当提供的函数执行结束时,它会返回给调用者(从跟踪点位置继续执行)。
你可以将跟踪点放在代码的重要位置。它们是轻量级的钩子,可以传递任意数量的参数,其原型在放置于头文件中的跟踪点声明中描述。
它们可用于跟踪和性能统计。
用法¶
跟踪点需要两个要素
跟踪点定义,放在头文件中。
跟踪点语句,在 C 代码中。
为了使用跟踪点,你应该包含 linux/tracepoint.h。
在 include/trace/events/subsys.h 中
#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 中(必须在此处添加跟踪语句)
#include <trace/events/subsys.h>
#define CREATE_TRACE_POINTS
DEFINE_TRACE(subsys_eventname);
void somefct(void)
{
...
trace_subsys_eventname_tp(arg, task);
...
}
- 其中
subsys_eventname 是你的事件独有的标识符
subsys 是你的子系统名称。
eventname 是要跟踪的事件名称。
TP_PROTO(int firstarg,
struct task_struct*p) 是由此跟踪点调用的函数的原型。TP_ARGS(firstarg, p) 是参数名称,与原型中找到的相同。
如果你在多个源文件中使用该头文件,#define CREATE_TRACE_POINTS 应该只在一个源文件中出现。
将函数(探测)连接到跟踪点,是通过 register_trace_subsys_eventname() 为特定跟踪点提供探测(要调用的函数)来完成的。移除探测是通过 unregister_trace_subsys_eventname() 完成的;它将移除该探测。
必须在模块退出函数结束之前调用 tracepoint_synchronize_unregister(),以确保没有剩余的调用者正在使用该探测。这一点,再加上在探测调用周围禁用了抢占这一事实,确保了探测移除和模块卸载是安全的。
跟踪点机制支持插入同一跟踪点的多个实例,但在整个内核中必须对给定的跟踪点名称进行单一的定义,以确保不会发生类型冲突。跟踪点的名称修饰是通过使用原型来完成的,以确保类型正确。探测类型正确性的验证由编译器在注册点进行。跟踪点可以放在内联函数、内联静态函数、展开的循环以及常规函数中。
这里建议采用“subsys_event”命名方案,作为旨在限制冲突的约定。跟踪点名称对内核是全局的:无论它们是在核心内核镜像中还是在模块中,它们都被认为是相同的。
如果必须在内核模块中使用跟踪点,可以使用 EXPORT_TRACEPOINT_SYMBOL_GPL() 或 EXPORT_TRACEPOINT_SYMBOL() 来导出定义的跟踪点。
如果你需要为跟踪点参数做一些工作,并且该工作仅用于该跟踪点,则该工作可以封装在一个带有以下内容的 if 语句中
if (trace_foo_bar_enabled()) {
int i;
int tot = 0;
for (i = 0; i < count; i++)
tot += calculate_nuggets();
trace_foo_bar_tp(tot);
}
所有 trace_<tracepoint>_tp() 调用都有一个匹配的 trace_<tracepoint>_enabled() 函数定义,如果启用跟踪点则返回 true,否则返回 false. trace_<tracepoint>_tp() 应始终位于 if (trace_<tracepoint>_enabled()) 的代码块内,以防止跟踪点被启用与检查被看到之间发生竞态条件。
使用 trace_<tracepoint>_enabled() 的好处是它使用跟踪点的 static_key 允许用跳转标签实现 if 语句,并避免条件分支。
注意
便利宏 TRACE_EVENT 提供了一种定义跟踪点的替代方法。注意,DECLARE_TRACE(foo) 创建一个函数“trace_foo_tp()”而 TRACE_EVENT(foo) 创建一个函数“trace_foo()”,并且还在 /sys/kernel/tracing/events 目录中将该跟踪点公开为跟踪事件。查看 http://lwn.net/Articles/379903、http://lwn.net/Articles/381064 和 http://lwn.net/Articles/383362 了解包含更多细节的一系列文章。
如果你需要从头文件中调用跟踪点,不建议直接调用或使用 trace_<tracepoint>_enabled() 函数调用,因为如果从设置了 CREATE_TRACE_POINTS 的文件中包含该头文件,头文件中的跟踪点可能会产生副作用,而且 trace_<tracepoint>() 作为内联函数并不是那么小,如果被其他内联函数使用可能会使内核臃肿。相反,请包含 tracepoint-defs.h 并使用 tracepoint_enabled()。
在 C 文件中
void do_trace_foo_bar_wrapper(args)
{
trace_foo_bar_tp(args); // for tracepoints created via DECLARE_TRACE
// or
trace_foo_bar(args); // for tracepoints created via TRACE_EVENT
}
在头文件中
DECLARE_TRACEPOINT(foo_bar);
static inline void some_inline_function()
{
[..]
if (tracepoint_enabled(foo_bar))
do_trace_foo_bar_wrapper(args);
[..]
}