使用 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/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);
        [..]
}