zl程序教程

您现在的位置是:首页 >  系统

当前栏目

Linux 调试之 TRACE_EVENT(三)

Linux调试 Event Trace
2023-09-11 14:18:25 时间

前言

内核内的跟踪点有助于分析内核的性能。可以跟踪和检查关键信息流,以便调试延迟问题,或者简单地找出更好的方法来调整系统。核心内核跟踪点(如调度器和中断跟踪点)让用户看到事件在内核内发生的时间和方式。模块开发人员还可以利用跟踪点;如果用户或客户有问题,开发人员可以让他们启用跟踪点并分析情况。本文将解释如何在核心内核代码之外的模块中添加跟踪点。

Linux 调试之 TRACE_EVENT(一),解释了在核心内核中创建跟踪点的过程。
Linux 调试之 TRACE_EVENT(二),解释了如何使用DECLARE_EVENT_CLASS()和DEFINE_EVENT()合并跟踪点,并介绍了TP_STRUCT__entry的字段宏和TP_printk()的辅助函数。

前面说过内核中的大部分跟踪点都是定义在该目录下:

linux-3.10/include/trace/events/

本文将介绍如何在核心内核之外添加跟踪点(不在linux-3.10/include/trace/events/目录下的 tracepoint点),这些跟踪点可以由模块或特定于体系结构的代码使用。
比如:

// linux-3.10/arch/x86/kernel/vsyscall_trace.h

#undef TRACE_SYSTEM
#define TRACE_SYSTEM vsyscall

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

#include <linux/tracepoint.h>

TRACE_EVENT(emulate_vsyscall,

	    TP_PROTO(int nr),

	    TP_ARGS(nr),

	    TP_STRUCT__entry(__field(int, nr)),

	    TP_fast_assign(
			   __entry->nr = nr;
			   ),

	    TP_printk("nr = %d", __entry->nr)
);

#endif

#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH ../../arch/x86/kernel
#define TRACE_INCLUDE_FILE vsyscall_trace
#include <trace/define_trace.h>
[root@localhost ~]# ls -l /sys/kernel/debug/tracing/events/vsyscall/
emulate_vsyscall/ enable            filter
// linux-3.10/arch/x86/kvm/trace.h

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

#include <linux/tracepoint.h>
#include <asm/vmx.h>
#include <asm/svm.h>
#include <asm/clocksource.h>

#undef TRACE_SYSTEM
#define TRACE_SYSTEM kvm

/*
 * Tracepoint for guest mode entry.
 */
TRACE_EVENT(kvm_entry,
	TP_PROTO(unsigned int vcpu_id),
	TP_ARGS(vcpu_id),

	TP_STRUCT__entry(
		__field(	unsigned int,	vcpu_id		)
	),

	TP_fast_assign(
		__entry->vcpu_id	= vcpu_id;
	),

	TP_printk("vcpu %u", __entry->vcpu_id)
);

/*
 * Tracepoint for hypercall.
 */
TRACE_EVENT(kvm_hypercall,
	TP_PROTO(unsigned long nr, unsigned long a0, unsigned long a1,
		 unsigned long a2, unsigned long a3),
	TP_ARGS(nr, a0, a1, a2, a3),

	TP_STRUCT__entry(
		__field(	unsigned long, 	nr		)
		__field(	unsigned long,	a0		)
		__field(	unsigned long,	a1		)
		__field(	unsigned long,	a2		)
		__field(	unsigned long,	a3		)
	),

	TP_fast_assign(
		__entry->nr		= nr;
		__entry->a0		= a0;
		__entry->a1		= a1;
		__entry->a2		= a2;
		__entry->a3		= a3;
	),

	TP_printk("nr 0x%lx a0 0x%lx a1 0x%lx a2 0x%lx a3 0x%lx",
		 __entry->nr, __entry->a0, __entry->a1,  __entry->a2,
		 __entry->a3)
);

/*
 * Tracepoint for hypercall.
 */
TRACE_EVENT(kvm_hv_hypercall,
	TP_PROTO(__u16 code, bool fast, __u16 rep_cnt, __u16 rep_idx,
		 __u64 ingpa, __u64 outgpa),
	TP_ARGS(code, fast, rep_cnt, rep_idx, ingpa, outgpa),

	TP_STRUCT__entry(
		__field(	__u16,		rep_cnt		)
		__field(	__u16,		rep_idx		)
		__field(	__u64,		ingpa		)
		__field(	__u64,		outgpa		)
		__field(	__u16, 		code		)
		__field(	bool,		fast		)
	),

	TP_fast_assign(
		__entry->rep_cnt	= rep_cnt;
		__entry->rep_idx	= rep_idx;
		__entry->ingpa		= ingpa;
		__entry->outgpa		= outgpa;
		__entry->code		= code;
		__entry->fast		= fast;
	),

	TP_printk("code 0x%x %s cnt 0x%x idx 0x%x in 0x%llx out 0x%llx",
		  __entry->code, __entry->fast ? "fast" : "slow",
		  __entry->rep_cnt, __entry->rep_idx,  __entry->ingpa,
		  __entry->outgpa)
);

