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