调试Linux内核驱动时,很多开发者习惯性地依赖printk打印日志。这种方法虽然简单直接,但频繁的打印会拖慢系统性能,而且难以捕捉到函数调用关系和执行时序这类关键信息。今天,我将分享如何利用ftrace这个强大的内核跟踪工具,快速定位驱动开发中的各种疑难杂症。
printk就像在黑暗中用手电筒寻找丢失的钥匙,而ftrace则像是打开了整个房间的灯。ftrace最初由Steven Rostedt开发,现已成为Linux内核的标准组件,它提供了多种跟踪器来监控内核行为:
相比printk,ftrace的优势在于:
提示:ftrace通过debugfs文件系统提供用户接口,无需额外工具链支持
确保内核编译时启用了以下选项:
bash复制CONFIG_FTRACE=y
CONFIG_FUNCTION_TRACER=y
CONFIG_FUNCTION_GRAPH_TRACER=y
CONFIG_DYNAMIC_FTRACE=y
CONFIG_DEBUG_FS=y
可以通过以下命令检查配置:
bash复制grep -E "FTRACE|DEBUG_FS" /boot/config-$(uname -r)
ftrace通过debugfs提供用户接口,挂载方法如下:
bash复制# 临时挂载
mount -t debugfs none /sys/kernel/debug
# 永久挂载(添加到/etc/fstab)
echo "debugfs /sys/kernel/debug debugfs defaults 0 0" >> /etc/fstab
挂载后,ftrace的控制接口位于:
code复制/sys/kernel/debug/tracing/
典型的ftrace使用流程如下:
示例:跟踪USB驱动中的urb提交过程
bash复制# 进入tracing目录
cd /sys/kernel/debug/tracing
# 清空之前的跟踪记录
echo 0 > tracing_on
echo > trace
# 选择function_graph跟踪器
echo function_graph > current_tracer
# 过滤只跟踪USB相关函数
echo "usb_*" > set_ftrace_filter
echo "*urb*" >> set_ftrace_filter
# 开始记录
echo 1 > tracing_on
# 在此插入你的测试操作(如插入USB设备)
# 停止记录
echo 0 > tracing_on
# 查看结果
cat trace > /tmp/usb_trace.log
ftrace提供了强大的过滤功能,可以精确控制跟踪范围:
| 过滤类型 | 命令示例 | 说明 |
|---|---|---|
| 模块过滤 | echo ':mod:usb_storage' > set_ftrace_filter |
只跟踪指定模块的函数 |
| 通配符过滤 | echo 'usb*' > set_ftrace_filter |
匹配usb开头的所有函数 |
| 排除过滤 | echo '!*lock*' > set_ftrace_notrace |
不跟踪包含lock的函数 |
| PID过滤 | echo 1234 > set_ftrace_pid |
只跟踪特定进程 |
注意:过滤器的设置顺序会影响最终效果,建议先设置排除规则
假设我们遇到一个网卡驱动问题:设备能正常识别,但无法接收数据包。使用ftrace的排查过程如下:
bash复制cd /sys/kernel/debug/tracing
# 使用function_graph跟踪器
echo function_graph > current_tracer
# 过滤网卡驱动相关函数
echo "e1000*" > set_ftrace_filter
echo "*irq*" >> set_ftrace_filter
# 启用函数调用栈记录
echo 1 > options/func_stack_trace
# 开始记录
echo 1 > tracing_on
执行网络操作后停止跟踪,查看trace文件会发现:
code复制 0) | e1000_intr() {
0) 0.120 us | napi_schedule();
0) 0.230 us | __raise_softirq_irqoff();
0) 0.340 us | _raw_spin_unlock();
0) + 12.450 us | }
关键发现:中断处理函数e1000_intr被正常触发,但处理时间异常短(仅12μs),且没有调用预期的数据包处理函数。
进一步跟踪NAPI处理流程:
bash复制echo "napi*" >> set_ftrace_filter
echo 1 > tracing_on
跟踪结果显示napi_complete未被调用,最终定位到驱动中错误地设置了NAPI_STATE_DISABLE标志,导致数据包处理被跳过。
对于需要高性能数据传输的驱动(如视频采集卡),ftrace可以帮助定位性能瓶颈:
bash复制# 使用wakeup跟踪器分析调度延迟
echo wakeup > current_tracer
# 设置跟踪特定进程
echo $(pidof my_driver) > set_ftrace_pid
# 记录DMA相关函数
echo "*dma*" > set_ftrace_filter
分析跟踪数据时,重点关注:
在一次实际优化中,通过ftrace发现DMA配置函数被频繁调用,最终通过增加预分配缓冲区使吞吐量提升了40%。
除了函数跟踪,ftrace还可以监控内核事件:
bash复制# 查看可用事件
cat available_events
# 启用irq相关事件
echo "irq:*" > set_event
# 启用sched相关事件
echo "sched:*" >> set_event
创建可复用的跟踪脚本:
bash复制#!/bin/bash
TRACE_DIR=/sys/kernel/debug/tracing
# 初始化
echo 0 > $TRACE_DIR/tracing_on
echo > $TRACE_DIR/trace
echo function_graph > $TRACE_DIR/current_tracer
echo "usb_*" > $TRACE_DIR/set_ftrace_filter
# 开始跟踪
echo 1 > $TRACE_DIR/tracing_on
$@ # 执行测试命令
echo 0 > $TRACE_DIR/tracing_on
# 保存结果
cat $TRACE_DIR/trace > trace.log
使用trace-cmd工具将数据转换为可视化图表:
bash复制# 记录跟踪数据
trace-cmd record -p function_graph -l "e1000*"
# 生成报告
trace-cmd report > report.txt
在实际驱动开发中,ftrace已经成为我不可或缺的调试工具。记得有一次调试一个SPI设备驱动,通过function_graph跟踪器发现时钟配置函数被意外调用了两次,节省了至少两天的排查时间。