......

#endif /* _TRACE_KVM_H */

#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH arch/x86/kvm
#undef TRACE_INCLUDE_FILE
#define TRACE_INCLUDE_FILE trace

/* This part must be outside protection */
#include <trace/define_trace.h>
[root@localhost ~]# ls -l /sys/kernel/debug/tracing/events/kvm/
enable                         kvm_async_pf_ready/            kvm_fast_mmio/                 kvm_ioapic_set_irq/            kvm_pic_set_irq/               kvm_try_async_get_page/
filter                         kvm_avic_incomplete_ipi/       kvm_fpu/                       kvm_mmio/                      kvm_pi_irte_update/            kvm_update_master_clock/
kvm_ack_irq/                   kvm_avic_unaccelerated_access/ kvm_halt_poll_ns/              kvm_msi_set_irq/               kvm_pio/                       kvm_userspace_exit/
kvm_age_page/                  kvm_cpuid/                     kvm_hv_hypercall/              kvm_msr/                       kvm_ple_window/                kvm_vcpu_wakeup/
kvm_apic/                      kvm_cr/                        kvm_hv_timer_state/            kvm_nested_intercepts/         kvm_pml_full/                  kvm_wait_lapic_expire/
kvm_apic_accept_irq/           kvm_emulate_insn/              kvm_hypercall/                 kvm_nested_intr_vmexit/        kvm_pvclock_update/            kvm_write_tsc_offset/
kvm_apic_ipi/                  kvm_enter_smm/                 kvm_inj_exception/             kvm_nested_vmexit/             kvm_pv_eoi/                    vcpu_match_mmio/
kvm_async_pf_completed/        kvm_entry/                     kvm_inj_virq/                  kvm_nested_vmexit_inject/      kvm_set_irq/
kvm_async_pf_doublefault/      kvm_eoi/                       kvm_invlpga/                   kvm_nested_vmrun/              kvm_skinit/
kvm_async_pf_not_present/      kvm_exit/                      kvm_ioapic_delayed_eoi_inj/    kvm_page_fault/                kvm_track_tsc/

一、include/trace/events之外定义跟踪头文件

对于模块或体系结构特定目录中的跟踪点,在全局include/trace/events目录中包含跟踪头文件可能会使其混乱。结果是将核心内核不需要的mips_cpuh或arm_cpuh等文件放入该目录。它最终将类似于旧的include/asm-*/setup。此外,如果跟踪点进入暂存驱动程序,则将暂存头文件放入核心内核代码库将是一个糟糕的设计。

因此对于模块或体系结构特定目录中的跟踪点,不要放在全局include/trace/events目录中,include/trace/events目录中的 tracepoint 对于体系架构都是通用的。

因为 trace header files 的处理方式与其他普通的 c header files 非常不同,所以最好的解决方案是将头文件放置在使用它们的位置。例如,XFS跟踪点位于fs/xfs/xfs_trace.h中的XFS子目录中。如下:

// linux-3.10/fs/xfs/xfs_trace.h

#undef TRACE_SYSTEM
#define TRACE_SYSTEM xfs

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

#include <linux/tracepoint.h>

struct xfs_agf;
struct xfs_alloc_arg;
struct xfs_attr_list_context;
struct xfs_buf_log_item;
struct xfs_da_args;
struct xfs_da_node_entry;
struct xfs_dquot;
struct xfs_log_item;
struct xlog_ticket;
struct xlog;
struct xlog_recover;
struct xlog_recover_item;
struct xfs_buf_log_format;
struct xfs_inode_log_format;
struct xfs_bmbt_irec;

DECLARE_EVENT_CLASS(xfs_attr_list_class,
	TP_PROTO(struct xfs_attr_list_context *ctx),
	TP_ARGS(ctx),
	TP_STRUCT__entry(
		__field(dev_t, dev)
		__field(xfs_ino_t, ino)
		__field(u32, hashval)
		__field(u32, blkno)
		__field(u32, offset)
		__field(void *, alist)
		__field(int, bufsize)
		__field(int, count)
		__field(int, firstu)
		__field(int, dupcnt)
		__field(int, flags)
	),
	TP_fast_assign(
		__entry->dev = VFS_I(ctx->dp)->i_sb->s_dev;
		__entry->ino = ctx->dp->i_ino;
		__entry->hashval = ctx->cursor->hashval;
		__entry->blkno = ctx->cursor->blkno;
		__entry->offset = ctx->cursor->offset;
		__entry->alist = ctx->alist;
		__entry->bufsize = ctx->bufsize;
		__entry->count = ctx->count;
		__entry->firstu = ctx->firstu;
		__entry->flags = ctx->flags;
	),
	TP_printk("dev %d:%d ino 0x%llx cursor h/b/o 0x%x/0x%x/%u dupcnt %u "
		  "alist 0x%p size %u count %u firstu %u flags %d %s",
		  MAJOR(__entry->dev), MINOR(__entry->dev),
		   __entry->ino,
		   __entry->hashval,
		   __entry->blkno,
		   __entry->offset,
		   __entry->dupcnt,
		   __entry->alist,
		   __entry->bufsize,
		   __entry->count,
		   __entry->firstu,
		   __entry->flags,
		   __print_flags(__entry->flags, "|", XFS_ATTR_FLAGS)
	)
)

