ftrace

阅读数:699 评论数:0

跳转到新版页面

分类

Linux

正文

一、概述 

在日常工作中,经常会需要对内核进行Debug、或者进行优化工作。一些简单的问题,可以通过dmesg/printk查看,优化借助一些工具进行。

但是当问题逻辑复杂,优化面宽泛的时候,往往无从下手。

需要从上到下、模块到模块之间分析,这时候就不得不借助于Linux提供的静态(Trace Event)动态(各种Tracer)进行分析。

ftrace是Function Trace的意思,最开始主要用于记录内核函数运行轨迹,随着功能的逐渐增加,演变成一个跟踪框架。

ftrace的帮助文档在Documentation/trace,ftrace代码主要在kernel/trace,ftrace相关头文件在include/trace中。

二、ftrace框架介绍

整个ftrace框架可以分为几个部分:ftrace核心框架,RingBuffer,debugfs,Tracepoint,各种Tracer。

ftrace核心框架是整个ftrace功能的纽带,包括对内核的修改,Tracer的注册,RingBuffer的控制等。

RingBuffer是静态动态ftrace的载体。

debugfs则提供了用户空间对ftrace设置接口。

tracepoint是静态trace,它需要提前编译进内核,可以定制打印内容,自由添加,并且内核对主要subsystem提供了tracepoint.

tracer有很多种,主要几大类:

(1)函数类:function, functin_graph, stack

(2)延时类:irqsoff, preemptoff, preempirqsoff, wakeup, wakeup_rt, wakeup_dl

(3)其他类:nop, mmiotrace, blk

三、ftrace核心初始化

trace.c是ftrace的核心,包括三个initcall:tracer_alloc_buffers、trace_init_debugfs、 clear_boot_tracer。

start_kernel-->
    trace_init-->
        tracer_alloc_buffers--------------------分配ftrace需要的RingBuffer            register_tracer(&nop_trace)---------默认nop跟踪器
        trace_event_init------------------------创建静态Trace Event,进行初始化。

fs_initcall(tracer_init_debugfs);-->    tracing_init_dentry--------------------------在sys/kernel/debug下,创建tracing目录
    init_tracer_debugfs--------------------------        ftrace_create_function_files-------------创建主要的tracing目录下节点            allocate_ftrace_ops------------------function_trace_call            ftrace_create_filter_files-----------创建function tracer相关的节点set_ftrace_filter/set_ftrace_notrace
    trace_create_file----------------------------创建saved_cmdlines等
    create_trace_instances-----------------------创建tracing/instances/目录
    create_trace_options_dir---------------------创建tracing/optoins/目录    tracing_init_debugfs_percpu------------------创建per_cpu目录
late_initcall(clear_boot_tracer);

tracer_alloc_buffers主要申请一个最小1KB的RingBuffer,然后注册一些Notifier和初始化一些列表。

在trace_init->trace_event_init->event_trace_eanble中,已经创建了tracing/events下的节点,并且做好了准备工作

void __init trace_event_init(void)
{
/*创建field_cachep、file_cachep高速缓存*/
    event_trace_memsetup();
/*对所有系统调用如后保存在syscalls_metadata*/
    init_ftrace_syscalls();
/*在系统启动阶段初始化Trace Event,在debug创建后在附着到上面*/
    event_trace_enable();
}

其他event相关初始化如下

early_initcall(event_trace_enable_again);----------在trace_event_init已经调用过early_enable_events,这里在early_initcall再次使能。
fs_initcall(event_trace_init);---------------------创建available_events节点

四、function跟踪器

function tracer的初始化通过init_function_trace建立

core_initcall(init_function_trace);

init_function_trace在init_func_cmd_traceon中,创建一系列命令:traceon/ traceoff/ stacktrace/ dump/ cpudump。然后注册function_trace这个tracer。

Tracer结构体如下

static struct tracer function_trace __tracer_data =
{
    .name        = "function",
    .init        = function_trace_init,--------------通过echo function > current_tracer触发
    .reset        = function_trace_reset,------------通过echo 1 >tracing_on触发
    .start        = function_trace_start,------------通过echo 0 >tracing_off触发
    .flags        = &func_flags,
    .set_flag    = func_set_flag,
    .allow_instances = true,
#ifdef CONFIG_FTRACE_SELFTEST
    .selftest    = trace_selftest_startup_function,
#endif
}

