☰
Linux 6.6内核 CPU 深度解析(十五):CPU 状态全景 — hotplug / C-state / P-state 三个正交维度
2026/10/25 1:53:35 网站建设 项目流程

〇、全景:一台 CPU 的一生 = 三个正交状态机的叠加

前面 #3(hotplug 状态机)、#5(cpuidle)、#6(cpufreq)分别讲了三个状态机,但各自独立,没讲它们怎么叠加成"一台 CPU 的一生"。本篇把它们串成一条时间线:CPU 的一生,是三个正交维度同时变化的过程——每个维度内部又有一大堆具体状态,状态之间有交互和抢占,某个状态缺失还会有连锁影响。

维度状态机回答的问题具体状态取值
存不存在hotplug(cpuhp_state)这台 CPU 能不能用OFFLINE → 几十个中间态 → ONLINE
睡多深cpuidle(C-state)空闲时省多少电C0 / C1 / C1E / C3 / C6 / C7 / …
跑多快cpufreq(P-state)干活时出多大力P0 / P1 / … / Pn + turbo

三者正交:一台 CPU 可以同时是"online + C6 深睡 + P0 满频"(准备随时满血干活)、“online + C0 运行 + P1 降频”(轻负载省电)、或者"offline"(直接不存在了)。正交意味着互不冲突,但有时序依赖和抢占——hotplug 是另两个的前提(CPU 下线了,C-state/P-state 都没意义),中断和 hotplug 又会抢占C-state。

生命周期

有活来了

没活了

cpu_up 再来一次

① 上线
hotplug: OFFLINE→ONLINE

② 干活
C0 + P-state 调频

③ 空闲
C0→C1→C6 下沉

④ 下线
hotplug: ONLINE→OFFLINE

hotplug 维度
存不存在

P-state 维度
跑多快(只在 C0)

C-state 维度
睡多深

一句话主线:CPU 的一生,是三个正交状态机在同一颗核上的叠加——hotplug 决定"存不存在"(几十个中间态),C-state 决定"空闲时睡多深"(C0/C1/C1E/C3/C6/C7 逐级加深),P-state 决定"干活时跑多快"(P0/Pn+boost)。生命周期是上线 → 干活 → 空闲 → 下线的循环;状态之间会交互和抢占(中断唤醒抢占 C-state、hotplug 抢占前两者、C1E 自带降频),某个状态缺失也有连锁影响(缺 C6 省电差、缺 hotplug 回调则子系统没初始化)。三者最终在idle 循环do_idle里交汇。


一、三个维度的具体状态清单

1.1 hotplug:不是只有 OFFLINE/ONLINE,中间有几十个状态

enum cpuhp_state(include/linux/cpuhotplug.h:57)把"拉起一台 CPU"拆成一条几十个状态的有序回调链,状态值是声明顺序(不是行号)。除了首尾(OFFLINE=0、ONLINE),中间每个状态对应一个子系统的 per-CPU 初始化/清理,挑几个关键的:

状态段对应子系统回调
CPUHP_OFFLINE = 0(:61)起点不在线
CPUHP_WORKQUEUE_PREP(:104)PREPARE上线时 workqueue 准备 per-CPU worker
CPUHP_RCUTREE_PREP(:113)PREPARERCU 每核状态准备
CPUHP_TIMERS_PREPARE(:131)PREPARE高精度定时器准备
CPUHP_BRINGUP_CPU(:136)PREPARE→STARTING 分界控制 CPU 代劳 → 目标 CPU 自己跑
CPUHP_AP_IDLE_DEAD(:142)STARTING 起点目标 CPU 关中断跑
CPUHP_AP_SCHED_STARTING(:145)STARTING调度器 rq 就位
CPUHP_CPUIDLE_DEAD(:81)PREPARE(下线)下线时 cpuidle 的 teardown
CPUHP_TEARDOWN_CPU(:199)下线分界AP 线程逆序拆自己
CPUHP_AP_ONLINE_IDLE(:202)ONLINE 起点能跑 hotplug 线程了
CPUHP_AP_ACTIVE(:257)ONLINE已激活、可被调度
CPUHP_ONLINE(:258)终点完全在线

