目录
kernel版本:5.10
平台:arm64
aarch64-linux-gnu-gcc: (Linaro GCC 5.4-2017.01) 5.4.1 20161213
1.前言
本文主要介绍tracefs的初始化,为后续trace学习做好铺垫。我们主要解决如下几个问题:
trace fs目录如何建立起来的?
early trace如何开启的?
我们先来梳理一下,启动过程中有哪些与ftrace有关,此处我们把start_kernel启动流程中与ftrace初始化相关的部分截取出来如下:
start_kernel
|--ftrace_init
|--early_trace_init
|--trace_init
|--arch_call_rest_init
|--core_initcall(tracefs_init);
|--core_initcall(ftrace_mod_cmd_init)
|--core_initcall_sync(trace_boot_init)
|--core_initcall(init_branch_tracer)
|--fs_initcall(tracer_init_tracefs)
|--fs_initcall(bpf_event_init)
|--fs_initcall(init_dynamic_event)
|--device_initcall(init_blk_tracer)
|--late_initcall(test_ringbuffer)
|--late_initcall_sync(clear_boot_tracer)
下面我们逐个看下上面每个初始化函数
- ftrace_init
编译阶段,给gcc加入了-pg -mrecord-mcoun参数,则会在所有可trace的函数中插入 bl <_mcount> 指令,ftrace_init就是遍历__mcount_loc section段,将所有的插入指令替换为nop,这样可以减少性能损耗 - early_trace_init
early_trace_init最重要的就是为trace_printk等分配了buffer,同时注册了nop tracer,function tracer,注册tracer就是通过将tracer链入全局trace_types链表完成注册 - trace_init
主要是对trace event的初始化,它会批量化注册trace_event,将每个trace_event_call(本质上也是一个tracepoint)挂接到全局ftrace_events链表,为每一个trace_event_call创建trace_event_file,对应每个子系统下的一个trace event;
(1)注册filter相关的ftrace_func_command到全局ftrace_commands,这些注册的ftrace_func_command将会在写入/sys/kernel/debug/tracing/set_ftrace_filter文件时被调用,各子系统的filter文件节点是在event_trace_init->early_event_add_tracer时被创建;
(2)注册trigger相关的event_command到全局trigger_commands,这些注册的event_command将会在写/sys/kernel/debug/tracing/xxx/trigger文件时被调用, 各子系统的trigger文件节点是在
event_trace_init->early_event_add_tracer时被创建 - tracefs_init
在/sys/kernel下创建tracing挂载目录, 注册trace fs文件系统 - 初始化阶段将通过fs_initcall(tracer_init_tracefs);来调用tracer_init_tracefs,通过tracer_init_tracefs搭建起tracefs的目录结构
- tracer_init_tracefs
创建/sys/kernel/debug/traing顶级目录,创建events目录下顶层目录;通过遍历所有的trace_event_call(通过TRACE_EVENT定义),如果所属的trace_subsystem_dir没有创建则进行创建,这个就是events目录下的各个trace event子系统目录,在此目录下还会创建enable和filter文件节点。顶层目录下创建一系列tracer公用文件,包括:available_tracers,current_tracer, tracing_cpumask, trace_options,trace, trace_pipe, buffer_size_kb,buffer_total_size_kb, free_buffer, trace_marker, tracing_on, snapshot, error_log, 每一个文件都单独定义了相应的file_operations。ftrace_init_tracefs_toplevel:顶层目录下创建其它ftrace相关文件
2. ftrace_init
ftrace_init
\--ftrace_process_locs(NULL, __start_mcount_loc, __stop_mcount_loc)
| //按插入点的地址进行排序
|--sort(start, count, sizeof(*start),ftrace_cmp_ips, NULL);
| //为插入点的指令地址分配空间,每个插入点都会有一个struct ftrace_page表示
|--start_pg = ftrace_allocate_pages(count)
| //遍历所有struct ftrace_page下的struct dyn_ftrace,初始化它的ip
|--pg = start_pg;
| p = start;
| while (p < end) {
| rec = &pg->records[pg->index++];
| rec->ip = addr;
\--ftrace_update_code(mod, start_pg)
为了实现function trace,我们知道在编译阶段,给gcc加入了-pg -mrecord-mcoun参数,则会在所有可trace的函数中插入 bl <_mcount> 指令,之后会通过scripts/recordmcount.c将所有插入此条指令的位置记录到__mcount_loc section,链接时将会把所有.o文件的__mcount_loc section链接到一起,它的起始地址为__start_mcount_loc,结束地址为__stop_mcount_loc。ftrace_init就是遍历__mcount_loc section段,将所有的插入指令替换为nop,这样可以减少性能损耗,至于为何不在gcc编译阶段就做此替换,可能有它的历史原因,估计是考虑到gcc的通用性吧
-
ftrace_allocate_pages:为分配尽量连续的物理地址,因此可能会分为多段,每个地址段由一个struct ftrace_page进行管理,多个ftrace_page组成一个链表,每个struct dyn_ftrace代表一个entry,用于描述插入点
-
之后将通过一个while循环,遍历每一个struct dyn_ftrace,初始化它的ip,此处的ip就是插入指令的地址,也就是bl <_mcount>的地址
-
ftrace_update_code:由于编译阶段在每个可trace函数里插入了bl <_mcount> 指令,为了减少性能损耗,此处将这些插入的指令替换为nop
3. early_trace_init
early_trace_init最重要的就是为trace_printk等分配了buffer,同时注册了nop tracer,function tracer,register_tracer就是通过将tracer链入全局trace_types链表完成注册
void __init early_trace_init(void)
{
if (tracepoint_printk) {
tracepoint_print_iter =
kmalloc(sizeof(*tracepoint_print_iter), GFP_KERNEL);
if (MEM_FAIL(!tracepoint_print_iter,
"Failed to allocate trace iterator\n"))
tracepoint_printk = 0;
else
static_key_enable(&tracepoint_printk_key.key);
}
tracer_alloc_buffers(); //分配ring buffer
}
如果cmdline中有参数tp_printk,tracepoint_printk可用于控制是否将trace信息写入到printk
echo 0 > /proc/sys/kernel/tracepoint_printk禁用trace信息写入到printk
echo 1 > /proc/sys/kernel/tracepoint_printk使能trace信息写入到printk
tracer_alloc_buffers
|--alloc_cpumask_var(&tracing_buffer_mask, GFP_KERNEL)
|--alloc_cpumask_var(&global_trace.tracing_cpumask, GFP_KERNEL)
|--if (&__stop___trace_bprintk_fmt != &__start___trace_bprintk_fmt)
| trace_printk_init_buffers()
|--ring_buf_size = trace_buf_size;
|--cpumask_copy(tracing_buffer_mask, cpu_possible_mask)
| cpumask_copy(global_trace.tracing_cpumask, cpu_all_mask)
|--temp_buffer = ring_buffer_alloc(PAGE_SIZE, RB_FL_OVERWRITE)
|--trace_create_savedcmd()
|--tracing_set_clock(&global_trace, trace_boot_clock)
|--global_trace.current_trace = &nop_trace
| global_trace.max_lock = (arch_spinlock_t)__ARCH_SPIN_LOCK_UNLOCKED;
| ftrace_init_global_array_ops(&global_trace)
| init_trace_flags_index(&global_trace);
|--register_tracer(&nop_trace)//注册nop_tracer
|--init_function_trace()
| |--init_func_cmd_traceon()
| \--register_tracer(&function_trace)//注册function_tracer
|--tracing_disabled = 0 //开启trace
| //向内核注册notifier
|--atomic_notifier_chain_register(&panic_notifier_list,&trace_panic_notifier)
| register_die_notifier(&trace_die_notifier);
|--INIT_LIST_HEAD(&global_trace.systems);
| INIT_LIST_HEAD(&global_trace.events)
| INIT_LIST_HEAD(&global_trace.hist_vars)
| INIT_LIST_HEAD(&global_trace.err_log)
| list_add(&global_trace.list, &ftrace_trace_arrays)
|--apply_trace_boot_options()//获取trace相关启动选项设置到global_trace->current_trace->flags->opts
\--register_snapshot_cmd()
-
trace_printk_init_buffers:__stop___trace_bprintk_fmt和__start___trace_bprintk_fmt分别是__trace_printk_fmt section的起始和结束地址,如果有定义__trace_printk_fmt section段,则要通过trace_printk_init_buffers为其分配buffer,这个buffer主要用于trace_printk、ftrace_vprintk、trace_puts等的打印
-
ring_buf_size是可以调整的,可以通过boot cmdline传递,默认大小为1K
-
ring_buffer_alloc(PAGE_SIZE, RB_FL_OVERWRITE):通过注释看是为event trigger分配
-
trace_create_savedcmd:存放cmd与pid的对应关系
-
tracing_set_clock
-
register_tracer(&nop_trace):注册nop tracer
-
init_function_trace
(1)init_func_cmd_traceon:通过register_ftrace_command来注册ftrace_func_command,如:traceon, traceoff,stacktrace,dump,ftrace_func_command定义了命令名称和命令处理函数,它会挂接在全局ftrace_commands?
(2)register_tracer(&function_trace):注册function_trace
//以register_tracer(&function_trace)为例
register_tracer(struct tracer *type)
|--type->flags->trace = type
|--run_tracer_selftest(type)
| //将trace链入链表trace_types,完成注册
|--type->next = trace_types
| trace_types = type;
\--add_tracer_options(&global_trace, type)
|--struct trace_option_dentry *topts;
| struct trace_options *tr_topts;
| struct tracer_flags *flags;
| struct tracer_opt *opts
|--flags = tracer->flags; opts = flags->opts;
|--for (cnt = 0; opts[cnt].name; cnt++);//cnt为1
| //分配2个trace_option_dentry
|--topts = kcalloc(cnt + 1, sizeof(*topts), GFP_KERNEL);
| //分配
|--tr_topts = krealloc(tr->topts, sizeof(*tr->topts)*(tr->nr_topts+1)..)
|--tr->topts = tr_topts;
tr->topts[tr->nr_topts].tracer = tracer;
tr->topts[tr->nr_topts].topts = topts;
tr->nr_topts++;
register_tracer通过将tracer链入全局trace_types链表完成注册,同时通过add_tracer_options在tracing/options目录下创建选项文件,这些文件控制了相关tracer的信息是否会被写入到ringbuffer
- type->next = trace_types;trace_types = type将tracer链入全局trace_types链表完成注册
- add_tracer_options:主要是在tracing/options下创建各种选项文件,这些选项文件直接影响到tracer的内容是否输出到ring buffer
以function_trace 为例,定义如下:
static struct tracer function_trace __tracer_data =
{
.name = "function",
.init = function_trace_init,
.reset = function_trace_reset,
.start = function_trace_start,
.flags = &func_flags,
.set_flag = func_set_flag,
.allow_instances = true,
#ifdef CONFIG_FTRACE_SELFTEST
.selftest = trace_selftest_startup_function,
#endif
};
static struct tracer_flags func_flags = {
.val = 0, /* By default: all flags disabled */
.opts = func_opts
};
static struct tracer_opt func_opts[] = {
#ifdef CONFIG_STACKTRACE
{ TRACER_OPT(func_stack_trace, TRACE_FUNC_OPT_STACK) },
#endif
{ } /* Always set a last empty entry */
};
如果使能func_stack_trace选项,会将所跟踪函数的调用栈打印出来:
#!/bin/bash
debugfs=/sys/kernel/debug
echo nop > $debugfs/tracing/current_tracer
echo 0 > $debugfs/tracing/tracing_on
echo $$ > $debugfs/tracing/set_ftrace_pid
echo function > $debugfs/tracing/current_tracer
echo 1 > /sys/kernel/debug/tracing/options/func_stack_trace
echo ksys_write > $debugfs/tracing/set_ftrace_filter
echo 1 > $debugfs/tracing/tracing_on
exec "$@"
关于更多的option的含义可以看到内核文档:ftrace - Function Tracer
从代码中看出tracer_opt与trace_option_dentry 有一一对应的关系
从上面可以看到每个tracer都有一个tracer_flags,tracer_flags的val变量每一个bit有不同的含义,通过set/clear某一个bit,来控制tracer的相关信息是否写入ringbuffer,而val的每一个bit是由tracer_opt管理的,它管理着一组{name, bit}对,通过set_flag来开启或关闭相应的选项,通过ls /sys/kernel/debug/tracing/options可以看到有哪些option
3. trace_init
void __init trace_init(void)
{
trace_event_init();
}
start_kernel中会调用trace_init
void __init trace_event_init(void)
{
// trace event slab相关操作, 为ftrace_event_field和trace_event_file创建slab描述符
event_trace_memsetup();
//系统调用表初始化,主要通过包含头文件及宏定义的方式完成系统调用表的初始化
init_ftrace_syscalls();
//event_trace_enable就是批量化注册trace_event的函数
event_trace_enable();
// trace event初始化域,包括一些trace的公共域,通用域
event_trace_init_fields();
}
trace_init主要就是对trace event的初始化
-
event_trace_memsetup: trace event slab相关操作, 为ftrace_event_field和trace_event_file创建slab描述符
-
init_ftrace_syscalls: 系统调用表初始化,主要通过包含头文件及宏定义的方式完成系统调用表的初始化
-
event_trace_enable:event_trace_enable就是批量化注册trace_event
event_trace_enable
|--for_each_event(iter, __start_ftrace_events, __stop_ftrace_events)
| call = *iter; event_init(call);
|--__trace_early_add_events(tr)
|--early_enable_events(tr, false)
|--trace_printk_start_comm()
|--register_event_cmds()
\--register_trigger_cmds()
(1)for_each_event:__start_ftrace_events和__stop_ftrace_events是_ftrace_events section的起止地址,event_trace_enable就是批量化注册trace_event_call(本质上是一个trace point),将每个trace_event_call挂接到全局ftrace_events链表,其中event_init中会调用call->class->raw_init(call),根据对TRACE_EVENT宏的展开分析可知,此处的raw_init就是trace_event_raw_init。
(2) __trace_early_add_events:为每一个trace_event_call创建trace_event_file,对应每个子系统下的一个trace event,如:
/sys/kernel/debug/tracing/events/irq/irq_handler_entry
(3)early_enable_events:对cmdline中设置的trace_event进行使能操作,它实际会记录在trace_event_file的flags变量中,下面是一个cmdline下设置trace event例子:
trace_event=”initcall:*,irq:*,exceptions:*”
(4)trace_printk_start_comm:开始记录command 到ring buffer,前面说过trace event本质也是tracepoint,此处实际就是对将tracepoint和回调函数进行绑定,后面就可以在插入点触发这个回调,此处的trace event主要是sched_switch。可参考tracepoint简介 .说明在很早的阶段sched_switch的trace event就可以work了
(5)register_event_cmds:注册filter相关的ftrace_func_command到全局ftrace_commands,这些注册的ftrace_func_command将会在写入/sys/kernel/debug/tracing/set_ftrace_filter文件时被调用,各子系统的filter文件节点是在event_trace_init->early_event_add_tracer时被创建
(6)register_trigger_cmds: 注册trigger相关的event_command到全局trigger_commands,这些注册的event_command将会在写/sys/kernel/debug/tracing/xxx/trigger文件时被调用, 各子系统的trigger文件节点是在event_trace_init->early_event_add_tracer时被创建
trace_init初始化完成之后将会形成如下的关系:
4. tracefs_init
static int __init tracefs_init(void)
{
int retval;
/* 在/sys/kernel下创建tracing挂载目录 */
retval = sysfs_create_mount_point(kernel_kobj, "tracing");
if (retval)
return -EINVAL;
/* 注册trace fs文件系统 */
retval = register_filesystem(&trace_fs_type);
if (!retval)
tracefs_registered = true;
return retval;
}
core_initcall(tracefs_init);
tracefs的初始化
- sysfs_create_mount_point: 在/sys/kernel下创建tracing挂载目录
- register_filesystem: 注册trace fs文件系统
5. tracer_init_tracefs
//kernel/trace/trace.c
static __init int tracer_init_tracefs(void)
|--trace_access_lock_init()
| //创建/sys/kernel/debug/traing顶级目录
|--tracing_init_dentry();
|--event_trace_init()
| |--tracefs_create_file("available_events"
| \--early_event_add_tracer(NULL, tr)
| |--create_event_toplevel_files(parent, tr)
| |--__trace_early_add_event_dirs(tr)
|--init_tracer_tracefs(&global_trace, NULL)
|--ftrace_init_tracefs_toplevel(&global_trace, NULL);
| |--ftrace_init_dyn_tracefs(d_tracer)
| \--ftrace_profile_tracefs(d_tracer)
| //顶层目录下创建文件
|--trace_create_file("tracing_thresh", 0644, NULL...)
| trace_create_file("README", 0444, NULL,...)
| trace_create_file("saved_cmdlines", 0444, NULL,...)
| trace_create_file("saved_cmdlines_size", 0644, NULL...)
| trace_create_file("saved_tgids", 0444, NULL,...)
|--trace_eval_init()
|--trace_create_eval_file(NULL...)
|--trace_create_file("dyn_ftrace_total_info", 0444, NULL...)
|--create_trace_instances(NULL)
\--update_tracer_options(&global_trace)
初始化阶段将通过fs_initcall(tracer_init_tracefs);来调用tracer_init_tracefs,通过tracer_init_tracefs搭建起tracefs的目录结构
-
tracing_init_dentry:创建/sys/kernel/debug/traing顶级目录
-
event_trace_init:通过create_event_toplevel_files创建events目录下顶层目录;通过__trace_early_add_event_dirs将遍历所有的trace_event_call(通过TRACE_EVENT定义),如果所属的trace_subsystem_dir没有创建则进行创建,这个就是events目录下的各个trace event子系统目录,在此目录下还会创建enable和filter文件节点
-
init_tracer_tracefs:在顶层目录下创建一系列tracer公用文件,包括:available_tracers,current_tracer, tracing_cpumask, trace_options,trace, trace_pipe, buffer_size_kb,buffer_total_size_kb, free_buffer, trace_marker, tracing_on, snapshot, error_log, 每一个文件都单独定义了相应的file_operations
-
ftrace_init_tracefs_toplevel:顶层目录下创建其它ftrace相关文件
-
顶层目录下创建其它的文件
5.1 event_trace_init
event_trace_init()
|--tracefs_create_file("available_events", 0444, NULL,...)
\--early_event_add_tracer(NULL, tr)
|--create_event_toplevel_files(parent, tr)
| | //创建/sys/kernel/debug/tracing/set_event文件
| |--entry = tracefs_create_file("set_event", 0644, parent, tr, &ftrace_set_event_fops);
| | //创建/sys/kernel/debug/tracing/events目录
| |--d_events = tracefs_create_dir("events", parent);
| | //创建/sys/kernel/debug/tracing/events/enable文件
| |--entry = trace_create_file("enable", 0644, d_events,tr, &ftrace_tr_enable_fops);
| | //创建/sys/kernel/debug/tracing/set_event_pid文件
| |--entry = tracefs_create_file("set_event_pid", 0644, parent, tr, ftrace_set_event_pid_fops);
| | //创建/sys/kernel/debug/tracing/set_event_notrace_pid文件
| |--entry = tracefs_create_file("set_event_notrace_pid", 0644, parent,...)
| |--entry = trace_create_file("header_page", 0444,...)
| |--trace_create_file("header_event", 0444, d_events,...)
| // 为启动早期的event创建目录
|--__trace_early_add_event_dirs(tr)
|--list_for_each_entry(file, &tr->events, list)
event_create_dir(tr->event_dir, file)
event_trace_init通过create_event_toplevel_files创建events目录下顶层目录;通过__trace_early_add_event_dirs将遍历所有的trace_event_call(通过TRACE_EVENT定义),如果所属的trace_subsystem_dir没有创建则进行创建,这个就是events目录下的各个trace event子系统目录,在此目录下还会创建enable和filter文件节点
5.2 init_tracer_tracefs
init_tracer_tracefs(&global_trace, NULL)
| //创建/sys/kernel/debug/tracing/available_tracers
|--trace_create_file("available_tracers", 0444, d_tracer,...)
| //创建/sys/kernel/debug/tracing/current_tracer
|--trace_create_file("current_tracer", 0644, d_tracer,...)
| //创建/sys/kernel/debug/tracing/tracing_cpumask
|--trace_create_file("tracing_cpumask", 0644, d_tracer,...)
| //创建/sys/kernel/debug/tracing/trace_options
|--trace_create_file("trace_options", 0644, d_tracer,...)
| //创建/sys/kernel/debug/tracing/trace
|--trace_create_file("trace", 0644, d_tracer,...)
| //创建/sys/kernel/debug/tracing/trace_pipe
|--trace_create_file("trace_pipe", 0444, d_tracer...)
| //创建/sys/kernel/debug/tracing/buffer_size_kb
|--trace_create_file("buffer_size_kb", 0644, d_tracer...)
| //创建/sys/kernel/debug/tracing/buffer_total_size_kb
|--trace_create_file("buffer_total_size_kb", 0444, d_tracer...)
| //创建/sys/kernel/debug/tracing/free_buffer
|--trace_create_file("free_buffer", 0200, d_tracer...)
| //创建/sys/kernel/debug/tracing/trace_marker
|--trace_create_file("trace_marker", 0220, d_tracer...)
| //查找ftrace子系统下的名为print的trace_event_file
|--file = __find_event_file(tr, "ftrace", "print");
|...
init_tracer_tracefs:在顶层目录下创建一系列tracer公用文件
5.3 ftrace_init_tracefs_toplevel
ftrace_init_tracefs_toplevel(&global_trace, NULL);
|--ftrace_init_dyn_tracefs(d_tracer)
| | //创建/sys/kernel/debug/tracing/available_filter_functions目录
| |--trace_create_file("available_filter_functions", 0444,...)
| | //创建/sys/kernel/debug/tracing/enabled_functions目录
| |--trace_create_file("enabled_functions", 0444...)
| | //创建/sys/kernel/debug/tracing/set_graph_function目录
| |--trace_create_file("set_graph_function", 0644, ,...)
| | //创建/sys/kernel/debug/tracing/set_graph_notrace目录
| |--trace_create_file("set_graph_notrace", 0444,...)
|--ftrace_profile_tracefs(d_tracer)
ftrace_init_tracefs_toplevel:顶层目录下创建ftrace相关目录
6. init_blk_tracer
init_blk_tracer
|--register_trace_event(&trace_blk_event)
\--register_tracer(&blk_tracer)
- register_trace_event: 注册trace_blk_event,将trace_event注册到全局event_hash哈希链表;
- register_tracer:注册blk_tracer
参考文档
ftrace - Function Tracer
Linux TraceEvent - 我见过的史上最长宏定义
Linux内核跟踪之trace框架分析