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

〇、全景:CPU 有活干时,可以"跑慢点"省电

上一篇 cpuidle 讲的是"CPU 没活干时睡多深"。但 CPU 有活干 时,也不是非得全速跑------如果负载很轻,把频率降下来、电压降下来,照样能完成任务,还省电。这就是 cpufreq(P-state,性能状态) 管的事:决定 CPU 当前跑多快。
#mermaid-svg-2cSM7MDEgy7rfm5M{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-2cSM7MDEgy7rfm5M .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-2cSM7MDEgy7rfm5M .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-2cSM7MDEgy7rfm5M .error-icon{fill:#552222;}#mermaid-svg-2cSM7MDEgy7rfm5M .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-2cSM7MDEgy7rfm5M .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-2cSM7MDEgy7rfm5M .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-2cSM7MDEgy7rfm5M .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-2cSM7MDEgy7rfm5M .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-2cSM7MDEgy7rfm5M .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-2cSM7MDEgy7rfm5M .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-2cSM7MDEgy7rfm5M .marker{fill:#333333;stroke:#333333;}#mermaid-svg-2cSM7MDEgy7rfm5M .marker.cross{stroke:#333333;}#mermaid-svg-2cSM7MDEgy7rfm5M svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-2cSM7MDEgy7rfm5M p{margin:0;}#mermaid-svg-2cSM7MDEgy7rfm5M .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-2cSM7MDEgy7rfm5M .cluster-label text{fill:#333;}#mermaid-svg-2cSM7MDEgy7rfm5M .cluster-label span{color:#333;}#mermaid-svg-2cSM7MDEgy7rfm5M .cluster-label span p{background-color:transparent;}#mermaid-svg-2cSM7MDEgy7rfm5M .label text,#mermaid-svg-2cSM7MDEgy7rfm5M span{fill:#333;color:#333;}#mermaid-svg-2cSM7MDEgy7rfm5M .node rect,#mermaid-svg-2cSM7MDEgy7rfm5M .node circle,#mermaid-svg-2cSM7MDEgy7rfm5M .node ellipse,#mermaid-svg-2cSM7MDEgy7rfm5M .node polygon,#mermaid-svg-2cSM7MDEgy7rfm5M .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-2cSM7MDEgy7rfm5M .rough-node .label text,#mermaid-svg-2cSM7MDEgy7rfm5M .node .label text,#mermaid-svg-2cSM7MDEgy7rfm5M .image-shape .label,#mermaid-svg-2cSM7MDEgy7rfm5M .icon-shape .label{text-anchor:middle;}#mermaid-svg-2cSM7MDEgy7rfm5M .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-2cSM7MDEgy7rfm5M .rough-node .label,#mermaid-svg-2cSM7MDEgy7rfm5M .node .label,#mermaid-svg-2cSM7MDEgy7rfm5M .image-shape .label,#mermaid-svg-2cSM7MDEgy7rfm5M .icon-shape .label{text-align:center;}#mermaid-svg-2cSM7MDEgy7rfm5M .node.clickable{cursor:pointer;}#mermaid-svg-2cSM7MDEgy7rfm5M .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-2cSM7MDEgy7rfm5M .arrowheadPath{fill:#333333;}#mermaid-svg-2cSM7MDEgy7rfm5M .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-2cSM7MDEgy7rfm5M .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-2cSM7MDEgy7rfm5M .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2cSM7MDEgy7rfm5M .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-2cSM7MDEgy7rfm5M .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2cSM7MDEgy7rfm5M .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-2cSM7MDEgy7rfm5M .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-2cSM7MDEgy7rfm5M .cluster text{fill:#333;}#mermaid-svg-2cSM7MDEgy7rfm5M .cluster span{color:#333;}#mermaid-svg-2cSM7MDEgy7rfm5M div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-2cSM7MDEgy7rfm5M .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-2cSM7MDEgy7rfm5M rect.text{fill:none;stroke-width:0;}#mermaid-svg-2cSM7MDEgy7rfm5M .icon-shape,#mermaid-svg-2cSM7MDEgy7rfm5M .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2cSM7MDEgy7rfm5M .icon-shape p,#mermaid-svg-2cSM7MDEgy7rfm5M .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-2cSM7MDEgy7rfm5M .icon-shape .label rect,#mermaid-svg-2cSM7MDEgy7rfm5M .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2cSM7MDEgy7rfm5M .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-2cSM7MDEgy7rfm5M .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-2cSM7MDEgy7rfm5M :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 负载变化

