第四章 CPU调频与调度优化:让每颗核心都物尽其用

CPU调频和调度,说白了就是回答两个问题:跑多快?谁来跑? 调频决定电压频率点,调度决定任务扔给哪个核。这两者配合不好,性能上不去,功耗也下不来。

我在Rockchip平台上调过不少次,发现很多人只改了个governor就以为完事了。其实远没那么简单。这一章我们深入聊聊CPUFreq governor的选择、CPU Isolation的玩法,以及HMP和EAS调度器的调优思路。

核心观点: 调频和调度是联动的。选错了governor,调度器再聪明也白搭;隔离了CPU却不调整调度域,效果等于零。
CPU调频与调度优化知识体系 CPU调频与调度 CPUFreq Governor performance schedutil ondemand / conservative CPU Isolation isolcpus 内核参数 cpuset 控制器 中断亲和性 + NOHZ 调度器调优 HMP (旧) EAS (新) Energy Model 调度域 / 负载跟踪 调频提供“性能天花板”,调度决定“谁来用天花板”。两者必须协同配置。

4.1 CPUFreq Governor 选型:performance vs schedutil

Rockchip平台上的CPUFreq governor,我见得最多的就是 performance 和 schedutil。ondemand和conservative基本可以退休了,尤其在大小核架构上,它们反应太慢。

4.1.1 performance governor

这个最简单——直接把频率锁死在最高点。延迟最低,但功耗也最高。适合的场景:

  • 跑分测试、基准测试(你懂的)
  • 实时性要求极高的音频/视频处理
  • 前台交互响应敏感的应用
我的习惯: 调试阶段先用performance,排除调频引入的延迟干扰。等调度逻辑调稳了,再切回schedutil做功耗优化。

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
注意: schedutil依赖调度器的PELT负载跟踪。如果你在内核里关了CONFIG_SCHED_PELT或者用了老的负载算法,schedutil会变得很迟钝。我曾经在一个老内核上踩过这个坑,折腾了两天才发现是内核配置没开。

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回调从这些核上移走
关键点: isolcpus只是不让普通进程跑上去,但内核线程、中断、软中断仍然可能在这些核上执行。要彻底隔离,还得配合cpuset和中断亲和性。

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个核跑音频处理,结果发现延迟还是不稳定。查了半天,发现DMA中断没绑走,每隔几毫秒就来一次。把中断亲和性一改,延迟立刻稳了。

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的功耗系数
    // ...
};
我的建议: 如果你的内核版本在5.10以上,优先用EAS。HMP在4.19内核上还能用,但到了5.10之后,EAS已经非常成熟了。我在RK3588上切到EAS之后,同等性能下功耗降低了大约15%。

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调频与调度优化的标准流程:

  1. 先确认硬件拓扑:查看 /proc/cpuinfo,确认大小核分布
  2. 检查内核配置:确保CONFIG_ENERGY_MODEL、CONFIG_CPU_FREQ_GOV_SCHEDUTIL已开启
  3. 验证能量模型:cat /sys/kernel/debug/energy_model 看系数是否正确
  4. 选择governor:性能优先用performance,功耗优先用schedutil
  5. 配置隔离:根据业务需求,用isolcpus + cpuset + 中断亲和性做隔离
  6. 监控验证:用perf stat、trace-cmd、/proc/sched_debug 验证效果
一句话总结: 调频给调度提供“弹药”,调度给任务分配“阵地”。两者配合好了,你的Rockchip平台才能既跑得快又吃得少。