第四章 CPU调频与调度优化:让每颗核心都物尽其用
CPU调频和调度,说白了就是回答两个问题:跑多快?谁来跑? 调频决定电压频率点,调度决定任务扔给哪个核。这两者配合不好,性能上不去,功耗也下不来。
我在Rockchip平台上调过不少次,发现很多人只改了个governor就以为完事了。其实远没那么简单。这一章我们深入聊聊CPUFreq governor的选择、CPU Isolation的玩法,以及HMP和EAS调度器的调优思路。
4.1 CPUFreq Governor 选型:performance vs schedutil
Rockchip平台上的CPUFreq governor,我见得最多的就是 performance 和 schedutil。ondemand和conservative基本可以退休了,尤其在大小核架构上,它们反应太慢。
4.1.1 performance governor
这个最简单——直接把频率锁死在最高点。延迟最低,但功耗也最高。适合的场景:
- 跑分测试、基准测试(你懂的)
- 实时性要求极高的音频/视频处理
- 前台交互响应敏感的应用
4.1.2 schedutil governor
schedutil是内核4.7之后引入的,它直接从调度器的PELT负载信号里拿数据来做调频决策。说白了,调度器说“这个核很忙”,schedutil就提频;调度器说“空闲了”,它就降频。
比起ondemand那种定时采样,schedutil的响应快得多。我在RK3588上实测过,schedutil的调频延迟比ondemand低了大约 40%。
# 查看当前governor
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
# 切换governor(所有核)
for cpu in /sys/devices/system/cpu/cpu[0-9]*/cpufreq/scaling_governor; do
echo schedutil > $cpu
done
4.1.3 选型对比
| Governor | 响应速度 | 功耗 | 适用场景 |
|---|---|---|---|
| performance | 最快(锁频) | 最高 | 跑分、实时音频、调试阶段 |
| schedutil | 快(PELT驱动) | 中等 | 日常交互、混合负载、大小核 |
| ondemand | 慢(定时采样) | 中等偏高 | 老旧内核兼容 |
| conservative | 最慢 | 低(但延迟大) | 几乎不用了 |
4.2 CPU Isolation 与 isolcpus
CPU Isolation,说白了就是把某些CPU核心从通用调度器中摘出来,不让普通进程跑上去。这样做的目的很明确:给关键任务留出干净的执行环境。
4.2.1 isolcpus 内核参数
在kernel cmdline里加isolcpus参数是最直接的方式:
# 隔离CPU4-7(假设8核)
isolcpus=4,5,6,7 nohz_full=4,5,6,7 rcu_nocbs=4,5,6,7
这三个参数通常一起用:
- isolcpus:不让普通进程调度到这些核
- nohz_full:关闭这些核的周期性tick,减少中断干扰
- rcu_nocbs:把RCU回调从这些核上移走
4.2.2 cpuset 控制器
cpuset比isolcpus更灵活。你可以在运行时动态调整哪些进程可以跑在哪些核上。我一般在产品里用cpuset做精细控制:
# 创建cpuset给实时任务
mkdir /sys/fs/cgroup/cpuset/rt_tasks
echo 4-7 > /sys/fs/cgroup/cpuset/rt_tasks/cpuset.cpus
echo 0-3 > /sys/fs/cgroup/cpuset/rt_tasks/cpuset.mems
# 把实时进程移进去
echo $PID > /sys/fs/cgroup/cpuset/rt_tasks/cgroup.procs
4.2.3 中断亲和性
隔离了CPU,但中断还在往这些核上发,那就白干了。调整中断亲和性:
# 查看网卡中断
cat /proc/interrupts | grep eth0
# 把中断绑定到CPU0-3
echo 0f > /proc/irq/123/smp_affinity
4.3 HMP 与 EAS 调度器调优
Rockchip的大小核架构(比如RK3588的4×A76 + 4×A55),调度器怎么把任务分配到合适的核上,直接决定了性能和功耗。
4.3.1 HMP(Heterogeneous Multi-Processing)
HMP是早期的方案。它的思路很简单:大核跑重任务,小核跑轻任务。但问题是——谁来定义“重”和“轻”?
HMP靠的是负载阈值。比如负载超过70%就迁移到大核,低于30%就迁回小核。但这个阈值很难调,调高了小核过载,调低了大核浪费。
# HMP相关参数(在/sys/kernel/hmp/下)
# 迁移阈值
/sys/kernel/hmp/up_threshold # 升迁阈值(默认70%)
/sys/kernel/hmp/down_threshold # 降迁阈值(默认30%)
4.3.2 EAS(Energy-Aware Scheduling)
EAS是HMP的进化版。它不再靠简单的负载阈值,而是引入了一个能量模型(Energy Model)。调度器会算一笔账:这个任务跑在大核上要多少能量?跑在小核上要多少能量?然后选一个“性价比”最高的方案。
EAS在Rockchip平台上的配置要点:
- 内核必须开启 CONFIG_ENERGY_MODEL 和 CONFIG_CPU_FREQ_GOV_SCHEDUTIL
- 设备树里要提供 动态功率系数(dynamic-power-coefficient)
- 调度域(sched domain)的配置要正确
// 设备树中的能量模型示例(RK3588)
cpu0: cpu@0 {
compatible = "arm,cortex-a55";
reg = <0x0 0x0>;
dynamic-power-coefficient = <100>; // A55的功耗系数
// ...
};
cpu4: cpu@4 {
compatible = "arm,cortex-a76";
reg = <0x0 0x4>;
dynamic-power-coefficient = <350>; // A76的功耗系数
// ...
};
4.3.3 调度域调优
EAS依赖调度域来知道哪些核是同一个簇、哪些核跨簇。调度域配错了,EAS会做出错误的迁移决策。
# 查看当前调度域
cat /proc/sched_domain
# 输出示例(简化)
# CPU0-3 (A55簇) -> level 1: MC (SMT) -> level 2: DIE
# CPU4-7 (A76簇) -> level 1: MC (SMT) -> level 2: DIE
如果调度域不对,检查设备树里的 cpu-map 节点:
cpu-map {
cluster0 {
core0 { cpu = <&cpu0>; };
core1 { cpu = <&cpu1>; };
core2 { cpu = <&cpu2>; };
core3 { cpu = <&cpu3>; };
};
cluster1 {
core0 { cpu = <&cpu4>; };
core1 { cpu = <&cpu5>; };
core2 { cpu = <&cpu6>; };
core3 { cpu = <&cpu7>; };
};
};
4.4 实战调优流程
最后,我总结一下在Rockchip平台上做CPU调频与调度优化的标准流程:
- 先确认硬件拓扑:查看 /proc/cpuinfo,确认大小核分布
- 检查内核配置:确保CONFIG_ENERGY_MODEL、CONFIG_CPU_FREQ_GOV_SCHEDUTIL已开启
- 验证能量模型:cat /sys/kernel/debug/energy_model 看系数是否正确
- 选择governor:性能优先用performance,功耗优先用schedutil
- 配置隔离:根据业务需求,用isolcpus + cpuset + 中断亲和性做隔离
- 监控验证:用perf stat、trace-cmd、/proc/sched_debug 验证效果