(调度器 tick / 任务切换)
governor 决定频率

(schedutil 默认)
读调度器 util

计算目标频率
driver 设置频率

(写 MSR / HWP request)
CPU 跑到新频率

一句话主线:cpufreq 是"CPU 跑多快"的状态机 ------负载变化时,由 governor 根据调度器的 CPU 利用率算出目标频率 ,再由 driver 把它写到硬件(写 MSR,或交给 HWP 硬件自主调频)。它管的是"运行中怎么调频",和 cpuidle 的"空闲时睡多深"是一对------一个管 C-state,一个管 P-state。


一、预备概念:P-state 与两个状态机

1.1 P-state:频率 + 电压的组合

P-state(Performance state)是 CPU 的一组"频率 + 电压"工作点。频率越高、电压越高,性能越强、功耗越大。x86 上 P-state 的数量和值由 CPU 型号决定(比如 P0 是最高 turbo 频率,P1 是基频,往下依次降低)。

关键:P-state 和 C-state 是两个正交的维度。C-state 是"睡多深"(空闲省电),P-state 是"跑多快"(运行时省电)。一个 CPU 可以同时处于某个 C-state(空闲)和某个 P-state(当它醒来干活时的频率档位)。

1.2 两个状态机别混(延续上一篇)

状态机 管什么 核心问题
cpufreq(P-state) CPU 跑多快(调频调压) 多高的频率
cpuidle(C-state) CPU 空闲时睡多深 进多深的 C-state

一句话记:cpufreq 管"醒着时跑多快",cpuidle 管"睡着时睡多深"。


二、cpufreq 框架三层:policy / driver / governor

和 cpuidle 类似,cpufreq 也是三层结构。

2.1 cpufreq_policy:一组共享时钟的 CPU

一个 policy 管一组共享同一时钟域的 CPU(同 cluster / 同 core 的 siblings),它们必须一起调频:

c 复制代码
// include/linux/cpufreq.h (v6.6, line 55)
struct cpufreq_policy {
	cpumask_var_t		cpus;          /* 共享这个 policy 的 online CPU */
	unsigned int		cpu;           /* 管理这个 policy 的 CPU */
	unsigned int		min;           /* 允许的最低频率(kHz) */
	unsigned int		max;           /* 允许的最高频率(kHz) */
	unsigned int		cur;           /* 当前频率 */
	struct cpufreq_governor	*governor; /* 当前 governor */
	struct cpufreq_frequency_table *freq_table;  /* 可用频率表 */
	bool			fast_switch_possible;
	bool			fast_switch_enabled;
	// ...
};

min/max 是用户/热管理可调的边界 (/sys/.../scaling_min_freq、scaling_max_freq),governor 只能在这个范围内选频率。freq_table 是硬件支持的离散频率点。

2.2 cpufreq_driver:怎么把频率写进硬件

driver 负责"设置频率"这个底层动作,提供几种接口之一:

c 复制代码
// include/linux/cpufreq.h (v6.6, line 325)
struct cpufreq_driver {
	char		name[CPUFREQ_NAME_LEN];
	/* 二选一:老式 setpolicy,或现代的 target 系列 */
	int		(*setpolicy)(struct cpufreq_policy *policy);
	int		(*target)(struct cpufreq_policy *policy,
				  unsigned int target_freq,
				  unsigned int relation);	/* Deprecated */
	int		(*target_index)(struct cpufreq_policy *policy,
					unsigned int index);
	unsigned int	(*fast_switch)(struct cpufreq_policy *policy,
				       unsigned int target_freq);
	void		(*adjust_perf)(unsigned int cpu,
				       unsigned long min_perf,
				       unsigned long target_perf,
				       unsigned long capacity);
	// ...
};

这四种接口对应两种工作模式:

  • setpolicy 模式 (老式):driver 自己决定频率,内核只给它两个档位------CPUFREQ_POLICY_PERFORMANCE(满频)或 CPUFREQ_POLICY_POWERSAVE(最低频)。intel_pstate 的 active 模式走这条。
  • target_index / fast_switch / adjust_perf 模式 (现代):governor 算出一个目标频率(或 perf),driver 把它设置到硬件。target_index 是"把目标频率映射到频率表索引",fast_switch 是"调度器上下文的快速切换",adjust_perf 是"直接传性能等级(HWP 用)"。