#define DEFINE_ATTR_LIST_EVENT(name) \
DEFINE_EVENT(xfs_attr_list_class, name, \
	TP_PROTO(struct xfs_attr_list_context *ctx), \
	TP_ARGS(ctx))
DEFINE_ATTR_LIST_EVENT(xfs_attr_list_sf);
DEFINE_ATTR_LIST_EVENT(xfs_attr_list_sf_all);
DEFINE_ATTR_LIST_EVENT(xfs_attr_list_leaf);
DEFINE_ATTR_LIST_EVENT(xfs_attr_list_leaf_end);
DEFINE_ATTR_LIST_EVENT(xfs_attr_list_full);
DEFINE_ATTR_LIST_EVENT(xfs_attr_list_add);
DEFINE_ATTR_LIST_EVENT(xfs_attr_list_wrong_blk);
DEFINE_ATTR_LIST_EVENT(xfs_attr_list_notfound);
DEFINE_ATTR_LIST_EVENT(xfs_attr_leaf_list);
DEFINE_ATTR_LIST_EVENT(xfs_attr_node_list);

......

#endif /* _TRACE_XFS_H */

#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH .
#define TRACE_INCLUDE_FILE xfs_trace
#include <trace/define_trace.h>

注意这里头文件的末尾与/include/trace/events/目录下的头文件末尾不一样:
include/trace/events/sched.h作为示例,文件结尾为:

#endif /* _TRACE_SCHED_H */

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

但是,define_trace.h的一些神奇之处在于,它必须能够包括包含它的跟踪文件(TRACE_HEADER_MULTI_READ的原因)。如第一篇文章所述,跟踪头文件以CPP条件开头:

// linux-3.10/include/trace/events/sched.h

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

第一篇文章解释了包含特定跟踪头的C文件中只有一个文件将在包含跟踪头之前定义CREATE_TRACE_POINTS:

// linux-3.10/kernel/sched/core.c

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

它激活跟踪头文件包含的define_trace.h。define_trace.h文件将再次包含 header ,但将首先定义TRACE_HEADER_MULTI_READ。如cpp条件所示,此定义将允许再次读取 the trace header 的内容。

为了使define_trace.h包含跟踪头文件,它必须能够找到它。要做到这一点,需要对包含跟踪文件的Makefile进行一些更改,并且该文件需要告诉define_trace.h不要在默认位置查找它(include/trace/events)。

要告诉define_trace在哪里找到跟踪头,Makefile必须定义跟踪文件位置的路径。一种方法是扩展CFLAGS以包括以下路径:

 EXTRA_CFLAGS = -I$(src)

但这会影响Makefile构建的所有文件的CFLAGS。如果希望只修改定义了CREATE_TRACE_POINTS的C文件的CFLAGS,则可以使用net/mac80211/Makefile使用的方法:

// linux-3.10/net/mac80211/Makefile

 CFLAGS_driver-trace.o = -I$(src)

driver-trace.c文件包含CREATE_TRACE_POINTS定义和包含driver-trace.h,其中包含mac80211跟踪点的TRACE_EVENT()宏。

// linux-3.10/net/mac80211/trace.h

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

#include <linux/tracepoint.h>
#include <net/mac80211.h>
#include "ieee80211_i.h"

#undef TRACE_SYSTEM
#define TRACE_SYSTEM mac80211

......

#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH .
#undef TRACE_INCLUDE_FILE
#define TRACE_INCLUDE_FILE trace
#include <trace/define_trace.h>
// linux-3.10/net/mac80211/trace.c

#define CREATE_TRACE_POINTS
#include "trace.h"

二、tracepoint 使用演示

为了演示如何将跟踪点添加到模块中,我编写了一个名为sillymod的简单模块,它只创建一个线程,该线程每秒唤醒一次,执行一次printk并记录执行次数。我将查看文件的相关部分,但完整的文件内容也可用: module, Makefile, the module with tracepoint 和 the trace header file.

