trace系列0 - ftrace初始化

本文详细解析了tracefs在Linux 5.10 kernel中的初始化过程,涉及ftrace、early_trace_init、trace_event_init、tracer_init_tracefs等关键步骤,包括目录创建、事件注册、功能tracer设置和文件系统注册。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


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的通用性吧

  1. ftrace_allocate_pages:为分配尽量连续的物理地址,因此可能会分为多段,每个地址段由一个struct ftrace_page进行管理,多个ftrace_page组成一个链表,每个struct dyn_ftrace代表一个entry,用于描述插入点
    在这里插入图片描述

  2. 之后将通过一个while循环,遍历每一个struct dyn_ftrace,初始化它的ip,此处的ip就是插入指令的地址,也就是bl <_mcount>的地址

  3. 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()
  1. 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等的打印

  2. ring_buf_size是可以调整的,可以通过boot cmdline传递,默认大小为1K

  3. ring_buffer_alloc(PAGE_SIZE, RB_FL_OVERWRITE):通过注释看是为event trigger分配

  4. trace_create_savedcmd:存放cmd与pid的对应关系

  5. tracing_set_clock

  6. register_tracer(&nop_trace):注册nop tracer

  7. 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

  1. type->next = trace_types;trace_types = type将tracer链入全局trace_types链表完成注册
  2. 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的初始化

  1. event_trace_memsetup: trace event slab相关操作, 为ftrace_event_field和trace_event_file创建slab描述符

  2. init_ftrace_syscalls: 系统调用表初始化,主要通过包含头文件及宏定义的方式完成系统调用表的初始化

  3. 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的初始化

  1. sysfs_create_mount_point: 在/sys/kernel下创建tracing挂载目录
  2. 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的目录结构

  1. tracing_init_dentry:创建/sys/kernel/debug/traing顶级目录

  2. 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文件节点

  3. 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

  4. ftrace_init_tracefs_toplevel:顶层目录下创建其它ftrace相关文件

  5. 顶层目录下创建其它的文件

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)
  1. register_trace_event: 注册trace_blk_event,将trace_event注册到全局event_hash哈希链表;
  2. register_tracer:注册blk_tracer

参考文档

ftrace - Function Tracer
Linux TraceEvent - 我见过的史上最长宏定义
Linux内核跟踪之trace框架分析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值