2、Ftrace核心架构:tracefs文件系统、trace_array、tracer与function tracer的关系
Ftrace 的架构设计遵循“分层解耦”原则,从上到下依次为:用户态接口层(tracefs 文件系统)、核心管理层(trace_array 与 trace 实例)、插件层(各种 tracer 插件)以及 底层钩子层(函数调用跟踪与事件回调)。理解这四层之间的协作关系,是掌握 Ftrace 性能分析能力的基础。
2.1 整体架构分层
Ftrace 的架构可以抽象为以下四个层次,每一层都依赖下一层提供的服务:
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 |
/debug/tracing/ 下,且需要先挂载 debugfs。新内核统一使用 /sys/kernel/tracing/,无需额外挂载。
2.3 trace_array:跟踪实例的核心容器
每个 trace_array 结构体代表一个独立的跟踪实例,包含以下关键组件:
- 环形缓冲区(ring buffer):每个 CPU 一个 per-CPU 缓冲区,用于存储跟踪事件。
- 当前 tracer 指针:指向当前激活的 tracer 插件实例。
- 过滤规则:函数过滤、事件过滤、PID 过滤等。
- 标志位:跟踪开关、快照模式、递归保护等。
- 引用计数:管理实例的生命周期。
默认实例(global_trace)在系统启动时自动创建,用户可以通过 instances/ 目录创建多个独立实例,每个实例拥有独立的缓冲区和配置,互不干扰。
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。它的工作原理如下:
- 编译期插桩:内核编译时,每个函数入口被插入
mcount或fentry指令(取决于架构和内核版本)。 - 动态补丁:Ftrace 在启动时将这些指令替换为
nop(空操作),避免性能开销。 - 激活回调:当用户启用 function tracer 时,Ftrace 将
nop替换为对ftrace_caller的调用。 - 数据采集:
ftrace_caller检查过滤规则,若匹配则记录函数地址、时间戳、进程信息到 ring buffer。 - 用户读取:用户通过
trace或trace_pipe文件读取格式化后的跟踪数据。
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 的一种具体实现 |
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。通过这种父子关系可以重建完整的调用链。