一、简介

   在Linux的整个历史中,人们一直希望在内核中添加静态跟踪点(在内核中的特定位置记录数据以便稍后检索的函数)。由于担心跟踪点会牺牲性能,这些努力并不是很成功。与Ftrace函数跟踪程序不同,跟踪点可以记录的不仅仅是所输入的函数。跟踪点可以记录函数的局部变量。随着时间的推移,已经尝试了各种添加跟踪点的策略,并取得了不同的成功,TRACE_EVENT()宏是添加内核跟踪点的最新方法。

   

1. TRACE_EVENT() 宏的剖析

自动跟踪点具有各种必须满足的要求:

  • 它必须创建一个可以放在内核代码中的跟踪点。
  • 它必须创建一个可以挂接到此跟踪点的回调函数。
  • 回调函数必须能够以最快的方式将传递给它的数据记录到跟踪环缓冲区中。
  • 它必须创建一个函数,可以解析记录到环形缓冲区的数据,并将其转换为跟踪程序可以显示给用户的可读格式。

为此,TRACE_EVENT() 宏分为六个组件,它们对应于宏的参数:

TRACE_EVENT(name, proto, args, struct, assign, print)

  • name - 要创建的跟踪点的名称。
  • prototype - 跟踪点回调函数的原型。
  • args - 与函数原型匹配的参数。
  • struct - 跟踪点可以使用(并不是必须的)这个结构来存储传入到跟踪点的数据。
  • assign - 将数据分配给结构的类似于C的方法。
  • print - 以人类可读的ASCII格式输出结构的方法。

2. 举例

   这里可以找到 sched_switch 跟踪点定义的一个很好的例子。下面将使用该定义来描述 TRACE_EVENT() 宏的每个部分。

   除第一个参数外,所有参数都用另一个宏(TP_PROTO、TP_ARGS、TP_STRUCT_uentry、TP_fast_assign和TP_printk)封装。这些宏在处理过程中提供了更多的控制,还允许在 TRACE_EVENT() 宏中使用逗号。

(1) Name

