2、CPU频率调节(CPUFreq):框架、策略与工具

CPU频率调节,说白了就是让CPU在「跑得快」和「省点电」之间找个平衡。我刚开始接触嵌入式Linux时,总觉得这玩意儿就是个简单的调频开关。后来在项目里被功耗问题折磨过几次,才真正理解了CPUFreq框架的深度。

嗯,咱们今天就把这个框架拆开看看。

2.1 CPUFreq框架介绍

CPUFreq是Linux内核里负责动态调整CPU频率和电压的子系统。它不是一个简单的驱动,而是一整套机制。我个人习惯把它分成三层来看:

  • 底层:硬件相关的驱动层,直接操作CPU的PLL、分频器、电压调节器。这部分跟具体芯片绑定,比如高通、全志、瑞芯微都有自己的实现。
  • 中间层:核心框架层,提供统一的接口给上层调用。它管理着频率表、电压表、以及各种策略的切换逻辑。
  • 上层:Governor策略层,决定「什么时候该升频,什么时候该降频」。

我在一个平板项目里遇到过一个问题:CPU频率死活上不去,跑分只有标称值的一半。查了两天才发现,是底层驱动里某个分频系数配置错了。你看,底层出问题,上层再怎么调策略都没用。

小提示:调试CPUFreq问题时,先确认/sys/devices/system/cpu/cpu0/cpufreq/目录下的文件是否存在。如果连这个目录都没有,说明驱动层就没加载成功。

2.2 Governor策略详解

Governor是CPUFreq的灵魂。它决定了CPU频率的调节行为。Linux内核目前支持6种Governor,我一个个说。

2.2.1 performance

这个最简单——CPU始终跑在最高频率。说白了就是「性能至上,功耗不管」。适合对响应速度要求极高的场景,比如实时控制系统。但你要是在电池设备上用这个,续航会很难看。

2.2.2 powersave

跟performance相反,始终跑在最低频率。省电是省电了,但系统会卡得让你怀疑人生。我一般只在调试低功耗模式时临时用一下。

2.2.3 userspace

把频率控制权交给用户态。你可以通过cpufrequtils工具手动设置频率。这个策略给了开发者最大的灵活性,但也最容易出问题。我曾经见过一个产品,开发者在userspace策略里写了个死循环调频,结果CPU温度直接飙到90度。

2.2.4 ondemand

这是经典策略。它根据CPU负载动态调频:负载高了就升频,负载低了就降频。但它的响应有延迟,因为它是周期性采样负载的。在负载剧烈波动的场景下,你会感觉到卡顿。

2.2.5 conservative

跟ondemand类似,但调频更温和。它不会一下子跳到最高频率,而是逐步升降。适合对功耗敏感、对性能要求不高的场景。我个人觉得它在某些嵌入式设备上比ondemand更省电。

2.2.6 schedutil

这是目前最推荐的策略。它直接跟调度器(CFS)集成,利用调度器已经计算好的负载信息来调频。说白了,它比ondemand更「聪明」,响应更快,功耗控制也更精细。从内核4.7开始引入,现在已经是很多平台默认的Governor了。

重要对比:schedutil和ondemand最大的区别在于,schedutil是「事件驱动」的——调度器每次切换进程时都可能触发调频;而ondemand是「定时采样」的——每隔几十毫秒才检查一次负载。所以schedutil的响应速度更快。

2.3 驱动接口与用户态工具

2.3.1 内核驱动接口

CPUFreq驱动需要实现一组回调函数。核心接口定义在include/linux/cpufreq.h中。我挑几个关键的说说:

// 驱动注册结构体
struct cpufreq_driver {
    char name[CPUFREQ_NAME_LEN];
    int (*init)(struct cpufreq_policy *policy);
    int (*verify)(struct cpufreq_policy *policy);
    int (*target)(struct cpufreq_policy *policy,
                  unsigned int target_freq,
                  unsigned int relation);
    unsigned int (*get)(unsigned int cpu);
    // ... 其他回调
};

init在CPU上线时调用,负责初始化频率表。target是核心函数,负责实际设置频率。get返回当前频率。我在写驱动时,最常踩的坑是verify函数没处理好——它负责校验请求的频率是否在合法范围内。如果这个函数写错了,上层设置频率时会静默失败。

警告:写CPUFreq驱动时,一定要处理好频率表的排序和去重。我曾经见过一个驱动,频率表里有重复项,结果调频时内核panic了。嗯,这种bug很难复现,但一旦出现就很致命。

2.3.2 用户态工具:cpufrequtils

cpufrequtils是一套用户态工具,用来查看和设置CPUFreq参数。最常用的几个命令:

  • cpufreq-info:查看当前CPU频率、可用Governor、可用频率等信息。
  • cpufreq-set:设置Governor策略或手动指定频率。
  • cpufreq-aperf:查看CPU的实际性能计数。

举个例子,查看当前CPU状态:

# cpufreq-info
analyzing CPU 0:
  driver: cpufreq-dt
  CPUs which run at the same hardware frequency: 0
  hardware limits: 408 MHz - 1.51 GHz
  available frequency steps: 408 MHz, 600 MHz, 816 MHz, 1.01 GHz, 1.21 GHz, 1.41 GHz, 1.51 GHz
  available cpufreq governors: conservative, ondemand, userspace, powersave, performance, schedutil
  current policy: frequency should be within 408 MHz and 1.51 GHz.
                  The governor "schedutil" may decide which speed to use
                  within this range.
  current CPU frequency is 816 MHz.

设置Governor为performance:

# cpufreq-set -g performance

手动设置频率(需要userspace策略):

# cpufreq-set -g userspace
# cpufreq-set -f 1.01GHz
调试技巧:如果你发现设置频率后没生效,先检查一下是不是有thermal框架在干预。很多SoC都有温度保护机制,温度高了会自动限频。用cat /sys/class/thermal/thermal_zone*/temp看看温度,往往能发现问题。

2.4 实际项目中的选择建议

说了这么多,到底该用哪个Governor?我根据经验给个参考:

场景 推荐Governor 理由
服务器/高性能计算 performance 不需要考虑功耗,性能最大化
手机/平板(日常使用) schedutil 响应快,功耗控制好
IoT设备(电池供电) conservative 调频温和,省电效果明显
实时控制系统 performance 避免调频带来的延迟抖动
调试/测试阶段 userspace 可以手动固定频率,方便复现问题

最后说一句:没有万能的Governor。我建议你在产品开发阶段,把几种策略都跑一遍性能测试和功耗测试,用数据说话。毕竟,理论再漂亮,不如实际跑出来的结果有说服力。