【开发工具】【Ftrace】内核追踪器(ftrace)的使用

本文详细介绍Ftrace在嵌入式开发中的应用,涵盖从基础配置到高级用法,包括功能追踪、事件追踪、过滤器设置及实用案例,助力开发者高效诊断和优化内核性能。

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

获取更多相关的嵌入式开发工具,可收藏系列博文,持续更新中:
【开发工具】嵌入式常用开发工具汇总帖


目录

Ftrace简介

Ftrace的应用场景

Ftrace的配置

Ftrace 的基本使用步骤

Ftrace 的进阶使用

Ftrace 提供的函数使用

简单的 Ftrace 脚本案例


Ftrace简介

Ftrace 是一个内核中的追踪器,用于帮助系统开发者或设计者查看内核运行情况,它可以被用来调试或者分析延迟/性能问题。最早 ftrace 是一个 function tracer,仅能够记录内核的函数调用流程。如今 ftrace 已经成为一个framework,采用 plugin 的方式支持开发人员添加更多种类的 trace 功能。

它使用gcc的-pg选项让内核中的每个函数在执行前都调用一个特殊的函数mcount()。该函数本来是在c库中实现,用于prof跟踪调试性能。内核不链接C库,所以就利用了这个gcc特性。mcount函数必现在汇编中实现,因为调用不遵循正常的C ABI。

如果每个函数都调用这个函数,势必会影响性能,所以内核支持动态开启函数跟踪。配置选项为CONFIG_DYNAMIC_FTRACE。在编译期间,使用了一个脚本将所有的mcount调用都替换为NOP指令,并把位置点记录在一个表里面。当用户通过proc开启函数跟踪功能后,再把NOP替换为mcount调用。


Ftrace的应用场景

  • 模块学习时的函数调用过于复杂,查看code跟的云里雾里,则可以通过function-graph来建立脉络
  • 分析某个过程的函数耗时
  • 排查问题时,追踪某个某个函数的调用情况

Ftrace的配置

PS:内核添加trace配置之后,需要对所有的KO进行重新编译,否则在新的内核里加载老的KO模块会报错

配置config文件,有两种方式:

知道具体添加哪些config,则直接找到对应的config文件,手动写入,然后重新build。

Ftrace 相关的配置选项列表如下:

CONFIG_FUNCTION_TRACER        //使能函数追踪功能,可以用来跟踪函数的调用和返回。
CONFIG_FUNCTION_GRAPH_TRACER  //使能函数图形追踪功能,可以用来跟踪函数之间的调用关系。
CONFIG_CONTEXT_SWITCH_TRACER  //使能上下文切换追踪功能,可以用来跟踪进程之间的切换。
CONFIG_NOP_TRACER             //使能空操作追踪功能,可以用来在不需要追踪的情况下占位。
CONFIG_SCHED_TRACER           //使能调度追踪功能,可以用来跟踪进程的调度情况。
CONFIG_DYNAMIC_FTRACE         //使能动态函数追踪功能,可以在运行时动态地添加和删除函数追踪。
CONFIG_DEBUG_FS               //使能调试文件系统,可以用来在运行时查看内核的调试信息。

通过make menuconfig的方式写入:

    Kernel hacking  --->
        Tracers ─>
            [*]   Kernel Function Tracer
            [*]     Kernel Function Graph Tracer (NEW)
            ...  (下面还有几个追踪器的选项,可以根据自己的需要选择)

Ftrace 通过 debugfs 向用户态提供了访问接口,所以还需要将 debugfs 编译进内核:

    Kernel hacking  --->
        -*- Debug Filesystem

Ftrace 的基本使用步骤

需要关注的部分文件如下:

相关说明:

trace                         :抓取到的文件,可以load到解析工具上作为可视化解析
tracing_on                    :打开关闭trace的开关echo 0 关闭 echo 1打开;
current_tracer                :查看当前追踪器
available_tracers             :支持的tracer,查看当前内核中可用跟踪器
available_events              :支持的events,查看当前内核中可用事件
available_filter_functions    :支持的filter_functions,查看当前内核中可用函数
set_ftrace_pid                :配置需要监测的pid

