2、Ftrace核心架构:tracefs文件系统、trace_array、tracer与function tracer的关系

Ftrace 的架构设计遵循“分层解耦”原则,从上到下依次为:用户态接口层(tracefs 文件系统)、核心管理层(trace_array 与 trace 实例)、插件层(各种 tracer 插件)以及 底层钩子层(函数调用跟踪与事件回调)。理解这四层之间的协作关系,是掌握 Ftrace 性能分析能力的基础。

核心要点: tracefs 是用户与内核交互的“窗口”,trace_array 是每个跟踪实例的“容器”,tracer 是执行具体跟踪逻辑的“引擎”,而 function tracer 是最常用的一种 tracer 实现。

2.1 整体架构分层

Ftrace 的架构可以抽象为以下四个层次,每一层都依赖下一层提供的服务:

Ftrace 核心架构分层图 用户态接口层 tracefs 文件系统(/sys/kernel/tracing/) 核心管理层 trace_array 管理、实例化、缓冲区分配 插件层(Tracer 插件) function / function_graph / wakeup / irqsoff / mmiotrace ... 底层钩子层 mcount / fentry / kprobe / uprobe / tracepoint
架构解读: 用户通过 tracefs 写入控制文件,trace_array 根据配置创建或选择跟踪实例,tracer 插件注册对应的回调函数到底层钩子,最终由 mcount 或 fentry 在函数入口处采集数据。

2.2 tracefs 文件系统:用户态的控制面板

tracefs 是一个轻量级的内存文件系统,通常挂载在 /sys/kernel/tracing/(旧内核为 /debug/tracing/)。它不存储持久化数据,而是通过文件读写操作直接与内核跟踪子系统交互。

文件/目录 作用 典型操作
available_tracers 列出当前内核支持的所有 tracer 插件 cat available_tracers
current_tracer 读取或设置当前激活的 tracer echo function > current_tracer
tracing_on 全局跟踪开关(0 关闭,1 开启) echo 1 > tracing_on
trace 读取跟踪缓冲区内容(人类可读格式) cat trace
trace_pipe 流式读取跟踪数据(阻塞模式) cat trace_pipe
set_ftrace_filter 设置函数过滤白名单 echo 'schedule*' > set_ftrace_filter
set_ftrace_notrace 设置函数过滤黑名单 echo 'unlikely*' > set_ftrace_notrace
instances/ 跟踪实例目录,每个子目录独立配置 mkdir instances/my_trace
注意事项: 在旧内核(4.x 之前)中,tracefs 可能挂载在 /debug/tracing/ 下,且需要先挂载 debugfs。新内核统一使用 /sys/kernel/tracing/,无需额外挂载。

2.3 trace_array:跟踪实例的核心容器

每个 trace_array 结构体代表一个独立的跟踪实例,包含以下关键组件:

  • 环形缓冲区(ring buffer):每个 CPU 一个 per-CPU 缓冲区,用于存储跟踪事件。
  • 当前 tracer 指针:指向当前激活的 tracer 插件实例。
  • 过滤规则:函数过滤、事件过滤、PID 过滤等。
  • 标志位:跟踪开关、快照模式、递归保护等。
  • 引用计数:管理实例的生命周期。

默认实例(global_trace)在系统启动时自动创建,用户可以通过 instances/ 目录创建多个独立实例,每个实例拥有独立的缓冲区和配置,互不干扰。

trace_array 内部结构 trace_array 结构体 tracer 指针 → function_tracer ring_buffer → per-CPU 缓冲区 过滤规则 / 标志位 / 引用计数 tracefs 目录关联(dentry) 实例化关系 global_trace(默认实例) instances/my_trace(用户创建) instances/perf_trace(perf 使用) 每个实例独立 buffer + 独立 tracer

2.4 tracer 插件体系:多态跟踪引擎

Ftrace 的 tracer 采用插件化设计,每个 tracer 都是一个 struct tracer 实例,包含初始化、启动、停止、打印等回调函数。内核启动时会注册所有可用的 tracer,用户通过 current_tracer 文件切换。

