2. Ftrace架构:核心组件与文件系统
好,咱们来聊聊Ftrace的架构。说实话,我第一次接触Ftrace时,也被它那一堆文件系统和组件搞得有点懵。但摸清楚之后,你会发现它的设计其实非常优雅——说白了,就是一套「插桩-收集-输出」的流水线。
2.1 三大核心组件
Ftrace的核心组件就三个:function tracer、function_graph tracer 和 event tracer。我习惯把它们比作三种不同精度的「探针」。
2.1.1 function tracer
这是最基础的追踪器。它会在每个内核函数的入口和出口处插入钩子。你想想看,这意味着什么?意味着你能看到系统里所有内核函数的调用情况。
关键特性:
- 记录每个函数的调用次数
- 记录每个函数的执行时间(纳秒级)
- 支持动态过滤(只追踪你关心的函数)
我在项目中遇到过一个问题:某个驱动模块加载后,系统响应突然变慢。用function tracer一查,发现某个函数被调用了上百万次——嗯,问题一下就找到了。
2.1.2 function_graph tracer
这个就更有意思了。它不仅能记录函数调用,还能画出调用关系图。说白了,就是告诉你「谁调了谁,花了多长时间」。
# 启用function_graph tracer
echo function_graph > /sys/kernel/tracing/current_tracer
# 查看输出
cat /sys/kernel/tracing/trace
# 输出示例:
# 2) | do_sys_open() {
# 2) | getname() {
# 2) 0.120 us | kmem_cache_alloc();
# 2) 0.080 us | getname_flags();
# 2) 1.200 us | }
# 2) 0.050 us | do_filp_open();
# 2) 3.500 us | }
看到那个缩进和花括号了吗?这就是调用关系的可视化。每个函数的执行时间都清清楚楚。我曾经用这个工具定位过一个网络驱动的性能瓶颈——一个看似简单的函数,内部竟然嵌套了7层调用,耗时超过100微秒。
我的经验:function_graph tracer的开销比function tracer大,因为它要维护调用栈。生产环境慎用,但调试阶段非常好使。
2.1.3 event tracer
event tracer是另一种思路。它不追踪函数调用,而是追踪内核中预定义的「事件点」。比如:
- 系统调用事件(sys_enter/sys_exit)
- 调度事件(sched_switch, sched_wakeup)
- 中断事件(irq_handler_entry, irq_handler_exit)
- 内存管理事件(mm_page_alloc, mm_page_free)
你想想看,这些事件点就像是内核里的「监控摄像头」。每个摄像头只拍一个特定场景,但组合起来就能还原出完整的系统行为。
# 启用调度事件
echo 1 > /sys/kernel/tracing/events/sched/enable
# 查看调度记录
cat /sys/kernel/tracing/trace
# 输出示例:
# <idle>-0 [000] d..2. 123.456789: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=kworker/0:1 next_pid=45 next_prio=120
2.2 tracefs文件系统
好了,组件说完了,那怎么用呢?答案就是tracefs。这是一个虚拟文件系统,通常挂载在 /sys/kernel/tracing 或 /sys/kernel/debug/tracing 下。
我个人习惯用 /sys/kernel/tracing,因为它是专门为tracing设计的,更干净。
2.2.1 核心文件一览
| 文件/目录 | 作用 | 我常用的操作 |
|---|---|---|
| current_tracer | 设置当前使用的追踪器 | echo function_graph > current_tracer |
| trace | 读取追踪结果 | cat trace | head -100 |
| tracing_on | 开关追踪(1开0关) | echo 0 > tracing_on(暂停) |
| set_ftrace_filter | 设置函数过滤白名单 | echo 'do_sys*' > set_ftrace_filter |
| set_ftrace_notrace | 设置函数过滤黑名单 | echo 'schedule' > set_ftrace_notrace |
| events/ | 事件追踪的配置目录 | echo 1 > events/sched/enable |
| buffer_size_kb | 追踪缓冲区大小 | echo 4096 > buffer_size_kb |
注意:tracefs中的文件大多是伪文件,你不能用vim去编辑它们。只能用echo/cat这种简单的读写操作。我曾经见过有人用编辑器打开current_tracer,结果把系统搞挂了——嗯,血的教训。
2.2.2 典型工作流程
我一般按这个步骤来:
- 清空缓冲区:echo > trace(避免旧数据干扰)
- 设置过滤器:echo 'my_function*' > set_ftrace_filter
- 选择追踪器:echo function_graph > current_tracer
- 触发问题场景:运行你的测试程序或操作
- 读取结果:cat trace
- 关闭追踪:echo nop > current_tracer
这里有个小技巧:我习惯在步骤4之前先 echo 0 > tracing_on,等场景触发时再 echo 1 > tracing_on。这样可以精确控制采集窗口,避免收集到大量无关数据。
2.3 组件之间的关系
这三个组件不是互斥的,它们可以协同工作。比如:
- 用function tracer做全局扫描,找到可疑函数
- 用function_graph tracer深入分析该函数的调用链
- 用event tracer查看相关事件(比如调度、中断)的上下文
说白了,这就是一个「由粗到细」的排查过程。我在定位一个数据库的延迟抖动问题时,就是按这个思路来的——先用function tracer发现某个锁函数调用频繁,再用function_graph看到锁的持有时间异常,最后用event tracer确认是中断导致的任务抢占。
避坑指南:我曾经同时开启function tracer和event tracer,结果系统直接卡死了。原因是缓冲区太小,数据量太大。所以我的建议是:一次只开一个追踪器,或者至少把buffer_size_kb调大(比如16MB以上)。
好了,Ftrace的架构就聊到这里。记住这三个组件和tracefs的核心文件,后面的章节我们会深入每个组件的具体用法。你想想看,掌握了这些,内核调度对你来说还有秘密吗?