2. ftrace入门
ftrace(Function Tracer)是Linux内核内置的跟踪框架,专为性能分析和调试设计。它能够在几乎不产生额外开销的情况下,捕获内核函数的执行流、中断上下文、调度事件以及延迟信息。本章将系统介绍ftrace的核心概念、用户态接口(tracefs)以及其分层架构。
2.1 ftrace简介
ftrace最初由Steven Rostedt在2008年合入Linux 2.6.27主线内核,最初仅支持函数跟踪,后逐步扩展为包含多个跟踪器(tracer)的通用框架。其核心设计原则是:低开销、可配置、动态插桩。
ftrace支持的主要跟踪能力包括:
- 函数跟踪(Function Tracer):记录内核函数的进入和退出,支持过滤特定函数。
- 函数图跟踪(Function Graph Tracer):以缩进方式展示函数调用链,包含执行耗时。
- 调度事件跟踪(Sched Events):跟踪进程的调度、迁移、阻塞和唤醒。
- 中断/软中断跟踪(Irq/Softirq Events):记录中断上下文的进入和退出。
- 硬件延迟跟踪(HW Latency Detector):检测CPU长时间关闭中断导致的延迟。
- 事件跟踪(Trace Events):基于tracepoint的静态事件,覆盖文件系统、网络、内存管理等子系统。
2.2 tracefs文件系统
tracefs是ftrace的用户态控制接口,通常挂载在 /sys/kernel/tracing 目录下。它通过一组虚拟文件暴露ftrace的所有配置、控制和数据读取能力。驱动开发者通过读写这些文件即可完成跟踪器的启停、过滤条件的设置以及跟踪数据的提取。
2.2.1 挂载与基本结构
在大多数现代Linux发行版中,tracefs默认已挂载。若未挂载,可手动执行:
# 挂载 tracefs
mount -t tracefs tracefs /sys/kernel/tracing
# 验证挂载
ls /sys/kernel/tracing/
挂载后,/sys/kernel/tracing/ 目录下包含数十个控制文件和目录。下表列出了驱动性能分析中最常用的文件及其用途:
| 文件/目录 | 类型 | 用途说明 |
|---|---|---|
available_tracers |
只读 | 列出当前内核支持的跟踪器列表 |
current_tracer |
读写 | 设置或查看当前激活的跟踪器 |
tracing_on |
读写 | 全局跟踪开关(1=开启,0=关闭) |
trace |
只读 | 输出当前跟踪缓冲区内容(人类可读格式) |
trace_pipe |
只读 | 流式输出跟踪数据,支持实时读取 |
set_ftrace_filter |
读写 | 设置函数跟踪的白名单(只跟踪指定函数) |
set_ftrace_notrace |
读写 | 设置函数跟踪的黑名单(排除指定函数) |
available_events |
只读 | 列出所有可用的tracepoint事件 |
events/ |
目录 | 按子系统组织的tracepoint事件控制目录 |
buffer_size_kb |
读写 | 设置每个CPU的跟踪缓冲区大小(KB) |
trace_clock |
读写 | 选择跟踪时间戳时钟源(local/global/counter等) |
set_ftrace_filter 时,函数名必须与内核符号表中的名称完全匹配。可以使用通配符 * 进行批量匹配,例如 irq_* 匹配所有以 irq_ 开头的函数。
2.2.2 典型操作流程
使用ftrace进行驱动性能分析通常遵循以下步骤:
- 查看可用跟踪器:
cat available_tracers,确认内核支持function_graph等跟踪器。 - 设置跟踪器:
echo function_graph > current_tracer。 - 设置过滤(可选):
echo 'my_driver_*' > set_ftrace_filter,只跟踪驱动相关函数。 - 开启跟踪:
echo 1 > tracing_on。 - 复现性能问题:执行驱动操作或触发中断。
- 关闭跟踪:
echo 0 > tracing_on。 - 读取数据:
cat trace或cat trace_pipe实时查看。 - 清理:
echo nop > current_tracer重置跟踪器。
2.3 ftrace框架架构
ftrace的架构采用分层设计,从上到下依次为:用户态接口层(tracefs)、核心管理层(ring buffer + tracer逻辑)、插桩层(动态插桩/静态tracepoint)。这种分层保证了各模块之间的低耦合,也使得扩展新的跟踪器变得简单。
2.3.1 架构分层详解
| 层级 | 组件 | 职责 | 关键文件/函数 |
|---|---|---|---|
| 用户态接口层 | tracefs | 提供文件系统接口,供用户态读写控制参数和获取数据 | /sys/kernel/tracing/* |
| 核心管理层 | Ring Buffer | 高性能无锁环形缓冲区,存储跟踪事件,支持每CPU独立缓冲区 | ring_buffer.c |
| 核心管理层 | Tracer逻辑 | 实现具体跟踪器的行为(function、function_graph、irqsoff等) | trace_functions.c, trace_irqsoff.c |
| 插桩层 | 动态插桩(ftrace) | 通过修改函数入口指令(mcount/fentry)实现动态跟踪 | ftrace.c, arch/*/kernel/ftrace.c |
| 插桩层 | 静态tracepoint | 内核源码中预定义的跟踪点,用于事件跟踪 | include/trace/events/*.h |
2.3.2 数据流路径
当跟踪被激活后,数据从插桩点产生,经过核心管理层写入ring buffer,最终通过tracefs暴露给用户态。整个路径设计为无锁或极少锁竞争,以最小化对目标系统性能的影响。
用户态 (cat trace / trace_pipe)
↑
tracefs 文件系统接口
↑
核心管理层 (tracer 逻辑 + ring buffer)
↑
插桩层 (动态插桩 / 静态 tracepoint)
↑
内核代码执行流 (函数调用 / 事件触发)
2.4 驱动开发者的切入点
对于驱动开发者,ftrace提供了三个最常用的分析路径:
- 中断延迟分析:使用
irqsoff或preemptoff跟踪器,检测驱动中断处理函数是否长时间关闭中断或抢占。 - 函数调用路径:使用
function_graph跟踪器配合set_ftrace_filter,精确分析驱动关键函数的调用链和执行耗时。 - 事件关联分析:通过
events/目录启用irq_handler_entry/exit、sched_switch等事件,将驱动行为与系统调度、中断上下文关联。
trace_printk() 调用。该函数专为ftrace设计,输出会直接写入ring buffer,比 printk() 开销低得多,且不会污染dmesg日志。
/* 驱动代码中使用 trace_printk 示例 */
#include <linux/kernel.h>
static irqreturn_t my_driver_irq_handler(int irq, void *dev_id)
{
/* 记录中断触发 */
trace_printk("my_driver: IRQ %d triggered at %llu\n",
irq, ktime_get_ns());
/* ... 中断处理逻辑 ... */
trace_printk("my_driver: IRQ %d handled\n", irq);
return IRQ_HANDLED;
}