使用 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(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(tot);
}

所有 trace_<tracepoint>() 调用都有一个匹配的 trace_<tracepoint>_enabled() 函数定义,如果启用了跟踪点,则返回 true,否则返回 false。 trace_<tracepoint>() 应该始终在 if (trace_<tracepoint>_enabled()) 的块内,以防止在启用跟踪点和看到检查之间出现竞争。

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

注意

方便宏 TRACE_EVENT 提供了一种定义跟踪点的替代方法。 请查看 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(args);
}

在头文件中

DECLARE_TRACEPOINT(foo_bar);

static inline void some_inline_function()
{
        [..]
        if (tracepoint_enabled(foo_bar))
                do_trace_foo_bar_wrapper(args);
        [..]
}