linux ftrace追踪一(基本技术结构粗略剖析)


一   文档说明

本文为2.6.32下trace机制(以下简称trace)的调研文档。trace实现的基础为tracepoint机制,存放数据的缓存实现为ring buffer。

阅读代码路径:

samples/tracepoints

kernel/trace

include/trace

二   tracepoint

tracepoint是实现ftrace架构的基础。在内核代码路径samples/tracepoint下对tracepoint有个简单的实例。

struct tracepoint {

       constchar *name;          /* Tracepoint name */

       intstate;                /* State. */

       void(*regfunc)(void);

       void(*unregfunc)(void);

       void**funcs;

} __attribute__((aligned(32)));

2.1 两个宏定义

这两个宏传递的name是一致的。

2.1.1   DEFINE_TRACE(name);

该宏定义如下:

#define DEFINE_TRACE_FN(name, reg,unreg)                            \

       staticconst char __tpstrtab_##name[]                        \

       __attribute__((section("__tracepoints_strings")))= #name; \

       structtracepoint __tracepoint_##name                       \

       __attribute__((section("__tracepoints"),aligned(32))) =     \

              {__tpstrtab_##name, 0, reg, unreg, NULL }

 

#define DEFINE_TRACE(name)                                     \

    DEFINE_TRACE_FN(name, NULL,NULL);

在内核初始化时分配了__tracepoints_strings,__tracepoints这两个section。当利用DEFINE_TRACE定义一个tracepoint后,整个内核将可以看到该tracepoint。

2.1.2   DECLARE_TRACE(name, proto, args)

#define DECLARE_TRACE(name, proto,args)                       \

       externstruct tracepoint __tracepoint_##name;                   \

       staticinline void trace_##name(proto)                       \

       {                                                      \

              if(unlikely(__tracepoint_##name.state))             \

                     __DO_TRACE(&__tracepoint_##name,             \

                            TP_PROTO(proto),TP_ARGS(args));  \

       }                                                      \

       staticinline int register_trace_##name(void (*probe)(proto))     \

       {                                                      \

              returntracepoint_probe_register(#name, (void *)probe);    \

       }                                                      \

       staticinline int unregister_trace_##name(void (*probe)(proto))  \

       {                                                      \

              returntracepoint_probe_unregister(#name, (void *)probe);\

           }

       该宏定义了三个函数:

       trace_##name:放在想抓trace的代码路径中。

       register_trace_##name:注册一个钩子函数,这个钩子函数在trace_##name执行的时候被调用。

       unregister_trace_##name:注销一个钩子函数。

2.2钩子函数是怎样被注册的

当用以上两个宏定义并声明了一个tracepoint之后,在抓取trace之前调用上文的register_trace_##name函数,完成钩子函数的注册。但是如果仅仅是注册了针对某一个tracepoint的钩子函数,目前内核中的处理是更新内核中目前所有的tracepoint。感觉这一点是不合理的。

函数原型:int tracepoint_probe_register(const char *name, void *probe)

tracepoint钩子函数的注册与撤销是通过一个哈希表来维护的。当注册或撤销钩子函数结束后,同步到tracepoint中。至于这个哈希表和tracepoint的联系完全依赖于定义tracepoint时的名字。在哈希表中存放的数据结构:

struct tracepoint_entry {

       structhlist_node hlist;

       void**funcs;

       intrefcount;    /* Number of times armed. 0if disarmed. */

       charname[0];

};


linux ftrace追踪一(基本技术结构粗略剖析)_第1张图片

2.3 钩子函数的执行

在要抓trace的地方,添加trace_##name,这样便可以调用我们注册的钩子函数。

三   ftrace机制

3.1  ftrace架构的初始化

在2.6.32中trace的初始化时在内核启动时完成的。通过以下代码:

early_initcall(tracer_alloc_buffers);

fs_initcall(tracer_init_debugfs);

late_initcall(clear_boot_tracer);

3.1.1   分配ftrace所需要的缓存

1.        首先定义三个cpu位图变量tracing_buffer_mask,tracing_cpumask,tracing_reader_cpumask[M1] 。

2.        给ring buffer分配空间,给ring buffer分配空间之前会去判断是否在启动时已经指定了具体的tracer。通过ring_buffer_expanded来判断,如果此值为1,则说明指定了具体的tracer,如果为0,则说明没有指定具体的tracer。

       if (ring_buffer_expanded)

              ring_buf_size = trace_buf_size;

       else

                     ring_buf_size = 1;

trace的缓存通过一个全局变量global_trace来保存。

global_trace.buffer= ring_buffer_alloc(ring_buf_size, TRACE_BUFFER_FLAGS);

3.        如果定义了宏CONFIG_TRACER_MAX_TRACE[M2] ,也会为max_tr[M3] 分配同样大小的ring buffer:

max_tr.buffer= ring_buffer_alloc(ring_buf_size, TRACE_BUFFER_FLAGS);

4.        另外给global_trace和max_tr分配per cpu[M4] 变量

for_each_tracing_cpu(i){

              global_trace.data[i] =&per_cpu(global_trace_cpu, i);

              max_tr.data[i] =&per_cpu(max_data, i);

       }

5.        trace_init_cmdlines();初始化和线程号、线程名相关的数据结构。

6.        注册一个tracer,如果内核启动时没有指定具体的tracer,则在这注册nop_trace。如果指定了具体的tracer,在这儿注册指定的tracer。具体注册过程参看3.2小节。

7.        最后将tracing_disabled赋值为0,该变量默认值为1。如果为1,系统中的trace是禁用的。当准备工作全部结束,把该值赋值为0。系统中的trace便开始运行了。

3.1.2   在debufs中创建ftrace的目录和文件

1、  创建根目录tracing

2、  创建该目录下的目录结构和相关的文件。

3、  注意事项:

如果想用采用function这种tracer,还要echo 1 > /proc/sys/kernel/ftrace_enabled。如果ftrace_enabled设置为0,则function这种tracer的行为类似于nop。

如果想查看内核栈的信息,开启办法echo 1 > /proc/sys/kernel/stack_tracer_enabled,而且记录下的内核栈的信息不会保存在ring buffer中。

4、  目录结构如下:

文件

文件所属者

作用

README

trace架构

 

available_events

event

只读,当前系统可用的events

available_filter_functions

ftrace

只读,显示了当前系统中允许跟踪的函数[M5] ,不在该文件中的函数无法抓取trace

available_tracers

trace架构

显示已经被编译进内核的tracer。当前版本还不支持动态添加、删除tracer。

buffer_size_kb

trace架构

每个cpu缓存的大小。但是只有当current_tracer这个文件为nop的时候,在这个文件中指定的新的buffer大小才会生效。

current_tracer

trace架构

用来显示或者设置系统当前运行的tracer

dyn_ftrace_total_info

trace架构

?

events目录

event

包含各个类型的event

failures

ftrace

?

function_profile_enabled

ftrace

用来配置是否统计每个函数被执行的时间已经被执行的次数。

options目录

trace架构

该目录在trace初始化时被创建,并将trace_options文件中的选项在该目录下创建相应的文件。当具体的tracer安装时,会将具体tracer可配置的文件在该目录下创建出来。

per_cpu目录

trace架构

该目录下会为每个cpu创建一个目录。每个cpu目录记录着属于自己的trace信息。

printk_formats

trace_printk

saved_cmdlines

trace架构

set_event

event

用来设置要跟踪的event。

set_ftrace_filter

ftrace

显示或设置当前跟踪的函数。可以指定模块,例如:echo ':mod:cbd' > set_ftrace_filter

set_ftrace_notrace

ftrace

和set_ftrace_filter功能相反,用来显示或者设置不被跟踪的函数。

set_ftrace_pid

ftrace

跟踪指定pid号的内核线程

set_graph_function

ftrace

当tracer选择为function_graph,可以通过该文件指定要跟踪那些具体函数。默认是全部。

stack_max_size

ftrace_stack

记录每个function所使用的栈最大值

stack_trace[M6] 

ftrace_stack

显示所有执行的function使用内核栈的信息

sysprof_sample_period

trace架构

trace

trace架构

通过该文件我们可以查看系统中抓取的trace

trace_clock

trace架构

trace_marker

trace架构

可以随时将一些有意义的标志信息写到该文件中,用来区分大量的trace那部分是自己想要的。

trace_options

trace架构

显示ftrace可以配置的文件。

trace_pipe

trace架构

该文件也是显示抓取的trace,不同的是显示之后会将缓存中的信息置位无效。当再次cat这个文件时,先前读到的内容就没有了。trace和该文件读的缓存是一致的。

trace_stat目录

ftrace

该目录针对每个cpu会创建一个function$cpunum文件。每个文件显示在该cpu上执行的函数所用的时间,以及被调用的次数。

tracing_cpumask

trace架构

用来显示或者设置cpu掩码,用来表示在指定的cpu上抓取trace。

tracing_enabled

trace架构

显示或者设置当前系统中tracer是运行状态,还是非运行状态。echo 0 到该文件让tracer停止,echo 1到该文件让tracer运行

tracing_max_latency

trace架构

有些tracer关心最大延迟的时间?

tracing_on

ring buffer

暂停或继续跟踪信息的记录,此时tracer仍是活跃的。

tracing_thresh

trace架构

 

3.1.3   各个tracer的注册

目前内核中的tracer也是在内核启动时,将自己注册到系统中。在将自己注册到系统之前要完成自己的一些初始化工作。

3.2  tracer是怎样注册到ftrace系统中的

系统注册一个tracer通过register_tracer这个函数来实现。

函数原型:intregister_tracer(struct tracer *type)

__releases(kernel_lock)[M7] 

__acquires(kernel_lock)

1、  合法条件的判断,包括tracer->name是否为空,tracer->name的长度是否超过最大值。不合法返回-1。

2、  调用unlock_kernel[M8] 

3、  mutex_lock(&trace_types_lock);保护系统中串联所有tracer的链表

4、  tracing_selftest_running[M9] 赋值为true。

5、  查看要注册的tracer是否已经被注册了,如果已经被注册了返回-1。

6、  此时可以放心得注册tracer了。首先判断该tracer是否有自己的options文件,以及设置option需要做的操作。如果没有,则注册系统的默认值。对于option文件数据结构的组织参看3.3小节。

if (!type->set_flag)

        type->set_flag = &dummy_set_flag;

if (!type->flags)

        type->flags =&dummy_tracer_flags;

else

        if (!type->flags->opts)

               type->flags->opts= dummy_tracer_opt;

7、  判断wait_pipe是否为空,如果为空注册系统的默认操作函数。

if (!type->wait_pipe)

        type->wait_pipe= default_wait_pipe;

8、  如果tracer有自己的自检函数,并且系统此时没有禁止自检。此时便会进入tracer的自检流程。如果自检失败,则清理环境,并退出。如果自检成功,变将该tracer添加到trace_types链表上。

9、  如果在内核启动时指定了具体的tracer,还需要执行安装tracer的操作。如果没有指定具体的tracer则清理环境,退出。对于tracer的具体安装,参看3.4小节。

3.3  内核启动时指定具体tracer

内核启动时trace机制允许指定具体的tracer。在3.1.3小节中各个tracer注册时,如果发现自己被配置成为默认的tracer,除了将自己注册到系统之外,还要完成自己的安装操作。

在/boot/grub/menu.list中可以修改内核启动参数,加粗部分是用来指定具体tracer的选项。kernel/boot/vmlinuz-2.6.32-71.el6.x86_64 ro root=/dev/sda1ftrace=function console=ttyS0,9600n8 console=tty0 rhgb quiet

这样在启动时,会执行如下代码:

static int__init set_ftrace(char *str)

{

       strncpy(bootup_tracer_buf, str,MAX_TRACER_SIZE);

       default_bootup_tracer =bootup_tracer_buf;

       /* We are using ftrace early, expand it*/

       ring_buffer_expanded = 1;

       return 1;

}

__setup("ftrace=", set_ftrace);

这样ring_buffer_expanded便会赋值为1,在计算ring buffer大小时便会赋值为trace_buf_size

3.4  tracer的安装

tracer的安装通过tracing_set_tracer来实现,函数原型:

static int tracing_set_tracer(const char*buf)

1、  首先根据传入的字符串,找到相应的tracer。如果找不到返回-EINVAL。如果找到但是和current_trace相等,直接退出。

2、  为tracer的切换做准备:

trace_branch_disable[M10] ();

if (current_trace &¤t_trace->reset)

        current_trace->reset(tr);         //注销当前的tracer

 

       destroy_trace_option_files(topts);   //删除当前tracer相关的option文件

3、  创建要切换的tracer相关的option文件,并调用该tracer的init方法来完成安装。

topts = create_trace_option_files(current_trace);

 

if (t->init) {

        ret = tracer_init(t, tr);

        if (ret)

               goto out;

       }

4、  在此以function为例,来说明tracer如何将自己

3.5  tracer的option文件

tracer数据结构中有一项为flags,类型为structtracer_flags,具体有哪些option文件通过opts来指定。一种option对应一个文件。

/*

 * The set of specific options for a tracer.Your tracer

 * have to set the initial value of the flagsval.

 */

struct tracer_flags {

       u32                val;

       structtracer_opt     *opts;

};

/*

 * An option specific to a tracer. This is aboolean value.

 * The bit is the bit index that sets its valueon the

 * flags value in struct tracer_flags.

 */

struct tracer_opt {

       constchar       *name; /* Will appear on thetrace_options file */

       u32         bit; /* Mask assigned in val field intracer_flags */

};

四   ftrace怎样利用tracepoint

4.1 tracepoint的声明与定义

首先是定义需要的tracepoint。但是内核各个tracer在定义tracepoint时并没有直接采用2.1节所描述的两个宏。而是进行了进一步的封装,以tracer sched_switch为例进行说明。

在trace_sched_switch.c中包含头文件,该头文件的结构如下:

#undef TRACE_SYSTEM

#defineTRACE_SYSTEM sched

 

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

#define _TRACE_SCHED_H

 

#include

#include

 

四个宏的调用

DEFINE_EVENT

DEFINE_EVENT_PRINT

TRACE_EVENT

TRACE_EVENT_FN

#endif /* _TRACE_SCHED_H */

 

/* This part must be outsideprotection */

#include

在define_trace.h中又一次包含头文件,但是再一次包含该头文件之前,已经将DEFINE_EVENT,DEFINE_EVENT_PRINT,TRACE_EVENT,TRACE_EVENT_FN四个宏重定义了。再一次包含该头文件时,因为tracepoint.h发现这四个宏已经被定义过不会再重新定义。所以再次调用这四个宏,会执行define_trace.h中的代码。

在define_trace.h中完成重新include动作的代码如下:

#define TRACE_INCLUDE_FILETRACE_SYSTEM

#define __TRACE_INCLUDE(system)

 

#define TRACE_INCLUDE(system)__TRACE_INCLUDE(system)

 

/* Let the trace headers be reread */

#define TRACE_HEADER_MULTI_READ

 

#includeTRACE_INCLUDE(TRACE_INCLUDE_FILE)

4.2 钩子函数的注册

当echo tracer > current_trace时,系统会调用具体tracer注册的init函数。此时会调用register_trace_tracepoint-name该函数来注册钩子函数。

4.3 钩子函数的注销

在切换tracer时,会首先把当前的环境清理干净。包括删除options文件,以及注销已经注册的钩子函数。注销动作由tracer的reset函数来完成。reset中会调用unregister_trace_tracepoint-name函数来注销钩子函数。

五  trace文件的读

当cattracing/trace时执行

tracing_open和seq_read函数

六   events机制

events机制也是一种依赖于tracepoint的架构。一个event对应着一个tracepoint。它和tracer用tracepoint的方法一样。

首先要用tracepoint.h中的宏进行声明,定义三个函数。再用define_trace.h定义tracepoint。当这一步做完之后,tracer利用tracepoint到此就结束了。但是再define_trace.h中回去判断内核编译时是否定义了CONFIG_EVENT_TRACING该宏。如果定义了该宏,会包含。在ftrace.h中会去定义和该tracepoint相对应的event(例如writeback)。

与tracer不同的是,这种event支持动态添加。当新的模块添加时,会去判断该模块中是否定义了event。如果有定义会依次对各个event进行初始化。

如果模块中定义了TRACE_SYSTEM(例如#define TRACE_SYSTEM cbd),初始化时便会在tracing/event目录下创建目录cbd。然后在cbd目录下创建writeback目录,在writeback目录下有文件enable。echo 0 > enable会注销掉钩子函数。echo 1 > enable会注册钩子函数。此时在我们想抓取trace的地方便开始抓取trace了。


七  ring buffer

7.1 ring buffer缓存分布

linux ftrace追踪一(基本技术结构粗略剖析)_第2张图片

你可能感兴趣的:(linux内核技术追踪)