Linux嵌入式性能分析实战:用perf+Ftrace定位应用卡顿的根因
嵌入式板子“偶尔一卡”最忌盲改:先用数据分CPU热点、内核路径、调度阻塞、IO等待四层,再动手。perf做采样画像,Ftrace做内核函数级时间线,二者配合可从用户态一路追到驱动和调度器。
一、内核与工具准备
内核至少开:
CONFIG_PERF_EVENTS=y
CONFIG_DEBUG_INFO=y
CONFIG_FRAME_POINTER=y
CONFIG_FTRACE=y
CONFIG_FUNCTION_TRACER=y
CONFIG_FUNCTION_GRAPH_TRACER=y
CONFIG_IRQSOFF_TRACER=y
CONFIG_PREEMPTIRQ_TRACERS=y
CONFIG_SCHEDSTATS=y
自研内核交叉编perf:
cd linux-src
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- perf
cp tools/perf/perf /target/usr/bin/
新版内核tracefs在/sys/kernel/tracing,老内核在/sys/kernel/debug/tracing,后面统一用前者;没挂载就:
mount -t tracefs tracefs /sys/kernel/tracing
小ARM板采样频率别太高,99~199Hz足够,避免采样本身拖慢业务。
二、perf先定热点方向
卡顿时先top看是哪个进程,再perf实时扫:
perf top -p $(pidof app) -g
若看到memcpy/ memset/ do_anonymous_page占比高,偏向内存拷贝或缺页;看到pthread_mutex、futex、spin_lock,偏向锁;看到vfs_read/write、submit_bio,偏向IO。离线抓调用链:
perf record -p $(pidof app) -g -F 99 -o perf.data -- sleep 20
perf report -g --no-children
想出火焰图:
perf script -i perf.data > app.perf
stackcollapse-perf.pl app.perf > app.folded
flamegraph.pl app.folded > app.svg
某无人机飞控曾用类似流程发现spi_transfer里spin_lock占30% CPU,改DMA批传后降下来。 量化硬件层再补:
perf stat -p $(pidof app) -e cycles,instructions,cache-misses,branch-misses -- sleep 10
IPC低、cache-miss高,优先查数据结构局部性和false sharing;多核争用可用perf c2c看缓存行。
三、锁与调度卡顿用sched事件
应用“CPU不高但发呆”,多是抢锁、被抢占、等唤醒:
perf record -e sched:sched_switch,sched:sched_wakeup -g -p $(pidof app) -- sleep 15
perf script | grep -E "sched_(switch|wakeup)" > sched.log
全系统看调度延迟:
perf sched record -a -- sleep 10
perf sched latency --sort max
输出按任务列平均/最大等待CPU时间。若控制线程最大等待几十毫秒,查是否被普通线程、软中断、RCU、kswapd抢核;实时场景把控制线程SCHED_FIFO并isolcpus,irqbalance避开控制核。
用户态锁可先用轻量strace看方向:
strace -f -p $(pidof app) -e trace=futex -T 2>&1 | head
futex平均耗时高再回perf看具体调用栈,别一上来长开strace,ptrace会让实时业务变形。
四、Ftrace看内核内部耗时
只卡在内核态时,function_graph最直观。例如驱动读传感器慢:
T=/sys/kernel/tracing
echo 0 > $T/tracing_on
echo function_graph > $T/current_tracer
echo my_spi_xfer > $T/set_graph_function
echo 1 > $T/tracing_on
# 触发业务
sleep 2
echo 0 > $T/tracing_on
cat $T/trace
trace会按缩进列出my_spi_xfer→spi_sync→controller_transfer及每个子函数微秒级耗时;若mutex_lock、wait_for_completion、dma_mapping长时间停留,就能定位是锁、完成量还是DMA映射慢。
系统调用级卡顿过滤单函数:
echo function_graph > $T/current_tracer
echo vfs_read > $T/set_graph_function
echo 1 > $T/tracing_on; sleep 1; echo 0 > $T/tracing_on
cat $T/trace
调度切换卡用事件:
echo 1 > $T/events/sched/sched_switch/enable
echo 1 > $T/events/sched/sched_wakeup/enable
echo 1 > $T/tracing_on; sleep 3; echo 0 > $T/tracing_on
cat $T/trace
可看到目标线程在哪个CPU、被谁抢占、唤醒到真正运行隔多久。
五、中断关闭与软中断抖动
工业卡顿常由长关中断、软中断批处理引起:
echo irqsoff > $T/current_tracer
echo 1 > $T/tracing_on
# 复现卡顿
echo 0 > $T/tracing_on
cat $T/trace
报告会给出最大关中断时长和调用路径;preemptoff同理查可抢占关闭路径。软中断可单独录:
trace-cmd record -e softirq:softirq_entry -e softirq:softirq_exit \
-e irq:irq_handler_entry -e sched:sched_switch -- sleep 5
trace-cmd report
网络高吞吐时NET_RX软中断占核,可把网卡中断绑到非实时核、开RPS/N API预算、或把控制线程隔离。
六、page fault与IO回写
频繁申请大缓冲会触发匿名页缺页、compact、direct reclaim:
perf stat -e page-faults,minor-faults,major-faults -p $(pidof app) -- sleep 10
解决方向:预分配缓冲池、mlockall锁内存、实时线程启动期一次性分配;量产别在控制循环里malloc。写文件卡看block和writeback:
trace-cmd record -e block:block_rq_issue -e writeback:* -P $(pidof app) -- sleep 8
trace-cmd report
日志类改成tmpfs环形、定时批刷,避免每次采集都fsync。
七、联合定位示例
现象:1ms控制环偶发延迟8ms。流程:
1)perf sched latency确认控制线程最大等待CPU约7ms;
2)perf record -e sched/irq,发现同期NET_RX软中断在控制核跑满;
3)ftrace irqsoff查驱动关中断时长,定位SPI中断里调了msleep式轮询;
4)整改:SPI中断只清标志、底半部threaded处理,网卡IRQ绑CPU0/1,控制线程isolcpus=2,3、SCHED_FIFO 90;
5)复测perf sched latency最大等待降到百微秒级。
八、避坑
perf必须与运行内核同版本/同config,否则符号错乱;
小板record别-a全系统长时间,数据大且影响业务;按PID、短窗口、低频率抓;
ftrace用完复位:echo nop > current_tracer; echo 0 > tracing_on,避免ring buffer常驻;
function_graph全内核开代价大,务必set_*filter/set_graph_function缩范围;
实时内核调优别只看average,看p99/p999和worst;cyclictest复现配合ftrace抓最差窗口最准。
按“perf定热点→sched/irq查抢占→ftrace函数图查内核耗时→page/block查内存IO”闭环,绝大多数嵌入式卡顿都能落到具体函数、锁、中断或调度策略,避免凭经验乱改。





