2. ftrace 基础架构:框架设计、tracefs 文件系统、核心数据结构与环形缓冲区
ftrace 是 Linux 内核内置的追踪器,其设计遵循“低开销、可扩展、易用”的原则。理解 ftrace 的基础架构,是掌握其高级用法的前提。本章将深入剖析 ftrace 的框架设计、用户态接口 tracefs、核心数据结构以及数据存储的基石——环形缓冲区。
2.1 ftrace 框架设计:分层与插件化
ftrace 的整体架构采用分层设计,从上到下依次为:用户态接口层、核心框架层和底层硬件/软件事件层。这种分层设计使得 ftrace 能够灵活地支持多种追踪器(如 function、function_graph、trace_printk 等),并保持核心代码的稳定性。
2.2 tracefs 文件系统:用户态的控制枢纽
tracefs 是一个专门为 ftrace 设计的虚拟文件系统,通常挂载在 /sys/kernel/tracing/ 目录下。用户通过读写该目录下的文件,即可控制 ftrace 的行为、配置追踪器、读取追踪结果。
mount -t tracefs tracefs /sys/kernel/tracing/
2.2.1 核心控制文件
tracefs 目录下包含数十个文件,以下列出最常用的核心控制文件及其功能:
| 文件名 | 功能描述 | 典型操作 |
|---|---|---|
available_tracers |
列出当前内核支持的追踪器列表 | cat available_tracers |
current_tracer |
设置或查看当前激活的追踪器 | echo function > current_tracer |
tracing_on |
全局追踪开关(1 开启,0 关闭) | echo 1 > tracing_on |
trace |
读取追踪结果(文本格式) | cat trace |
trace_pipe |
以流式读取追踪结果(阻塞读取) | cat trace_pipe |
set_ftrace_filter |
设置函数过滤规则(白名单) | echo 'schedule' > set_ftrace_filter |
set_ftrace_notrace |
设置函数过滤规则(黑名单) | echo 'lock*' > set_ftrace_notrace |
buffer_size_kb |
设置每个 CPU 的环形缓冲区大小(KB) | echo 1024 > buffer_size_kb |
2.2.2 典型操作流程
以下是一个使用 tracefs 进行函数追踪的典型流程:
- 查看可用追踪器:
cat /sys/kernel/tracing/available_tracers - 设置追踪器:
echo function > /sys/kernel/tracing/current_tracer - 设置过滤规则(可选):
echo 'mutex*' > /sys/kernel/tracing/set_ftrace_filter - 开启追踪:
echo 1 > /sys/kernel/tracing/tracing_on - 执行待追踪的任务:
./my_test_app - 关闭追踪:
echo 0 > /sys/kernel/tracing/tracing_on - 读取结果:
cat /sys/kernel/tracing/trace
2.3 核心数据结构:ftrace_ops 与回调机制
ftrace 的核心数据结构是 struct ftrace_ops,它代表一个追踪操作的实例。每个追踪器或 kprobe 都需要注册一个或多个 ftrace_ops 来定义其行为。
2.3.1 struct ftrace_ops 关键字段
struct ftrace_ops {
ftrace_func_t func; /* 追踪回调函数 */
struct ftrace_ops *next; /* 链表指针,用于链接多个 ops */
unsigned long flags; /* 标志位,控制行为 */
int (*trampoline)(unsigned long ip, unsigned long parent_ip,
struct ftrace_ops *op, struct pt_regs *regs);
/* ... 其他字段 */
};
- func: 核心回调函数指针。当被追踪的函数被调用时,ftrace 会调用此函数。
- flags: 控制 ops 的行为,例如是否支持递归、是否保存寄存器等。
- trampoline: 可选的蹦床函数,用于实现更高效的调用路径。
2.3.2 回调注册与注销
内核模块或追踪器通过以下 API 注册和注销 ftrace_ops:
int register_ftrace_function(struct ftrace_ops *ops);
void unregister_ftrace_function(struct ftrace_ops *ops);
2.4 环形缓冲区:高效的数据存储基石
ftrace 使用 per-CPU 环形缓冲区 来存储追踪事件。这种设计避免了多核竞争,保证了极高的写入性能。每个 CPU 拥有独立的缓冲区,事件写入时无需加锁,仅在读取时需要进行必要的同步。
2.4.1 环形缓冲区的工作原理
环形缓冲区本质上是一个固定大小的循环数组。它包含两个关键指针:写指针 (write) 和 读指针 (read)。
- 写入: 事件从写指针位置写入,写指针向前移动。如果写指针追上读指针,则覆盖旧数据(默认行为)。
- 读取: 用户从读指针位置读取事件,读指针向前移动。读取操作不会删除数据,只是标记为已读。
- 覆盖策略: 默认情况下,当缓冲区满时,新事件会覆盖最旧的事件。可以通过
trace文件或trace_pipe文件控制读取行为。
2.4.2 缓冲区配置与监控
通过 tracefs 文件可以灵活配置环形缓冲区:
| 文件 | 作用 | 示例 |
|---|---|---|
buffer_size_kb |
设置每个 CPU 缓冲区的大小(单位 KB) | echo 2048 > buffer_size_kb |
buffer_total_size_kb |
查看所有 CPU 缓冲区总大小 | cat buffer_total_size_kb |
per_cpu/cpu0/trace |
查看指定 CPU 的缓冲区内容 | cat per_cpu/cpu0/trace |
per_cpu/cpu0/stats |
查看指定 CPU 缓冲区的统计信息 | cat per_cpu/cpu0/stats |
2.4.3 缓冲区统计信息解读
通过 per_cpu/cpuX/stats 文件可以获取缓冲区的详细统计信息,帮助诊断丢事件等问题:
# cat /sys/kernel/tracing/per_cpu/cpu0/stats
entries: 1024 # 当前缓冲区中的事件数
overrun: 0 # 因缓冲区满而丢失的事件数
commit overrun: 0 # 提交时发生覆盖的次数
bytes: 65536 # 当前已使用的字节数
oldest event ts: 12345.678901 # 最旧事件的时间戳
now ts: 12346.789012 # 当前时间戳
dropped events: 0 # 因其他原因丢弃的事件数
read events: 512 # 已读取的事件数
overrun 或 dropped events 持续增长,说明缓冲区大小不足,应适当增大 buffer_size_kb 的值,或者减少追踪的事件量(通过更严格的过滤规则)。
2.5 本章小结
ftrace 的基础架构体现了 Linux 内核设计的精妙之处:
- 分层设计 将用户接口、核心逻辑和底层事件解耦,便于扩展和维护。
- tracefs 文件系统 提供了简洁、统一的用户态控制接口,符合 Linux “一切皆文件” 的哲学。
- ftrace_ops 回调机制 允许多个追踪器同时工作,互不干扰。
- per-CPU 环形缓冲区 保证了极低的追踪开销,是 ftrace 能够用于生产环境的关键。