一 文档说明
本文为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 &¤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中包含头文件<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了。