Linux tracepoint 简介

前言

跟踪点是放置在内核代码中较重要位置的硬编码检测点。例如,在系统调用、调度程序事件、文件系统操作和磁盘I/O的开始和结束处都有跟踪点。跟踪点基础设施由Mathieu Desnoyers开发,并于2009年在Linux 2.6.32版本中首次提供。跟踪点是一个稳定的API,数量有限。

跟踪点是性能分析的重要资源,因为它们为高级跟踪工具提供了强大的支持,超越了摘要统计,提供了对内核行为的深入了解。虽然基于功能的跟踪可以提供类似的功能(例如,kprobes),但只有跟踪点提供稳定的接口,允许开发健壮的工具。

一、跟踪点的目的

tracepoint是内核预先定义的静态探测点,是一种静态插桩方法,可以用于挂载钩子(hook)函数来做trace,放置在代码中的跟踪点提供了一个钩子(hook)来调用您可以在运行时提供的函数(probe,可以是多个probe,probe函数原型要与跟踪点函数原型一致)。跟踪点可以是“on”(一个 probe连接到跟踪点)或“off”(没有 probe 附加到跟踪点上)。当跟踪点“关闭”时,除了添加一个微小的时间惩罚(检查分支的条件)和空间惩罚(在插入指令的函数末尾为函数调用添加几个字节,并在单独的部分中添加一个数据结构)之外,它没有任何其它影响。当跟踪点为“on”时,每次在调用方的执行上下文中执行跟踪点时都会调用您提供的函数。当提供的函数结束其执行时,它返回到调用者(从 tracepoint site 继续)。

可以在代码中的重要位置放置跟踪点。它们是轻量级钩子,可以传递任意数量的参数,原型在放置在头文件中的跟踪点声明中描述。

当系统执行到 tracepoint 点时,会执行 tracepoint 上的我们注册的 probe 函数(可以注册多个 probe 函数),类似于printk,输出当前 tracepoint 上下文的环境信息,只是不是输出到终端,而是 ring buffer,这样我们在用户层通过debugfs(tracefs)接口就可以读取 ring buffer 中的信息,了解到内核中当前运行的状态。

tracepoints可以用于跟踪和性能计数。跟踪点表示内核中使用的一些重要事件,构建系统内正在发生的事情的“全貌”。跟踪点是内核源代码中的一个标记,当启用它时,可以用来在标记所在的点处挂钩到正在运行的内核。它们可由许多工具用于内核调试和性能问题诊断。

二、跟踪点的使用

跟踪点需要两个元素:

头文件中存放跟踪点语句(为了使用跟踪点,应该包括linux/tracepoint.h。)。
In 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>

//声明跟踪点:subsys_eventname
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>

c文件中跟踪点的声明。
In subsys/file.c (其中必须添加跟踪语句):

#include <trace/events/subsys.h>

#define CREATE_TRACE_POINTS
//定义跟踪点:subsys_eventname
DEFINE_TRACE(subsys_eventname);

void somefct(void)
{
        ...
        trace_subsys_eventname(arg, task);
        ...
}

(1)
subsys_eventname是事件的唯一标识符:
subsys是子系统的名称。
eventname是要跟踪的事件的名称。
会在 tracing/event/目录下产生: tracing/event/subsys/subsys_eventname/ 这样一个跟踪点目录。

(2)
TP_PROTO(int firstarg,struct task_struct*p)是此跟踪点调用的函数的原型。
TP_ARGS(firstarg,p)是与原型中相同的参数名称。

(3)
如果在多个源文件中使用 the header ,#define CREATE_TRACE_POINTS应该只出现在一个源文件中。

通过register_trace_subsys_eventname()为特定跟踪点提供 probe(要调用的函数),可以将函数(probe)连接到跟踪点。通过unregister_trace_subsys_eventname()删除probe;它将移除probe。必须在模块退出函数结束之前调用tracepoint_synchronize_unregister(),以确保没有使用 probe 的调用者。这一点,以及在探测调用周围禁用抢占的事实,确保探测移除和模块卸载是安全的。