关键认知:hotplug 状态机的价值不在首尾,在中间——CPUHP_WORKQUEUE_PREP、CPUHP_AP_SCHED_STARTING、CPUHP_CPUIDLE_DEAD这些状态,每个都挂着某个子系统的 startup/teardown 回调。上线时从 OFFLINE 顺序跑一遍 startup(workqueue→RCU→timer→sched→…→ONLINE),下线时逆序跑 teardown(…→cpuidle→…→OFFLINE)。这就是 #3 讲的"有序回调链"。

1.2 C-state:具体有 C1/C1E/C3/C6/C7…,每级有代价

C-state 不是"睡/醒"两档,而是一串由浅到深的档位,每档由struct cpuidle_state(include/linux/cpuidle.h:49)刻画,两个参数是选状态的依据:

// include/linux/cpuidle.h (v6.6, line 49)structcpuidle_state{charname[CPUIDLE_NAME_LEN];s64 exit_latency_ns;// :53 醒过来要多久(性能账)s64 target_residency_ns;// :54 停多久才回本(能量账)...int(*enter)(structcpuidle_device*,structcpuidle_driver*,int);// :60 进入状态的底层动作};

intel_idle(drivers/idle/intel_idle.c)按 CPU 代际维护真实的状态表,例如 Nehalem(nehalem_cstates,:234):

状态MWAIT hintexit_latency (µs)target_residency (µs)特性
C10x0036最浅,只停流水线
C1E0x011020停转 + 降频
C30x102080刷 TLB(CPUIDLE_FLAG_TLB_FLUSHED)
C60x20200800关核心电源、刷 cache

Sandy Bridge(snb_cstates,:271)多了C7(MWAIT 0x30,exit_latency=109、target_residency=345),Bay Trail(byt_cstates,:316)甚至有 C6N/C6S/C7 等变体(exit_latency 最高 1200µs)。规律很清晰:状态越深,exit_latency越大(醒得越慢)、target_residency越大(要停更久才回本)——这就是 menu governor 选状态的两个依据。

1.3 P-state:P0/Pn 频率档位 + turbo boost

