4. CPU频率调节:cpufreq框架、governor策略及驱动适配

CPU频率调节是Linux内核功耗管理中最直接、最有效的节能手段之一。通过动态调整CPU的运行频率和电压,系统可以在性能与功耗之间取得平衡。本章深入剖析cpufreq框架的架构设计、核心governor策略的工作原理,并给出驱动开发者适配cpufreq的实战指南。

cpufreq 框架整体架构 用户空间 sysfs 接口 (/sys/devices/system/cpu/cpuX/cpufreq/) 内核核心层 cpufreq 核心框架 (drivers/cpufreq/cpufreq.c) Governor 策略层 performance | powersave | userspace | ondemand | conservative | schedutil (每个 governor 实现 cpufreq_governor 接口) 驱动层 (cpufreq_driver) acpi-cpufreq | intel_pstate | cpufreq-dt | qcom-cpufreq-hw | ... 硬件层 CPU P-state / OPP (Operating Performance Point) 控制寄存器

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、主流发行版)
Governor 策略频率响应对比 时间 频率 performance powersave ondemand schedutil performance 恒定最高频;powersave 恒定最低频;ondemand 跳变剧烈;schedutil 平滑跟随负载

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);
}
性能优势:相比 ondemand 的采样-判断模式,schedutil 具有更快的响应速度(亚毫秒级)和更平滑的频率变化曲线。在 Android 和主流 Linux 发行版中,schedutil 已成为默认 governor。

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 驱动实现步骤

  1. 解析 OPP 表:在 .init() 回调中调用 dev_pm_opp_of_add_table() 加载 OPP。
  2. 填充频率表:遍历 OPP 表,构建 struct cpufreq_frequency_table。
  3. 实现 target_index:根据索引设置对应的频率和电压。
  4. 注册驱动:调用 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
调优建议:对于嵌入式设备,推荐使用 schedutil 配合 energy-aware scheduling (EAS),可以实现最佳的功耗-性能平衡。在电池供电的设备上,可以设置 scaling_max_freq 为最高频率的 80%,以显著降低峰值功耗。
cpufreq 驱动适配流程 1. 解析设备树 OPP 表 2. 构建频率表 (freq_table) 3. 实现 target_index 回调 4. 填充 cpufreq_driver 结构体 5. 注册驱动 (cpufreq_register_driver) 6. 验证 sysfs 接口

4.6 本章小结

cpufreq 框架为 Linux 系统提供了灵活、可扩展的 CPU 频率调节能力。驱动开发者需要理解分层架构,正确实现 cpufreq_driver 接口,并选择合适的 governor 策略。在现代系统中,schedutil 凭借其与调度器的深度集成,成为兼顾性能与功耗的最佳选择。掌握 cpufreq 的适配与调优,是嵌入式功耗优化工程师的必备技能。