4. ftrace 跟踪器类型:function、function_graph 与 wakeup

ftrace 的核心能力由多种跟踪器(tracer)提供,每种跟踪器针对不同的性能分析场景设计。本章深入剖析三种最常用的跟踪器:function、function_graph 和 wakeup,理解其工作原理、适用场景及使用方法。

4.1 function 跟踪器:轻量级函数调用追踪

function 跟踪器是 ftrace 中最基础的跟踪器,它记录内核函数的调用事件(进入和退出),但不包含调用深度和子函数信息。其核心机制是在每个内核函数的入口和出口插入探测点(通过 -pg 编译选项),当跟踪器激活时,这些探测点将函数调用记录到环形缓冲区。

适用场景:快速定位高频函数调用、确认函数是否被调用、分析函数调用路径的起点和终点。由于开销极低,适合在生产环境进行短时采样。

4.1.1 工作原理与数据结构

function 跟踪器使用 ftrace_ops 结构体管理回调函数,每个被跟踪的函数对应一个 ftrace_func_entry 记录。其核心流程如下:

  1. 注册回调:通过 register_ftrace_function() 将跟踪回调挂载到 ftrace_ops 链表。
  2. 动态修改指令:内核将目标函数入口处的 nop 指令替换为 call ftrace_caller。
  3. 记录事件:当函数被调用时,ftrace_caller 跳转到回调函数,将函数地址、时间戳、进程上下文写入 trace_array 的环形缓冲区。
  4. 读取输出:用户通过 /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 文件限制最大追踪深度,避免过深的递归调用导致缓冲区溢出。
function_graph 跟踪器调用深度追踪流程 函数入口探测点 深度计数器 +1 记录入口时间戳 执行子函数(递归调用) 函数出口探测点 深度计数器 -1 计算耗时 = 出口 - 入口 输出格式:深度缩进 + 函数名 + 耗时(us) 例如: 3) do_sys_open | do_filp_open [12.34 us]

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 跟踪器的工作流程包含以下关键步骤:

  1. 唤醒事件触发:当某个进程(唤醒者)调用 wake_up_process() 或类似函数时,跟踪器记录唤醒时间戳 T_wake。
  2. 目标进程入队:被唤醒的进程被放入运行队列(runqueue),跟踪器记录入队时间 T_enqueue。
  3. 调度决策:调度器选择下一个运行的进程,如果目标进程被选中,记录调度时间 T_schedule。
  4. 上下文切换:CPU 执行上下文切换,目标进程开始运行,记录执行时间 T_exec。
  5. 延迟计算:总延迟 = T_exec - T_wake,调度延迟 = T_schedule - T_wake。
wakeup 跟踪器延迟测量时间线 时间轴 T_wake 唤醒事件 T_enqueue 入队运行队列 T_schedule 调度决策 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
# | |            |        |         |
  -0     [001]  1234.5678: try_to_wake_up
  -0     [001]  1234.5679: select_task_rq_fair
  -0     [001]  1234.5680: enqueue_task_fair
  -0     [001]  1234.5681: check_preempt_curr
  -0     [001]  1234.5682: __schedule
  -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%。建议先在测试环境验证,再决定是否用于生产。