利用ftrace跟踪内核static tracepoint——实例writeback event

本文详细介绍了Linux静态跟踪点的概念、使用方法及在内核模块中的应用,包括如何使用ftrace跟踪静态跟踪点,提供了从基础知识到实际操作的全面指南。

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

摘要:和很多linux内核子系统一样,static tracepoint有很多层次,其中某个层次都展示给不同层次的开发者来满足他们的不同需求。关于linux tracepoint的详细信息,我们可以在linux内核文档Documentation/trace/tracepoints.txt和 samples/tracepoints这两个地方找到。大致来说,对tracepoints本身的定义是第一个层次,一般只有内核开发者才会定义这些tracepoints;trace event是第二个层次,用于debug;第三个层次就是perf这些内核测试工具,他们调用底层的trace events来监控系统内核的某些特性。

一个tracepoint可以理解为一个linux内核中的占位符函数,内核子系统的开发者常常用它们来debug。static表明这些tracepoint的位置是固定的,你可以把它理解位传统C程序中的 #if DEBUG部分。如果在运行时没有开启,它们是不占用任何系统开销的。本文主要为你讲解如何用ftrace来使用这些tracepoint,当然,你也可以使用perf来使用这些tracepoints。

本文来源:http://blog.youkuaiyun.com/trochiluses/article/details/10185951


1.什么是linux static tracepoint


        linux利用Kprobes进行动态跟踪内核调度已经有很长时间了。 Kprobe机制是内核提供的一种调试机制,它提供了一种方法,能够在不修改现有代码的基础上,灵活的跟踪内核函数的执行。它的基本工作原理是:用户指定一个探测点,并把一个用户定义的处理函数关联到该探测点,当内核执行到该探测点时,相应的关联函数被执行,然后继续执行正常的代码路径。动态tracing有很多优点:当disabled的时候是零负载的;probe可以放置在内核中任何一条指令的地方而不是内核开发者所认为的地方。

       所有这些灵活性也有一定的缺点。一个可执行的kprobe有一个显著的负载——因为它利用了breakpoints和exception hadlers。另一个方面是probe的放置地点:kprobes可以很方便的放置在函数入口或者出口,但是如果你需要把probe放置在函数内部或者需要probe局部变量,那么你就需要systemtap和配置了 CONFIG_DEBUG_INFO的自己编译的内核。 从这个角度上看,静态tracepoints可以被放置在函数的任意地方,而且可以越过任意重要的局部变量。linux 2.6.32的主线以后已经实现了比较多的静态tracepoint。

        增加一个静态tracepoint是非常简单的,你可以参考这个例子。在这个例子中,我给已经存在的trace组(irq)增加tracepoint,所以我仅仅需要定义tracepoint和这些tracepoints。你可以在内核的文档linux/samples/trace_events/trace-events-sample.h中找到一个tracepoint定义的五个部分的解释。针对更加复杂的例子,可以参考linux/samples/trace_events/


2.使用linux static tracepoint


2.1挂载debug文件系统

mount  -t  debugfs  nodev  /debug  


2.2查看可供使用的tracepoints

# cat /sys/kernel/debug/tracing/available_events

skb:skb_copy_datagram_iovec
skb:kfree_skb
block:block_rq_remap
block:block_remap
block:block_split
block:block_unplug_io
block:block_unplug_timer

Since we added our tracepoints to the irq group, we can find them in tracing/events/irq:

# ls /sys/kernel/debug/tracing/events/irq/

enable  irq_handler_entry  softirq_entry  tasklet_entry
filter  irq_handler_exit   softirq_exit   tasklet_exit

Enable the tasklet tracepoints:

# echo 1 >  /sys/kernel/debug/tracing/events/irq/tasklet_entry/enable
# echo 1 >  /sys/kernel/debug/tracing/events/irq/tasklet_exit/enable

And the output is available in the trace buffer:

# cat /sys/kernel/debug/tracing/trace

# tracer: nop
#
#           TASK-PID    CPU#    TIMESTAMP  FUNCTION
#              | |       |          |         |
-0     [000]   327.349213: tasklet_entry: func=.rpavscsi_task
-0     [000]   327.349217: tasklet_exit: func=.rpavscsi_task

When finished, we can disable the tracepoints. There are enable files at all levels of the hierarchy, so we can disable all tracepoints in one go:

# echo 0 > /sys/kernel/debug/tracing/events/enable


3.在内核模块中使用静态tracepoints


Kernel modules can also make use of static tracepoints. A simple module that hooks the tasklet_entry tracepoint and printks the function name of the tasklet might look like (I’ve called it tracepoint-example.c):

#include <linux/module.h>
#include <trace/events/irq.h>

static void probe_tasklet_entry(struct tasklet_struct *t)
{
        printk("tasklet_entry %pf\n", t->func);
}

static int __init trace_init(void)
{
        WARN_ON(register_trace_tasklet_entry(probe_tasklet_entry));
        return 0;
}

static void __exit trace_exit(void)
{
        unregister_trace_tasklet_entry(probe_tasklet_entry);
}

