第4章:CPU性能分析:CPU调度器原理(CFS/RT)、上下文切换与中断分析、perf工具的使用与火焰图生成
4.1 调度器:车载系统的“交通警察”
CPU调度器,说白了就是决定下一个该谁用CPU的“交通警察”。在车载Linux系统里,这个角色尤其关键。你想想看,一个ADAS摄像头数据流、一个CAN总线报文处理、一个HMI渲染线程,它们都在排队等着用CPU。调度器要是“指挥”不好,轻则界面卡顿,重则刹车延迟——这可是要命的事。
我个人习惯把调度器分成两类来理解:CFS(完全公平调度器)和RT(实时调度器)。它们俩的脾气完全不同。
4.2 CFS调度器:公平但不死板
CFS是Linux默认的调度器,它的核心思想是“让每个进程都能公平地分到CPU时间”。但它不是死板的平均主义。CFS引入了一个叫虚拟运行时间(vruntime)的概念。
嗯,这里要注意:vruntime不是真实的时间,而是经过优先级加权后的“虚拟时间”。优先级高的进程,它的vruntime增长得慢,所以它就能多占CPU。优先级低的进程,vruntime涨得快,自然就被“挤”到后面去了。
我在项目中遇到过一个问题:车载娱乐系统的后台OTA下载进程,把CPU吃满了,导致导航语音播报出现断断续续的“卡顿”。后来怎么解决的?其实就是调整了OTA进程的nice值,让它“谦让”一点。
关键参数:
- nice值:范围-20到19,值越小优先级越高。普通进程默认是0。
- sched_latency_ns:调度延迟,默认6ms。CFS保证在这个时间内,每个可运行进程至少被调度一次。
- min_granularity_ns:最小调度粒度,默认0.75ms。防止进程切换太频繁。
4.3 RT调度器:实时任务的“VIP通道”
RT调度器就不讲“公平”了。它给实时进程开了VIP通道。RT调度器有两种策略:SCHED_FIFO和SCHED_RR。
- SCHED_FIFO:先入先出。只要这个进程不主动让出CPU,优先级比它低的进程就别想跑。霸道吧?
- SCHED_RR:时间片轮转。同样是高优先级,但大家轮流用CPU,每个进程分一个时间片。
我曾经在调试一个激光雷达的数据采集进程时,发现它偶尔会丢包。查了半天,原来是它被设置成了SCHED_OTHER(普通调度),被其他进程抢占了。改成SCHED_FIFO,优先级设到80,问题立刻解决。
避坑指南:我曾经见过一个同事,把所有关键进程都设成SCHED_FIFO,结果一个死循环的进程把整个系统“锁死”了。RT进程优先级越高,责任越大。千万别滥用!
4.4 上下文切换:看不见的开销
上下文切换,就是CPU从一个进程切换到另一个进程的过程。每次切换,CPU都要保存当前进程的状态(寄存器、栈指针、内存映射等),再加载新进程的状态。这个过程是有代价的——它消耗CPU时间,却什么实际工作都没干。
怎么判断上下文切换是不是太多了?我一般看两个指标:
| 指标 | 正常范围 | 危险信号 |
|---|---|---|
| 每秒上下文切换次数 | < 10000 | > 50000 |
| 自愿切换 vs 非自愿切换 | 自愿占多数 | 非自愿突然飙升 |
用vmstat 1可以实时看上下文切换次数。如果cs列的数字一直很高,说明系统在“瞎忙活”。
我的经验:车载系统里,中断处理程序如果写得不好,也会导致大量上下文切换。我曾经优化过一个GPIO中断处理函数,把中断处理从上半部移到下半部(tasklet),上下文切换直接降了60%。
4.5 中断分析:别让外设“吵”到CPU
中断是外设通知CPU“我有事找你”的方式。但中断太多,CPU就一直在“接电话”,没时间干正事。
查看中断分布,我习惯用/proc/interrupts。这个文件会列出每个CPU核心上收到的中断次数。如果某个核心的中断数特别多,说明中断负载不均衡。
# 查看中断分布
cat /proc/interrupts | head -20
# 查看软中断
cat /proc/softirqs
嗯,这里有个技巧:在车载系统中,可以把网卡中断绑定到某个专用核心上,用irqbalance或者手动设置/proc/irq/<IRQ_NUM>/smp_affinity。这样其他核心就能专心处理实时任务了。
4.6 perf工具:性能分析的“瑞士军刀”
perf是Linux内核自带的性能分析工具。说实话,刚开始用的时候我觉得它有点“糙”,但用久了才发现,它才是真正的“硬核”工具。
我常用的几个perf命令:
# 统计CPU周期、指令数、缓存命中率
perf stat -e cycles,instructions,cache-misses ./my_app
# 实时采样,看哪些函数最耗CPU
perf top
# 记录采样数据,用于后续分析
perf record -a -g -- sleep 10
perf report
-g参数是关键,它开启调用链采样。没有调用链,你只知道哪个函数耗CPU,但不知道是谁调了它。
4.7 火焰图:一眼看穿CPU瓶颈
火焰图是perf数据的可视化呈现。它把CPU的调用栈画成一张“火焰”形状的图。y轴是调用栈深度,x轴是采样数量(越宽越耗CPU)。
生成火焰图的步骤:
# 1. 用perf record采集数据
perf record -a -g -F 99 -- sleep 30
# 2. 生成perf.data
# 3. 下载FlameGraph工具
git clone https://github.com/brendangregg/FlameGraph
# 4. 生成火焰图
perf script | ./FlameGraph/stackcollapse-perf.pl | ./FlameGraph/flamegraph.pl > cpu.svg
拿到火焰图后,怎么看?
- 看“平顶”:如果某个函数顶部很宽,说明它自己消耗了大量CPU,需要优化。
- 看“尖塔”:如果调用栈很深,说明函数调用层级太多,可能有性能问题。
- 看颜色:颜色没有特殊含义,只是随机分配,方便区分不同调用栈。
实战案例:有一次,我发现车载系统的CPU idle值很低,但业务负载并不高。生成火焰图一看,好家伙,一个日志打印函数占了30%的CPU。原来是某个模块在循环里疯狂打日志。关掉调试日志后,CPU idle立刻恢复到80%以上。
4.8 本章小结
CPU性能分析,说到底就是三件事:调度器选对了吗?上下文切换多不多?中断处理合理吗?perf和火焰图是帮你“看见”这些问题的眼睛。我建议你从今天开始,每次遇到CPU性能问题,先跑一遍perf,生成火焰图看看。很多时候,问题就一目了然了。
下一章,我们会聊内存性能分析。嗯,那又是一个“坑”很多的话题。