上一篇文章我们看到,内核展开一个 trace_point的宏以后,定义了一个trace_event_call 结构,并把这个结构放在”_ftrace_events” 段中。这一篇文章我们将解析 内核是如何加载 这些结构并使能的.
如果是内核vmlinux中的trace将在这个函数中 直接初始化,如果是模块中的调用trace_module_add_events 初始化。 这里只节选一个函数。
static __init int event_trace_enable(void)
{
>-------struct trace_array *tr = top_trace_array();
>-------struct trace_event_call **iter, *call;
>-------int ret;
>-------if (!tr)
>------->-------return -ENODEV;
>-------for_each_event(iter, __start_ftrace_events, __stop_ftrace_events) {
>------->-------call = *iter;
>------->-------ret = event_init(call); \\关键函数,对event进行初始化,
>------->-------if (!ret)
>------->------->-------list_add(&call->list, &ftrace_events);
>-------}
>-------/*
>------- * We need the top trace array to have a working set of trace
>------- * points at early init, before the debug files and directories
>------- * are created. Create the file entries now, and attach them
>------- * to the actual file dentries later.
>------- */
>-------__trace_early_add_events(tr);
>-------early_enable_events(tr, false);
>-------trace_printk_start_comm();
>-------register_event_cmds();
>-------register_trigger_cmds();
>-------return 0;
}
event_init 函数如下,
static int event_init(struct trace_event_call *call)
{
>-------int ret = 0;
>-------const char *name;
>-------name = trace_event_name(call);
>-------if (WARN_ON(!name))
>------->-------return -EINVAL;
>-------if (call->class->raw_init) {
>------->-------ret = call->class->raw_init(call); \\ trace_event_raw_init函数
>------->-------if (ret < 0 && ret != -ENOSYS)
>------->------->-------pr_warn("Could not initialize trace events/%s\n", name);
>-------}
>-------return ret;
}
这个函数只是调用trace_event_raw_init函数,这个函数调用了 register_trace_event 函数。这个register_trace_event 函数只是将trace_event 注册到全局结构中。
注册完成以后 会调用 __add_event_to_tracers 将trace_event 加入到各个tracer中,并在 /sys/kernel/debug/tracing/events/ 对应的目录下创建控制文件。到这一步 注册就完成了。
使能操作,使能时时给enable 文件中输入1
调用链条如下
system_enable_write --> __ftrace_set_clr_event --> __ftrace_set_clr_event_nolock --> ftrace_event_enable_disable --> __ftrace_event_enable_disable -->trace_event_reg --> tracepoint_probe_register --> tracepoint_probe_register_prio -->tracepoint_add_func
当这一串函数执行玩意后,在trace_出会执行 trace_event_rawevent##call 对应的函数。 到这儿使能完毕。
raw_trace_point 简介
基于 bpf的raw trace_point 逻辑非常简单, 宏展开如下
#define DECLARE_EVENT_CLASS(call, proto, args, tstruct, assign, print)>-\
static notrace void>---->------->------->------->------->------->-------\
__bpf_trace_##call(void *__data, proto)>>------->------->------->-------\
{>------>------->------->------->------->------->------->------->-------\
>-------struct bpf_prog *prog = __data;>>------->------->------->-------\
>-------CONCATENATE(bpf_trace_run, COUNT_ARGS(args))(prog, CAST_TO_U64(args));>-\
}
/*
* This part is compiled out, it is only here as a build time check
* to make sure that if the tracepoint handling changes, the
* bpf probe will fail to compile unless it too is updated.
*/
#undef DEFINE_EVENT
#define DEFINE_EVENT(template, call, proto, args)>------>------->-------\
static inline void bpf_test_probe_##call(void)>->------->------->-------\
{>------>------->------->------->------->------->------->------->-------\
>-------check_trace_callback_type_##call(__bpf_trace_##template);>------\
}>------>------->------->------->------->------->------->------->-------\
static struct bpf_raw_event_map>__used>->------->------->------->-------\
>-------__attribute__((section("__bpf_raw_tp_map")))>--->------->-------\
__bpf_trace_tp_map_##call = {>-->------->------->------->------->-------\
>-------.tp>---->-------= &__tracepoint_##call,>>------->------->-------\
>-------.bpf_func>------= (void *)__bpf_trace_##template,>------>-------\
>-------.num_args>------= COUNT_ARGS(args),>---->------->------->-------\
};
#undef DEFINE_EVENT_PRINT
#define DEFINE_EVENT_PRINT(template, name, proto, args, print)>-\
>-------DEFINE_EVENT(template, name, PARAMS(proto), PARAMS(args))
在4.19内核里面 还不支持 module 添加raw_tracepoint .通过bpf 可以在raw trace_point中添加任何执行,输入参数是trace_point直接输入。 有兴趣的可以看看bcc 是如何用raw_tracepoint 抓 一些数据的
注意事项:
trace_event 在复值并把 数据放到buffer的时候并没有格式化成字符串,只有当调用 读trace_pipe 或则trace文件时才会正真的将变量格式化成字符串输入。因此如果在写trace_point的时候 赋值指针的。去读的时候可能得到的不是当时的值。