module_init(trace_init)
module_exit(trace_exit)
MODULE_LICENSE("GPL");

If you are wondering, %pf is a printk formatter trick to pretty print a function name so you don’t have to go searching for the address in System.map.

Here is a Makefile to go with it:

obj-m := tracepoint-example.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
        $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules


4 利用ftrace跟踪static tracepoints


以下所有操作均在/debug/tracing目录下进行

大致的步骤为:设定要监控的event,实际上也就是要监控的一系列的tracepoints;设定tracer成nop;开启tracing_on;查看结果。


4.1 使用event有两种方式

i) 通过set_event文件
  1. #echo sched_wakeup >> set_event
  2. #echo '!sched_wakeup' >> set_event
  3. #echo > set_event
events被组织成一个个子系统,如ext4,irq,sched等待,一个完整的event name是这样的:<subsystem>:<event>,其中subsystem是可选的,一个子系统中所有的events可以通过<subsystem>:*指定。
  1. #echo *:* > set_event
  2. #echo 'irq:*' > set_event
ii) 通过'enable' toggle
  1. #echo 1 > /sys/kernel/debug/tracing/events/sched/sched_wakeup/enable
  2. #echo 1 > /sys/kernel/debug/tracing/events/sched/enable
  3. #echo 1 > /sys/kernel/debug/tracing/events/enable

实例:我们需要监控writeback子系统,那么需要进行如下设定
$:echo writeback > set_event


4.2开启监控
$:echo nop > current_tracer
$:echo 1 > tracing_on
(wait for a moment......)


4.3结果查看:event trace的意义

$:cat trace | head -20 
输出结果如下:


关于输出结果的意义,可以到对应events下面,利用cat fromat查看输出格式和意义

这里,不以common_开头的filed是每个event特有的。每一个event对应的有一个TRACE_EVENT定义一个record,这里offset为field在record中的偏移,size为其大小。下载一个内核,在samples/trace_events目录下有一个例子,可以查看TRACE_EVENT的定义方法。

4.4event filter
语法:
  1. field-name relation-operatior value                           //一个predicate
可以通过'&&', '||',以及圆括号将几个predicate组合起来。对于数字域,可以使用操作符==, !=, < , > , <=, >=,对于字符串域,可以使用==, !=。目前字符串只支持完全匹配,且最多可以组合16个predicate。
例子:
  1. #cd /sys/kernel/debug/tracing/events/signal/signal_generate
  2. #echo "((sig >= 10 && sig < 15) || sig == 17) && comm != bash" > filter
每个子系统都有独立的filter文件支持,如:
  1. #cd /sys/kernel/debug/tracing/events/sched
  2. #echo 0 > filter
  3. #cat sched_switch/filter
  4. none
  5. #cat sched_wakeup/filter
  6. #echo common_pid == 0 > filter
  7. #cat sched_switch/filter
  8. common_pid == 0
  9. #cat sched_wakeup/filter
  10. common_pid == 0
  11. #echo prev_pid == 0 > filter
  12. #cat sched_switch/filter
  13. prev_pid == 0
  14. #cat sched_wakeup/filter
  15. common_pid == 0
可以看到,虽然将sched子系统的filter设为prev_pid == 0,但由于只有sched_switch事件存在prev_pid域,所以对sched的filter文件的设置只影响了sched_switch。


4.5定义一个event-enabled tracepoint
定义tracepoint不在本文讨论范围之内

参考文档:

【1】Linux Static Tracepoints   http://anton.ozlabs.org/blog/2009/10/07/linux-static-tracepoints/

【2】 linux/Documentation/trace/ftrace.txt

【3】ftrace 学习笔记:http://biancheng.dnbcw.info/linux/340057.html