2.3 cpufreq_governor:谁来决定频率

c 复制代码
// include/linux/cpufreq.h (v6.6, line 577)
struct cpufreq_governor {
	char	name[CPUFREQ_NAME_LEN];
	int	(*start)(struct cpufreq_policy *policy);
	void	(*stop)(struct cpufreq_policy *policy);
	void	(*limits)(struct cpufreq_policy *policy);
	// ...
};

传统 governor 有 performance(永远满频)、powersave(永远最低频)、ondemand(按采样负载调频)、conservative(缓慢调频)。而现代默认是 schedutil------它不用采样,直接用调度器的利用率信息。


三、schedutil governor:用调度器的 util 算频率

schedutil 是 v6.6 的默认 governor,它的核心思想是:调度器本来就一直在算每个 CPU 的利用率(util),直接用这个数来定频率,省掉独立的采样轮询。

3.1 核心公式:频率和 util 成线性映射

c 复制代码
// 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.
 */
static unsigned int get_next_freq(struct sugov_policy *sg_policy,
				  unsigned long util, unsigned long max)
{
	struct cpufreq_policy *policy = sg_policy->policy;
	unsigned int freq = arch_scale_freq_invariant() ?
				policy->cpuinfo.max_freq : policy->cur;

	util = map_util_perf(util);              // util 映射到 perf 域(含 1.25 余量)
	freq = map_util_freq(util, freq, max);   // freq = max_freq * util / max

	return cpufreq_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

c 复制代码
// kernel/sched/cpufreq_schedutil.c (v6.6, line 156)
static void sugov_get_util(struct sugov_cpu *sg_cpu)
{
	unsigned long util = cpu_util_cfs_boost(sg_cpu->cpu);
	struct rq *rq = cpu_rq(sg_cpu->cpu);

	sg_cpu->bw_dl = cpu_bw_dl(rq);          // 实时任务(DL)带宽
	sg_cpu->util = effective_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、任务切换、唤醒时),而不是自己定时采样:

c 复制代码
// kernel/sched/cpufreq_schedutil.c (v6.6, line 331)
static void sugov_update_single_freq(struct update_util_data *hook, u64 time,
				     unsigned int flags)
{
	// ...
	if (!sugov_update_single_common(sg_cpu, time, max_cap, flags))
		return;                              // rate limit + 读 util

	next_f = get_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_f < sg_policy->next_freq &&
	    !sg_policy->need_freq_update) {
		next_f = sg_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);                     // 延迟更新
	}
}

两点值得注意:

  1. 忙时不降频:如果 CPU 一直忙、没空转,即使新算的频率更低也不降------因为"忙"说明负载还在,降频很可能是过早的,马上又要升回来。
  2. fast_switch vs 延迟更新 :如果 driver 支持 fast_switch,就在调度器上下文直接切换(极快);否则走 deferred update(irq_work/kthread),避免在调度器热路径做重操作。

3.4 慢路径的 worker 线程:sugov 线程

第 2 点里的"deferred update"值得展开------它背后是一个专门的 worker 线程 (kthread,名字 sugov:N)。为什么需要它?因为 sugov_update_single_freq 是在调度器持有 rq->lock 的上下文 里被调用的,此时不能直接调 driver 的 target(它可能加锁、甚至睡眠)。所以当 driver 不支持 fast_switch 时,内核用一个三层接力把"设置频率"推迟到独立线程:

c 复制代码
// kernel/sched/cpufreq_schedutil.c (v6.6, line 109)
static void sugov_deferred_update(struct sugov_policy *sg_policy)
{
	if (!sg_policy->work_in_progress) {
		sg_policy->work_in_progress = true;
		irq_work_queue(&sg_policy->irq_work);   // ① 排队 irq_work,尽快离开热路径
	}
}

// kernel/sched/cpufreq_schedutil.c (v6.6, line 492)
static void sugov_irq_work(struct irq_work *irq_work)
{
	struct sugov_policy *sg_policy;
	sg_policy = container_of(irq_work, struct sugov_policy, irq_work);
	kthread_queue_work(&sg_policy->worker, &sg_policy->work);  // ② 转交给 worker 线程
}