第一步是创建所需的跟踪点。跟踪头文件的创建方式与第1部分中描述的核心跟踪头文件相同,只是添加了一些内容。标头必须首先定义文件中所有跟踪点所属的系统:

   #undef TRACE_SYSTEM
   #define TRACE_SYSTEM silly

这个模块创建了一个名为 silly 的跟踪系统。然后包括特殊的cpp条件:

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

包括linux/tracepoint.h文件,最后是TRACE_EVENT()宏;本例中的一个:

   #include <linux/tracepoint.h>

   TRACE_EVENT(me_silly,

	TP_PROTO(unsigned long time, unsigned long count),

	TP_ARGS(time, count),

	TP_STRUCT__entry(
		__field(	unsigned long,	time	)
		__field(	unsigned long,	count	)
	),

	TP_fast_assign(
		__entry->time = jiffies;
		__entry->count = count;
	),

	TP_printk("time=%lu count=%lu", __entry->time, __entry->count)
   );

   #endif /* _SILLY_TRACE_H */

以上内容与第1部分中对核心内核跟踪点的描述相同。在#endif之后,事情变得有点不同。在包含 define_trace.h 文件之前,将添加以下内容:

   /* This part must be outside protection */
   #undef TRACE_INCLUDE_PATH
   #define TRACE_INCLUDE_PATH .
   #define TRACE_INCLUDE_FILE silly-trace
   #include <trace/define_trace.h>

TRACE_INCLUDE_PATH告诉define_TRACE.h不要查找跟踪标头的默认位置(INCLUDE/TRACE/events),而是查找include 搜索路径。默认情况下, define_trace.h将包括TRACE_SYSTEM定义的文件。TRACE_INCLUDE_FILE告诉define_trace.h the trace header 称为silly-trace.h。(.h将自动添加到TRACE_INCLUDE_PATH的末尾)。

要将跟踪点添加到模块,模块现在包括跟踪头。在包含跟踪标头之前,还必须定义CREATE_TRACE_POINTS:

   #define CREATE_TRACE_POINTS
   #include "silly-trace.h"

现在可以将跟踪点添加到代码中:

    set_current_state(TASK_INTERRUPTIBLE);
    schedule_timeout(HZ);
    printk("hello! %lu\n", count);
    trace_me_silly(jiffies, count);
    count++;

最后,Makefile必须设置CFLAGS,使includes包含 silly-trace.h 文件所在的本地目录:

  CFLAGS_sillymod-event.o = -I$(src)

如果模块位于内核树中,您可能会认为,在不修改Makefile的情况下,以下功能也可以正常工作:

 #define TRACE_INCLUDE_PATH ../../path/to/trace/header

在TRACE_INCLUDE_PATH 中使用“.”以外的路径名运行包含宏的风险。例如,如果XFS将TRACE_INCLUDE_PATH定义为…/…/fs/XFS/linux-3.10/XFS_TRACE.h, 它会失败。这是因为Linux构建 #defines 将名称Linux定义为空,这将使路径为…/…/fs/xfs/-3.10/xfs_trace.h。

/*  sillymod-event.c */

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

#define CREATE_TRACE_POINTS
#include "silly-trace.h"

static void silly_thread_func(void)
{
	static unsigned long count;

	set_current_state(TASK_INTERRUPTIBLE);
	schedule_timeout(HZ);
	printk("hello! %lu\n", count);
	trace_me_silly(jiffies, count);
	count++;
}

static int silly_thread(void *arg)
{
	while (!kthread_should_stop())
		silly_thread_func();

	return 0;
}

static struct task_struct *silly_tsk;

static int __init silly_init(void)
{
	silly_tsk = kthread_run(silly_thread, NULL, "silly-thread");
	if (IS_ERR(silly_tsk))
		return -1;

	return 0;
}

static void __exit silly_exit(void)
{
	kthread_stop(silly_tsk);
}

module_init(silly_init);
module_exit(silly_exit);

MODULE_AUTHOR("Steven Rostedt");
MODULE_DESCRIPTION("silly-module");
MODULE_LICENSE("GPL");
/* silly-trace.h */

#undef TRACE_SYSTEM
//在/sys/kernel/debug/tracing/events/ 目录下就会多一个 silly 目录,该目录都是和 silly 相关的 tracepoint
#define TRACE_SYSTEM silly

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

#include <linux/tracepoint.h>

///sys/kernel/debug/tracing/events/silly/ 目录下就会多一个 me_silly tracepoint
TRACE_EVENT(me_silly,

	TP_PROTO(unsigned long time, unsigned long count),

	TP_ARGS(time, count),

	TP_STRUCT__entry(
		__field(	unsigned long,	time	)
		__field(	unsigned long,	count	)
	),

	TP_fast_assign(
		__entry->time = jiffies;
		__entry->count = count;
	),

	TP_printk("time=%lu count=%lu", __entry->time, __entry->count)
);