<think>嗯,用户问的是如何利用Ftrace分析内核函数调用关系,并希望得到具体的命令步骤。首先,我需要回想一下Ftrace的基本用法和它在内核调试中的作用。Ftrace是Linux内核中的一个跟踪工具,主要用于跟踪内核函数的执行情况,帮助开发者分析性能问题和理解代码流程。 用户可能是一位开发人员,或者系统管理员,需要调试内核问题或者优化性能。他们可能已经知道一些基本的调试工具,但想深入使用Ftrace跟踪函数调用。需要确保回答的步骤清晰,适合不同层次用户的理解,特别是具体命令部分,不能有错误。 首先,需要确认用户是否了解如何访问Ftrace的目录,通常是在/sys/kernel/debug/tracing下。然后,可能需要指导他们挂载debugfs文件系统,如果系统没有自动挂载的话。接下来,选择跟踪器,比如function_graph,这样可以看到函数的调用关系。然后设置要跟踪的函数,比如通过echo命令将函数名写入set_ftrace_filter。之后启用跟踪,执行相关操作,最后停止跟踪并查看结果。 需要注意的细节包括权限问题,可能需要root权限;不同的内核版本可能命令略有不同;还要提醒用户跟踪所有函数可能会导致系统负载增加,建议先过滤需要的函数。另外,可能需要解释如何解析输出结果,比如函数调用的层级和时间消耗。 用户可能不仅需要步骤,还希望了解每个步骤的作用,所以在回答中适当解释每个命令的意义是有必要的。比如,echo function_graph > current_tracer是设置使用函数调用图跟踪器,这样输出的信息会更详细,显示函数的进入和退出。 还要考虑用户可能的错误操作,比如写错函数名,或者忘记启用跟踪,导致没有输出。这时候可能需要提醒检查步骤是否正确,或者使用cat命令查看available_filter_functions来确认可用的函数名。 另外,用户可能想知道如何将跟踪结果保存到文件,方便后续分析,所以可以加入重定向输出的步骤,比如cat trace > /path/to/logfile。 最后,总结关键点,确保用户能够按照步骤操作,并理解每个步骤的目的,以及如何根据输出分析函数调用关系。</think>--- ### **Ftrace内核函数调用分析步骤** --- #### **1. 环境准备** ```bash # 挂载debugfs(内核>=2.6.3) sudo mount -t debugfs none /sys/kernel/debug # 进入ftrace目录 cd /sys/kernel/debug/tracing # 查看可用tracer类型 cat available_tracers # 输出示例: function_graph function blk wakeup_dl... ``` --- #### **2. 基础配置** ```bash # 清空现有过滤器 echo > set_ftrace_filter echo > set_ftrace_notrace # 选择function_graph跟踪器 echo function_graph > current_tracer # 设置要跟踪的函数(支持通配符) echo 'vfs_*' > set_ftrace_filter # 跟踪所有vfs_开头的函数 # 或跟踪特定函数 echo __alloc_pages > set_ftrace_filter # 开启函数调用耗时统计 echo 1 > options/funcgraph-abstime # 显示绝对时间 echo 1 > options/funcgraph-proc # 显示进程信息 ``` --- #### **3. 启动跟踪** ```bash # 清空现有trace日志 echo > trace # 开始跟踪 echo 1 > tracing_on # 执行要跟踪的操作(例如触发系统调用) dd if=/dev/zero of=/dev/null bs=1M count=10 # 停止跟踪 echo 0 > tracing_on ``` --- #### **4. 解析跟踪结果** ```bash # 查看原始跟踪数据 cat trace # 典型输出示例: # 1) dd-2377 | | vfs_read() { # 1) dd-2377 | 0.120 us | __fsnotify_parent(); # 1) dd-2377 | 0.345 us | rw_verify_area(); # 1) dd-2377 | | ext4_file_read_iter() { # 1) dd-2377 | | generic_file_read_iter() { ``` --- #### **5. 高级技巧** **过滤特定进程:** ```bash # 查看进程PID pidof sshd # 设置PID过滤(例如跟踪PID为4413的进程) echo 4413 > set_ftrace_pid # 组合使用函数过滤 echo 'tcp_*' > set_ftrace_filter ``` **跟踪内核模块:** ```bash # 加载模块后动态添加跟踪 echo ':mod:nvidia' > set_ftrace_filter # 跟踪nvidia模块所有函数 ``` **保存跟踪记录:** ```bash # 保存到文件(支持文本分析) cat trace > /tmp/ftrace.log # 生成FlameGraph火焰图(需额外工具) awk '/\.\.\./ { if ($3 ~ />/) { depth-- } else { depth++ } } !/\.\.\./ { print $1, depth }' /tmp/ftrace.log > folded.txt ./FlameGraph/flamegraph.pl folded.txt > output.svg ``` --- #### **6. 关键配置项解析** | 配置文件 | 作用描述 | |------------------------|--------------------------------------------------------------------------| | `tracing_on` | 1=启用跟踪,0=停止跟踪 | | `trace_options` | 控制输出格式(如添加`print-parent`显示父函数) | | `buffer_size_kb` | 调整环形缓冲区大小(默认1.4MB,可设为最大数MB) | | `set_ftrace_notrace` | 黑名单过滤(如`echo schedule >> set_ftrace_notrace`忽略调度函数) | --- #### **7. 典型应用场景** 1. **系统调用链路分析** ```bash echo 'SyS_*' > set_ftrace_filter # 跟踪所有系统调用入口 ``` 2. **中断处理延迟分析** ```bash echo 'irq_handler' > set_ftrace_filter echo 1 > options/funcgraph-duration # 显示每个函数耗时 ``` 3. **内存分配路径追踪** ```bash echo '__alloc_pages\|kmem_cache_alloc' > set_ftrace_filter ``` --- ### **注意事项** 1. **性能影响** - 全函数跟踪可能导致**系统性能下降50%以上** - 建议通过`set_ftrace_filter`缩小跟踪范围 2. **权限要求** ```bash sudo chmod 755 /sys/kernel/debug/tracing # 普通用户需要权限 ``` 3. **内核配置验证** ```bash grep CONFIG_FTRACE /boot/config-$(uname -r) # 确保包含: # CONFIG_FTRACE=y # CONFIG_FUNCTION_TRACER=y ``` 通过上述步骤可清晰呈现内核函数调用层级、耗时及执行路径,是分析内核行为的重要调试手段。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值