function_trace_init进行function tracer相关设置。

五、主要代码分析

内核中不同功能需要有序初始化,但是相同等级的顺序是没有保证的。Linux ftrace相关的模块众多,使用了不同等级的initcall。

从下面定义可以看出它们在内核启动时的调用顺序、模块等级在include/linux/init.h中定义

#define __define_initcall(level,fn,id) \
    static initcall_t __initcall_##fn##id __used \
    __attribute__((__section__(".initcall" level ".init"))) = fn

/*
 * Early initcalls run before initializing SMP.
 *
 * Only for built-in code, not modules.
 */
#define early_initcall(fn)        __define_initcall("early",fn,early)--------------------所谓的early就是在初始化SMP之前调用

/*
 * A "pure" initcall has no dependencies on anything else, and purely
 * initializes variables that couldn't be statically initialized.
 *
 * This only exists for built-in code, not for modules.
 */
#define pure_initcall(fn)        __define_initcall("0",fn,0)

#define core_initcall(fn)        __define_initcall("1",fn,1)
#define core_initcall_sync(fn)        __define_initcall("1s",fn,1s)
#define postcore_initcall(fn)        __define_initcall("2",fn,2)
#define postcore_initcall_sync(fn)    __define_initcall("2s",fn,2s)
#define arch_initcall(fn)        __define_initcall("3",fn,3)
#define arch_initcall_sync(fn)        __define_initcall("3s",fn,3s)
#define subsys_initcall(fn)        __define_initcall("4",fn,4)
#define subsys_initcall_sync(fn)    __define_initcall("4s",fn,4s)
#define fs_initcall(fn)            __define_initcall("5",fn,5)
#define fs_initcall_sync(fn)        __define_initcall("5s",fn,5s)
#define rootfs_initcall(fn)        __define_initcall("rootfs",fn,rootfs)
#define device_initcall(fn)        __define_initcall("6",fn,6)----------------------------对应module_init
#define device_initcall_sync(fn)    __define_initcall("6s",fn,6s)
#define late_initcall(fn)        __define_initcall("7",fn,7)
#define late_initcall_sync(fn)        __define_initcall("7s",fn,7s)

六、ftrace的配置和使用

/sys/kernel/debug/tracing目录下提供了ftrace的设置和属性接口,对ftrace的配置可以通过echo。

此目录:

README------------------------一个简单的关于Tracepoing的HOWTO,cat读取,echo设置。

通用配置:
available_tracers-------------当前编译及内核的跟踪器列表,current_tracer必须是这里面支持的跟踪器。
current_tracer----------------用于设置或者显示当前使用的跟踪器列表。系统启动缺省值为nop,使用echo将跟踪器名字写入即可打开。可以通过写入nop重置跟踪器。

buffer_size_kb----------------用于设置单个CPU所使用的跟踪缓存的大小。跟踪缓存为RingBuffer形式,如果跟踪太多,旧的信息会被新的跟踪信息覆盖掉。需要先将current_trace设置为nop才可以。
buffer_total_size_kb----------显示所有的跟踪缓存大小,不同之处在于buffer_size_kb是单个CPU的,buffer_total_size_kb是所有CPU的和。

free_buffer-------------------此文件用于在一个进程被关闭后,同时释放RingBuffer内存,并将调整大小到最小值。
hwlat_detector/
instances/--------------------创建不同的trace buffer实例,可以在不同的trace buffers中分开记录。

tracing_cpumask---------------可以通过此文件设置允许跟踪特定CPU,二进制格式。
per_cpu-----------------------CPU相关的trace信息,包括stats、trace、trace_pipe和trace_pipe_raw。
                  stats:当前CPU的trace统计信息
                  trace:当前CPU的trace文件。
                  trace_pipe:当前CPU的trace_pipe文件。
printk_formats----------------提供给工具读取原始格式trace的文件。
saved_cmdlines----------------存放pid对应的comm名称作为ftrace的cache,这样ftrace中不光能显示pid还能显示comm。
saved_cmdlines_size-----------saved_cmdlines的数目
snapshot----------------------是对trace的snapshot。
                              echo 0清空缓存,并释放对应内存。
                              echo 1进行对当前trace进行snapshot,如没有内存则分配。
                              echo 2清空缓存,不释放也不分配内存。

