2. Ftrace架构:核心组件与文件系统

好,咱们来聊聊Ftrace的架构。说实话,我第一次接触Ftrace时,也被它那一堆文件系统和组件搞得有点懵。但摸清楚之后,你会发现它的设计其实非常优雅——说白了,就是一套「插桩-收集-输出」的流水线。

2.1 三大核心组件

Ftrace的核心组件就三个:function tracer、function_graph tracer 和 event tracer。我习惯把它们比作三种不同精度的「探针」。

2.1.1 function tracer

这是最基础的追踪器。它会在每个内核函数的入口和出口处插入钩子。你想想看,这意味着什么?意味着你能看到系统里所有内核函数的调用情况。

关键特性:

  • 记录每个函数的调用次数
  • 记录每个函数的执行时间(纳秒级)
  • 支持动态过滤(只追踪你关心的函数)

我在项目中遇到过一个问题:某个驱动模块加载后,系统响应突然变慢。用function tracer一查,发现某个函数被调用了上百万次——嗯,问题一下就找到了。

2.1.2 function_graph tracer

这个就更有意思了。它不仅能记录函数调用,还能画出调用关系图。说白了,就是告诉你「谁调了谁,花了多长时间」。

# 启用function_graph tracer
echo function_graph > /sys/kernel/tracing/current_tracer

# 查看输出
cat /sys/kernel/tracing/trace

# 输出示例:
# 2)               |  do_sys_open() {
# 2)               |    getname() {
# 2)   0.120 us    |      kmem_cache_alloc();
# 2)   0.080 us    |      getname_flags();
# 2)   1.200 us    |    }
# 2)   0.050 us    |    do_filp_open();
# 2)   3.500 us    |  }

看到那个缩进和花括号了吗?这就是调用关系的可视化。每个函数的执行时间都清清楚楚。我曾经用这个工具定位过一个网络驱动的性能瓶颈——一个看似简单的函数,内部竟然嵌套了7层调用,耗时超过100微秒。

我的经验:function_graph tracer的开销比function tracer大,因为它要维护调用栈。生产环境慎用,但调试阶段非常好使。

2.1.3 event tracer

event tracer是另一种思路。它不追踪函数调用,而是追踪内核中预定义的「事件点」。比如:

  • 系统调用事件(sys_enter/sys_exit)
  • 调度事件(sched_switch, sched_wakeup)
  • 中断事件(irq_handler_entry, irq_handler_exit)
  • 内存管理事件(mm_page_alloc, mm_page_free)

你想想看,这些事件点就像是内核里的「监控摄像头」。每个摄像头只拍一个特定场景,但组合起来就能还原出完整的系统行为。

# 启用调度事件
echo 1 > /sys/kernel/tracing/events/sched/enable

# 查看调度记录
cat /sys/kernel/tracing/trace

# 输出示例:
# <idle>-0     [000] d..2.  123.456789: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=kworker/0:1 next_pid=45 next_prio=120

2.2 tracefs文件系统

好了,组件说完了,那怎么用呢?答案就是tracefs。这是一个虚拟文件系统,通常挂载在 /sys/kernel/tracing/sys/kernel/debug/tracing 下。

我个人习惯用 /sys/kernel/tracing,因为它是专门为tracing设计的,更干净。

2.2.1 核心文件一览

文件/目录 作用 我常用的操作
current_tracer 设置当前使用的追踪器 echo function_graph > current_tracer
trace 读取追踪结果 cat trace | head -100
tracing_on 开关追踪(1开0关) echo 0 > tracing_on(暂停)
set_ftrace_filter 设置函数过滤白名单 echo 'do_sys*' > set_ftrace_filter
set_ftrace_notrace 设置函数过滤黑名单 echo 'schedule' > set_ftrace_notrace
events/ 事件追踪的配置目录 echo 1 > events/sched/enable
buffer_size_kb 追踪缓冲区大小 echo 4096 > buffer_size_kb

注意:tracefs中的文件大多是伪文件,你不能用vim去编辑它们。只能用echo/cat这种简单的读写操作。我曾经见过有人用编辑器打开current_tracer,结果把系统搞挂了——嗯,血的教训。

2.2.2 典型工作流程

我一般按这个步骤来:

  1. 清空缓冲区:echo > trace(避免旧数据干扰)
  2. 设置过滤器:echo 'my_function*' > set_ftrace_filter
  3. 选择追踪器:echo function_graph > current_tracer
  4. 触发问题场景:运行你的测试程序或操作
  5. 读取结果:cat trace
  6. 关闭追踪:echo nop > current_tracer

这里有个小技巧:我习惯在步骤4之前先 echo 0 > tracing_on,等场景触发时再 echo 1 > tracing_on。这样可以精确控制采集窗口,避免收集到大量无关数据。

2.3 组件之间的关系

这三个组件不是互斥的,它们可以协同工作。比如:

  • 用function tracer做全局扫描,找到可疑函数
  • 用function_graph tracer深入分析该函数的调用链
  • 用event tracer查看相关事件(比如调度、中断)的上下文

说白了,这就是一个「由粗到细」的排查过程。我在定位一个数据库的延迟抖动问题时,就是按这个思路来的——先用function tracer发现某个锁函数调用频繁,再用function_graph看到锁的持有时间异常,最后用event tracer确认是中断导致的任务抢占。

避坑指南:我曾经同时开启function tracer和event tracer,结果系统直接卡死了。原因是缓冲区太小,数据量太大。所以我的建议是:一次只开一个追踪器,或者至少把buffer_size_kb调大(比如16MB以上)。

好了,Ftrace的架构就聊到这里。记住这三个组件和tracefs的核心文件,后面的章节我们会深入每个组件的具体用法。你想想看,掌握了这些,内核调度对你来说还有秘密吗?