1、Ftrace初探:什么是Ftrace?内核追踪的“瑞士军刀”,我的第一个trace命令。
说实话,我第一次接触Ftrace的时候,心里是有点抵触的。那时候我还在做嵌入式系统优化,遇到一个诡异的调度延迟问题——进程明明该醒了,却要等好几毫秒才被调度上CPU。我翻遍了/proc下的各种统计文件,看了半天也没找到根因。后来一位老同事跟我说:“你试试Ftrace吧,这玩意儿能告诉你内核里到底发生了什么。”
一试,就回不去了。
1.1 Ftrace到底是什么?
Ftrace,全称是Function Tracer。它是Linux内核内置的一个追踪框架。说白了,它就像内核里的“黑匣子记录仪”——能记录函数调用、中断触发、进程调度、系统调用等几乎所有的内核活动。
为什么叫它“瑞士军刀”?因为它的功能太全了:
- 函数追踪:记录哪些函数被调用了,调用了多少次,花了多长时间
- 函数图追踪:展示函数调用的完整调用链,像火焰图一样直观
- 事件追踪:追踪特定内核事件,比如调度切换、中断上下文的进入和退出
- 延迟追踪:测量中断关闭时间、抢占关闭时间、调度延迟等关键指标
- 动态探针:可以在任意函数入口或出口动态插入追踪点,无需重新编译内核
核心要点:Ftrace最大的优势是——它就在内核里,不需要安装额外的工具,不需要加载内核模块(大部分功能),性能开销极低。你只需要挂载一个debugfs文件系统,就能开始追踪。
1.2 我的第一个trace命令
好,咱们直接上手。别怕,第一个命令很简单。
首先,确认你的内核支持Ftrace:
# 检查debugfs是否挂载
mount | grep debugfs
# 如果没有挂载,手动挂载
mount -t debugfs none /sys/kernel/debug
# 进入Ftrace目录
cd /sys/kernel/debug/tracing
嗯,这里要注意:有些发行版默认没挂载debugfs。我遇到过好几次,一上来就报错“目录不存在”,然后才想起来忘了挂载。所以第一步先检查挂载,养成习惯。
接下来,我们试试最简单的追踪——看看当前系统里有哪些可用的追踪器:
cat available_tracers
你会看到类似这样的输出:
hwlat blk mmiotrace function_graph wakeup_dl wakeup_rt wakeup function nop
这些就是Ftrace内置的追踪器。每个追踪器干不同的事:
| 追踪器名称 | 作用 |
|---|---|
| function | 追踪所有内核函数的调用 |
| function_graph | 以调用图形式展示函数调用关系 |
| wakeup | 测量进程从被唤醒到被调度执行的延迟 |
| wakeup_rt | 专门测量实时进程的调度延迟 |
| wakeup_dl | 测量Deadline调度类的延迟 |
| hwlat | 检测硬件延迟(比如SMI、中断风暴) |
| blk | 追踪块设备I/O |
| nop | 空追踪器,关闭追踪时使用 |
我个人习惯,第一次接触一个新系统时,先用function_graph看看内核在忙什么。咱们来试试:
# 选择追踪器
echo function_graph > current_tracer
# 开始追踪(默认已经开始了)
# 查看追踪结果
cat trace
你会看到类似这样的输出:
# tracer: function_graph
#
# CPU DURATION FUNCTION CALLS
# | | | | | | |
0) 0.125 us | __fdget();
0) | __fget_light() {
0) 0.083 us | __fget_files();
0) 0.375 us | }
0) | __fget_light() {
0) 0.083 us | __fget_files();
0) 0.333 us | }
0) 0.625 us | __fdget();
看到没?每一行都记录了:哪个CPU执行的、函数执行耗时、函数调用关系。缩进表示调用深度,花括号表示函数的进入和退出。
小技巧:如果输出太多,你可以先清空缓冲区再开始追踪:echo > trace。然后只追踪你关心的那几秒钟。
1.3 为什么Ftrace比printk强?
你可能会想:“我加printk也能看函数调用啊,干嘛用Ftrace?”
这个问题我当年也问过。直到有一次,我在一个生产环境的内核模块里加了几个printk,结果系统负载直接飙升了30%——printk的串行化输出太慢了,而且会触发控制台锁,影响实时性。
Ftrace不一样:
- 零开销开关:不启用时,Ftrace的hook点只多了一条条件判断指令(nop),几乎不影响性能
- 环形缓冲区:追踪数据写入内核内存中的环形缓冲区,不会触发磁盘I/O或控制台输出
- 动态过滤:可以只追踪你关心的函数,而不是全量追踪
- 无需重新编译:动态插桩技术让你可以在运行中的内核上随时开启/关闭追踪
说白了,printk是“大炮打蚊子”,而Ftrace是“手术刀”。
1.4 一个真实案例:用Ftrace揪出调度延迟
我记得有一次,一个音频处理进程在播放时出现卡顿。用户说“声音断断续续的”,但系统负载看起来很正常,CPU使用率也不高。
我用了wakeup追踪器:
# 切换到wakeup追踪器
echo wakeup > current_tracer
# 设置要追踪的目标进程PID(假设是1234)
echo 1234 > set_ftrace_pid
# 开始追踪
cat trace
结果发现,这个进程被唤醒后,平均要等2.3毫秒才被调度上CPU。而正常值应该在100微秒以内。进一步追踪发现,是另一个内核线程在持有某个锁时被中断打断了,导致唤醒延迟被拉长。
如果没有Ftrace,这种问题几乎不可能定位——你看到的只是“进程没跑”,但不知道“为什么没跑”。Ftrace把整个调度链完整地展示在你面前。
注意:Ftrace的环形缓冲区默认大小是1MB左右。如果追踪的数据量太大,旧数据会被覆盖。你可以通过buffer_size_kb文件调整缓冲区大小。我曾经因为没调大缓冲区,结果关键数据被冲掉了,白忙活半天。
1.5 小结:Ftrace的定位
Ftrace不是万能的。它擅长的是:
- 函数级别的调用追踪
- 调度延迟、中断延迟的精确测量
- 内核事件的时序分析
但它不擅长:
- 用户空间程序的性能分析(那是perf的事)
- 内存泄漏检测(那是kmemleak的事)
- 长时间、大范围的性能监控(那是eBPF的事)
不过,对于内核调度和性能瓶颈的初步诊断,Ftrace绝对是你工具箱里最趁手的那把刀。接下来的章节,我会带你深入每个追踪器的使用技巧,以及如何用它们解决真实世界中的性能问题。
准备好了吗?咱们继续往下走。