// kernel/sched/cpufreq_schedutil.c (v6.6, line 466)
static void sugov_work(struct kthread_work *work)
{
	struct sugov_policy *sg_policy = container_of(work, struct sugov_policy, work);
	// ...
	__cpufreq_driver_target(sg_policy->policy, freq, CPUFREQ_RELATION_L);  // ③ 真正调 driver
}

这个 worker 线程在 sugov_kthread_create(cpufreq_schedutil.c:579)里创建,有两个细节体现了"调频延迟敏感"的设计:

  • 用 SCHED_DEADLINE 调度策略(sched_setattr_nocheck),而不是普通 nice,保证它一被唤醒就尽快跑;
  • 绑定到 related_cpus (kthread_bind_mask),避免跑到无关 CPU 上徒增延迟。

而且它是按需创建 的------if (policy->fast_switch_enabled) return 0;(:600):driver 支持 fast_switch 就压根不建这个线程,直接走调度器上下文的 fast_switch 路径。这就是"能快切就快切,不能就交给 worker 线程"的完整闭环。


四、intel_pstate:Intel 的专用 driver

x86 上其实有两条路:老式 acpi-cpufreq(读 ACPI _PSS 表)和现代的 intel_pstate。后者是 Intel 的专用 driver,有 active / passive 两种模式,还支持 HWP。

4.1 active 模式(默认):driver 自己调频

intel_pstate 在 active 模式下走 setpolicy 接口,自己注册 update_util 回调,绕过标准 cpufreq governor:

c 复制代码
// drivers/cpufreq/intel_pstate.c (v6.6, line 2574)
static int intel_pstate_set_policy(struct cpufreq_policy *policy)
{
	// ...
	if (cpu->policy == CPUFREQ_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 回调
	}
	// ...
}

static struct cpufreq_driver intel_pstate = {
	.flags		= CPUFREQ_CONST_LOOPS,
	.setpolicy	= intel_pstate_set_policy,   // ← setpolicy 模式,不是 target_index
	// ...
	.name		= "intel_pstate",
};

在调度器上下文的调频逻辑是 intel_pstate_update_util(非 HWP 时):