#endif /* _SILLY_TRACE_H */

/* This part must be outside protection */
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH .
#define TRACE_INCLUDE_FILE silly-trace
#include <trace/define_trace.h>
/* Makefile */

INCLUDES = -I. -I$(KDIR)/include
KBUILD_CFLAGS += -g

all: kernel_modules

obj-m	:= sillymod-event.o

KDIR    := /lib/modules/$(shell uname -r)/build
PWD    := $(shell pwd)

CFLAGS_sillymod-event.o = -I$(src)

%.ko: %.o
	$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) $@

kernel_modules:
	$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules


clean:
	$(RM) *.o *~ *.ko *.mod.c .*.o.cmd .*.ko.cmd
	$(RM) -r .tmp_versions
[root@localhost tracepoint]# insmod sillymod-event.ko
[root@localhost ~]# cd /sys/kernel/debug/tracing
[root@localhost tracing]# ls -l events/si
signal/ silly/
[root@localhost tracing]# ls -l events/silly/
enable    filter    me_silly/

我们可以看到有 silly event tracepoint点了。

[root@localhost tracing]# echo 1 > events/silly/me_silly/enable
[root@localhost tracing]# cat trace
# tracer: nop
#
# entries-in-buffer/entries-written: 5/5   #P:4
#
#                              _-----=> irqs-off
#                             / _----=> need-resched
#                            | / _---=> hardirq/softirq
#                            || / _--=> preempt-depth
#                            ||| /     delay
#           TASK-PID   CPU#  ||||    TIMESTAMP  FUNCTION
#              | |       |   ||||       |         |
    silly-thread-30154 [002] .... 308202.105824: me_silly: time=4602867670 count=177
    silly-thread-30154 [003] .... 308203.106010: me_silly: time=4602868670 count=178
    silly-thread-30154 [003] .... 308204.105899: me_silly: time=4602869670 count=179
    silly-thread-30154 [003] .... 308205.105980: me_silly: time=4602870670 count=180
    silly-thread-30154 [003] .... 308206.105974: me_silly: time=4602871670 count=181

一旦define_trace.h文件可以安全地找到跟踪头,就可以创建模块的跟踪点。要了解为什么需要所有这些操作,请查看define_trace.h

三、A look inside the magic of TRACE_EVENT()

对于那些敢于进入C预处理器神秘世界的人来说,可以看看include/trace/ftrace.h。但要注意,你在那里发现的东西可能会让你有点疯狂,或者至少认为编写代码的人有点疯狂(在这种情况下,你可能是对的)。include/trace/define_trace.h文件为TRACE_EVENT()宏做了一些基本的设置,但是为了让跟踪能够利用它,它必须在 define_trace.h 中有一个头文件才能完成真正的工作(就像ftrace和perf一样)。

3.1 TRACE_SYSTEM宏

TRACE_SYSTEM宏用于定义跟踪点用于的系统:

#undef TRACE_SYSTEM
#define TRACE_SYSTEM sched
[root@localhost ~]# ls -l /sys/kernel/debug/tracing/events/sched/
#undef TRACE_SYSTEM
#define TRACE_SYSTEM kmem
[root@localhost ~]# ls -l /sys/kernel/debug/tracing/events/kmem/
#undef TRACE_SYSTEM
#define TRACE_SYSTEM irq
[root@localhost ~]# ls -l /sys/kernel/debug/tracing/events/irq/

3.2 TRACE_INCLUDE_FILE宏

如果文件名不是TRACE_SYSTEM.h,则为TRACE_INCLUDE_FILE。可以定义此宏来告诉define_trace.h要包含的文件。注意,不要使用“.h”,通常是用于模块或特定于体系结构的 tracepoint。

而/include/trace/events/目录下的头文件的文件名都是TRACE_SYSTEM.h。
比如:

// linux-3.10/include/trace/events/sched.h

#undef TRACE_SYSTEM
#define TRACE_SYSTEM sched //TRACE_SYSTEM.h等于头文件名sched.h

而用于模块或特定于体系结构的 tracepoint的头文件,文件名不是TRACE_SYSTEM.h:
#define TRACE_SYSTEM vsyscall 中的 vsyscall 和 vsyscall_trace.h不相等,告诉 define_trace.h 包含 vsyscall_trace.h 文件。

// linux-3.10/arch/x86/kernel/vsyscall_trace.h
#undef TRACE_SYSTEM
#define TRACE_SYSTEM vsyscall

#define TRACE_INCLUDE_FILE vsyscall_trace //不要加.h
#include <trace/define_trace.h>

#define TRACE_SYSTEM kvm 中的 kvm 和 trace.h不相等,告诉 define_trace.h 包含 trace.h 文件

