第3章 CPU频率与电压调节(CPUFreq)
CPUFreq 是嵌入式 Linux 功耗管理的核心模块。说白了,它就是根据系统负载动态调整 CPU 的运行频率和电压。我做了这么多年低功耗优化,可以负责任地告诉你——调好 CPUFreq,往往能省下 30% 以上的功耗。
3.1 CPUFreq 框架三层架构
CPUFreq 框架分为三层:governor(策略层)、core(核心层)、driver(驱动层)。我习惯把这三层比作「大脑、骨架、手脚」。
核心要点:三层分工明确,上层策略决定「调多少」,下层驱动决定「怎么调」。
3.2 Governor 策略深度解析
Governor 是 CPUFreq 的「大脑」。它决定什么时候升频、什么时候降频。我见过不少项目,选错 governor 导致功耗翻倍。
| Governor | 策略特点 | 适用场景 | 功耗表现 |
|---|---|---|---|
| performance | 始终最高频 | 性能优先,不考虑功耗 | 最差 |
| powersave | 始终最低频 | 极低负载、待机 | 最好 |
| ondemand | 负载突变时立即升频 | 传统嵌入式设备 | 中等 |
| conservative | 频率渐变,平滑调整 | 对响应不敏感的场景 | 较好 |
| schedutil | 基于调度器负载信号 | 新内核、实时性要求高 | 最优 |
| userspace | 用户程序手动控制 | 定制化策略 | 取决于实现 |
3.3 ondemand vs schedutil 实战对比
这两个 governor 是实际项目中最常用的。我分别说说它们的坑和优势。
ondemand 的工作机制
ondemand 通过采样周期检测 CPU 负载。当负载超过 up_threshold(默认 95%)时,立即跳到最高频。低于 down_threshold 时逐步降频。
我曾经踩过的坑:在某个 IoT 项目中,ondemand 的采样周期设成了 10ms,结果 CPU 在 200ms 内反复升降频 20 次,功耗反而比固定高频还高。后来我把 sampling_rate 调到 50ms,问题解决。
# 查看当前 governor
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
# 切换为 ondemand
echo ondemand > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
# 调整采样周期(单位:微秒)
echo 50000 > /sys/devices/system/cpu/cpufreq/ondemand/sampling_rate
# 调整升频阈值
echo 85 > /sys/devices/system/cpu/cpufreq/ondemand/up_threshold
schedutil 的优势
schedutil 是 4.7 内核引入的「新秀」。它直接利用调度器的 PELT(Per-Entity Load Tracking)信号,不需要额外采样。说白了,调度器已经知道每个任务需要多少算力,schedutil 直接拿来用。
我的建议:如果你的内核版本 ≥ 4.7,优先用 schedutil。它在响应速度和功耗之间取得了更好的平衡。我在一个视频编解码项目上对比过,schedutil 比 ondemand 省电约 12%。
# 启用 schedutil
echo schedutil > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
# 查看 schedutil 参数(内核 5.4+)
cat /sys/devices/system/cpu/cpufreq/schedutil/rate_limit_us
# 设置速率限制(默认 1000us)
echo 2000 > /sys/devices/system/cpu/cpufreq/schedutil/rate_limit_us
3.4 用户空间策略定制
有时候现成的 governor 满足不了需求。比如你的设备有特殊的功耗曲线,或者需要根据温度、电池电量做联合调频。这时候就要自己写策略了。
方法一:userspace governor + 用户态脚本
最简单的方式。用户程序通过 sysfs 直接写频率。
#!/bin/sh
# 简易温度感知调频脚本
while true; do
temp=$(cat /sys/class/thermal/thermal_zone0/temp)
if [ "$temp" -gt 65000 ]; then
# 温度超过 65°C,降频到 800MHz
echo 800000 > /sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed
elif [ "$temp" -lt 45000 ]; then
# 温度低于 45°C,升频到 1.2GHz
echo 1200000 > /sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed
fi
sleep 2
done
小技巧:userspace 模式下,记得先查看 scaling_available_frequencies,确保你写的频率是硬件支持的。我见过有人写了个不存在的频率,结果驱动直接忽略请求。
方法二:编写自定义 governor 内核模块
这是更高级的做法。你需要实现 cpufreq_governor 结构体中的回调函数。
#include <linux/cpufreq.h>
static struct cpufreq_governor my_gov = {
.name = "my_custom_gov",
.owner = THIS_MODULE,
.init = my_gov_init,
.exit = my_gov_exit,
.start = my_gov_start,
.stop = my_gov_stop,
.limits = my_gov_limits,
};
// 核心调频决策函数
static void my_gov_work(struct work_struct *work)
{
unsigned int freq;
unsigned long load = get_cpu_load(); // 获取当前负载
if (load > 80)
freq = 1400000; // 高频
else if (load > 40)
freq = 1000000; // 中频
else
freq = 600000; // 低频
__cpufreq_driver_target(policy, freq, CPUFREQ_RELATION_L);
}
module_init(my_gov_init);
module_exit(my_gov_exit);
注意:编写自定义 governor 时,一定要处理好并发问题。调频回调可能被多个 CPU 同时调用。我早期写的模块就因为这个导致死锁,整机挂死。
3.5 DVFS 配置实战
DVFS(Dynamic Voltage and Frequency Scaling)是 CPUFreq 的底层支撑。调频的同时必须调压,否则芯片会不稳定。
| 频率 (MHz) | 电压 (mV) | 功耗 (mW) | 说明 |
|---|---|---|---|
| 1400 | 1200 | 850 | 最高性能 |
| 1000 | 1050 | 520 | 均衡模式 |
| 600 | 900 | 210 | 省电模式 |
| 200 | 750 | 45 | 深度睡眠 |
从表格可以看出,频率降低 50%,功耗能降 75%。这就是 DVFS 的价值所在。
# 查看支持的频率和电压
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies
# 查看当前频率
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq
# 查看可用 governor
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors
# 设置频率范围(最小值 600MHz,最大值 1.2GHz)
echo 600000 > /sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq
echo 1200000 > /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq
关键经验:DVFS 的电压表(OPP Table)通常由设备树或固件提供。我在调试一个平板项目时,发现某颗芯片的 1.4GHz 档位电压偏低,导致高负载下随机重启。后来在设备树中微调了 OPP 的电压值才稳定。
3.6 调试与验证
配置完 CPUFreq 后,一定要验证效果。我常用的方法:
- 查看调频日志:开启内核的 CPUFreq 调试选项
CONFIG_CPU_FREQ_DEBUG - 实时监控频率:用
watch -n 1 "cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq" - 功耗测量:用电流计或板载 ADC 读取实际功耗
- 压力测试:运行
stress --cpu 4观察频率响应和温度变化
我的习惯:每次改完 CPUFreq 配置,先用 trace-cmd 抓一段 cpufreq 事件,看看调频是否过于频繁。如果 1 秒内调频超过 10 次,说明参数需要优化。
CPUFreq 调优没有银弹。不同的硬件、不同的 workload,最优配置都不一样。多试、多测、多分析 trace,慢慢就能找到感觉。