c 复制代码
// drivers/cpufreq/intel_pstate.c (v6.6, line 2306)
static void intel_pstate_update_util(struct update_util_data *data, u64 time,
				     unsigned int flags)
{
	// ...(iowait_boost 处理,翻倍机制)
	delta_ns = time - cpu->sample.time;
	if ((s64)delta_ns < INTEL_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 CPU(Skylake+)支持 HWP(Hardware P-state) ------调频算法搬进硬件,硬件根据负载自主选择频率 。软件不再"指挥每一档",而是写一个 MSR_HWP_REQUEST,告诉硬件"性能范围 + 偏好":

c 复制代码
// drivers/cpufreq/intel_pstate.c (v6.6, line 944)
static void intel_pstate_hwp_set(unsigned int cpu)
{
	// 写 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_boost (intel_pstate_update_util_hwp,intel_pstate.c:2158)------IO 等待或负载突增时,动态抬升 min perf 让硬件更快响应,忙完再降回去。还有一个 EPP(Energy Performance Preference) hint(MSR_HWP_REQUEST 的 bits 24-31),告诉硬件"偏性能还是偏省电",是性能/功耗偏好的粗调旋钮。

4.3 passive 模式:退回标准框架

intel_pstate=passive 会让它切换成标准 cpufreq driver(intel_cpufreq,用 .target + .fast_switch 接口),配合 schedutil 等标准 governor 工作。这个模式主要给"需要标准 cpufreq 接口/工具链"的场景用。


五、调频流程:负载变化 → 选频率 → 写硬件

把前面串起来,一次完整的调频是:

  1. 负载变化 :调度器 tick / 任务切换 / 唤醒,触发 update_util 回调;
  2. governor 算频率 :schedutil 读 effective_cpu_util,算 next_freq = max_freq × util/max(含 1.25 余量);
  3. driver 写硬件 :
    • 通用 driver:fast_switch(调度器上下文直接写)或 target_index(映射到频率表索引写);
    • intel_pstate HWP:写 MSR_HWP_REQUEST 的 min/max/desired + EPP,硬件自己落位;
    • intel_pstate 非 HWP:intel_pstate_adjust_pstate 采样后写 MSR。

对应两种调频粒度:软件调频 (软件每 tick 算一次、写一次)和 硬件调频(HWP 硬件自己快速调,软件只给范围)。


六、为什么这样设计(Why 层)

6.1 为什么有 governor,而不是固定一个频率

和 cpuidle 的 governor 同理:"跑多快"没有固定答案 。负载轻时跑满频是浪费,负载重时跑低频率是卡顿。所以必须有个决策者(governor)根据负载动态选频率。performance/powersave 是两个极端(永远满频 / 永远最低频),中间的 ondemand/schedutil 才是"动态权衡"。

6.2 为什么 schedutil 取代 ondemand

ondemand 是"定时采样 + 看负载"------它每隔一段时间读一次 CPU 负载,超过了阈值就升频。问题在于采样有滞后 :负载尖峰来了,要等下一个采样点才升频,这期间 CPU 在低频"扛"着重活。schedutil 用调度器已有的 util,负载一变调度器立刻通知,零额外采样、零滞后。这是"复用已有信息"胜过"自己另起炉灶采样"的典型例子。

6.3 为什么 intel_pstate 要专用 driver,而不是用通用 driver

通用 driver(acpi-cpufreq)通过 ACPI _PSS 表拿频率点、走标准接口,但没法利用 Intel CPU 的专属能力:

  1. HWP :硬件调频是 Intel 的独有能力,通用 driver 走不到;专用 driver 才能写 MSR_HWP_REQUEST 把调频交给硬件。
  2. active 模式的高效路径:绕过标准 governor 框架,直接在调度器上下文用自己的算法调频,减少开销。
  3. 更精细的 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(#3) hotplug 的 cpuhp_state 管"CPU 存不存在",cpufreq 管"存在的 CPU 跑多快"
调度器 schedutil 直接消费调度器的 util------调频和调度在同一视角下协同

附:本篇关键源码索引

符号 位置
cpufreq_policy(min/max/cur + governor) include/linux/cpufreq.h:55
cpufreq_driver(setpolicy/target_index/fast_switch/adjust_perf) include/linux/cpufreq.h:325
cpufreq_governor include/linux/cpufreq.h:577
CPUFREQ_RELATION_L/H/C(频率表查找关系) include/linux/cpufreq.h:284
get_next_freq(schedutil 算频率,含公式注释) kernel/sched/cpufreq_schedutil.c:139
sugov_get_util(读 effective_cpu_util) kernel/sched/cpufreq_schedutil.c:156
sugov_update_single_freq(触发 + fast_switch) kernel/sched/cpufreq_schedutil.c:331
sugov_update_single_perf(adjust_perf 路径,HWP) kernel/sched/cpufreq_schedutil.c:378
intel_pstate_update_util(active 模式采样调频) drivers/cpufreq/intel_pstate.c:2306
intel_pstate_set_policy / intel_pstate driver(setpolicy) drivers/cpufreq/intel_pstate.c:2574 / :2774
intel_pstate_hwp_set(写 MSR_HWP_REQUEST) drivers/cpufreq/intel_pstate.c:944
相关推荐
倔强的石头1061 小时前
【Linux指南】动静态库系列(十一):PLT 与延迟绑定:第一次调用动态库函数时发生了什么
linux·人工智能·python
天远数科1 小时前
零信任架构实战:基于天远全能消金报告构建自动化消费分期网关
运维·人工智能·架构·自动化
硅基手札2 小时前
【linux内核专栏 08】时间与定时器
linux·运维·服务器
想做小南娘,发现自己是女生喵2 小时前
Linux之Ubuntu入门篇知识总结
linux·运维·服务器
CHEEVEN_QY3 小时前
搅拌摩擦焊FSW工艺参数对焊缝成形影响的实验研究10.1
linux·服务器·数据库
帷幕落秋3 小时前
Redis哨兵模式
运维·数据库
H.莓飛3 小时前
【数据结构】链表_OJ题
linux·数据结构·算法·链表
木白CPP3 小时前
USB(一): USB 概述&&介绍
linux·网络·嵌入式硬件
熬夜敲代码的猫3 小时前
Linux:进程概念
linux