// linux-3.10/arch/x86/kvm/trace.h
#undef TRACE_SYSTEM
#define TRACE_SYSTEM kvm

#define TRACE_INCLUDE_FILE trace //不要加.h

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

#define TRACE_SYSTEM xfs 中的xfs 和xfs_trace.h不相等,告诉 define_trace.h 包含 xfs_trace.h 文件

// linux-3.10/fs/xfs/xfs_trace.h

#undef TRACE_SYSTEM
#define TRACE_SYSTEM xfs

#define TRACE_INCLUDE_FILE xfs_trace //不要加.h
#include <trace/define_trace.h>

3.3 TRACE_INCLUDE_PATH宏

TRACE_INCLUDE_PATH,如果路径不是核心内核 include/trace 。则该宏可以定义要使用的路径。注意,路径是相对于define_trace.h的,而不是包含它的文件。必须使用树外模块的完整路径名。

// linux-3.10/arch/x86/kernel/vsyscall_trace.h

#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH ../../arch/x86/kernel
// linux-3.10/fs/xfs/xfs_trace.h

#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH .
// linux-3.10/arch/x86/kvm/trace.h

#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH arch/x86/kvm

四、cpp tricks and treats

cpp的一个技巧,它可以使用相同的数据将字符串映射到enum:

   #define DOGS { C(JACK_RUSSELL), C(BULL_TERRIER), C(ITALIAN_GREYHOUND) }
   #undef C
   #define C(a) ENUM_##a
   enum dog_enums DOGS;
   #undef C
   #define C(a) #a
   char *dog_strings[] = DOGS;
   char *dog_to_string(enum dog_enums dog)
   {
           return dog_strings[dog];
   }

诀窍是宏DOGS包含一个子宏C(),我们可以重新定义和更改DOGS的行为。这个概念是TRACE_EVENT()宏如何工作的关键。可以重新定义TRACE_EVENT()中的所有子宏,并使用TRACE_EVENT()创建使用相同信息的不同代码。在第一篇文章中描述了创建跟踪点所需的需求。一组数据(在TRACE_EVENT()定义中)必须能够执行多项操作。使用这个cpp技巧,它能够完成这一点。

创建的跟踪点代码要求在头文件中定义DECLARE_TRACE(name,proto,args),在某些C文件中使用DEFINE_TRACE。TRACE_EVENT()现在同时执行这两项任务。在include/linux/tracepoint.h中:

在tracepoint.h中,宏TRACE_EVENT()第一次展开,定义了tracepoint需要的一些函数:

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

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

PARAMS()宏允许proto和args包含逗号,而不会被误认为DECLARE_TRACE()的多个参数。由于 tracepoint.h 必须包含在所有跟踪头中,这使得trace_EVENT()宏完成了跟踪点创建的第一部分。这使得TRACE_EVENT()宏完成了跟踪点创建的第一部分。当C文件在包含跟踪头之前定义CREATE_TRACE_POINTS时,define_trace.h将激活并执行:

在define_trace.h中,宏TRACE_EVENT()第二次的展开:

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

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

然而,这还不够,因为define_trace.h是在使用TRACE_EVENT()宏之后声明的。要使此代码产生影响,必须再次包含TRACE_EVENT()宏。

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

#ifndef TRACE_INCLUDE_FILE
# define TRACE_INCLUDE_FILE TRACE_SYSTEM
# define UNDEF_TRACE_INCLUDE_FILE
#endif

#ifndef TRACE_INCLUDE_PATH
# define __TRACE_INCLUDE(system) <trace/events/system.h>
# define UNDEF_TRACE_INCLUDE_PATH
#else
# define __TRACE_INCLUDE(system) __stringify(TRACE_INCLUDE_PATH/system.h)
#endif

# define TRACE_INCLUDE(system) __TRACE_INCLUDE(system)

/* Let the trace headers be reread */
#define TRACE_HEADER_MULTI_READ

#include TRACE_INCLUDE(TRACE_INCLUDE_FILE)

TRACE_HEADER_MULTI_READ的定义将允许再次读取 the trace header(这就是为什么首先需要它)。TRACE_INCLUDE(TRACE_INCLODE_FILE)是更多的cpp宏技巧,将包括包含它的文件。如前几篇文章所述,如果定义了TRACE_SYSTEM.h或TRACE_INCLUDE_FILE.h,则该宏将使用该文件,如果未定义TRACE_INCLODE_PATH,则将包含include/trace/events/中的文件。当文件再次包含时,TRACE_EVENT()宏将再次处理,但具有新的含义。

上面介绍了如何创建跟踪点。它只创建跟踪点本身,而不将其添加到跟踪程序的基础结构中。对于Ftrace,这是define_trace.h中的ftrace.h文件发挥作用的地方。ftrace.h中的宏创建 tracing/events 中的文件和目录。ftrace.h使用了前面解释的相同技巧,即重新定义TRACE_EVENT()宏内的宏以及重新定义TRACE-EVENT()宏本身。

