ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Linux 6.6内核 CPU 深度解析(六):cpufreq

2026/10/4 6:29:01 拓冰建站 浏览量
Linux 6.6内核 CPU 深度解析(六):cpufreq 〇、全景CPU 有活干时可以跑慢点省电上一篇 cpuidle 讲的是CPU 没活干时睡多深。但 CPU有活干时也不是非得全速跑——如果负载很轻把频率降下来、电压降下来照样能完成任务还省电。这就是cpufreqP-state性能状态管的事决定 CPU 当前跑多快。负载变化调度器 tick / 任务切换governor 决定频率schedutil 默认读调度器 util计算目标频率driver 设置频率写 MSR / HWP requestCPU 跑到新频率一句话主线cpufreq 是CPU 跑多快的状态机——负载变化时由governor 根据调度器的 CPU 利用率算出目标频率再由driver 把它写到硬件写 MSR或交给 HWP 硬件自主调频。它管的是运行中怎么调频和 cpuidle 的空闲时睡多深是一对——一个管 C-state一个管 P-state。一、预备概念P-state 与两个状态机1.1 P-state频率 电压的组合P-statePerformance state是 CPU 的一组频率 电压工作点。频率越高、电压越高性能越强、功耗越大。x86 上 P-state 的数量和值由 CPU 型号决定比如 P0 是最高 turbo 频率P1 是基频往下依次降低。关键P-state 和 C-state 是两个正交的维度。C-state 是睡多深空闲省电P-state 是跑多快运行时省电。一个 CPU 可以同时处于某个 C-state空闲和某个 P-state当它醒来干活时的频率档位。1.2 两个状态机别混延续上一篇状态机管什么核心问题cpufreqP-stateCPU跑多快调频调压多高的频率cpuidleC-stateCPU空闲时睡多深进多深的 C-state一句话记cpufreq 管醒着时跑多快cpuidle 管睡着时睡多深。二、cpufreq 框架三层policy / driver / governor和 cpuidle 类似cpufreq 也是三层结构。2.1cpufreq_policy一组共享时钟的 CPU一个 policy 管一组共享同一时钟域的 CPU同 cluster / 同 core 的 siblings它们必须一起调频// include/linux/cpufreq.h (v6.6, line 55)structcpufreq_policy{cpumask_var_tcpus;/* 共享这个 policy 的 online CPU */unsignedintcpu;/* 管理这个 policy 的 CPU */unsignedintmin;/* 允许的最低频率kHz */unsignedintmax;/* 允许的最高频率kHz */unsignedintcur;/* 当前频率 */structcpufreq_governor*governor;/* 当前 governor */structcpufreq_frequency_table*freq_table;/* 可用频率表 */bool fast_switch_possible;bool fast_switch_enabled;// …};min/max是用户/热管理可调的边界/sys/.../scaling_min_freq、scaling_max_freqgovernor 只能在这个范围内选频率。freq_table是硬件支持的离散频率点。2.2cpufreq_driver怎么把频率写进硬件driver 负责设置频率这个底层动作提供几种接口之一// include/linux/cpufreq.h (v6.6, line 325)structcpufreq_driver{charname[CPUFREQ_NAME_LEN];/* 二选一老式 setpolicy或现代的 target 系列 */int(*setpolicy)(structcpufreq_policy*policy);int(*target)(structcpufreq_policy*policy,unsignedinttarget_freq,unsignedintrelation);/* Deprecated */int(*target_index)(structcpufreq_policy*policy,unsignedintindex);unsignedint(*fast_switch)(structcpufreq_policy*policy,unsignedinttarget_freq);void(*adjust_perf)(unsignedintcpu,unsignedlongmin_perf,unsignedlongtarget_perf,unsignedlongcapacity);// …};这四种接口对应两种工作模式setpolicy模式老式driver 自己决定频率内核只给它两个档位——CPUFREQ_POLICY_PERFORMANCE满频或CPUFREQ_POLICY_POWERSAVE最低频。intel_pstate的 active 模式走这条。target_index/fast_switch/adjust_perf模式现代governor 算出一个目标频率或 perfdriver 把它设置到硬件。target_index是把目标频率映射到频率表索引fast_switch是调度器上下文的快速切换adjust_perf是直接传性能等级HWP 用。2.3cpufreq_governor谁来决定频率// include/linux/cpufreq.h (v6.6, line 577)structcpufreq_governor{charname[CPUFREQ_NAME_LEN];int(*start)(structcpufreq_policy*policy);void(*stop)(structcpufreq_policy*policy);void(*limits)(structcpufreq_policy*policy);// …};传统 governor 有performance永远满频、powersave永远最低频、ondemand按采样负载调频、conservative缓慢调频。而现代默认是schedutil——它不用采样直接用调度器的利用率信息。三、schedutil governor用调度器的 util 算频率schedutil是 v6.6 的默认 governor它的核心思想是调度器本来就一直在算每个 CPU 的利用率util直接用这个数来定频率省掉独立的采样轮询。3.1 核心公式频率和 util 成线性映射// kernel/sched/cpufreq_schedutil.c (v6.6, line 130)/* * next_freq C * curr_freq * util_raw / max * * Take C 1.25 for the frequency tipping point at (util / max) 0.8. */staticunsignedintget_next_freq(structsugov_policy*sg_policy,unsignedlongutil,unsignedlongmax){structcpufreq_policy*policysg_policy-policy;unsignedintfreqarch_scale_freq_invariant()?policy-cpuinfo.max_freq:policy-cur;utilmap_util_perf(util);// util 映射到 perf 域含 1.25 余量freqmap_util_freq(util,freq,max);// freq max_freq * util / maxreturncpufreq_driver_resolve_freq(policy,freq);// 取最接近的可用频率}核心是一个线性映射next_freq max_freq × util / max——CPU 利用率占最大能力的比例就是频率占最大频率的比例。util 拉满max就跑满频util 是 50% 就跑一半频率。那个C 1.25的系数是关键细节util 到80%时频率就顶到满频了留 25% 余量。为什么因为 util 是滞后指标——等 util 涨到 100% 才满频负载早就过了峰值会有性能损失。提前到 80% 就满频能更快响应负载尖峰。3.2 util 从哪来effective_cpu_util// kernel/sched/cpufreq_schedutil.c (v6.6, line 156)staticvoidsugov_get_util(structsugov_cpu*sg_cpu){unsignedlongutilcpu_util_cfs_boost(sg_cpu-cpu);structrq*rqcpu_rq(sg_cpu-cpu);sg_cpu-bw_dlcpu_bw_dl(rq);// 实时任务DL带宽sg_cpu-utileffective_cpu_util(sg_cpu-cpu,util,FREQUENCY_UTIL,NULL);// 有效利用率}util 来自调度器的effective_cpu_util——它把 CFS 任务负载、RT/DL 带宽、thermal 上限、uclamp 限制等都折算成一个有效利用率。这正是 schedutil 的精髓它和调度器是同一个视角调度器看到多少负载schedutil 就配多少频率不会像 ondemand 那样采样滞后。3.3 触发时机调度器回调不是轮询schedutil 通过update_util钩子被调度器主动调用每次 tick、任务切换、唤醒时而不是自己定时采样// kernel/sched/cpufreq_schedutil.c (v6.6, line 331)staticvoidsugov_update_single_freq(structupdate_util_data*hook,u64 time,unsignedintflags){// …if(!sugov_update_single_common(sg_cpu,time,max_cap,flags))return;// rate limit 读 utilnext_fget_next_freq(sg_policy,sg_cpu-util,max_cap);/* 忙的 CPU 不急着降频避免过早降频造成性能损失 */if(!uclamp_rq_is_capped(cpu_rq(sg_cpu-cpu))sugov_cpu_is_busy(sg_cpu)next_fsg_policy-next_freq!sg_policy-need_freq_update){next_fsg_policy-next_freq;}if(sg_policy-policy-fast_switch_enabled){cpufreq_driver_fast_switch(sg_policy-policy,next_f);// 快速切换}else{sugov_deferred_update(sg_policy);// 延迟更新}}两点值得注意忙时不降频如果 CPU 一直忙、没空转即使新算的频率更低也不降——因为忙说明负载还在降频很可能是过早的马上又要升回来。fast_switch vs 延迟更新如果 driver 支持fast_switch就在调度器上下文直接切换极快否则走 deferred updateirq_work/kthread避免在调度器热路径做重操作。3.4 慢路径的 worker 线程sugov线程第 2 点里的deferred update值得展开——它背后是一个专门的worker 线程kthread名字sugov:N。为什么需要它因为sugov_update_single_freq是在调度器持有rq-lock的上下文里被调用的此时不能直接调 driver 的target它可能加锁、甚至睡眠。所以当 driver 不支持fast_switch时内核用一个三层接力把设置频率推迟到独立线程// kernel/sched/cpufreq_schedutil.c (v6.6, line 109)staticvoidsugov_deferred_update(structsugov_policy*sg_policy){if(!sg_policy-work_in_progress){sg_policy-work_in_progresstrue;irq_work_queue(sg_policy-irq_work);// ① 排队 irq_work尽快离开热路径}}// kernel/sched/cpufreq_schedutil.c (v6.6, line 492)staticvoidsugov_irq_work(structirq_work*irq_work){structsugov_policy*sg_policy;sg_policycontainer_of(irq_work,structsugov_policy,irq_work);kthread_queue_work(sg_policy-worker,sg_policy-work);// ② 转交给 worker 线程}// kernel/sched/cpufreq_schedutil.c (v6.6, line 466)staticvoidsugov_work(structkthread_work*work){structsugov_policy*sg_policycontainer_of(work,structsugov_policy,work);// …__cpufreq_driver_target(sg_policy-policy,freq,CPUFREQ_RELATION_L);// ③ 真正调 driver}这个 worker 线程在sugov_kthread_createcpufreq_schedutil.c:579里创建有两个细节体现了调频延迟敏感的设计用SCHED_DEADLINE调度策略sched_setattr_nocheck而不是普通 nice保证它一被唤醒就尽快跑绑定到related_cpuskthread_bind_mask避免跑到无关 CPU 上徒增延迟。而且它是按需创建的——if (policy-fast_switch_enabled) return 0;:600driver 支持 fast_switch 就压根不建这个线程直接走调度器上下文的 fast_switch 路径。这就是能快切就快切不能就交给 worker 线程的完整闭环。四、intel_pstateIntel 的专用 driverx86 上其实有两条路老式acpi-cpufreq读 ACPI_PSS表和现代的intel_pstate。后者是 Intel 的专用 driver有 active / passive 两种模式还支持 HWP。4.1 active 模式默认driver 自己调频intel_pstate在 active 模式下走setpolicy接口自己注册 update_util 回调绕过标准 cpufreq governor// drivers/cpufreq/intel_pstate.c (v6.6, line 2574)staticintintel_pstate_set_policy(structcpufreq_policy*policy){// …if(cpu-policyCPUFREQ_POLICY_PERFORMANCE){intel_pstate_clear_update_util_hook(policy-cpu);// 满频不需要回调intel_pstate_max_within_limits(cpu);}else{intel_pstate_set_update_util_hook(policy-cpu);// 注册 update_util 回调}// …}staticstructcpufreq_driverintel_pstate{.flagsCPUFREQ_CONST_LOOPS,.setpolicyintel_pstate_set_policy,// ← setpolicy 模式不是 target_index// ….nameintel_pstate,};在调度器上下文的调频逻辑是intel_pstate_update_util非 HWP 时// drivers/cpufreq/intel_pstate.c (v6.6, line 2306)staticvoidintel_pstate_update_util(structupdate_util_data*data,u64 time,unsignedintflags){// …iowait_boost 处理翻倍机制delta_nstime-cpu-sample.time;if((s64)delta_nsINTEL_PSTATE_SAMPLING_INTERVAL)return;// 采样间隔内不重复调if(intel_pstate_sample(cpu,time))// 采样读 MSR 拿实际性能intel_pstate_adjust_pstate(cpu);// 调 P-state写 MSR}注意和 schedutil 的差别intel_pstate 内部用采样intel_pstate_sample读 MSR 看实际利用率有采样间隔限制而不是 schedutil 那样直接用调度器 util 实时映射。active 模式是 Intel 自己的调频算法不经过标准 governor。4.2 HWP硬件自己管理 P-state现代 Intel CPUSkylake支持HWPHardware P-state——调频算法搬进硬件硬件根据负载自主选择频率。软件不再指挥每一档而是写一个MSR_HWP_REQUEST告诉硬件性能范围 偏好// drivers/cpufreq/intel_pstate.c (v6.6, line 944)staticvoidintel_pstate_hwp_set(unsignedintcpu){// 写 MSR_HWP_REQUEST 的 min / max / desired perf 字段value~HWP_MIN_PERF(~0L);value|HWP_MIN_PERF(min);value~HWP_MAX_PERF(~0L);value|HWP_MAX_PERF(max);// …wrmsrl_on_cpu(cpu,MSR_HWP_REQUEST,value);}HWP 模式下intel_pstate的 update_util 回调主要做hwp_boostintel_pstate_update_util_hwpintel_pstate.c:2158——IO 等待或负载突增时动态抬升 min perf 让硬件更快响应忙完再降回去。还有一个EPPEnergy Performance PreferencehintMSR_HWP_REQUEST的 bits 24-31告诉硬件偏性能还是偏省电是性能/功耗偏好的粗调旋钮。4.3 passive 模式退回标准框架intel_pstatepassive会让它切换成标准 cpufreq driverintel_cpufreq用.target.fast_switch接口配合 schedutil 等标准 governor 工作。这个模式主要给需要标准 cpufreq 接口/工具链的场景用。五、调频流程负载变化 → 选频率 → 写硬件把前面串起来一次完整的调频是负载变化调度器 tick / 任务切换 / 唤醒触发update_util回调governor 算频率schedutil 读effective_cpu_util算next_freq max_freq × util/max含 1.25 余量driver 写硬件通用 driverfast_switch调度器上下文直接写或target_index映射到频率表索引写intel_pstateHWP写MSR_HWP_REQUEST的 min/max/desired EPP硬件自己落位intel_pstate非 HWPintel_pstate_adjust_pstate采样后写 MSR。对应两种调频粒度软件调频软件每 tick 算一次、写一次和硬件调频HWP 硬件自己快速调软件只给范围。六、为什么这样设计Why 层6.1 为什么有 governor而不是固定一个频率和 cpuidle 的 governor 同理跑多快没有固定答案。负载轻时跑满频是浪费负载重时跑低频率是卡顿。所以必须有个决策者governor根据负载动态选频率。performance/powersave是两个极端永远满频 / 永远最低频中间的ondemand/schedutil才是动态权衡。6.2 为什么 schedutil 取代 ondemandondemand是定时采样 看负载——它每隔一段时间读一次 CPU 负载超过了阈值就升频。问题在于采样有滞后负载尖峰来了要等下一个采样点才升频这期间 CPU 在低频扛着重活。schedutil用调度器已有的 util负载一变调度器立刻通知零额外采样、零滞后。这是复用已有信息胜过自己另起炉灶采样的典型例子。6.3 为什么 intel_pstate 要专用 driver而不是用通用 driver通用 driveracpi-cpufreq通过 ACPI_PSS表拿频率点、走标准接口但没法利用 Intel CPU 的专属能力HWP硬件调频是 Intel 的独有能力通用 driver 走不到专用 driver 才能写MSR_HWP_REQUEST把调频交给硬件。active 模式的高效路径绕过标准 governor 框架直接在调度器上下文用自己的算法调频减少开销。更精细的 P-state 控制intel_pstate直接读 MSR 拿 P-state 上限、turbo 状态通用 driver 靠 ACPI 表拿不到这些实时信息。6.4 为什么忙时不降频schedutil 里的 if 判断降频本身有代价切换频率、可能 miss 缓存如果 CPU 一直忙、负载没有实质下降降下去马上又得升回来来回抖动反而更差。所以 schedutil 对还在忙的 CPU延迟降频等 CPU 真的空下来了才降。这是避免频繁抖动的阻尼设计。七、与相邻主题的边界主题边界cpuidle上一篇cpuidle 管 C-state睡多深cpufreq 管 P-state跑多快——一对正交维度CPU hotplug#3hotplug 的cpuhp_state管CPU 存不存在cpufreq 管存在的 CPU 跑多快调度器schedutil 直接消费调度器的 util——调频和调度在同一视角下协同附本篇关键源码索引符号位置cpufreq_policymin/max/cur governorinclude/linux/cpufreq.h:55cpufreq_driversetpolicy/target_index/fast_switch/adjust_perfinclude/linux/cpufreq.h:325cpufreq_governorinclude/linux/cpufreq.h:577CPUFREQ_RELATION_L/H/C频率表查找关系include/linux/cpufreq.h:284get_next_freqschedutil 算频率含公式注释kernel/sched/cpufreq_schedutil.c:139sugov_get_util读 effective_cpu_utilkernel/sched/cpufreq_schedutil.c:156sugov_update_single_freq触发 fast_switchkernel/sched/cpufreq_schedutil.c:331sugov_update_single_perfadjust_perf 路径HWPkernel/sched/cpufreq_schedutil.c:378intel_pstate_update_utilactive 模式采样调频drivers/cpufreq/intel_pstate.c:2306intel_pstate_set_policy/intel_pstatedriversetpolicydrivers/cpufreq/intel_pstate.c:2574/:2774intel_pstate_hwp_set写 MSR_HWP_REQUESTdrivers/cpufreq/intel_pstate.c:944