发布时间:2023-07-20 11:00
本文为2.6.32下trace机制(以下简称trace)的调研文档。trace实现的基础为tracepoint机制,存放数据的缓存实现为ring buffer。
阅读代码路径:
samples/tracepoints
kernel/trace
include/trace
tracepoint是实现ftrace架构的基础。在内核代码路径samples/tracepoint下对tracepoint有个简单的实例。
struct tracepoint {
constchar *name; /* Tracepoint name */
intstate; /* State. */
void(*regfunc)(void);
void(*unregfunc)(void);
void**funcs;
} __attribute__((aligned(32)));
这两个宏传递的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。
#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:注销一个钩子函数。
当用以上两个宏定义并声明了一个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];
};
在要抓trace的地方,添加trace_##name,这样便可以调用我们注册的钩子函数。
在2.6.32中trace的初始化时在内核启动时完成的。通过以下代码:
early_initcall(tracer_alloc_buffers);
fs_initcall(tracer_init_debugfs);
late_initcall(clear_boot_tracer);
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便开始运行了。
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架构 |
? |
目前内核中的tracer也是在内核启动时,将自己注册到系统中。在将自己注册到系统之前要完成自己的一些初始化工作。
系统注册一个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小节。
内核启动时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
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如何将自己
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 */
};
首先是定义需要的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_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)
当echo tracer > current_trace时,系统会调用具体tracer注册的init函数。此时会调用register_trace_tracepoint-name该函数来注册钩子函数。
在切换tracer时,会首先把当前的环境清理干净。包括删除options文件,以及注销已经注册的钩子函数。注销动作由tracer的reset函数来完成。reset中会调用unregister_trace_tracepoint-name函数来注销钩子函数。
当cattracing/trace时执行
tracing_open和seq_read函数
events机制也是一种依赖于tracepoint的架构。一个event对应着一个tracepoint。它和tracer用tracepoint的方法一样。
首先要用tracepoint.h中的宏进行声明,定义三个函数。再用define_trace.h定义tracepoint。当这一步做完之后,tracer利用tracepoint到此就结束了。但是再define_trace.h中回去判断内核编译时是否定义了CONFIG_EVENT_TRACING该宏。如果定义了该宏,会包含
与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了。