trace-------------------------查看获取到的跟踪信息的接口,echo > trace可以清空当前RingBuffer。
trace_pipe--------------------输出和trace一样的内容,但是此文件输出Trace同时将RingBuffer中的内容删除,这样就避免了RingBuffer的溢出。可以通过cat trace_pipe > trace.txt &保存文件。

trace_clock-------------------显示当前Trace的timestamp所基于的时钟,默认使用local时钟。local:默认时钟;可能无法在不同CPU间同步;global:不同CUP间同步,但是可能比local慢;counter:这是一个跨CPU计数器,需要分析不同CPU间event顺序比较有效。
trace_marker------------------从用户空间写入标记到trace中,用于用户空间行为和内核时间同步。
trace_marker_raw--------------以二进制格式写入到trace中。

trace_options-----------------控制Trace打印内容或者操作跟踪器,可以通过trace_options添加很多附加信息。
options-----------------------trace选项的一系列文件,和trace_options对应。

trace_stat/-------------------每个CPU的Trace统计信息
tracing_max_latency-----------记录Tracer的最大延时,
tracing_on--------------------用于控制跟踪打开或停止,0停止跟踪,1继续跟踪。
tracing_thresh----------------延时记录Trace的阈值,当延时超过此值时才开始记录Trace。单位是ms,只有非0才起作用。

Events配置:
available_events--------------列出系统中所有可用的Trace events,分两个层级,用冒号隔开。
events/-----------------------系统Trace events目录,在每个events下面都有enable、filter和fotmat。enable是开关;format是events的格式,然后根据格式设置 filter。
set_event---------------------将Trace events名称直接写入set_event就可以打开。
set_event_pid-----------------指定追踪特定进程的events。

Function配置:
available_filter_functions----记录了当前可以跟踪的内核函数,不在该文件中列出的函数,无法跟踪其活动。
dyn_ftrace_total_info---------显示available_filter_functins中跟中函数的数目,两者一致。
enabled_functions-------------显示有回调附着的函数名称。
function_profile_enabled------打开此选项,在trace_stat中就会显示function的统计信息。
set_ftrace_filter-------------用于显示指定要跟踪的函数
set_ftrace_notrace------------用于指定不跟踪的函数,缺省为空。
set_ftrace_pid----------------用于指定要追踪特定进程的函数。

Function graph配置:
max_graph_depth---------------函数嵌套的最大深度。
set_graph_function------------设置要清晰显示调用关系的函数,在使用function_graph跟踪器是使用,缺省对所有函数都生成调用关系。
set_graph_notrace-------------不跟踪特定的函数嵌套调用。

Stack trace设置:
stack_max_size----------------当使用stack跟踪器时,记录产生过的最大stack size
stack_trace-------------------显示stack的back trace
stack_trace_filter------------设置stack tracer不检查的函数名称

Kernel dynamic events:
kprobe_events
kprobe_profile

Userspace dynamic events:
uprobe_events
uprobe_profile

通用配置

1、开关和配置大小

echo 0/1 > /sys/kernel/debug/tracing/tracing_on

设置RingBuffer大小(buffer_size_kb),同时buffer_total_size_kb就变成NR_CPUS的倍数。

2、trace、trace_pipe和snapshot的区别

trace是从RingBuffer中读取内容,trace_pipe会一直读取Buffer流。snapshot是trace的一个瞬间快照。

echo 0 > snapshot : Clears and frees snapshot buffer
echo 1 > snapshot : Allocates snapshot buffer, if not already allocated.
                      Takes a snapshot of the main buffer.
echo 2 > snapshot : Clears snapshot buffer (but does not allocate or free)
                      (Doesn't have to be '2' works with any number that
                       is not a '0' or '1')

3、Tracer

从available_tracers可以获取系统支持的tracer,current_tracer是当前使用的tracer,Events只有在nop tracer下才会起作用,同时多个tracer不能共享。同一时候只能一个tracer在生效

cat available_tracers

hwlat blk mmiotrace function_graph wakeup_dl wakeup_rt wakeup function nop

echo function > current_tracer

4、instances

在instances目录下,可以通过mkdir创建instance,rmdir删除instance。新目录下,有很多类似tracing下的文件,可以对其进行配置。然后读取专有的trace/trace_pipe。

