Linux 内核 CPUFreq 核心架构与通知器机制全解析:从 cpufreq.c 到 OPP 频率表生成
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
导读
本文基于 Linux 内核源码树中的 Documentation/cpu-freq/core.rst,系统讲解 CPUFreq(CPU 频率与电压动态调节)核心子系统的总体架构:核心代码的组织位置、面向架构驱动(architecture driver)的标准化接口、两类通知器(policy notifier 与 transition notifier)的触发时机与数据结构,以及借助 OPP(Operating Performance Point)库自动生成 cpufreq 频率表的转换函数。读完本文,你将掌握内核各模块(如 ACPI 热管理、定时器代码、ARM 平台 LCD 驱动等)如何订阅 CPU 频率变化事件、如何安全持有 policy 引用,以及驱动开发者如何通过dev_pm_opp_init_cpufreq_table()一行代码把 OPP 表转换为 cpufreq 可用的频率表。
1. CPUFreq 核心的定位与分层
CPUFreq 核心代码位于drivers/cpufreq/cpufreq.c,其职责是为两类使用者提供标准化接口:
- CPUFreq 架构驱动(architecture drivers):真正执行频率切换(frequency transitions)的底层代码,例如基于 ACPI 的 Intel 驱动、基于 clock framework 的 ARM 驱动。核心层为它们屏蔽了上层策略差异。
- 通知器(notifiers):内核中需要感知策略变化或频率变化的其他模块,例如:
- 需要感知 policy 变化的热管理模块(如 ACPI);
- 需要感知所有频率变化的定时器/计时代码(timing code);
- 需要强制设置某些速度上限的模块(如 ARM 架构上的 LCD 驱动);
- 以及内核常量
loops_per_jiffy——它会在频率变化时在此处被更新,以保证忙等待延时(如udelay/ndelay)在频率切换后依然准确。
从源码结构看,drivers/cpufreq/目录下还同时提供 cpu-drivers.rst(面向架构驱动编写者的说明)与 cpufreq-stats.rst(sysfs 统计接口说明),与本文的core.rst共同构成完整的 CPUFreq 文档族。
1.1 核心层的典型职责
结合 drivers/cpufreq/cpufreq.c 可以看到,核心层除了上面说的接口标准化外,还负责:
- 维护每个 CPU 对应的
cpufreq_policy对象(通过 per-CPU 指针cpufreq_cpu_data管理); - 提供
cpufreq_cpu_get_raw()/cpufreq_cpu_get()/cpufreq_cpu_put()等引用管理接口; - 在频率切换的前后阶段向 transition notifier 广播事件,并同步调整
loops_per_jiffy; - 对外导出
cpufreq_get()、cpufreq_quick_get()、cpufreq_quick_get_max()等查询接口(声明于 include/linux/cpufreq.h),供其他内核模块快速读取当前频率或硬件最高频率。
2. 核心数据结构:cpufreq_policy 与 cpufreq_freqs
2.1 struct cpufreq_policy
Policy 是 CPUFreq 核心最重要的管理对象,它描述一组共享时钟域的 CPU 的频率策略。其完整定义位于 include/linux/cpufreq.h,通知器所关心的核心字段包括:
| 字段 | 类型 | 含义 |
|---|---|---|
cpus | cpumask_var_t | 共享该 policy 的在线CPU 集合 |
related_cpus | cpumask_var_t | 在线 + 离线 CPU 集合(用于 ACPI 等需要整体协调的场景) |
real_cpus | cpumask_var_t | 相关且 present 的 CPU 集合 |
shared_type | unsigned int | 共享类型,仅 ACPI 使用:CPUFREQ_SHARED_TYPE_NONE/HW/ALL/ANY |
cpu | unsigned int | 管理该 policy 的 CPU(必须在线) |
cpuinfo.max_freq / min_freq | unsigned int | 硬件支持的最高/最低频率(kHz) |
cpuinfo.transition_latency | unsigned int | 切换延迟,单位纳秒 |
min/max | unsigned int | 策略的下限与上限频率,单位 kHz——这正是 policy notifier 第三参数指向的内容 |
cur | unsigned int | 当前频率(kHz),仅在使用 governor 时维护 |
policy/governor | — | 当前策略模式与所用 governor |
freq_table | struct cpufreq_frequency_table * | 频率表,可由 OPP 库生成(见第 5 节) |
rwsem | struct rw_semaphore | 保护 policy 结构的读写信号量 |
需要特别强调的是:文档与代码中所有频率数值都以 kHz 为单位(include/linux/cpufreq.h中明确注释 "Frequency values here are CPU kHz")。例如 1.8 GHz 写为1800000。
2.2 struct cpufreq_freqs
频率切换事件中传递给 transition notifier 的第三参数,定义于 include/linux/cpufreq.h:
| 字段 | 类型 | 含义 |
|---|---|---|
policy | struct cpufreq_policy * | 指向所属 policy 的指针 |
old | unsigned int | 切换前的旧频率(kHz) |
new | unsigned int | 切换后的新频率(kHz) |
flags | u8 | cpufreq 驱动标志 |
注意flags在源码中的实际类型是u8(8 位无符号整数),它携带的是 cpufreq 驱动相关的标志位,而非通知器阶段标志。
3. Policy 引用计数:cpufreq_cpu_get / cpufreq_cpu_put
由于 policy 对象可能在 CPU 热插拔(hotplug)或驱动卸载时被释放,任何想长期持有 policy 的模块都必须通过引用计数保护它。核心层为此提供了成对接口:
cpufreq_cpu_get(cpu):获取指定 CPU 对应的 policy 并使其"忙碌"(busy)。其实现(drivers/cpufreq/cpufreq.c)会:- 校验
cpu < nr_cpu_ids,否则WARN_ON并返回 NULL; - 在
cpufreq_driver_lock读锁保护下检查驱动是否已注册(cpufreq_driver非空); - 通过
cpufreq_cpu_get_raw()找到 policy,并调用kobject_get(&policy->kobj)递增 kobject 引用计数; - 返回 policy 指针(未注册驱动或 CPU 不在 policy 中时返回 NULL)。
这保证了:cpufreq 驱动已正确与核心注册,且在调用
cpufreq_put_cpu之前不会被卸载,对应的 policy 也不会在使用期间被释放。- 校验
cpufreq_cpu_put(policy):对cpufreq_cpu_get()返回的 policy 调用kobject_put(),平衡 kobject 引用计数(drivers/cpufreq/cpufreq.c)。
此外,头文件中还提供了基于作用域的清理宏DEFINE_FREE(put_cpufreq_policy, ...)(include/linux/cpufreq.h),配合__free(put_cpufreq_policy)使用,可在函数退出时自动调用cpufreq_cpu_put(),简化错误路径处理——drivers/cpufreq/cpufreq.c内部的多处调用(如cpufreq_update_policy、sysfs 操作路径)正是采用这一模式。
4. CPUFreq 通知器(Notifiers)机制
CPUFreq 通知器符合内核标准通知器接口(详见 include/linux/notifier.h)。它们分为两类,通过 include/linux/cpufreq.h 中的列表常量区分:
#define CPUFREQ_TRANSITION_NOTIFIER (0) #define CPUFREQ_POLICY_NOTIFIER (1) /* Transition notifiers */ #define CPUFREQ_PRECHANGE (0) #define CPUFREQ_POSTCHANGE (1) /* Policy Notifiers */ #define CPUFREQ_CREATE_POLICY (0) #define CPUFREQ_REMOVE_POLICY (1)注册/注销接口为cpufreq_register_notifier(struct notifier_block *nb, unsigned int list)与cpufreq_unregister_notifier()(include/linux/cpufreq.h),list参数即上面的CPUFREQ_TRANSITION_NOTIFIER或CPUFREQ_POLICY_NOTIFIER。
4.1 Policy 通知器:策略创建与移除
- 触发时机:当一个新的 policy 被创建或移除时通知;
- 第二参数(phase):首次创建时为
CPUFREQ_CREATE_POLICY,移除时为CPUFREQ_REMOVE_POLICY; - 第三参数(
void *):指向struct cpufreq_policy,其中min、max分别表示新策略的下限与上限频率(kHz)。
典型订阅者是需要感知策略范围变化的模块——例如热管理(ACPI)需要根据新的频率上下限调整冷却策略。
4.2 Transition 通知器:频率切换前后
- 触发时机:当 cpufreq 驱动切换某 CPU 核心频率、且该变化没有外部影响(no external implications,即纯内部频率变更)时,对 policy 中的每个在线 CPU 各通知两次;
- 第二参数(phase):
CPUFREQ_PRECHANGE(切换前)或CPUFREQ_POSTCHANGE(切换后); - 第三参数:
struct cpufreq_freqs,携带policy、old(旧频率)、new(新频率)、flags(驱动标志)。
切换前后两次通知的设计,是为了让依赖频率的模块(如定时器/计时代码)能在频率变化前暂停或调整其校准值、在变化后重新校准。与此配套,核心层还提供cpufreq_freq_transition_begin()与cpufreq_freq_transition_end()(include/linux/cpufreq.h)供驱动在切换过程中广播这两阶段事件。
4.3 loops_per_jiffy 的同步更新
在"对外部有影响的频率变化"处理路径中,核心层通过adjust_jiffies()(drivers/cpufreq/cpufreq.c 附近)在CPUFREQ_PRECHANGE与CPUFREQ_POSTCHANGE两个阶段调整系统常量loops_per_jiffy。注释明确指出:该值在 SMP 系统上无法简单更新(受其他 CPU 并发影响),因此仅适用于单处理器等受限场景——这是内核中基于忙循环延时(loops_per_jiffy换算)在频率切换后仍保持正确性的关键机制。
5. 借助 OPP 库生成 cpufreq 频率表
5.1 OPP 是什么
OPP(Operating Performance Point)是设备支持的一组离散的"频率-电压"元组。例如一个 MPU 设备支持{300MHz, 1.0V}、{800MHz, 1.2V}、{1GHz, 1.3V}三个工作点,即三个 OPP。OPP 库位于drivers/opp/,头文件为 include/linux/pm_opp.h,通过CONFIG_PM_OPP启用。详细背景可阅读 Documentation/power/opp.rst。
5.2 dev_pm_opp_init_cpufreq_table()
该函数提供了一条开箱即用的转换例程,把 OPP 层内部记录的可用频率信息翻译成 cpufreq 可直接使用的struct cpufreq_frequency_table。其实现位于 drivers/opp/cpu.c,关键逻辑:
- 通过
dev_pm_opp_get_opp_count(dev)获取 OPP 数量,若<= 0返回-ENODATA; - 分配
(max_opps + 1)个表项(多出的 1 项用于结束标记); - 用
dev_pm_opp_find_freq_ceil()逐个取到不小于期望值的 OPP 频率,将frequency字段除以 1000(Hz → kHz)填入表项; - 若该 OPP 是 turbo/boost 工作点(
dev_pm_opp_is_turbo()为真),则给表项打上CPUFREQ_BOOST_FREQ标志; - 最后一项目以
CPUFREQ_TABLE_END作为表尾标记。
可能的返回值:成功返回 0;-EINVAL(非法指针)、-ENODEV(设备未找到)、-ENOMEM(内存不足,表未填充)、-ENODATA(无可用 OPP)。文档特别提醒:不要在中断上下文调用本函数(涉及内存分配与 OPP 查找)。
典型用法示例(源自原文档,频率表最终挂到 policy 上):
soc_pm_init() { /* Do things */ r = dev_pm_opp_init_cpufreq_table(dev, &freq_table); if (!r) policy->freq_table = freq_table; /* Do other things */ }5.3 dev_pm_opp_free_cpufreq_table()
释放由dev_pm_opp_init_cpufreq_table()分配的表。实现见 drivers/opp/cpu.c:校验指针后kfree(*opp_table)并将表指针置 NULL,防止悬挂引用。
5.4 编译前提
文档明确要求:仅当CONFIG_CPU_FREQ与CONFIG_PM_OPP同时启用时,dev_pm_opp_init_cpufreq_table()才可用。因此面向 SoC 的 cpufreq 驱动若使用 OPP 建表,其 Kconfig 需要同时依赖这两个开关。
6. 与其余文档的衔接
- 架构驱动编写者:若你是要新写一个 cpufreq 驱动,请继续阅读 Documentation/cpu-freq/cpu-drivers.rst,其中涵盖
cpufreq_driver结构体、cpufreq_register_driver()注册流程、以及 target/setpolicy/fast_switch等回调约定; - 统计与调试:
/sys/.../cpufreq/stats的统计输出、时间窗与重置语义见 Documentation/cpu-freq/cpufreq-stats.rst; - OPP 深层机制:OPP 表的注册、搜索、可用性控制与数据结构见 Documentation/power/opp.rst;
- 核心源码:一切机制的最终实现以 drivers/cpufreq/cpufreq.c 与 include/linux/cpufreq.h 为准。
7. 小结
CPUFreq 核心通过drivers/cpufreq/cpufreq.c向下为架构驱动提供标准化接口,向上以两类通知器(policy 通知器与 transition 通知器)向内核其他模块广播策略与频率事件;引用计数接口cpufreq_cpu_get/cpufreq_cpu_put保证了 policy 在热插拔与驱动卸载场景下的安全使用;loops_per_jiffy的同步更新保证了延时校准的准确性。对于使用 OPP 的 SoC,dev_pm_opp_init_cpufreq_table()/dev_pm_opp_free_cpufreq_table()提供了从 OPP 工作点到 cpufreq 频率表的便捷转换路径,是连接 DVFS 电源管理与 cpufreq 策略层之间的重要桥梁。
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考