4. CPU频率调节
CPU频率调节是Linux内核功耗管理中最直接、最有效的节能手段之一。通过动态调整CPU的运行频率和电压,系统可以在性能与功耗之间取得平衡。本章深入剖析cpufreq框架的架构设计、核心governor策略的工作原理,并给出驱动开发者适配cpufreq的实战指南。
4.1 cpufreq 核心框架
cpufreq 框架位于 drivers/cpufreq/ 目录下,采用分层设计,将策略制定与硬件操作解耦。核心框架负责管理所有 CPU 的频率缩放策略,并提供统一的 sysfs 接口供用户空间查询和配置。
4.1.1 核心数据结构
框架的核心数据结构是 struct cpufreq_policy,它代表一个或多个共享同一频率域的 CPU 集群的策略对象。
struct cpufreq_policy {
unsigned int cpu; /* 策略管理的第一个 CPU */
unsigned int related_cpus; /* 共享该策略的所有 CPU 位图 */
unsigned int min; /* 最小频率 (kHz) */
unsigned int max; /* 最大频率 (kHz) */
unsigned int cur; /* 当前频率 (kHz) */
struct cpufreq_governor *governor; /* 当前使用的 governor */
struct cpufreq_driver *driver; /* 绑定的 cpufreq 驱动 */
/* ... 其他字段 */
};
4.1.2 驱动注册接口
每个 CPU 频率驱动必须实现 struct cpufreq_driver 结构体,并调用 cpufreq_register_driver() 注册到核心框架。
static struct cpufreq_driver my_cpufreq_driver = {
.name = "my_cpufreq",
.flags = CPUFREQ_CONST_LOOPS,
.init = my_cpufreq_init,
.verify = my_cpufreq_verify,
.target = my_cpufreq_target,
.get = my_cpufreq_get,
.exit = my_cpufreq_exit,
};
static int __init my_cpufreq_init_module(void)
{
return cpufreq_register_driver(&my_cpufreq_driver);
}
.target() 或 .target_index() 回调,用于实际设置 CPU 频率。对于支持频率表(frequency table)的硬件,推荐使用 .target_index(),它接收频率表索引,简化了频率验证逻辑。
4.2 Governor 策略详解
Governor 是 cpufreq 框架的“大脑”,它决定何时以及如何调整 CPU 频率。Linux 内核内置了多种 governor,每种适用于不同的场景。
| Governor | 策略描述 | 功耗表现 | 适用场景 |
|---|---|---|---|
| performance | 始终将 CPU 频率设置为最高可用频率 | 最高功耗 | 对延迟极度敏感的场景(如实时音频处理) |
| powersave | 始终将 CPU 频率设置为最低可用频率 | 最低功耗 | 后台任务、待机模式、对性能无要求的场景 |
| userspace | 允许用户空间程序通过 sysfs 手动设置频率 | 取决于用户配置 | 自定义调频策略、调试与测试 |
| ondemand | 根据 CPU 使用率动态调频,负载高时立即升频,负载低时降频 | 中等功耗 | 通用桌面和服务器场景(传统默认) |
| conservative | 类似 ondemand,但频率升降更平滑、更渐进 | 较低功耗 | 对频率突变敏感的场景(如嵌入式设备) |
| schedutil | 基于调度器利用率(utilization)的调频策略,与 CFS 调度器深度集成 | 低功耗且高性能 | 现代 Linux 系统(推荐默认,Android、主流发行版) |
4.3 schedutil:现代调频首选
schedutil 是 Linux 内核 4.7 引入的 governor,它直接利用 CFS 调度器提供的 CPU 利用率(utilization)数据来驱动频率调整,实现了“调度感知”的调频策略。
4.3.1 工作原理
schedutil 的核心思想是:频率 = 当前利用率 × 最大频率。调度器在每次 tick 或任务迁移时更新每个 CPU 的运行队列利用率,schedutil 根据这个利用率按比例调整频率。
// schedutil 的核心算法(简化)
static void sugov_update_single(struct sugov_cpu *sg_cpu, u64 time, unsigned int flags)
{
unsigned long util, max, freq;
struct cpufreq_policy *policy = sg_cpu->policy;
// 1. 获取调度器利用率(0 ~ 1024)
util = sugov_get_util(sg_cpu);
max = arch_scale_cpu_capacity(sg_cpu->cpu);
// 2. 计算目标频率
freq = map_util_freq(util, policy->max, max);
// 3. 设置频率
cpufreq_driver_target(policy, freq, CPUFREQ_RELATION_L);
}
4.4 驱动适配实战
驱动开发者适配 cpufreq 时,需要关注以下几个关键环节:
4.4.1 频率表与 OPP
大多数嵌入式平台使用 OPP(Operating Performance Points) 来描述 CPU 支持的频率-电压组合。驱动应通过 dev_pm_opp_of_add_table() 从设备树加载 OPP 表。
// 设备树中的 OPP 表示例
cpu0_opp_table: opp-table {
compatible = "operating-points-v2";
opp-shared;
opp-1000000000 {
opp-hz = /bits/ 64 <1000000000>;
opp-microvolt = <1100000>;
};
opp-1400000000 {
opp-hz = /bits/ 64 <1400000000>;
opp-microvolt = <1200000>;
};
opp-1800000000 {
opp-hz = /bits/ 64 <1800000000>;
opp-microvolt = <1300000>;
};
};
4.4.2 驱动实现步骤
- 解析 OPP 表:在
.init()回调中调用dev_pm_opp_of_add_table()加载 OPP。 - 填充频率表:遍历 OPP 表,构建
struct cpufreq_frequency_table。 - 实现 target_index:根据索引设置对应的频率和电压。
- 注册驱动:调用
cpufreq_register_driver()。
static int my_cpufreq_target_index(struct cpufreq_policy *policy,
unsigned int index)
{
struct cpufreq_frequency_table *freq_table = policy->freq_table;
unsigned int target_freq = freq_table[index].frequency;
unsigned int target_volt;
// 1. 根据频率查找对应的电压
target_volt = my_get_voltage_for_freq(target_freq);
// 2. 设置 CPU 频率和电压(调用硬件寄存器操作)
my_set_cpu_freq_volt(target_freq, target_volt);
// 3. 更新策略的当前频率
policy->cur = target_freq;
return 0;
}
4.4.3 避坑指南
- 频率域共享:如果多个 CPU 共享同一时钟域,必须使用
cpufreq_policy的related_cpus位图,避免重复设置。 - 电压-频率依赖:升频前必须先升压,降频后才可以降压,否则可能导致系统不稳定甚至损坏硬件。
- 延迟容忍:频率切换可能涉及 PLL 锁定延迟,驱动应在
.target_index()中处理忙等待或调度延迟。 - 热插拔场景:CPU 热插拔时,必须正确调用
cpufreq_update_policy()更新策略。
4.5 用户空间调优接口
系统运行时,可以通过 sysfs 接口动态调整 governor 和频率范围:
# 查看当前 governor
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
# 切换 governor
echo schedutil > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
# 设置频率范围(仅 userspace governor 生效)
echo 1000000 > /sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq
echo 1800000 > /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq
scaling_max_freq 为最高频率的 80%,以显著降低峰值功耗。
4.6 本章小结
cpufreq 框架为 Linux 系统提供了灵活、可扩展的 CPU 频率调节能力。驱动开发者需要理解分层架构,正确实现 cpufreq_driver 接口,并选择合适的 governor 策略。在现代系统中,schedutil 凭借其与调度器的深度集成,成为兼顾性能与功耗的最佳选择。掌握 cpufreq 的适配与调优,是嵌入式功耗优化工程师的必备技能。