挂载Debugfs

Ftrace 通过 debugfs 向用户态提供访问接口。配置内核时激活 debugfs 后会创建目录 /sys/kernel/debug ,debugfs 文件系统就是挂载到该目录。

运行时挂载:

//官方挂载方法 :
mount -t debugfs nodev /sys/kernel/debug
//或
mount -t tracefs nodev /sys/kernel/tracing

系统启动自动挂载:

要在系统启动自动挂载debugfs,需要将如下内容添加到 /etc/fstab 文件:
        debugfs  /sys/kernel/debug  debugfs  defaults  0  0

选择一种 tracer

cd /sys/kernel/debug/tracing

cat current_tracer                // 查看当前追踪器

cat available_tracers             // 查看当前内核中可用跟踪器
cat available_events              // 查看当前内核中可用事件
cat available_filter_functions    // 查看当前内核中可用函数

echo function_graph > current_tracer        // 我们选用 function_graph 追踪器

打开关闭追踪
在老一点版本的内核上tracing目录下有tracing_enabled,需要给tracing_enabled和tracing_on同时赋值 1 才能打开追踪,而在比较新的内核上已经去掉 tracing_enabled ,我们只需要控制tracing_on 即可打开关闭追踪。

echo 1 > tracing_on             // 打开跟踪

echo 0 > tracing_on             // 关闭跟踪

echo 1 > tracing_on; run_test; echo 0 > tracing_on          //  打开跟踪后做一件事再关闭跟踪,所以此次跟踪的结果基本

上是这个运行的程序的跟踪结果,但是肯定会包括很多杂讯,所以后面会介绍跟踪某个 pid 的或者跟踪某个函数。
注:对于为什么去掉 tracing_enabled 我问过 Ftrace 的维护人Steven Rostedt,他说使用 tracing_on 可以快速的打开 Ftrace 的追踪,这让 tracing_enabled 显得很轻量级或者说显得比较冗余,下面可以会说到,我们写内核程序时可以使用Ftrace 提供的内核函数 tracing_on() or tracing_off() 直接打开追踪,这其实就是使用的 tracing_on ,所以在新内核中 tracing_enabled 这个看起来比较冗余的选项已经被删除。
查看追踪结果
ftrace 的输出信息主要保存在 3 个文件中。

*   trace,该文件保存 ftrace 的输出信息,其内容可以直接阅读。
*   latency_trace,保存与 trace 相同的信息,不过组织方式略有不同。主要为了用户能方便地分析系统中有关延迟的信息。
*   trace_pipe 是一个管道文件,主要为了方便应用程序读取 trace 内容。算是扩展接口吧。

所以可以直接查看 trace 追踪文件,也可以在追踪之前使用trace_pipe 将追踪结果直接导向其他的文件。
比如: # cat trace_pipe > /tmp/log &     // 使用trace_pipe 将跟踪结果导入 /tmp/log 里,我们可以直接 “ cat /tmp/log” 查看跟踪信息。
当然也可以直接查看trace文件 #cat trace 或者使用 cat trace  > /tmp/log 将跟踪信息导入 /tmp/log

Ftrace的基本用法

cd /sys/kernel/debug/tracing

cat current_tracer                    // 查看当前追踪器
cat available_tracers                 // 查看当前内核中可用跟踪器
cat available_events                  // 查看当前内核中可用事件
cat available_filter_functions        // 查看当前内核中可用函数

echo function > current_tracer        // 选用 function 追踪器,
echo function_graph > current_tracer  // 选用 function_graph 追踪器,
echo [func] > set_ftrace_filter       // 选择追踪指定 [func] 函数的调用栈
echo [pid] > set_ftrace_pid           // 选择追踪指定 [pid] 进程的调用栈