P-state 是频率+电压的组合档位:P0(最高频、最高压)→Pn(逐级降低)。现代 x86 还有一个turbo boost(睿频):短暂地把单核频率超到标称 P0 之上(用别核空闲的热预算),由硬件自主管理(X86_FEATURE_HWP,见 #13)。

schedutil(默认 governor)在 P0~Pn 之间按 util 实时调频,触发点是调度器的update_util 回调(每次调度 tick、任务切换时):

// kernel/sched/cpufreq_schedutil.c (v6.6, line 139)staticunsignedintget_next_freq(structsugov_policy*sg_policy,...){...freq=map_util_freq(util,freq,max);// :147 频率 = max × util / max_cap}

P-state 只在 C0(运行态)有意义:CPU 都睡了,就没有"跑多快"的问题(详见 §3.4)。turbo 则是硬件层面的额外加成。


二、完整生命周期时间线:什么时候跳什么状态

阶段 ① 上线:hotplug OFFLINE → ONLINE

  • BSP:加电即在线,boot_cpu_init(kernel/cpu.c:3155)直接四态置位、boot_cpu_hotplug_init直接写CPUHP_ONLINE(不走完整cpu_up,因为它本来就"已经上线")。
  • AP:INIT-SIPI 唤醒(#2)→ trampoline →secondary_startup_64→start_secondary→ 状态机从CPUHP_OFFLINE一路 startup(workqueue→RCU→timer→sched→…)到CPUHP_ONLINE。关键分界CPUHP_BRINGUP_CPU:之前控制 CPU 代劳准备,之后目标 CPU 能跑线程了自己做。

上线完成的标志:状态走到CPUHP_ONLINE,CPU 进入cpu_online_mask,可被调度。

阶段 ② 干活:C0 + P-state 调频

上线后 idle 线程进cpu_startup_entry→do_idle。有任务时need_resched置位 → 跳出 idle →schedule_idle()(idle.c:310)切到任务。此刻 CPU 在C0,schedutil 按 util 调 P-state:负载高 → 升频(逼近 P0,甚至 turbo),负载低 → 降频(往 Pn)。"干活"这一秒,hotplug 恒 ONLINE、C-state 恒 C0,只有 P-state 在动。

阶段 ③ 空闲:C0 → C1 → C6 下沉

没活干,do_idle里while (!need_resched())(idle.c:258)成立,进cpuidle_idle_call(:282)→ menu governor 预测 idle 时长 → 选一个 C-state →cpuidle_enter_state执行enter(intel_idle 用mwait指定 hint)。C-state 维度在动(C0→C1→C1E→C3→C6 逐级下沉),P-state 冻结(睡了没频率),hotplug 恒 ONLINE。

阶段 ④ 下线:hotplug ONLINE → OFFLINE

cpu_down→ 状态机逆序 teardown(…→cpuidle→workqueue→…)→takedown_cpu→stop_machine_cpuslocked(take_cpu_down)(cpu.c:1345)关中断空转做下线收尾(并发 #3 死锁篇讲了这个过程的死锁风险)。最后 idle 线程回do_idle,发现cpu_is_offline(cpu)(idle.c:263):

// kernel/sched/idle.c (v6.6, line 263)if(cpu_is_offline(cpu)){tick_nohz_idle_stop_tick();cpuhp_report_idle_dead();// :265 报告"我死了"arch_cpu_idle_dead();// :266 hlt 死等,永不再醒}

下线完成 = hotplug 走到 OFFLINE,CPU 移出cpu_online_mask,idle 线程 hlt 死等——C-state 和 P-state 随之失去意义。


三、状态的交互与抢占:谁抢谁

三个状态机不是各玩各的,它们之间有明确的抢占关系:

3.1 中断 / 任务唤醒,抢占 C-state(最频繁)

CPU 在 C6 深睡时,硬件收到中断(网卡包、定时器、IPI)或 wakeup 事件 →硬件立刻把核心拉回 C0处理。这是最频繁的"抢占"——每秒成千上万次。代价就是exit_latency(C6 要 200µs 才能醒),所以 menu governor 宁可预测 idle 太短时选浅 C-state,也不让"深睡→立刻被唤醒"白白付 exit_latency 的账。

3.2 hotplug 抢占 C-state + P-state(最高优先级)

cpu_down是最高优先级的抢占:不管 CPU 此刻在 C0 跑任务、还是 C6 深睡,都先唤醒(如果在睡),然后逆序 teardown 掉 cpuidle(CPUHP_CPUIDLE_DEAD)、cpufreq、调度器等,最后 idle 线程arch_cpu_idle_dead死等。hotplug 一下线,C-state 和 P-state 直接失去存在基础——这是三者之间唯一的硬性依赖。

3.3 C-state 内部:menu governor 预测,决定"浅睡 vs 深睡"

同一颗空闲的 CPU,选 C1 还是 C6,是 menu governor(#5)在"抢":它用两个预测器(next_timer × 校正因子 + 重复间隔检测)估出 idle 时长,再和每个状态的target_residency/exit_latency比——预测短就选浅睡(C1),预测长才敢选深睡(C6)。选深了醒太慢(below),选浅了浪费省电机会(above),这个反馈闭环就是"预测"对"状态选择"的持续抢占。

3.4 C-state 与 P-state:C0 才有 P-state,C1E 自带降频

严格说 C-state 和 P-state 是互斥的:P-state 只在C0(运行态)有意义,进 C1 以上核心睡了,频率设置失效(尤其 C6 关核心电源,P-state 寄存器直接断电)。唯一的例外是C1E——它是"停转 + 降频"的复合态,等于 C-state 里内置了一次 P-state 的降低。所以"省电"这条主线上,C-state 和 P-state 是接力关系:C0 时靠 P-state 降频省电,进 C1+ 后靠 C-state 关部件省电。


四、状态缺失的影响:缺一个状态会怎样

4.1 hotplug 状态缺失:子系统 per-CPU 数据没初始化

某个子系统的 CONFIG 没开(比如没编CONFIG_SMP相关的某个功能),它就不会往cpuhp_hp_states[]注册回调,对应状态是"空"的。后果:上线时那个子系统的 per-CPU 数据没初始化(访问到空指针/未初始化内存),下线时没清理(泄漏 per-CPU 资源)。这是 #10 初始化全景讲的"每核那份 per-CPU 数据"在缺失时的直接后果。

4.2 C-state 深度缺失:省电能力被砍

硬件只支持到 C1(没有 C6),或intel_idle没加载(退化成只有hlt= C1),idle 时最深只能停流水线,关不了核心电源。功能完全正常,但待机功耗高——C1 只省"空转"的电,C6 才省"核心电源"的电,移动设备/数据中心待机的差距巨大。反过来,若 CPU 支持深睡但target_residency设太大,menu governor 永远不敢选深睡,效果等同缺失。

4.3 P-state 缺失:跑在固定频率

cpufreq driver 没加载(acpi_cpufreq/intel_pstate没起来,或X86_FEATURE_HWP没开),CPU 跑在BIOS 设定的固定频率。后果:负载低时不能降频省电,负载高时(若非 turbo)不能升频,功耗和性能两头都不讨好。现代服务器基本都靠intel_pstate的 HWP 让硬件自主调频,缺失等于退回"固定频率"的原始状态。


五、三者的交汇点:do_idle是枢纽

三个状态机在同一段代码里交汇,枢纽就是idle 循环do_idle(kernel/sched/idle.c:237):

  1. C-state 的载体:cpuidle_idle_call()(:282)在这里调用,idle 循环就是 C-state 的"驾驶舱"——反复选状态、进状态、被唤醒、再选。
  2. hotplug 下线的归宿:cpu_is_offline(cpu)(:263)+cpuhp_report_idle_dead(:265)+arch_cpu_idle_dead(:266)在这里执行,下线后的 CPU 走到"报告 dead + hlt 死等"的终点。
  3. P-state 的边界:while (!need_resched())(:258)是 C-state(睡)和 P-state(跑)的分水岭——need_resched一置位就跳出 idle 去跑任务(进入 C0 + P-state 调频),否则钻进 C-state 睡觉。

一句话:do_idle同时是"空闲时睡多深"(C-state)、“下线后去哪”(hotplug 终点)、“什么时候回到干活”(P-state 起点)的交汇点。


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

6.1 为什么"存在 / 深浅 / 快慢"要拆成三个正交状态机

因为它们是三个独立的问题、三个独立的优化目标:hotplug 管"节能之外还要不要这台 CPU"(调度、隔离、故障下线),C-state 管"空闲时怎么省电"(性能 vs 能量的两本账),P-state 管"干活时怎么省电"(利用率 vs 频率)。耦合在一个状态机里会导致"想下线"和"想降频"互相牵制、状态爆炸。正交拆解,让每个维度各自用最合适的机制(回调链 / 预测 governor / 利用率调频)独立演进。

6.2 为什么 C-state 要分这么多档,而不是"睡/醒"两档

因为省电深度和唤醒延迟不可兼得:睡得越深(关核心电源)越省电,但醒来越慢(exit_latency 越大)。如果只有"睡/醒"两档,"睡"必须选最深,短空闲也付 200µs 的醒来代价,得不偿失。多档让 governor 按"预计空闲多久"选"最划算的那一档"——短空闲选浅睡、长空闲选深睡,省电和延迟两头兼顾。

6.3 为什么 hotplug 是另两个的前提,且能"抢占"它们

C-state 和 P-state 描述的都是"一台在线CPU 的行为"。CPU 一下线,idle 线程在do_idle里直接arch_cpu_idle_dead死等,C-state/P-state 的机制根本不会再被走到。所以 hotplug 是底层,“下线"是比"进 C-state”"调 P-state"都更高的优先级——这是三者之间唯一的硬性依赖,其余维度彼此正交。

6.4 为什么交汇点选在 idle 循环,而不是别处

idle 循环是 CPU唯一"既不干活、又没下线"的稳定状态——它是"有任务"和"没任务"之间的边界,天然适合做两个方向的裁判:没任务 → 沉入 C-state,检测到 offline → 沉入死亡。把三个维度的切换都收进do_idle一个循环,就避免了三套状态机各自维护一套"CPU 空闲时我该干嘛"的逻辑。


七、与已有博客的交叉引用

已有博客本篇覆盖的关系
CPU #3 hotplug 状态机§1.1 的关键状态(WORKQUEUE_PREP/AP_SCHED_STARTING/CPUIDLE_DEAD)是 #3 状态机链上的具体回调;本篇只取关键状态讲生命周期
CPU #5 cpuidle§1.2 的 C-state 深度表(intel_idle 的 exit_latency/target_residency)是 #5 menu governor 选状态的输入
CPU #6 cpufreq§1.3 的 P0/Pn+boost、§3.4 的"C0 才有 P-state"是 #6 schedutil 调频的状态边界
CPU #2 AP 拉起start_secondary→cpu_startup_entry→do_idle,是"上线 → idle"衔接的链路来源
CPU #10 初始化全景#10 串"初始化流程"(哪些数据做几次),本篇串"运行期状态"(什么时候跳什么状态)+ 状态缺失的连锁(§4.1 呼应 #10 的 per-CPU 数据)
并发 #3 死锁下线阶段(阶段④)的stop_machine+do_idle检测 offline,正是死锁风险的藏身处

附:本篇关键源码索引

符号位置
enum cpuhp_state(hotplug 状态机)include/linux/cpuhotplug.h:57
CPUHP_OFFLINE/CPUHP_BRINGUP_CPU/CPUHP_ONLINEinclude/linux/cpuhotplug.h:61/:136/:258
CPUHP_WORKQUEUE_PREP/CPUHP_AP_SCHED_STARTING/CPUHP_CPUIDLE_DEADinclude/linux/cpuhotplug.h:104/:145/:81
CPUHP_TEARDOWN_CPU/CPUHP_AP_ONLINE_IDLE/CPUHP_AP_ACTIVEinclude/linux/cpuhotplug.h:199/:202/:257
struct cpuidle_state(C-state)include/linux/cpuidle.h:49
nehalem_cstates(C1/C1E/C3/C6 深度表)drivers/idle/intel_idle.c:234
snb_cstates(含 C7)/byt_cstates(含 C6N/C6S)drivers/idle/intel_idle.c:271/:316
flg2MWAIT(mwait hint 编码)drivers/idle/intel_idle.c:130
do_idle(idle 循环,三者交汇点)kernel/sched/idle.c:237
cpuidle_idle_call(C-state 入口)kernel/sched/idle.c:282
cpu_is_offline→cpuhp_report_idle_dead→arch_cpu_idle_dead(下线归宿)kernel/sched/idle.c:263/:265/:266
schedule_idle(从 idle 切回任务)kernel/sched/idle.c:310
get_next_freq(schedutil 调频)kernel/sched/cpufreq_schedutil.c:139
takedown_cpu→stop_machine_cpuslocked(下线收尾)kernel/cpu.c:1328/:1345
boot_cpu_init(BSP 直接置在线)kernel/cpu.c:3155

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

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

立即咨询