5、特定cpu信息

抓取特定cpu信息

echo f > tracing_cpumask

查看特定cpu信息

cat per_cpu/cpu3/trace

6、用户空间插入trace标记

有时候需要往Trace中插入标记,trace_marker/trace_marker_raw提供了这样功能。

    echo CAPTURE_START > trace_marker

    echo CAPTURE_START > trace_marker_raw

7、trace选项设置

通过options内容设置,对Trace的输出进行定制,控制输出大小。

trace_option是options设置的结果,可以看出开了哪些选项,关闭了哪些选项。

echo 0/1 > options/irq-info

tracepoint

可以对系统特定事件进行跟踪,在available_events可以找到所有事件。然后将需要的时间通过echo xxx >> set_event写入。也可以通过events目录来打开。

1、trace events生效条件

在current_tracer为nop,然后设置tracing/events下面的enable,即可通过tracing/trace或者tracing/trace_pipe查看内容。

2、trace event的过滤功能

禁用在事件前面加!

过滤的表达式是:field-name relational-operator value,多表达式可以通过逻辑运算符&&或者||来组合。

数字可以通过==、!=、>、<、&&、||等等来组合filter,来过滤掉很多不需要信息。

文字可以通过==、!=、~l来组合filter。

echo "!sys_enter_nic" >> set_event
echo net >set_event--------------------------------打开所有net目录下的事件
echo skb >>set_event------------------------------附加设置skb到目录下

将对应pid写入set_event_pid,就可达到只监控某个进程的Events。

对子系统的filter写入0,即可清空整个子系统的filter

function跟踪器的配置

1、打开

echo function > /sys/kernel/debug/tracing/current_tracer

2、在trace_stat中显示function的统计信息

trace_stat/function0在系统初始化就创建,通过function_profile_enabled进行开关。

统计的函数在set_ftrace_filter和set_ftrace_notrace中设置。

在使能function优化功能之后,可以查看不同CPU下每个函数执行时间统计信息。

每列表示的内容分别是:函数名称、调用次数、总耗时、平均耗时、耗时均方差

cat trace_stat/function0

3、过滤

在打开CONFIG_DYNAMIC_FTRACE的情况下,增加一些动态跟踪功能,比如available_filter_functions、set_ftrace_filter、set_ftrace_notrace

set_ftrace_filter跟踪某些函数

1. 默认情况下set_ftrace_filter是全部函数都开的。
   cat set_ftrace_filter如下:
   #### all functions enabled ####

2. 如果想只监控某些函数,通过echo mod_timer add_timer > set_ftrace_filter即可。
   cat set_ftrace_filter如下:
   mod_timer
   add_timer   如果要附加function,通过echo xxx >> set_ftrace_filter即可。3. 使用通配符*  echo "sched*" > set_ftrace_filter----------------选择所有以sched开头的函数  echo "*sched*" > set_ftrace_filter---------------选择所有包含sched的函数  echo "*sched" > set_ftrace_filter----------------选择所有以sched结尾的函数
4. 如果想恢复全开,只需要echo > set_ftrace_filter,即清空filter。

set_ftrace_notrace指定不跟踪哪个函数

set_ftrace_pid只跟踪某一个进程

function_graph跟踪器的配置

function_graph和function类似,但是function跟踪器只在函数入口点探测,而function_graph在函数入口和退出都进行探测。

function_graph提供了类似C语言函数调用关系图,并且记录了函数执行耗时。

echo function_graph > /sys/kernel/debug/tracing/current_tracer。

function_graph没有设置pid,但是可以设置跟踪哪些函数,不跟踪那些函数:

    echo xxx > set_graph_function

    echo xxx > set_graph_notrace

设置function_graph嵌套层数:

echo 10 > max_graph_depth

irqsoff/preemtoff/premptirqsoff跟踪器配置

中断屏蔽和强占禁止非常影响系统性能,所以对中断屏蔽和强占禁止进行统计监控,发现异常点很有必要。

当中断被屏蔽后,CPU无法响应外部事件(除了不可屏蔽中断NMI和系统管理中断SMI)。这就会阻止比如系统Tick中断或者键盘中断,导致响应时间变长。

同样强占禁止,我们还可以收到中断,但是任务强占被禁止导致更高优先级的任务得不到调度,直到强占被再次允许。