第一个参数是名称。
TRACE_EVENT(sched_switch,
这是用于调用此跟踪点的名称。实际使用的跟踪点的名称前面有trace_前缀(即trace_sched_switch)。

(2) Prototype

下一个参数是原型。
TP_PROTO(struct rq *rq, struct task_struct *prev, struct task_struct *next),
原型的编写就像您要直接声明跟踪点一样:
trace_sched_switch(struct rq *rq, struct task_struct *prev, struct task_struct *next);
它用作添加到内核代码的跟踪点和回调函数的原型。请记住,跟踪点调用回调函数,就好像在跟踪点的位置调用回调函数一样。

(3) Arguments

第三个参数是原型使用的参数。
TP_ARGS(rq, prev, next),
这可能看起来很奇怪,但它不仅是TRACE_EVENT() 宏所必需的,而且也是下面的跟踪点基础结构所必需的。跟踪点代码在激活时将调用回调函数(可以将多个回调分配给给定的跟踪点)。创建跟踪点的宏必须同时访问原型和参数。下面是跟踪点宏完成此操作所需的示例:

#define TRACE_POINT(name, proto, args) //\
        void trace_##name(proto)            \
        {                                   \
                if (trace_##name##_active)  \
                        callback(args);     \
        }

(4) Structure

第四个参数有点复杂。

TP_STRUCT__entry(
	__array(	char,	prev_comm,	TASK_COMM_LEN	)
	__field(	pid_t,	prev_pid			)
	__field(	int,	prev_prio			)
	__field(	long,	prev_state			)
	__array(	char,	next_comm,	TASK_COMM_LEN	)
	__field(	pid_t,	next_pid			)
	__field(	int,	next_prio			)
),

此参数描述将存储在跟踪器环缓冲区中的数据的结构布局。结构的每个元素都由另一个宏定义。这些宏用于自动创建结构,而不是功能类似的。请注意,宏之间没有任何分隔符(没有逗号或分号)。

sched_switch tracepoint使用的宏有:

  • __field(type, name) - 它定义了一个普通的结构元素,比如int var;其中type是int,name是var。
  • __array(type, name, len) - 这将定义一个数组项,相当于int name[len];其中type是int,数组的名称是array,数组中的项数是len。

还有其他元素宏将在后面的文章中介绍。来自sched_switch tracepoint的定义将生成一个如下所示的结构:

struct {
	char   prev_comm[TASK_COMM_LEN];
	pid_t  prev_pid;
	int    prev_prio;
	long   prev_state;
	char   next_comm[TASK_COMM_LEN];
	pid_t  next_pid;
	int    next_prio;
};

Note that the spacing used in the TP_STRUCT__entry definition breaks the rules outlined by checkpatch.pl. That is done because these macros are not function-like but, instead, are used to define a structure. The spacing follows the rules of structure spacing and not of function spacing, so that the names line up in the structure declaration. Needless to say, checkpatch.pl fails horribly when processing changes to TRACE_EVENT() definitions.

(5) Assignment

第五个参数定义了将参数中的数据保存到环形缓冲区的方式。

TP_fast_assign(
		memcpy(__entry->next_comm, next->comm, TASK_COMM_LEN);
		__entry->prev_pid	= prev->pid;
		__entry->prev_prio	= prev->prio;
		__entry->prev_state	= prev->state;
		memcpy(__entry->prev_comm, prev->comm, TASK_COMM_LEN);
		__entry->next_pid	= next->pid;
		__entry->next_prio	= next->prio;
),

TP_fast_assign() 中的代码是普通的C代码。一个特殊的变量 __entry 表示指向由 TP_STRUCT__entry 项定义的结构类型的指针,并直接指向环缓冲区。TP_fast_assign用于填充 TP_STRUCT_entry 中创建的所有字段。然后,可以使用 TP_PROTO() 和 TP_ARGS() 定义的参数的变量名来将适当的数据分配到该输入结构中。

(6) Print

最后一个参数定义如何使用 printk() 从 TP_STRUCT_uentry 结构中打印字段。

TP_printk("prev_comm=%s prev_pid=%d prev_prio=%d prev_state=%s ==> " \
 		  "next_comm=%s next_pid=%d next_prio=%d",
		__entry->prev_comm, __entry->prev_pid, __entry->prev_prio,
		__entry->prev_state ?
		  __print_flags(__entry->prev_state, "|",
				{ 1, "S"} , { 2, "D" }, { 4, "T" }, { 8, "t" },
				{ 16, "Z" }, { 32, "X" }, { 64, "x" },
				{ 128, "W" }) : "R",
		__entry->next_comm, __entry->next_pid, __entry->next_prio)

变量 __entry 再次被用来引用包含数据的结构体的指针。Format string 就像其他 printf 的 format 一样。__print_flags() 是 TRACE_EVENT() 附带的一组帮助函数的一部分,将在另一篇文章中讨论。不要创建新的跟踪点特定的帮助程序,因为这会混淆用户空间工具,这些工具知道TRACE_EVENT() helper宏,但不知道如何处理为单个跟踪点创建的宏。

二、头文件

TRACE_EVENT()宏不能仅仅放置在期望它能与Ftrace或任何其他跟踪程序一起工作的任何位置。包含TRACE_EVENT()宏的头文件必须遵循特定格式。这些头文件通常位于include/trace/events目录中,但不需要。如果它们不在此目录中,则需要其他配置。

TRACE_EVENT()头中的第一行不是普通的ifdef_TRACE_SCHED_H,而是:

#undef TRACE_SYSTEM
#define TRACE_SYSTEM sched

#if !defined(_TRACE_SCHED_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_SCHED_H

此示例用于调度程序跟踪事件,其他事件头将使用除sched和trace_sched_H之外的其他内容。trace_HEADER_MULTI_READ test允许此文件包含多次;这对于trace_event()宏的处理非常重要。还必须为文件定义跟踪系统,并且跟踪系统必须在if的保护范围之外。TRACE_系统定义文件中TRACE_EVENT()宏所属的组。这也是事件将在debugfs tracing/events目录中分组的目录名。此分组对Ftrace很重要,因为它允许用户按组启用或禁用事件。

然后,该文件包含TRACE_EVENT()宏内容所需的任何头。(例如,include<linux/sched.h>)。tracepoint.h文件是必需的。

#include <linux/tracepoint.h>

现在可以使用trace_EVENT()宏定义所有跟踪事件。请在TRACE_EVENT()宏上方包含描述跟踪点的注释。以include/trace/events/sched.h为例。文件结尾为:

#endif /* _TRACE_SCHED_H */

/* This part must be outside protection */
#include <trace/define_trace.h>

define_trace.h是创建跟踪点的所有魔力所在。关于这个文件如何工作的解释将留给另一篇文章。现在,只需知道这个文件必须包含在跟踪头文件的底部,而不受endif的保护就足够了。

三、使用 tracepoint

如果跟踪点不在任何地方使用,那么定义它就没有意义。要使用跟踪点,必须包含跟踪头,但在包含跟踪之前,还必须定义一个C文件(并且只有一个)的创建跟踪点。这将导致define_trace.h创建生成跟踪事件所需的必要函数。在kernel/sched.c中定义了以下内容:

#define CREATE_TRACE_POINTS
#include <trace/events/sched.h>

如果另一个文件需要使用跟踪文件中定义的跟踪点,则它只需要包含跟踪文件,而不需要定义创建跟踪点。为同一头文件定义多次将在生成时导致链接器错误。例如,在kernel/fork.c中,只包含头文件:

#include <trace/events/sched.h>

最后,在代码中使用跟踪点,就像在TRACE_EVENT()宏中定义的一样:

static inline void
context_switch(struct rq *rq, struct task_struct *prev,
				struct task_struct *next)
{
	struct mm_struct *mm, *oldmm;
	
	prepare_task_switch(rq, prev, next);
	trace_sched_switch(rq, prev, next);
	mm = next->mm;
	oldmm = prev->active_mm;

四、实际使用

1. module_traceevent.h

#undef TRACE_SYSTEM
#define TRACE_SYSTEM xhr_test

#if !defined(_TRACE_TE_TEST_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_TE_TEST_H

#include <linux/tracepoint.h>

TRACE_EVENT(te_test,
    TP_PROTO(int num),
    TP_ARGS(num),
    TP_STRUCT__entry(
        __field(int, output)
        __field(int, count)
    ),
    TP_fast_assign(
        __entry->count++;
        __entry->output = num;
    ),
    TP_printk("count=%d output=%d",
        __entry->count, __entry->output)
);

#endif /* _TRACE_TE_TEST_H */
    
/* This part must be outside protection */
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH .
#define TRACE_INCLUDE_FILE module_traceevent

#include <trace/define_trace.h>

2. module_entry.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kthread.h>

#define CREATE_TRACE_POINTS
#include "module_traceevent.h"

static int xhr_thread(void *arg)
{
    static unsigned long count;

    while (!kthread_should_stop())
    {
        set_current_state(TASK_INTERRUPTIBLE);
        schedule_timeout(HZ);
        trace_te_test(count);
        count++;
    }

    return 0;
}

static struct task_struct *xhr_task;

static __init int __module_init(void)
{
    printk("Hello, %s.\n", __func__);

    xhr_task = kthread_run(xhr_thread, NULL, "xhr-thread");
    if (IS_ERR(xhr_task))
        return -1;

    return 0;
}
static __exit void __module_exit(void)
{
    kthread_stop(xhr_task);
    printk("Hello, %s.\n", __func__);
    return;
}

module_init(__module_init);
module_exit(__module_exit);
MODULE_LICENSE("GPL");

3. Makefile

INCLUDES = -I. -I$(KDIR)/include
MODULE := xhr
obj-m := $(MODULE).o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

CFLAGS_module_entry.o = -I$(src)

.PHONY: all
all:
	$(MAKE) -C $(KDIR) M=$(PWD) modules

.PHONY: clean
clean:
	$(MAKE) -C $(KDIR) M=$(PWD) clean

$(MODULE)-objs += module_entry.o

4. user space

sudo -s
insmod xhr.ko
cd /sys/kernel/debug/tracing/
echo 1 > tracing_on
echo 1 > events/xhr_test/te_test/enable
cat trace

output:
# entries-in-buffer/entries-written: 8/8   #P:8
#
#                              _-----=> irqs-off
#                             / _----=> need-resched
#                            | / _---=> hardirq/softirq
#                            || / _--=> preempt-depth
#                            ||| /     delay
#           TASK-PID   CPU#  ||||    TIMESTAMP  FUNCTION
#              | |       |   ||||       |         |
      xhr-thread-3732  [000] ....  1739.034184: te_test: count=1 output=48
      xhr-thread-3732  [000] ....  1740.058510: te_test: count=1 output=49
      xhr-thread-3732  [000] ....  1741.081934: te_test: count=1 output=50
      xhr-thread-3732  [000] ....  1742.106095: te_test: count=1 output=51

参考链接

Using the TRACE_EVENT() macro (Part 1)
Using the TRACE_EVENT() macro (Part 3)

GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