echo 1 > tracing_on                   // 打开跟踪
echo 0 > tracing_on                   // 关闭跟踪
cat trace                             // 查看追踪结果

Ftrace 的进阶使用

追踪指定的进程

使用 echo pid > set_ftrace_pid  来追踪指定的进程!
我们写程序时可以使用getpid 获取进程PID,然后使用 write 将pid 写入 /debug/tracing/set_ftrace_pid ,并使用write 写1 到 tracing_on 打开追踪(因为在用户空间使用不了tracing_on函数),此时即可追踪当前这个进程。

追踪事件
首先查看事件文件夹下面有哪些选项

# ls events/
block    ext4    header_event  jbd2 napi  raw_syscalls…… enable
# ls events/sched/
enable     sched_kthread_stop_ret  sched_process_exit  sched_process_wait ……

追踪一个/若干事件

# echo 1 > events/sched/sched_wakeup/enable
                ...(省略追踪过程)
# cat trace | head -10
# tracer: nop
#TASK-PID  CPU#  TIMESTAMP    FUNCTION
# || | |
bash-2613 [001] 425.078164: sched_wakeup: task bash:2613 [120] success=0 [001]
bash-2613 [001] 425.078184: sched_wakeup: task bash:2613 [120] success=0 [001]

追踪一类事件

# echo 1 > events/sched/enable
                ... 
# cat trace | head -10
# tracer: nop
#TASK-PID            CPU#  TIMESTAMP    FUNCTION
#   |                           |                |                     | 
events/0-9               [000]   638.042792:   sched_switch: task events/0:9 [120] (S) ==> kondemand/0:1305 [120]
ondemand/0-1305   [000]    638.042796:  sched_stat_wait: task: restorecond:1395 wait: 15023 [ns]
...

追踪所有事件

# echo 1 > events/enable
                ...
# cat trace | head -10
# tracer: nop
#TASK-PID     CPU#     TIMESTAMP    FUNCTION
#   |                    |                    |                   | 
cpid-1470       [001]   794.947181:         kfree: call_site=ffffffff810c996d ptr=(null)
acpid-1470     [001] 794.947182:      sys_read -> 0x1
acpid-1470     [001]   794.947183:      sys_exit: NR 0 = 1
...

stack_trace

# echo 1 > /proc/sys/kernel/stack_tracer_enabled
OR # kernel command line “stacktrace”
查看: # cat stack_trace

设置追踪过滤器

将要跟踪的函数写入文件 set_ftrace_filter ,将不希望跟踪的函数写入文件 set_ftrace_notrace。通常直接操作文件 set_ftrace_filter 就可以了. 


Ftrace 提供的函数使用

内核头文件 include/linux/kernel.h 中描述了 ftrace 提供的工具函数的原型,这些函数包括 trace_printk、tracing_on/tracing_off 等。

使用 trace_printk 打印跟踪信息

ftrace 提供了一个用于向 ftrace 跟踪缓冲区输出跟踪信息的工具函数,叫做 trace_printk(),它的使用方式与 printk() 类似。
可以通过 trace 文件读取该函数的输出。从头文件 include/linux/kernel.h 中可以看到,在激活配置 CONFIG_TRACING 后,trace_printk() 定义为宏:

     #define trace_printk(fmt, args...)   \ 
        ...

所以在使用时:(例子是在一个内核模块中添加打印信息)

    #include <linux/init.h> 
     #include <linux/module.h> 
     #include <linux/kernel.h> 
    
     MODULE_LICENSE("GPL"); 
    
     static int ftrace_demo_init(void) 
     { 
         trace_printk("Can not see this in trace unless loaded for the second time\n"); 
         return 0; 
     } 
    
     static void ftrace_demo_exit(void) 
     { 
         trace_printk("Module unloading\n"); 
     } 
    
     module_init(ftrace_demo_init); 

使用 tracing_on/tracing_off 控制跟踪信息的记录