Tracer 名称 用途 数据量 典型场景
function 记录函数调用(仅入口) 中等 函数调用频率统计、热点分析
function_graph 记录函数调用链(入口+出口) 较大 调用路径追踪、延迟分析
wakeup 跟踪进程唤醒延迟 较小 调度延迟分析、RT 性能
irqsoff 跟踪中断关闭时间 较小 实时性分析、中断延迟
preemptoff 跟踪抢占关闭时间 较小 抢占延迟分析
mmiotrace 跟踪 MMIO 访问 较大 驱动调试、硬件访问追踪

2.5 function tracer:最基础的调用跟踪器

function tracer 是 Ftrace 中最基础、最常用的 tracer。它的工作原理如下:

  1. 编译期插桩:内核编译时,每个函数入口被插入 mcount 或 fentry 指令(取决于架构和内核版本)。
  2. 动态补丁:Ftrace 在启动时将这些指令替换为 nop(空操作),避免性能开销。
  3. 激活回调:当用户启用 function tracer 时,Ftrace 将 nop 替换为对 ftrace_caller 的调用。
  4. 数据采集:ftrace_caller 检查过滤规则,若匹配则记录函数地址、时间戳、进程信息到 ring buffer。
  5. 用户读取:用户通过 trace 或 trace_pipe 文件读取格式化后的跟踪数据。
function tracer 数据流 用户空间:echo function > current_tracer tracefs 解析 → trace_array 切换 tracer ftrace 核心:将 nop 替换为 ftrace_caller 调用 目标函数执行 mcount → ftrace_caller → 回调 Ring Buffer 记录:pid、函数地址、时间戳

2.6 四者关系总结

tracefs、trace_array、tracer 和 function tracer 之间的关系可以用一句话概括:用户通过 tracefs 操作 trace_array,trace_array 持有并调度 tracer,function tracer 是 tracer 家族中最基础的成员,负责采集函数调用信息。

组件 角色 关键操作 与上下层关系
tracefs 用户态接口 文件读写 暴露 trace_array 的控制接口
trace_array 跟踪实例容器 创建/销毁/切换 tracer 持有 ring buffer 和 tracer 指针
tracer 跟踪逻辑插件 注册/启动/停止/打印 实现具体跟踪语义
function tracer 函数调用跟踪器 记录函数入口 tracer 的一种具体实现
实战要点: 在排查性能问题时,通常先使用 function tracer 统计函数调用频率,定位热点函数;然后切换到 function_graph tracer 查看调用链;最后结合 trace_array 实例隔离不同场景的跟踪数据,避免相互干扰。

2.7 代码示例:手动切换 tracer 并观察

以下操作演示了如何通过 tracefs 接口切换 tracer 并查看效果:

# 查看当前支持的 tracer
cat /sys/kernel/tracing/available_tracers
# 输出示例:function function_graph wakeup irqsoff preemptoff nop

# 切换到 function tracer
echo function > /sys/kernel/tracing/current_tracer

# 确认切换成功
cat /sys/kernel/tracing/current_tracer
# 输出:function

# 开启跟踪
echo 1 > /sys/kernel/tracing/tracing_on

# 等待几秒产生跟踪数据
sleep 2

# 关闭跟踪
echo 0 > /sys/kernel/tracing/tracing_on

# 查看跟踪结果(前 20 行)
head -20 /sys/kernel/tracing/trace

输出示例(简化):

# tracer: function
#
# entries-in-buffer/entries-written: 1423/1423   #P:4
#
#                              _-----=> irqs-off
#                             / _----=> need-resched
#                            | / _---=> hardirq/softirq
#                            || / _--=> preempt-depth
#                            ||| /     delay
#           TASK-PID   CPU#  ||||    TIMESTAMP  FUNCTION
#              | |       |   ||||       |         |
            bash-1234  [000] .... 12345.678901: mutex_lock <-do_fork
            bash-1234  [000] .... 12345.678902: _raw_spin_lock <-mutex_lock
            bash-1234  [000] .... 12345.678903: __cond_resched <-mutex_lock
            bash-1234  [000] .... 12345.678904: _raw_spin_unlock <-mutex_lock
解读技巧: 输出中 FUNCTION 列显示当前函数名,<- 左侧是当前函数,右侧是调用者。例如 mutex_lock <-do_fork 表示 do_fork 调用了 mutex_lock。通过这种父子关系可以重建完整的调用链。