这里我只是简单介绍了TRACE_EVENT()宏的工作原理,详细请参考:
https://pwl999.blog.csdn.net/article/details/80514271
https://blog.csdn.net/jasonactions/article/details/121098704

五、使用 trace events

如果您将目录更改为debugfs文件系统装载点(通常是/sys/kernel/debug)并查看跟踪/事件,您将看到内核中定义的所有跟踪事件系统(即定义TRACE_SYSTEM的跟踪头)。

[root@localhost ~]# cd /sys/kernel/debug/tracing/events/
block/            exceptions/       hda/              iommu/            mce/              pagemap/          rcu/              sock/             vmscan/
bridge/           filelock/         hda_controller/   irq/              migrate/          power/            regmap/           sunrpc/           vsyscall/
compaction/       filemap/          hda_intel/        irq_vectors/      module/           printk/           rpm/              syscalls/         workqueue/
context_tracking/ fs_dax/           hyperv/           kmem/             mpx/              qdisc/            sched/            task/             writeback/
devlink/          ftrace/           i2c/              kvm/              napi/             random/           scsi/             timer/            xen/
dma_fence/        gpio/             i915/             kvmmmu/           net/              ras/              signal/           ucsi/             xfs/
drm/              gvt/              intel_ish/        libata/           oom/              raw_syscalls/     skb/              udp/              xhci-hcd/

比如调度的 trace.h文件:

// linux-3.10/include/trace/events/sched.h

#undef TRACE_SYSTEM
//在/sys/kernel/debug/tracing/events/ 目录下就会多一个 sched 目录,该目录都是和调度相关的 tracepoint
#define TRACE_SYSTEM sched

the enable files 用于启用 sched_switch 跟踪点:

[root@localhost ~]# ls /sys/kernel/debug/tracing/events/sched/sched_switch/
enable  filter  format  id

the events directory 中的 the enable file 可以启用或禁用系统中的所有事件:

[root@localhost ~]# ls /sys/kernel/debug/tracing/events/enable

系统目录之一(比如:events/sched/)中的 the enable file 可以启用或禁用系统内(比如:sched子系统)的所有事件:

[root@localhost ~]# ls /sys/kernel/debug/tracing/events/sched/enable

特定事件目录(比如:/events/sched/sched_switch/)中的 the enable file 可以启用或禁用该事件(比如:sched_switch tracepoint):

[root@localhost ~]# ls /sys/kernel/debug/tracing/events/sched/sched_switch/enable

注意,通过将“1”写入任何 the enable files ,将启用该目录及其以下的所有事件。写入“0”将禁用该目录及其以下的所有事件。

关于事件的一个很好的特点是,它们也会出现在Ftrace跟踪程序中。如果在跟踪程序运行时启用了事件,则这些事件将显示在跟踪中。启用事件可以使函数跟踪程序更具信息性:

使能调度中所有 tracepoint :

[root@localhost tracing]# echo 1 > events/sched/enable
[root@localhost tracing]# echo function > current_tracer

查看 trace 文件:

[root@localhost tracing]# echo 1 > events/sched/enable
[root@localhost tracing]# echo function > current_tracer
[root@localhost tracing]# cat trace | more
# tracer: function
#
# entries-in-buffer/entries-written: 204256/2731329   #P:4
#
#                              _-----=> irqs-off
#                             / _----=> need-resched
#                            | / _---=> hardirq/softirq
#                            || / _--=> preempt-depth
#                            ||| /     delay
#           TASK-PID   CPU#  ||||    TIMESTAMP  FUNCTION
#              | |       |   ||||       |         |
    xfsaild/dm-0-2229  [002] d... 375729.983779: clear_buddies <-dequeue_entity
    xfsaild/dm-0-2229  [002] d... 375729.983779: account_entity_dequeue <-dequeue_entity
    xfsaild/dm-0-2229  [002] d... 375729.983780: update_min_vruntime <-dequeue_entity
    xfsaild/dm-0-2229  [002] d... 375729.983780: update_cfs_shares <-dequeue_entity
    xfsaild/dm-0-2229  [002] d... 375729.983780: hrtick_update <-dequeue_task_fair
    xfsaild/dm-0-2229  [002] d... 375729.983781: idle_balance <-__schedule
    xfsaild/dm-0-2229  [002] d... 375729.983781: msecs_to_jiffies <-idle_balance
    xfsaild/dm-0-2229  [002] d... 375729.983781: put_prev_task_fair <-__schedule
    xfsaild/dm-0-2229  [002] d... 375729.983781: pick_next_task_fair <-__schedule
    xfsaild/dm-0-2229  [002] d... 375729.983782: pick_next_task_idle <-__schedule
    xfsaild/dm-0-2229  [002] d... 375729.983782: sched_switch: prev_comm=xfsaild/dm-0 prev_pid=2229 prev_prio=120 prev_state=S ==> next_comm=swapper/2 next_pid=0 next_prio=120
          <idle>-0     [002] d... 375729.983783: finish_task_switch <-__schedule
          <idle>-0     [002] .... 375729.983783: _raw_spin_lock_irqsave <-__schedule
          <idle>-0     [002] d... 375729.983783: post_schedule_idle <-__schedule
          <idle>-0     [002] d... 375729.983784: idle_enter_fair <-post_schedule_idle

