Linux 内核 CPUFreq 核心架构与通知器机制全解析从 cpufreq.c 到 OPP 频率表生成【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux导读本文基于 Linux 内核源码树中的 Documentation/cpu-freq/core.rst系统讲解 CPUFreqCPU 频率与电压动态调节核心子系统的总体架构核心代码的组织位置、面向架构驱动architecture driver的标准化接口、两类通知器policy notifier 与 transition notifier的触发时机与数据结构以及借助 OPPOperating 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.rstsysfs 统计接口说明与本文的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_freqs2.1 struct cpufreq_policyPolicy 是 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/ANYcpuunsigned int管理该 policy 的 CPU必须在线cpuinfo.max_freq / min_frequnsigned int硬件支持的最高/最低频率kHzcpuinfo.transition_latencyunsigned int切换延迟单位纳秒min/maxunsigned int策略的下限与上限频率单位 kHz——这正是 policy notifier 第三参数指向的内容curunsigned int当前频率kHz仅在使用 governor 时维护policy/governor—当前策略模式与所用 governorfreq_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切换前的旧频率kHznewunsigned int切换后的新频率kHzflagsu8cpufreq 驱动标志注意flags在源码中的实际类型是u88 位无符号整数它携带的是 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.hlist参数即上面的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 各通知两次第二参数phaseCPUFREQ_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 是什么OPPOperating 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字段除以 1000Hz → 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.rstOPP 深层机制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 的 SoCdev_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),仅供参考