Linux 内核 CPUFreq 核心架构与通知器机制全解析:从 cpufreq.c 到 OPP 频率表生成
2026/9/15 22:22:30 网站建设 项目流程

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,通知器所关心的核心字段包括:

字段类型含义
cpuscpumask_var_t共享该 policy 的在线CPU 集合
related_cpuscpumask_var_t在线 + 离线 CPU 集合(用于 ACPI 等需要整体协调的场景)
real_cpuscpumask_var_t相关且 present 的 CPU 集合
shared_typeunsigned int共享类型,仅 ACPI 使用:CPUFREQ_SHARED_TYPE_NONE/HW/ALL/ANY
cpuunsigned int管理该 policy 的 CPU(必须在线)
cpuinfo.max_freq / min_frequnsigned int硬件支持的最高/最低频率(kHz)
cpuinfo.transition_latencyunsigned int切换延迟,单位纳秒
min/maxunsigned int策略的下限与上限频率,单位 kHz——这正是 policy notifier 第三参数指向的内容
curunsigned int当前频率(kHz),仅在使用 governor 时维护
policy/governor当前策略模式与所用 governor
freq_tablestruct cpufreq_frequency_table *频率表,可由 OPP 库生成(见第 5 节)
rwsemstruct 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:

字段类型含义
policystruct cpufreq_policy *指向所属 policy 的指针
oldunsigned int切换前的旧频率(kHz)
newunsigned int切换后的新频率(kHz)
flagsu8cpufreq 驱动标志

注意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)会:

    1. 校验cpu < nr_cpu_ids,否则WARN_ON并返回 NULL;
    2. cpufreq_driver_lock读锁保护下检查驱动是否已注册(cpufreq_driver非空);
    3. 通过cpufreq_cpu_get_raw()找到 policy,并调用kobject_get(&policy->kobj)递增 kobject 引用计数;
    4. 返回 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_NOTIFIERCPUFREQ_POLICY_NOTIFIER

4.1 Policy 通知器:策略创建与移除

  • 触发时机:当一个新的 policy 被创建移除时通知;
  • 第二参数(phase):首次创建时为CPUFREQ_CREATE_POLICY,移除时为CPUFREQ_REMOVE_POLICY
  • 第三参数(void *:指向struct cpufreq_policy,其中minmax分别表示新策略的下限与上限频率(kHz)。

典型订阅者是需要感知策略范围变化的模块——例如热管理(ACPI)需要根据新的频率上下限调整冷却策略。

4.2 Transition 通知器:频率切换前后

  • 触发时机:当 cpufreq 驱动切换某 CPU 核心频率、且该变化没有外部影响(no external implications,即纯内部频率变更)时,对 policy 中的每个在线 CPU 各通知两次;
  • 第二参数(phase)CPUFREQ_PRECHANGE(切换前)或CPUFREQ_POSTCHANGE(切换后);
  • 第三参数struct cpufreq_freqs,携带policyold(旧频率)、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_PRECHANGECPUFREQ_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,关键逻辑:

  1. 通过dev_pm_opp_get_opp_count(dev)获取 OPP 数量,若<= 0返回-ENODATA
  2. 分配(max_opps + 1)个表项(多出的 1 项用于结束标记);
  3. dev_pm_opp_find_freq_ceil()逐个取到不小于期望值的 OPP 频率,将frequency字段除以 1000(Hz → kHz)填入表项;
  4. 若该 OPP 是 turbo/boost 工作点(dev_pm_opp_is_turbo()为真),则给表项打上CPUFREQ_BOOST_FREQ标志;
  5. 最后一项目以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_FREQCONFIG_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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询