4. ftrace跟踪器类型
ftrace 的核心能力由多种跟踪器(tracer)提供,每种跟踪器针对不同的性能分析场景设计。本章深入剖析三种最常用的跟踪器:function、function_graph 和 wakeup,理解其工作原理、适用场景及使用方法。
4.1 function 跟踪器:轻量级函数调用追踪
function 跟踪器是 ftrace 中最基础的跟踪器,它记录内核函数的调用事件(进入和退出),但不包含调用深度和子函数信息。其核心机制是在每个内核函数的入口和出口插入探测点(通过 -pg 编译选项),当跟踪器激活时,这些探测点将函数调用记录到环形缓冲区。
适用场景:快速定位高频函数调用、确认函数是否被调用、分析函数调用路径的起点和终点。由于开销极低,适合在生产环境进行短时采样。
4.1.1 工作原理与数据结构
function 跟踪器使用 ftrace_ops 结构体管理回调函数,每个被跟踪的函数对应一个 ftrace_func_entry 记录。其核心流程如下:
- 注册回调:通过
register_ftrace_function()将跟踪回调挂载到ftrace_ops链表。 - 动态修改指令:内核将目标函数入口处的
nop指令替换为call ftrace_caller。 - 记录事件:当函数被调用时,
ftrace_caller跳转到回调函数,将函数地址、时间戳、进程上下文写入trace_array的环形缓冲区。 - 读取输出:用户通过
/sys/kernel/tracing/trace读取缓冲区内容。
关键性能指标:function 跟踪器单次记录开销约为 50-100 纳秒(取决于 CPU 架构),远低于 kprobe 或 tracepoint 的数百纳秒级别。
4.1.2 配置与使用示例
# 挂载 tracefs
mount -t tracefs tracefs /sys/kernel/tracing
cd /sys/kernel/tracing
# 选择 function 跟踪器
echo function > current_tracer
# 设置过滤:仅跟踪特定模块或函数
echo 'ext4_*' > set_ftrace_filter
echo 'do_sys_open' >> set_ftrace_filter
# 开始跟踪
echo 1 > tracing_on
# 执行目标操作(如打开文件)
cat /etc/passwd
# 停止跟踪
echo 0 > tracing_on
# 查看结果
head -30 trace
输出示例:
# tracer: function
# TASK-PID CPU# TIMESTAMP FUNCTION
# | | | | |
cat-1234 [001] ...1 1234.5678: do_sys_open
cat-1234 [001] ...1 1234.5679: do_filp_open
cat-1234 [001] ...1 1234.5680: path_openat
cat-1234 [001] ...1 1234.5681: getname_flags
cat-1234 [001] ...1 1234.5682: getname_kernel
4.2 function_graph 跟踪器:带调用深度的函数调用图
function_graph 跟踪器在 function 跟踪器的基础上增加了调用深度追踪和子函数耗时统计。它通过维护一个每 CPU 的深度计数器,在函数入口和出口分别记录,从而构建出完整的函数调用树。
开销警告:function_graph 跟踪器的开销是 function 跟踪器的 2-3 倍,因为每个函数调用需要记录两次事件(进入和退出),并且需要维护深度栈。在频繁调用的函数(如调度器、内存分配器)上启用时需谨慎。
4.2.1 调用深度追踪机制
function_graph 跟踪器使用 ftrace_graph_ret_ops 和 ftrace_graph_entry_ops 两个回调:
- 入口回调:记录函数地址、时间戳,并将深度计数器加 1。
- 出口回调:记录函数返回时间,计算执行耗时,将深度计数器减 1。
- 深度控制:通过
max_graph_depth文件限制最大追踪深度,避免过深的递归调用导致缓冲区溢出。
4.2.2 配置与使用示例
# 切换到 function_graph 跟踪器
echo function_graph > current_tracer
# 设置最大追踪深度(避免过深)
echo 10 > max_graph_depth
# 过滤:仅跟踪特定函数及其子函数
echo 'do_sys_open' > set_graph_function
# 开始跟踪
echo 1 > tracing_on
ls /tmp
echo 0 > tracing_on
# 查看结果
cat trace | head -40
输出示例:
# tracer: function_graph
# CPU DURATION FUNCTION CALLS
# | | | | | | |
1) 0.123 us | do_sys_open();
1) 0.456 us | do_filp_open();
1) 0.789 us | path_openat();
1) 0.012 us | getname_flags();
1) 0.034 us | getname_kernel();
1) 0.567 us | alloc_fd();
1) 0.890 us | do_dentry_open();
1) 0.111 us | vfs_open();
1) 0.222 us | open_check_o_direct();
1) 0.333 us | do_last();
4.3 wakeup 跟踪器:调度延迟分析利器
wakeup 跟踪器专门用于分析进程唤醒到被调度执行之间的延迟(wakeup latency)。它记录从唤醒事件发生到目标进程在 CPU 上开始执行的全过程,是实时系统性能分析和调度器调优的核心工具。
核心价值:wakeup 跟踪器能够精确测量调度延迟,帮助开发者识别调度器瓶颈、中断处理延迟、优先级反转等问题。对于实时进程(如使用 SCHED_FIFO 或 SCHED_RR 的线程)尤其重要。
4.3.1 跟踪器变体
| 跟踪器名称 | 追踪目标 | 适用场景 |
|---|---|---|
| wakeup | 普通进程(SCHED_OTHER) | 通用调度延迟分析 |
| wakeup_rt | 实时进程(SCHED_FIFO/SCHED_RR) | 实时系统性能验证 |
| wakeup_dl | Deadline 调度类进程 | 截止时间调度分析 |
4.3.2 延迟测量流程
wakeup 跟踪器的工作流程包含以下关键步骤:
- 唤醒事件触发:当某个进程(唤醒者)调用
wake_up_process()或类似函数时,跟踪器记录唤醒时间戳T_wake。 - 目标进程入队:被唤醒的进程被放入运行队列(runqueue),跟踪器记录入队时间
T_enqueue。 - 调度决策:调度器选择下一个运行的进程,如果目标进程被选中,记录调度时间
T_schedule。 - 上下文切换:CPU 执行上下文切换,目标进程开始运行,记录执行时间
T_exec。 - 延迟计算:总延迟 =
T_exec - T_wake,调度延迟 =T_schedule - T_wake。
4.3.3 配置与使用示例
# 选择 wakeup 跟踪器(追踪普通进程)
echo wakeup > current_tracer
# 设置目标进程 PID(追踪特定进程的唤醒延迟)
echo 1234 > wakeup_rt_prio # 对于 wakeup_rt,设置优先级阈值
# 开始跟踪
echo 1 > tracing_on
# 触发目标进程被唤醒(例如发送信号或 IO 完成)
kill -USR1 1234
# 停止跟踪
echo 0 > tracing_on
# 查看最大延迟和详细记录
cat trace | grep -E "wakeup|latency"
输出示例:
# tracer: wakeup
# wakeup latency: 12.345 us
#
# TASK-PID CPU# TIMESTAMP FUNCTION
# | | | | |
<idle>-0 [001] 1234.5678: try_to_wake_up
<idle>-0 [001] 1234.5679: select_task_rq_fair
<idle>-0 [001] 1234.5680: enqueue_task_fair
<idle>-0 [001] 1234.5681: check_preempt_curr
<idle>-0 [001] 1234.5682: __schedule
<idle>-0 [001] 1234.5683: context_switch
myapp-1234 [001] 1234.5684: finish_task_switch
4.4 三种跟踪器对比总结
| 特性 | function | function_graph | wakeup |
|---|---|---|---|
| 追踪粒度 | 函数调用事件 | 函数调用树 + 耗时 | 进程唤醒到执行延迟 |
| 开销等级 | 低(~50ns/次) | 中(~150ns/次) | 低(仅追踪特定进程) |
| 输出信息 | 函数名、时间戳 | 深度缩进、耗时、子函数 | 唤醒源、延迟值、调度路径 |
| 典型场景 | 函数调用频率统计 | 性能热点定位、调用链分析 | 实时系统延迟验证、调度器调优 |
| 过滤能力 | set_ftrace_filter | set_graph_function | wakeup_rt_prio / PID 过滤 |
| 生产环境适用性 | 可短时使用 | 谨慎使用 | 推荐使用 |
选择建议:
- 需要快速确认函数是否被调用 → function
- 需要分析函数内部调用路径和耗时 → function_graph
- 需要测量进程唤醒延迟、分析调度问题 → wakeup 系列
4.5 实战技巧与注意事项
- 缓冲区大小调整:function_graph 跟踪器可能产生大量数据,建议先设置
echo 4096 > buffer_size_kb增加缓冲区。 - 避免递归陷阱:function_graph 跟踪器在追踪自身回调函数时可能导致递归,内核已通过
ftrace_graph_recursion保护,但仍建议避免追踪ftrace_*相关函数。 - wakeup 跟踪器的优先级过滤:对于
wakeup_rt跟踪器,通过wakeup_rt_prio设置优先级阈值,只追踪优先级高于该值的进程,减少噪声。 - 结合 trace_printk:在驱动代码中插入
trace_printk(),可以在跟踪器输出中混合自定义日志,便于关联分析。
性能影响提醒:function_graph 跟踪器在追踪高频函数(如
schedule()、kmalloc())时,可能导致系统吞吐量下降 10%-30%。建议先在测试环境验证,再决定是否用于生产。