在跟踪过程中,有时候在检测到某些事件发生时,想要停止跟踪信息的记录,这样,跟踪缓冲区中较新的数据是与该事件有关的。
在用户态,可以通过向文件 tracing_on 写入 0 来停止记录跟踪信息,写入 1 会继续记录跟踪信息。
而在内核代码中,可以通过函数 tracing_on() 和 tracing_off() 来做到这一点,
它们的行为类似于对 /sys/kernel/debug/tracing 下的文件 tracing_on 分别执行写 1 和 写 0 的操作。
使用这两个函数,会对跟踪信息的记录控制地更准确一些,这是因为在用户态写文件 tracing_on 到实际暂停跟踪,
中间由于上下文切换、系统调度控制等可能已经经过较长的时间,这样会积累大量的跟踪信息,而感兴趣的那部分可能会被覆盖掉了。
实际代码中,可以通过特定条件(比如检测到某种异常状况,等等)来控制跟踪信息的记录,函数的使用方式类似如下的形式:
 if (condition) 
tracing_on() or tracing_off()
跟踪模块运行状况时,使用 ftrace 命令操作序列在用户态进行必要的设置,而在代码中则可以通过 traceing_on() 控制在进入特定代码区域时开启跟踪信息,
并在遇到某些条件时通过 tracing_off() 暂停;读者可以在查看完感兴趣的信息后,将 1 写入 tracing_on 文件以继续记录跟踪信息。
实践中,可以通过宏来控制是否将对这些函数的调用编译进内核模块,这样可以在调试时将其开启,在最终发布时将其关闭。
用户态的应用程序可以通过直接读写文件 tracing_on 来控制记录跟踪信息的暂停状态,以便了解应用程序运行期间内核中发生的活动。


简单的 Ftrace 脚本案例

我在下面使用一个脚本执行 Ftrace 的操作,自动追踪 HelloWorld 程序执行

#!/bin/bash
debugfs_path=/debug                     
ftrace_call_path=$debugfs_path/tracing
app_path=/home/dslab/test

# 测试我们的 /debug 路径是否已经建立
if ! test -d $debugfs_path; then
        echo "I'll create the directory /debug"
        mkdir $debugfs_path
else echo "Hum ,the /debug directory is standing by"
fi 

# 测试debugfs是否已经挂载
mount | grep "debugfs" > /dev/null
if [ $? != 0 ]; then
        echo "we have not mount debugfs"
        mount -t debugfs nodev $debugfs_path
else echo "yeah,debugfs has been mounted"
fi

# 测试ftrace 是否可用
if ! test -d $ftrace_call_path; then
        echo "sorry, may be your kernel is not support for the ftrace"
        exit -1;
else echo "Hum ,Ftrace is standing by"
fi        
# 将目前的trace 文件清空
echo "" >  $ftrace_call_path/trace
# 选择 function_graph 跟踪器
echo "function_graph" > $ftrace_call_path/current_tracer
# 我的Debian7 使用的3.2 内核上还有tracing_enabled ,先给它赋值 1
echo 1 > $ftrace_call_path/tracing_enabled
# 使用 trace_pipe 将追踪结果重定向到/tmp/result(后台运行,使得下面的步骤得以进行)
cat $ftrace_call_path/trace_pipe > /tmp/result &
# 打开追踪
echo 1 > $ftrace_call_path/tracing_on
# 我在这里执行了一个已经编译好helloworld 程序
exec $app_path/hello
# 关闭追踪
echo 0 > $ftrace_call_path/tracing_on

运行效果如下:
dslab@wheezy:~$ sudo ./ftrace.sh
[sudo] password for dslab: 
Hum ,the /debug directory is standing by
yeah,debugfs has been mounted
Hum ,Ftrace is standing by
Hello World
dslab@wheezy:~$ 

本文转载自:Linux内核调试工具 Ftrace 进阶使用手册_trace_pipe里面的[000]d.h2打印是啥-CSDN博客

因为对方已经写的非常详细了,暂时没有什么需要加的内容,就直接转载了.

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值