Linux 内核 CPUFreq 子系统全解析:核心接口、驱动开发指南与 sysfs 统计
Linux 内核 CPUFreq 子系统全解析核心接口、驱动开发指南与 sysfs 统计【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux本指南以 Linux 内核官方文档Documentation/cpu-freq/文档集入口 index.rst含 core.rst、cpu-drivers.rst、cpufreq-stats.rst为主体结合本仓库drivers/cpufreq/下的真实源码与头文件系统讲解 CPUFreq 子系统的整体架构、notifier 机制、处理器驱动cpufreq driver的完整实现套路以及面向用户的 sysfs 频率统计接口。读完本文你将掌握 CPUFreq 的三大骨架核心层如何管理策略policy与引用计数、如何从零编写一个 cpufreq 处理器驱动含频率表辅助函数、以及如何通过/sys/devices/system/cpu/cpuX/cpufreq/stats/统计并诊断 CPU 调频行为。一、CPUFreq 子系统概述为何要动态调节 CPU 频率CPUFreqCPU frequency and voltage scaling是 Linux 内核中负责在运行时动态改变 CPU 时钟频率的子系统。其核心动机正如官方文档开篇所述降低时钟频率即可降低 CPU 功耗因此在笔记本、嵌入式设备等电池供电场景下时钟缩放clock scaling是省电的有效手段而在服务器与移动平台上它同样是散热与性能平衡的关键机制。CPUFreq 的核心代码位于 drivers/cpufreq/cpufreq.c。该核心层向上层提供一套标准化接口服务于两类消费者CPUFreq 架构驱动processor driver真正执行频率切换动作的代码例如本仓库中的 acpi-cpufreq.c、intel_pstate.c、cpufreq-dt.c、qcom-cpufreq-hw.c 等Notifier 订阅者需要感知策略policy变化或频率变化的设备驱动与内核模块例如 ACPI 热管理模块、计时timing代码甚至 ARM 平台上需要强制设置速度上限的 LCD 驱动。此外内核中的常量loops_per_jiffy也会在频率变化时由核心层统一更新保证基于延迟校准如udelay系列的代码在调频后依然准确。在深入各子主题前先给出全仓库层面的文件地图便于后续对照阅读主题文档核心源码核心层与 notifierDocumentation/cpu-freq/core.rstdrivers/cpufreq/cpufreq.c、include/linux/cpufreq.h处理器驱动开发Documentation/cpu-freq/cpu-drivers.rstdrivers/cpufreq/freq_table.c、drivers/cpufreq/longrun.csysfs 统计Documentation/cpu-freq/cpufreq-stats.rstdrivers/cpufreq/cpufreq_stats.c二、CPUFreq 核心层与策略Policy管理2.1 核心层提供的标准化接口核心层负责调度驱动、governor调频策略如cpufreq_ondemand、cpufreq_conservative、cpufreq_performance、cpufreq_powersave、cpufreq_userspace见 drivers/cpufreq/ 目录下对应文件与 notifier 三者之间的协作。架构驱动通过cpufreq_register_driver()向核心注册一个struct cpufreq_drivergovernor 则通过核心层决定何时把频率切到哪个档位。2.2 策略cpufreq_policy与引用计数一个struct cpufreq_policy定义于 include/linux/cpufreq.h代表一组共享时钟/电压轨的 CPU的调频约束包含min/max可设置的频率下限/上限单位 kHz、governor、cpus等字段。核心层通过cpufreq_cpu_get()与cpufreq_cpu_put()完成策略的引用计数管理cpufreq_cpu_get(cpu)内部先调用cpufreq_cpu_get_raw()取得该 CPU 对应的 policy然后对其 kobject 执行kobject_get()从而保证驱动已正确注册到核心层、在cpufreq_cpu_put()被调用前驱动不会被卸载、对应的 policy 在使用期间不会被释放见 drivers/cpufreq/cpufreq.c 的注释与实现cpufreq_cpu_put(policy)则通过kobject_put()平衡引用计数drivers/cpufreq/cpufreq.c。这套引用计数机制是所有驱动/模块安全访问 policy 的前提文档明确强调取得 policy 后必须调用cpufreq_cpu_put释放否则会导致 kobject 引用泄漏。三、CPUFreq Notifier策略通知与频率切换通知CPUFreq 的 notifier 遵循内核标准 notifier 接口见 include/linux/notifier.h。核心层在 drivers/cpufreq/cpufreq.c 中维护了两条通知链static BLOCKING_NOTIFIER_HEAD(cpufreq_policy_notifier_list); SRCU_NOTIFIER_HEAD_STATIC(cpufreq_transition_notifier_list);策略通知链使用阻塞型 notifierblocking_notifier_call_chain因为创建/移除策略时可睡眠频率切换通知链使用 SRCU notifier配合cpufreq_freq_transition_begin()/cpufreq_freq_transition_end()使用适合快速切换路径。3.1 策略通知policy notifier当一个新的 policy 被创建或移除时触发。阶段phase由第二个参数指定CPUFREQ_CREATE_POLICYpolicy 首次创建CPUFREQ_REMOVE_POLICYpolicy 被移除。定义见 include/linux/cpufreq.h/* Policy Notifiers */ #define CPUFREQ_CREATE_POLICY (0) #define CPUFREQ_REMOVE_POLICY (1)第三个参数void *pointer指向struct cpufreq_policy其中包含新策略的min、max新策略的频率下限与上限单位 kHz。核心层在 drivers/cpufreq/cpufreq.c 与 drivers/cpufreq/cpufreq.c 中分别于 policy 创建后、移除前发出这两类通知。3.2 频率切换通知transition notifier当 cpufreq 驱动把 CPU 核心频率从一档切换到另一档、且该变化不涉及外部影响时对 policy 中每一个在线 CPU各通知两次阶段参数为CPUFREQ_PRECHANGE切换前CPUFREQ_POSTCHANGE切换后。定义见 include/linux/cpufreq.h。第三个参数是struct cpufreq_freqs其字段如下对应文档表格结构体定义见 include/linux/cpufreq.h字段含义policy指向struct cpufreq_policy的指针old切换前的旧频率new切换后的新频率flagscpufreq 驱动的 flags核心层的实现函数为cpufreq_notify_transition()它同时负责调用adjust_jiffies()修正内核定时器校准见 drivers/cpufreq/cpufreq.c。在切换失败时cpufreq_notify_post_transition()会交换old/new后补发一对 PRECHANGE/POSTCHANGE 通知保证订阅者能够感知到回滚动作drivers/cpufreq/cpufreq.c。四、如何实现一个新的 CPUFreq 处理器驱动文档 Documentation/cpu-freq/cpu-drivers.rst 给出了从零编写处理器驱动的完整套路下面按文档脉络展开并补充源码细节。4.1 初始化Initialization在__initcall级别 7即module_init()或更晚的函数中先检查当前内核运行在正确的 CPU / 芯片组上确认无误后通过cpufreq_register_driver()向核心注册一个struct cpufreq_driver。该结构体的必填字段包括.name驱动名称.initper-policy 初始化函数指针.verify策略校验函数指针.setpolicy或.fast_switch或.target或.target_index四选一具体差异见下文 4.44.7。可选字段有.flags给核心层的提示.driver_data驱动私有数据.get_intermediate/.target_intermediate切换中间稳定频率时使用.get返回 CPU 当前频率.bios_limit返回硬件/BIOS 对该 CPU 的最高频率限制.exitper-policy 清理函数在 CPU 热插拔的CPU_POST_DEAD阶段调用.suspendper-policy 挂起函数在中断关闭、且 governor 已停止之后调用.resumeper-policy 恢复函数在中断关闭、governor 重新启动之前调用.readypolicy 完全初始化完成后调用的 per-policy 回调.attr以 NULL 结尾的struct freq_attr列表用于向 sysfs 导出属性.boost_enabled置位表示开启 boost 频率.set_boostper-policy 的 boost 频率使能/禁用函数。4.2 Per-CPU 初始化.init当新 CPU 注册到设备模型、或 cpufreq 驱动注册自身后若该 CPU 尚无 policy核心会调用cpufreq_driver.init。注意.init()与.exit()对同一个 policy 只调用一次而不是对 policy 管辖的每个 CPU 各调用一次。该回调接收struct cpufreq_policy *policy参数需要在其中填写以下字段对照文档表格字段含义policy-cpuinfo.min_freq/policy-cpuinfo.max_freq该 CPU 支持的最低/最高频率kHzpolicy-cpuinfo.transition_latency该 CPU 在两档频率间切换所需时间纳秒policy-cur当前运行频率如适用policy-min/policy-max缩放频率上下限若在.init()中设置将作为 policy 频率 QoS 请求的上下界否则反映驱动可设置的最小/最大频率policy-policy及必要的policy-governor该 CPU 的默认策略稍后核心会携带这些值调用verify与setpolicy/targetpolicy-cpus更新为所有与该 CPU 共享时钟/电压轨即共同做 DVFS的在线离线 CPU 掩码这些字段的取值可以借助下一节介绍的频率表辅助函数来填充。4.3 verify策略校验当用户设定一个新策略由policy, governor, min, max组成时必须经过校验以修正不兼容的值。可借助cpufreq_verify_within_limits(struct cpufreq_policy *policy, unsigned int min_freq, unsigned int max_freq)完成范围校验。文档特别强调一个关键原则必须保证在policy-min与policy-max之间至少存在一个有效频率或有效频率区间。如果不存在应先尝试提高policy-max只有提高上限仍无解时才降低policy-min。4.4 target / target_index / setpolicy / fast_switch四种切换回调怎么选大多数 cpufreq 驱动乃至大多数调频算法只允许把 CPU 频率设置到预定义的固定档位这类驱动使用-target()、-target_index()或-fast_switch()而少数处理器能够自行在上下限之间动态切换频率这类处理器应使用-setpolicy()。4.5 target_index 与已废弃的 targettarget_index是当前推荐方式带两个参数struct cpufreq_policy *policy与unsigned int index指向暴露频率表的索引。驱动被调用时必须将频率设为freq_table[index].frequency。出错时应恢复到之前的频率即policy-restore_freq即使此前已经切换到过中间频率。target已废弃Deprecated带三个参数struct cpufreq_policy *policy、unsigned int target_frequency、unsigned int relation。驱动选择新频率必须遵循以下规则尽量贴近target_freq必须满足policy-min new_freq policy-max若relation CPUFREQ_REL_L选择不低于target_freq的新频率L for lowest, but no lower than若relation CPUFREQ_REL_H选择不高于target_freq的新频率H for highest, but no higher than。这两个回调的频率选择逻辑都可借助频率表辅助函数完成见第六节。4.6 fast_switch调度器上下文中的快速切换fast_switch用于从调度器scheduler上下文发起频率切换。并非所有驱动都必须实现它因为该回调内部不允许睡眠且必须高度优化以尽可能快地完成切换。它带两个参数struct cpufreq_policy *policy与unsigned int target_frequency。4.7 setpolicy处理器自管式调频setpolicy只接收一个struct cpufreq_policy *policy参数。驱动需要把处理器/芯片组内部动态调频的下限设为policy-min、上限设为policy-max如果硬件支持当policy-policy CPUFREQ_POLICY_PERFORMANCE时选择性能优先设置当policy-policy CPUFREQ_POLICY_POWERSAVE时选择省电优先设置。文档给出的参考实现是本仓库中的 drivers/cpufreq/longrun.c。4.8 get_intermediate 与 target_intermediate中间稳定频率仅适用于使用target_index()且未设置CPUFREQ_ASYNC_NOTIFICATION的驱动get_intermediate()应返回平台希望先切入的稳定中间频率在跳到index对应频率前target_intermediate()把 CPU 设到该中间频率核心层会负责发送通知驱动无需在target_intermediate()或target_index()中自行处理通知若驱动对某个目标频率不希望使用中间频率get_intermediate()可返回0此时核心会直接调用-target_index()注意失败时-target_index()应恢复到policy-restore_freq因为核心会为恢复动作发送通知。五、频率表辅助函数Frequency Table Helpers由于大多数 cpufreq 处理器只能被设置到少数几个固定频率文档建议驱动使用频率表 辅助函数来简化工作。5.1 频率表的结构与终止符一张频率表是struct cpufreq_frequency_table条目的数组每个条目在driver_data中保存驱动特定值在frequency中保存对应频率并可设置 flags表尾必须有一个frequency设为CPUFREQ_TABLE_END的条目想跳过某个条目将其频率设为CPUFREQ_ENTRY_INVALID条目不要求排序但排好序的话核心做 DVFS 时搜索最佳匹配更快。只要 policy 的policy-freq_table字段指向合法频率表核心就会自动校验该表。5.2 两个核心辅助函数cpufreq_frequency_table_verify()保证在policy-min与policy-max之间至少存在一个有效频率并满足其余条件适用于-verify回调。其实现位于 drivers/cpufreq/freq_table.c并通过cpufreq_generic_frequency_table_verify()封装成可直接赋给.verify的通用回调drivers/cpufreq/freq_table.c。cpufreq_frequency_table_target()-target阶段的对应辅助函数传入目标频率等信息后返回 CPU 应被设置到的频率表条目。5.3 迭代器宏cpufreq_for_each_entry(pos, table)遍历频率表全部条目cpufreq_for_each_valid_entry(pos, table)遍历除CPUFREQ_ENTRY_INVALID之外的所有条目。两者都以poscpufreq_frequency_table *循环游标和table待遍历的频率表为参数示例struct cpufreq_frequency_table *pos, *driver_freq_table; cpufreq_for_each_entry(pos, driver_freq_table) { /* Do something with pos */ pos-frequency ...; }如需获取pos在表中的位置不要用指针相减开销大应改用cpufreq_for_each_entry_idx()与cpufreq_for_each_valid_entry_idx()宏。六、OPP 生成 CPUFreq 频率表对于使用 Linux OPPOperating Performance Point操作性能点框架的平台核心文档 Documentation/cpu-freq/core.rst 提供了两个转换辅助函数OPP 框架详见 Documentation/power/opp.rstdev_pm_opp_init_cpufreq_table(dev, freq_table)把 OPP 层内部的可用频率信息直接转换为可供 cpufreq 使用的频率表。注意禁止在中断上下文调用此函数。示例用法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 */ }该函数仅在同时开启CONFIG_CPU_FREQ与CONFIG_PM_OPP时可用。dev_pm_opp_free_cpufreq_table释放由dev_pm_opp_init_cpufreq_table分配的表。从仓库源码看cpufreq-dt.c等基于设备树/OPP 的驱动正是这类转换的典型实践场景。七、sysfs CPUFreq 统计cpufreq-stats文档 Documentation/cpu-freq/cpufreq-stats.rst 描述的cpufreq-stats是一个为每个 CPU 提供频率统计信息的驱动实现在 drivers/cpufreq/cpufreq_stats.c。它的最大特点是与具体的 cpufreq 驱动解耦——无论当前运行的是哪个处理器驱动它都能工作。7.1 接口位置与属性文件启用后每个 CPU 会在 sysfs 中生成独立目录sysfs 根/devices/system/cpu/cpuX/cpufreq/stats/。典型内容如下来自官方文档示例mysystem:/sys/devices/system/cpu/cpu0/cpufreq/stats # ls -l total 0 drwxr-xr-x 2 root root 0 May 14 16:06 . drwxr-xr-x 3 root root 0 May 14 15:58 .. --w------- 1 root root 4096 May 14 16:06 reset -r--r--r-- 1 root root 4096 May 14 16:06 time_in_state -r--r--r-- 1 root root 4096 May 14 16:06 total_trans -r--r--r-- 1 root root 4096 May 14 16:06 trans_table对应源码中default_attrs[]与stats_attr_group名称为stats在 drivers/cpufreq/cpufreq_stats.c 定义通过sysfs_create_group(policy-kobj, stats_attr_group)挂载到 policy 上。7.2 各统计项详解time_in_state只读。给出 CPU 在支持的每个频率上花费的时间每行输出frequency time其中 time 的单位是 usertime 单位即10ms与/proc中导出的其他时间单位一致。示例mysystem:/sys/devices/system/cpu/cpu0/cpufreq/stats # cat time_in_state 3600000 2089 3400000 136 3200000 34 3000000 67 2800000 172488从源码看show_time_in_state()内部用nsec_to_clock_t(time)将纳秒时间换算为时钟节拍后输出drivers/cpufreq/cpufreq_stats.c而底层累计由cpufreq_stats_update()基于local_clock()差值完成。total_trans只读。该 CPU 上发生的频率切换总次数。示例mysystem:/sys/devices/system/cpu/cpu0/cpufreq/stats # cat total_trans 20trans_table只读。细粒度的切换矩阵。i,j第 i 行、第 j 列表示从Freq_i切到Freq_j的次数行列顺序与驱动最初提交给 cpufreq 核心的频率表顺序一致可升序、可降序、可不排序输出同时附带实际频率值便于阅读。示例mysystem:/sys/devices/system/cpu/cpu0/cpufreq/stats # cat trans_table From : To : 3600000 3400000 3200000 3000000 2800000 3600000: 0 5 0 0 0 3400000: 4 0 2 0 0 3200000: 0 1 0 2 0 3000000: 0 0 1 0 3 2800000: 0 0 0 2 0若切换表超过PAGE_SIZE读取该文件会返回-EFBIG错误对应源码 drivers/cpufreq/cpufreq_stats.c 处的边界检查。reset只写。写入任意内容即可重置统计计数器。这对于在不重启系统的情况下评估不同 governor 的行为非常有用。源码中的store_reset()出于并发安全考虑把重置操作延迟到下次cpufreq_stats_record_transition()时执行通过reset_pending/reset_time标志与smp_rmb()内存屏障配合见 drivers/cpufreq/cpufreq_stats.c 与 drivers/cpufreq/cpufreq_stats.c。所有统计数据的统计窗口为stats 驱动插入或统计被重置之时起至读取该统计项之时止在 stats 驱动插入之前发生的频率切换显然不在统计范围内。7.3 切换记录的核心实现cpufreq_stats_record_transition()drivers/cpufreq/cpufreq_stats.c是统计更新的核心它在每次频率切换时用freq_table_get_index()将新旧频率换算为表内索引累加trans_table[old_index * max_state new_index]与total_trans并借助cpufreq_stats_update()将停留时间记入time_in_state[last_index]。其中time_in_state、freq_table、trans_table三块内存在cpufreq_stats_create_table()中一次性分配drivers/cpufreq/cpufreq_stats.c并通过cpufreq_for_each_valid_entry收集去重后的有效频率。7.4 内核配置cpufreq-stats 的配置项如下对应文档中的菜单导航Config Main Menu Power management options (ACPI, APM) --- CPU Frequency scaling --- [*] CPU Frequency scaling [*] CPU frequency translation statistics必须先启用CPU Frequency scalingCONFIG_CPU_FREQCPU frequency translation statisticsCONFIG_CPU_FREQ_STAT提供time_in_state、total_trans与trans_table三项统计启用后只要 CPU 支持 cpufreq即可在/sysfs中看到频率统计。八、实践建议与阅读路线想快速上手调频优先阅读 Documentation/cpu-freq/cpufreq-stats.rst并在支持 cpufreq 的机器上直接cat /sys/devices/system/cpu/cpu0/cpufreq/stats/*观察现状想理解核心机制通读 Documentation/cpu-freq/core.rst 后对照 drivers/cpufreq/cpufreq.c 中的cpufreq_freq_transition_begin()/cpufreq_freq_transition_end()与 notifier 链实现想为新产品写驱动以 Documentation/cpu-freq/cpu-drivers.rst 为开发手册参考 drivers/cpufreq/longrun.csetpolicy范式、drivers/cpufreq/cpufreq-dt.cOPP 频率表范式以及 drivers/cpufreq/freq_table.c 的频率表辅助函数即可覆盖从验证、初始化到切换回调的完整驱动骨架想排查调频异常结合trans_table矩阵分析频繁震荡的频率对例如示例中 3.6GHz↔3.4GHz 之间的来回切换配合reset属性在更换 governor 前后对比统计快速定位问题。在实现驱动时请务必牢记文档强调的三条硬性约束.target阶段必须保证policy-min new_freq policy-maxfast_switch回调内禁止睡眠dev_pm_opp_init_cpufreq_table禁止在中断上下文调用。这三条直接关系到 CPUFreq 子系统在多核、热插拔与实时场景下的正确性。【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联
返回资讯列表 →