使用 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/379903http://lwn.net/Articles/381064http://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);
        [..]
}