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

来源:互联网 发布:金牛考勤软件下载 编辑:程序博客网 时间:2024/05/04 11:52

一   文档说明

本文为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];

};



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=functionconsole=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 &&current_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中包含头文件<trace/events/sched.h>,该头文件的结构如下:

#undef TRACE_SYSTEM

#defineTRACE_SYSTEM sched

 

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

#define _TRACE_SCHED_H

 

#include <linux/sched.h>

#include<linux/tracepoint.h>

 

四个宏的调用

DEFINE_EVENT

DEFINE_EVENT_PRINT

TRACE_EVENT

TRACE_EVENT_FN

#endif /* _TRACE_SCHED_H */

 

/* This part must be outsideprotection */

#include<trace/define_trace.h>

在define_trace.h中又一次包含头文件<trace/events/sched.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)<trace/events/system.h>

 

#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该宏。如果定义了该宏,会包含<trace/ftrace.h>。在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缓存分布


原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 头发太多太厚怎么办_百度经验 米诺地尔搽剂喷在头皮上痛怎么办 米诺地尔擦了头皮痒怎么办 头发又细又少一天不洗就油怎么办 头又尖头发又细又少不知怎么办 蘑菇头发型留长尴尬期怎么办 月经期间洗了下头量很少了怎么办 宝宝喝了有沐浴露的洗澡水怎么办 4个月宝宝头发长的慢怎么办 宝宝不小心吃了自己拉的屎怎么办 手机不小心弄成耳机模式怎么办 苹果手机不小心按了丢失模式怎么办 苹果手机不小心调成耳机模式怎么办 不小心把图书馆的书弄坏了怎么办 不小心把图书馆书拿出来了怎么办 八个月宝宝吃母乳缺铁怎么办 大头笔写的字褪色了怎么办 小画板用记号笔画了擦不下来怎么办 新换的大灯和原大灯有色差怎么办? 微信信息写好没有发送两字怎么办 3d模型导进去 材质丢失怎么办 3d模型导入材质没了怎么办 淘宝上的电视尺寸与实际不符怎么办 医师面试题号忘了写怎么办 淘宝买东西东西被别人拿走了怎么办 9个月宝宝上肢支撑不好怎么办 宝宝快十一个月了留口水怎么办 两岁宝宝不会自己登着大小便怎么办 小新家的房子被炸了房贷怎么办 怀孕8个月感冒鼻窦炎头疼怎么办 买的全身镜下面的框子坏了怎么办 单位上司姐姐每天要接我上班怎么办 企业微信公众号中的文章边框怎么办 怎么办我在数学答题卡上画了分割线 游泳的时候泳裙飘起来怎么办 两岁宝宝误服了酵素梅怎么办 人被困在山洞里没有氧气怎么办? 一个人太爱你可你不爱他怎么办 牙齿还没掉又长了新牙齿怎么办 两岁宝宝牙齿发黑烂牙怎么办 怀孕八个月被小孩压到肚子了怎么办