echo 0 > options/function-trace
echo irqsoff > current_tracer
echo 1 > tracing_on
echo 0 > tracing_max_latency
 [...]
echo 0 > tracing_on
cat trace

wakeup/ wakeup_rt/ wakeup_dl跟踪器配置

wakeup类调度器记录调度延时,也即从系统被唤醒到被调度到的延时。显示的结果类似irqsoff跟踪器。

echo 0 > options/function-trace
echo wakeup > current_tracer
echo 1 > tracing_on
echo 0 > tracing_max_latency
chrt -f 5 sleep 1
echo 0 > tracing_on
cat trace

akeup:显示进程从waken到wake up的延时,包括所有进程。

wakeup_dl:显示SCHED_DEADLINE类型调度延时。

wakeup_rt:显示实时进程的调度延时。

stack跟踪器配置

stack跟踪器用于追踪内核栈的使用情况,它记录了每个内核对栈的使用情况。

stack跟踪器比较特殊,它的开关不在tracing目录:

Depth    Size   Location    (-1 entries)
        -----    ----   --------
#
#  Stack tracer disabled
#
# To enable the stack tracer, either add 'stacktrace' to the
# kernel command line
# or 'echo 1 > /proc/sys/kernel/stack_tracer_enabled'
#

然后通过/sys/kernel/debug/tracing/stack_trace可以查看堆栈轨迹。

 

-

ftrace的mcount功能是如何实现的

在c文件编译完之后,recordmount增加一个__mcount_loc段,在vmlinux.lds.h文件中对__mcount_loc段归集,在系统初始化的时候有两个参数很重要__start_mcount_loc和__stop_mcount_loc。

#define MCOUNT_REC()    . = ALIGN(8);                \
            VMLINUX_SYMBOL(__start_mcount_loc) = .; \
            *(__mcount_loc)                \
            VMLINUX_SYMBOL(__stop_mcount_loc) = .;



/* init and exit section handling */
#define INIT_DATA                            \
    *(.init.data)                            \
...
    KERNEL_CTORS()                            \
    *(.init.rodata)                            \
    MCOUNT_REC()                            \
...
    KERNEL_DTB()


#define INIT_DATA_SECTION(initsetup_align)                \
    .init.data : AT(ADDR(.init.data) - LOAD_OFFSET) {        \
        INIT_DATA                        \
...
    }

引入ftrace对性的影响有多大

在启动阶段,SMP初始化之前,ftrace扫描__mcount_loc段,将所有入口地址mcount使用nop替代。这样只要不打开,开销非常小,基本上不产生性能影响。

当用户打开 ftrace 功能时,ftrace 将这些 nop 指令动态替换为 ftrace_caller,该函数将调用用户注册的 trace 函数。

ftrace相关工具

trace-cmd和kernelshark

 




相关推荐

mainline 、longterm、stable、linux-next、snapshot这些名词,都是linux kernel sou

__CONCAT,连接两个参数,##用于粘贴两个参数,#用于替换参数 #define __CONCAT(a, b) a ##

<img src="http://123.56.17.129:2177/images/linux_kernel.png" alt="" /

一、概述 在 Linux 内核中,tracepoints 提供了一种机制来监控和记录系统运行时的特定点的信息,这对于调试和性能分析是非常有用的。Tracepoints 被设计为对系统性能的影响最小,即

在编程中,一个符号symbol是一个程序的创建块,它是一个变量名或一个函数名。 内核符号表(Kernel Symbol Table) 内核并不使用符号名。它是通过

在Kernel_path/Makefile中可以查看到 # SPDX-License-Identifier: GPL

一、概述 在 Linux 内核中,printk 是一个类似于用户空间 C 语言中的 printf 函数的内核级日志记录功能。它用于内核代码中输出调试信息到控制台或系统日志。printk 可以在几乎任何

一、概述 在 Linux 内核编程中,asmlinkage 是一个关键字,用于告诉编译器该函数将通过堆栈而不是寄存器接收所有的参数。这个宏主要用于那些需要从用户空间通过系统调用接口调用的函数。在系统调

上图是Intel手册中对标志寄存器的图示,几个

一、概述 自从Linux内核代码迁移到Git以来,Linux内核配置/构建系统(也称为Kconfig / kbuild)已存在很长时间了。 二、Kconfig (Kernel config) menu