会打印所有与调度有关的跟踪点信息:

......
sched_stat_runtime: comm=pool pid=7428 runtime=337111 [ns] vruntime=1137470113803 [ns]
......
sched_switch: prev_comm=pool prev_pid=7428 prev_prio=120 prev_state=S ==> next_comm=swapper/2 next_pid=0 next_prio=120
......
sched_stat_runtime: comm=pool pid=7428 runtime=194840 [ns] vruntime=1137470308643 [ns]
......

将事件与函数图跟踪程序中的技巧相结合,我们可以找到中断延迟,以及哪些中断导致了长延迟。

[root@localhost tracing]# cat set_ftrace_filter
#### all functions enabled ####
[root@localhost tracing]# echo do_IRQ > set_ftrace_filter
[root@localhost tracing]# cat set_ftrace_filter
do_IRQ
[root@localhost tracing]# echo 1 > events/irq/irq_handler_entry/enable
[root@localhost tracing]# echo function_graph > current_tracer
[root@localhost tracing]# cat trace
# tracer: function_graph
#
# CPU  DURATION                  FUNCTION CALLS
# |     |   |                     |   |   |   |
 0)   ==========> |
 0)               |  do_IRQ() {
 0)               |  /* irq_handler_entry: irq=125 name=enp1s0 */
 0) + 10.181 us   |  }
 0)   <========== |
 0)   ==========> |
 0)               |  do_IRQ() {
 0)               |  /* irq_handler_entry: irq=125 name=enp1s0 */
 0) + 16.057 us   |  }
 0)   <========== |
 0)   ==========> |
 0)               |  do_IRQ() {
 0)               |  /* irq_handler_entry: irq=125 name=enp1s0 */
 0)   5.765 us    |  }
 0)   <========== |
 0)   ==========> |
 0)               |  do_IRQ() {
 0)               |  /* irq_handler_entry: irq=125 name=enp1s0 */
 0) + 12.851 us   |  }
 0)   <========== |
 0)   ==========> |
 0)               |  do_IRQ() {
 0)               |  /* irq_handler_entry: irq=125 name=enp1s0 */
 0) + 18.991 us   |  }
 0)   <========== |
 0)   ==========> |
 0)               |  do_IRQ() {
 0)               |  /* irq_handler_entry: irq=125 name=enp1s0 */
 0) + 12.146 us   |  }
 0)   <========== |
 0)   ==========> |
 0)               |  do_IRQ() {
 0)               |  /* irq_handler_entry: irq=125 name=enp1s0 */
 0) + 20.180 us   |  }
 0)   <========== |
 0)   ==========> |
 0)               |  do_IRQ() {
 0)               |  /* irq_handler_entry: irq=125 name=enp1s0 */
 0)   7.643 us    |  }
 0)   <========== |
 
 ......
 

将do_IRQ写入set_ftrace_filter使得函数跟踪器只跟踪do_IRQ()函数。然后激活irq_handler_entry跟踪点,并选择function_graph跟踪程序。由于 the function graph tracer 显示函数执行的时间,因此我们可以看到中断运行的时间。但是 the function graph tracer 只显示do_IRQ()函数运行,而不显示它执行的中断。通过启用irq_handler_entry事件,我们现在可以看到哪个中断正在运行。

总结

跟踪点是一个非常强大的工具,但要使它们有用,它们必须是灵活且易于添加的。添加TRACE_EVENT()宏非常简单,而且它们在内核中随处可见。实现跟踪事件的代码使用许多cpp技巧来完成其任务。但是实现的复杂性简化了跟踪点的使用。创建TRACE_EVENT()宏的经验法则是:尽可能简单地使用TRACE_EVENT()宏,即使这会使其实现极其复杂。有时,使用的东西越容易,创建它的复杂性就越大。现在,开发人员不需要知道TRACE_EVENT()宏是如何工作的,只需要知道已经为他们完成了工作,添加TRACE_EVENT()很容易,现在任何开发人员都可以利用它们。

参考资料

Linux 3.10.0

https://lwn.net/Articles/383362/

https://pwl999.blog.csdn.net/article/details/80514271
https://blog.csdn.net/jasonactions/article/details/121098704