跟踪点机制支持插入同一跟踪点的多个实例,但必须在所有内核上对给定的跟踪点名称进行单个定义,以确保不会发生类型冲突。使用原型对跟踪点进行名称修改,以确保类型正确。编译器在注册站点验证探针类型的正确性。跟踪点可以放在内联函数、内联静态函数、展开循环以及常规函数中。

这里建议使用命名方案“subsys_event”作为限制冲突的约定。跟踪点名称对内核是全局的:无论是在核心内核映像中还是在模块中,它们都被认为是相同的。

如果必须在内核模块中使用跟踪点,则可以使用EXPORT_TRACEPOINT_SYMBOL_GPL()或EXPORT_TRACEPOINT_SYMBOL()导出定义的跟踪点。

#define EXPORT_TRACEPOINT_SYMBOL_GPL(name)				\
	EXPORT_SYMBOL_GPL(__tracepoint_##name)
#define EXPORT_TRACEPOINT_SYMBOL(name)					\
	EXPORT_SYMBOL(__tracepoint_##name)

如果您需要为跟踪点参数做一些工作,并且该工作仅用于跟踪点,则可以使用以下语句将该工作封装在 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_trackpoint_enabled())的块内,以防止启用的跟踪点和看到的检查之间的竞争。

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

如果需要从头文件中调用跟踪点,建议不要直接调用跟踪点或使用trace_tracepoint_enabled()函数调用,因为如果头文件中包含的头文件设置了CREATE_TRACE_POINTS ,则头文件中的跟踪点可能会产生副作用,此外,trace_tracepoint()在内联函数中并没有那么小,如果被其他内联函数使用,可能会导致内核膨胀。取而代之的是,包括tracepoint-defs.h并使用tracepoint_Enable()。

In a C file:

void do_trace_foo_bar_wrapper(args)
{
        trace_foo_bar(args);
}

In the header file:

DECLARE_TRACEPOINT(foo_bar);

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

三、DECLARE_TRACE

DECLARE_TRACE宏主要用来声明 struct tracepoint 结构体 ,定义register_trace_##name函数,给 tracepoint 点注册 probe 函数。
(struct tracepoint 结构体 在 DEFINE_TRACE宏中定义)

(1)
DECLARE_TRACE()传递“proto”作为跟踪点原型,传递“void*__data,proto”为回调原型。

#define DECLARE_TRACE(name, proto, args)				\
		__DECLARE_TRACE(name, PARAMS(proto), PARAMS(args), 1,	\
				PARAMS(void *__data, proto),		\
				PARAMS(__data, args))

(2)

/*
 * Make sure the alignment of the structure in the __tracepoints section will
 * not add unwanted padding between the beginning of the section and the
 * structure. Force alignment to the same alignment as the section start.
 */
#define __DECLARE_TRACE(name, proto, args, cond, data_proto, data_args) \
	//声明 struct tracepoint 结构体
	extern struct tracepoint __tracepoint_##name;			\
	static inline void trace_##name(proto)				\
	{								\
		//判断tracepoint是否使能
		if (static_key_false(&__tracepoint_##name.key))		\
			__DO_TRACE(&__tracepoint_##name,		\
				TP_PROTO(data_proto),			\
				TP_ARGS(data_args),			\
				TP_CONDITION(cond),,);			\
	}								\
	__DECLARE_TRACE_RCU(name, PARAMS(proto), PARAMS(args),		\
		PARAMS(cond), PARAMS(data_proto), PARAMS(data_args))	\
		
	//通过register_trace_##name()为跟踪点提供 probe
	static inline int						\
	register_trace_##name(void (*probe)(data_proto), void *data)	\
	{								\
		return tracepoint_probe_register(#name, (void *)probe,	\
						 data);			\
	}								\
	
	//通过unregister_trace_##name()删除probe,移除probe
	static inline int						\
	unregister_trace_##name(void (*probe)(data_proto), void *data)	\
	{								\
		return tracepoint_probe_unregister(#name, (void *)probe, \
						   data);		\
	}								\
	static inline void						\
	check_trace_callback_type_##name(void (*cb)(data_proto))	\
	{								\
	}	
// linux-3.10/kernel/tracepoint.c

/**
 * tracepoint_probe_register -  Connect a probe to a tracepoint
 * @name: tracepoint name
 * @probe: probe handler
 *
 * Returns 0 if ok, error value on error.
 * The probe address must at least be aligned on the architecture pointer size.
 */
int tracepoint_probe_register(const char *name, void *probe, void *data)
{
	struct tracepoint_func *old;

	mutex_lock(&tracepoints_mutex);
	old = tracepoint_add_probe(name, probe, data);
	if (IS_ERR(old)) {
		mutex_unlock(&tracepoints_mutex);
		return PTR_ERR(old);
	}
	tracepoint_update_probes();		/* may update entry */
	mutex_unlock(&tracepoints_mutex);
	release_probes(old);
	return 0;
}
EXPORT_SYMBOL_GPL(tracepoint_probe_register);

/**
 * tracepoint_probe_unregister -  Disconnect a probe from a tracepoint
 * @name: tracepoint name
 * @probe: probe function pointer
 *
 * We do not need to call a synchronize_sched to make sure the probes have
 * finished running before doing a module unload, because the module unload
 * itself uses stop_machine(), which insures that every preempt disabled section
 * have finished.
 */
int tracepoint_probe_unregister(const char *name, void *probe, void *data)
{
	struct tracepoint_func *old;

	mutex_lock(&tracepoints_mutex);
	old = tracepoint_remove_probe(name, probe, data);
	if (IS_ERR(old)) {
		mutex_unlock(&tracepoints_mutex);
		return PTR_ERR(old);
	}
	tracepoint_update_probes();		/* may update entry */
	mutex_unlock(&tracepoints_mutex);
	release_probes(old);
	return 0;
}
EXPORT_SYMBOL_GPL(tracepoint_probe_unregister);
struct tracepoint_func {
	void *func;
	void *data;
};

struct tracepoint {
	const char *name;		/* Tracepoint name */
	struct static_key key;  
	void (*regfunc)(void);
	void (*unregfunc)(void);
	struct tracepoint_func __rcu *funcs;
};

每种Tracepoint有一个name、一个key开关、一系列桩函数。

key(static_key来允许使用跳转标签实现if语句,并避免条件分支):tracepoint是否使能的开关trace_tracepoint_enabled(),如果回调函数数组为空则key为disable,如果回调函数数组中有函数指针则key为enable。

regfunc:注册回调函数时用到的hook函数(注意:不是用来注册回调函数的,注册回调函数是register_trace_##name)。

unregfunc:注销回调函数时的的hook函数(注意:不是用来注销回调函数的,注销回调函数是unregister_trace_##name)。

/*
 * Sets the probe callback corresponding to one tracepoint.
 */
static void set_tracepoint(struct tracepoint_entry **entry,
	struct tracepoint *elem, int active)
{
	WARN_ON(strcmp((*entry)->name, elem->name) != 0);

	if (elem->regfunc && !static_key_enabled(&elem->key) && active)
		elem->regfunc();
	else if (elem->unregfunc && static_key_enabled(&elem->key) && !active)
		elem->unregfunc();

	/*
	 * rcu_assign_pointer has a smp_wmb() which makes sure that the new
	 * probe callbacks array is consistent before setting a pointer to it.
	 * This array is referenced by __DO_TRACE from
	 * include/linux/tracepoints.h. A matching smp_read_barrier_depends()
	 * is used.
	 */
	rcu_assign_pointer(elem->funcs, (*entry)->funcs);
	if (active && !static_key_enabled(&elem->key))
		static_key_slow_inc(&elem->key);
	else if (!active && static_key_enabled(&elem->key))
		static_key_slow_dec(&elem->key);
}

/*
 * Disable a tracepoint and its probe callback.
 * Note: only waiting an RCU period after setting elem->call to the empty
 * function insures that the original callback is not used anymore. This insured
 * by preempt_disable around the call site.
 */
static void disable_tracepoint(struct tracepoint *elem)
{
	if (elem->unregfunc && static_key_enabled(&elem->key))
		elem->unregfunc();

	if (static_key_enabled(&elem->key))
		static_key_slow_dec(&elem->key);
	rcu_assign_pointer(elem->funcs, NULL);
}

funcs:回调函数数组,tracepoint的作用就是在桩函数被命中时,依次调用回调函数数组中的 probe 函数。
在这里插入图片描述

(3)

#ifdef CONFIG_TRACEPOINTS

/*
 * it_func[0] is never NULL because there is at least one element in the array
 * when the array itself is non NULL.
 *
 * Note, the proto and args passed in includes "__data" as the first parameter.
 * The reason for this is to handle the "void" prototype. If a tracepoint
 * has a "void" prototype, then it is invalid to declare a function
 * as "(void *, void)". The DECLARE_TRACE_NOARGS() will pass in just
 * "void *data", where as the DECLARE_TRACE() will pass in "void *data, proto".
 */
#define __DO_TRACE(tp, proto, args, cond, prercu, postrcu)		\
	do {								\
		struct tracepoint_func *it_func_ptr;			\
		void *it_func;						\
		void *__data;						\
									\
		if (!(cond))						\
			return;						\
		prercu;							\
		rcu_read_lock_sched_notrace();				\
		it_func_ptr = rcu_dereference_sched((tp)->funcs);	\
		if (it_func_ptr) {					\
			do {						\
				it_func = (it_func_ptr)->func;		\
				__data = (it_func_ptr)->data;		\
				((void(*)(proto))(it_func))(args);	\
			} while ((++it_func_ptr)->func);		\
		}							\
		rcu_read_unlock_sched_notrace();			\
		postrcu;						\
	} while (0)

__DO_TRACE宏是用来依次执行 tracepoint 上注册的 probe 函数。
tracepoint提供了统一的框架,用void *指向任何函数,所以各个tracepoint取出桩函数指针后,需要转换成自己的函数指针类型,TP_PROTO(data_proto)传递函数指针类型用于转换。
如果跟踪点使能,在 while 循环中,依次执行回调函数数组 struct tracepoint_func __rcu *funcs 的 回调函数 func ;即依次执行 tracepoint 上的注册的 probe 函数。

四、sched_switch例程

以 tracepoint :TRACE_EVENT(sched_switch)为例,看一下内核中 sched_switch tracepoint 的 probe函数:

/*
 * Tracepoint for task switches, performed by the scheduler:
 */
TRACE_EVENT(sched_switch,

	TP_PROTO(struct task_struct *prev,
		 struct task_struct *next),

	TP_ARGS(prev, next),

	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			)
	),

	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	= __trace_sched_switch_state(prev);
		memcpy(__entry->prev_comm, prev->comm, TASK_COMM_LEN);
		__entry->next_pid	= next->pid;
		__entry->next_prio	= next->prio;
	),

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

register_trace_sched_switch函数将probe函数注册到 sched_switch tracepoint 上。
probe函数要与sched_switch tracepoint的函数原型一致,即:

	TP_PROTO(struct task_struct *prev,
		 struct task_struct *next),

	TP_ARGS(prev, next),

(1)
将probe函数 ftrace_graph_probe_sched_switch 与 tracepoint:sched_switch 相关联:

// linux-3.10/kernel/trace/ftrace.c

#ifdef CONFIG_FUNCTION_GRAPH_TRACER

static void
ftrace_graph_probe_sched_switch(void *ignore,
			struct task_struct *prev, struct task_struct *next)
{
	unsigned long long timestamp;
	int index;

	/*
	 * Does the user want to count the time a function was asleep.
	 * If so, do not update the time stamps.
	 */
	if (trace_flags & TRACE_ITER_SLEEP_TIME)
		return;

	timestamp = trace_clock_local();

	prev->ftrace_timestamp = timestamp;

	/* only process tasks that we timestamped */
	if (!next->ftrace_timestamp)
		return;

	/*
	 * Update all the counters in next to make up for the
	 * time next was sleeping.
	 */
	timestamp -= next->ftrace_timestamp;

	for (index = next->curr_ret_stack; index >= 0; index--)
		next->ret_stack[index].calltime += timestamp;
}

static int start_graph_tracing(void)
{
	......
	register_trace_sched_switch(ftrace_graph_probe_sched_switch, NULL)
	......
}

(2)
将probe函数 probe_sched_switch与 tracepoint:sched_switch 相关联:

// linux-3.10/kernel/trace/trace_sched_switch.c

static void
probe_sched_switch(void *ignore, struct task_struct *prev, struct task_struct *next)
{
	struct trace_array_cpu *data;
	unsigned long flags;
	int cpu;
	int pc;

	if (unlikely(!sched_ref))
		return;

	tracing_record_cmdline(prev);
	tracing_record_cmdline(next);

	if (!tracer_enabled || sched_stopped)
		return;

	pc = preempt_count();
	local_irq_save(flags);
	cpu = raw_smp_processor_id();
	data = per_cpu_ptr(ctx_trace->trace_buffer.data, cpu);

	if (likely(!atomic_read(&data->disabled)))
		tracing_sched_switch_trace(ctx_trace, prev, next, flags, pc);

	local_irq_restore(flags);
}

static int tracing_sched_register(void)
{
	......
	ret = register_trace_sched_switch(probe_sched_switch, NULL);
	......
}

五、TRACE_EVENT

我在这三篇文章中描述了 TRACE_EVENT :
Linux 调试之 TRACE_EVENT(一)
Linux 调试之 TRACE_EVENT(二)
Linux 调试之 TRACE_EVENT(三)

(1)
注意这里 TRACE_EVENT 宏展开是 DECLARE_TRACE 宏, DECLARE_TRACE 宏声明 struct tracepoint _tracepoint##name ,注册 tracepoint 的probe 函数,该宏在前面已经说明。

// linux-3.10/include/linux/tracepoint.h

#define TRACE_EVENT(name, proto, args, struct, assign, print)	\
	DECLARE_TRACE(name, PARAMS(proto), PARAMS(args))

(2)
注意这里 TRACE_EVENT 宏展开是 DEFINE_TRACE 宏,DEFINE_TRACE的关键是定义了struct tracepoint __tracepoint_name变量,即定义tracepoint。

初始化static key数据结构为STATIC_KEY_INIT_FALSE:

// linux-3.10/include/linux/jump_label.h

#if defined(CC_HAVE_ASM_GOTO) && defined(CONFIG_JUMP_LABEL)

struct static_key {
	atomic_t enabled;
/* Set lsb bit to 1 if branch is default true, 0 ot */
	struct jump_entry *entries;
#ifdef CONFIG_MODULES
	struct static_key_mod *next;
#endif
};

#define STATIC_KEY_INIT_FALSE ((struct static_key) \
	{ .enabled = ATOMIC_INIT(0), .entries = (void *)0 })
// linux-3.10/include/linux/tracepoint.h

/*
 * We have no guarantee that gcc and the linker won't up-align the tracepoint
 * structures, so we create an array of pointers that will be used for iteration
 * on the tracepoints.
 */
#define DEFINE_TRACE_FN(name, reg, unreg)				 \
	static const char __tpstrtab_##name[]				 \
	__attribute__((section("__tracepoints_strings"))) = #name;	 \
	
	//定义struct tracepoint 结构体 和 初始化struct static_key(STATIC_KEY_INIT_FALSE)结构体
	struct tracepoint __tracepoint_##name				 \
	__attribute__((section("__tracepoints"))) =			 \
		{ __tpstrtab_##name, STATIC_KEY_INIT_FALSE, reg, unreg, NULL };\
	static struct tracepoint * const __tracepoint_ptr_##name __used	 \
	__attribute__((section("__tracepoints_ptrs"))) =		 \
		&__tracepoint_##name;

#define DEFINE_TRACE(name)						\
	DEFINE_TRACE_FN(name, NULL, NULL);

// linux-3.10/include/trace/define_trace.h

#undef TRACE_EVENT
#define TRACE_EVENT(name, proto, args, tstruct, assign, print)	\
	DEFINE_TRACE(name)

定义 tracepoint 执行时对应的probe函数(可以是多个probe函数,可以在probe函数中定义自己的行为),将DEFINE_TRACE定义的 struct tracepoint _tracepoint##name 变量与probe回调进行绑定,这样在执行到 tracepoint 时会触发probe函数。

[root@localhost ]# readelf -S xfs.ko
There are 48 section headers, starting at offset 0x181bf0:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  ......
  [17] __tracepoints_ptr PROGBITS         0000000000000000  000f7f98
       0000000000000bc0  0000000000000000   A       0     0     8
  [18] .rela__tracepoint RELA             0000000000000000  000f8b58
       0000000000002340  0000000000000018   I      44    17     8
  [19] __tracepoints_str PROGBITS         0000000000000000  000faea0
  ......
  [37] __tracepoints     PROGBITS         0000000000000000  00137520
       0000000000005df8  0000000000000000  WA       0     0     32
  [38] .rela__tracepoint RELA             0000000000000000  0013d318
       0000000000002340  0000000000000018   I      44    37     8
  ......
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)

DECLARE_TRACE 宏 和 DEFINE_TRACE 宏 不一样。

从 TRACE_EVENT 的宏定义可以看到,TRACE_EVENT使用的是 tracepoint 机制。需要静态的定义桩函数,并且在插桩位置显式的调用。

宏TRACE_EVENT提供了定义跟踪点的另一种方法。trace event可以使用 tracepoint 机制,内核其它机制也可以使用 tracepoint ,只是kernel的绝大部分tracepoint都是trace event在使用,trace event也必须向tracepoint注册自己的回调函数,这些回调函数的作用就是在函数被命中时往ring buffer中写入trace信息。内核提前帮我们实现了钩子(hook)函数并挂到tracepoint上,当使能一个 trace event 时,它会输出内容到ftrace ringbuffer中,这样就可以获取到内核运行信息了。

六、跟踪点的开销

当跟踪点被激活时,它们会为每个事件增加少量的CPU开销。当跟踪点“关闭”时,除了添加一个微小的时间惩罚(检查分支的条件)和空间惩罚(在插入指令的函数末尾为函数调用添加几个字节,并在单独的部分中添加一个数据结构)之外,它没有任何其它影响。
禁用的跟踪点变成了少量指令:对于x86_64,它是一个5字节的无操作(NOP)指令。在函数的末尾还添加了一个跟踪点处理程序,这会稍微增加其文本大小。虽然这些开销非常小,但在向内核添加跟踪点时,应该分析和理解它们。

跟踪点往往不是单独使用,跟踪点只是内核中的数据源,还有内核中的跟踪框架和用户态的前端工具也会有一定的开销:
在这里插入图片描述
数据源:内核中提供数据的来源(也有用户态的数据源,比如:uprobe)
Tracing 框架:负责对接数据源,采集解析发送数据,并对用户态提供接口
前端工具/库: 对接Tracing框架,直接与用户交互,负责采集配置和数据分析

跟踪工具(比如:trace-cmd,perf等等)可以增加 post-process events 的CPU开销,以及记录事件的文件系统(debugfs或者tracfs)开销。开销是否高到足以干扰生产应用程序取决于事件的速率和CPU的数量,这是使用跟踪点时需要考虑的问题。

在当今的典型系统(4到128个CPU)上,我发现低于每秒10000个的事件速率的开销可以忽略不计,只有超过100000个的开销才开始变得可测量。作为事件示例,您可能会发现磁盘事件通常小于每秒10000个,但调度程序事件可能远远超过每秒100000个,因此跟踪成本可能很高。

在一些特定的系统中,发现最小的跟踪点开销是96纳秒的CPU时间。在2018年Linux 4.7中添加了一种名为原始跟踪点的新类型的跟踪点,它避免了创建稳定的跟踪点参数的成本,从而减少了这一开销。

参考资料

Linux 3.10.0

Systems.Performance.Enterprise.and.the.Cloud.2nd.Edition

https://static.lwn.net/kerneldoc/trace/tracepoints.html
https://pwl999.blog.csdn.net/article/details/80514271
https://blog.csdn.net/jasonactions/article/details/123470620
https://blog.csdn.net/Rong_Toa/article/details/116602224
https://zhuanlan.zhihu.com/p/547477490

  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值