Linux 内核 CPU 热插拔深度实战:状态机、调度器交互与异构计算核绑定工程
作者:ybb | 日期:2026-10-06
标签:Linux Kernel, CPU Hotplug, CPUIdle, CPUFreq, Scheduling, Heterogeneous Computing
关键词:热插拔状态机、调度域拓扑、RCU/热插拔同步、CPU 遮蔽、SMP 启动路径
一、引言:为什么 CPU 热插拔比你想象的更复杂
在现代数据中心,CPU 热插拔(CPU Hotplug)已不再是"笔记本拔电池"时代的稀罕功能。它被广泛用于:
- 虚拟化场景:vCPU 热添加/移除(QEMU/KVM live resize)
- 异构计算:大小核架构下,小核集群在空闲时断电节能
- 高可用维护:固件升级或硬件故障时,在线摘除物理 CPU
- 机密计算:TDX/SEV-SNP 场景下,将 CPU 从通用调度域移出并交给安全监视器
多数开发者对 CPU 热插拔的认知停留在 echo 0 > /sys/devices/system/cpu/cpuN/online。实际上,一次成功的 CPU 下线(CPU-down)路径要跨越十几个内核子系统:调度器、RCU、定时器迁移、中断亲和性重排、cpuset 更新、内存 NUMA 平衡……任何一个环节卡死,都可能引发系统抖动甚至死锁。
本文深入剖析 Linux 内核 CPU 热插拔的完整状态机模型,揭示调度器与热插拔的深度交互机制,并给出生产环境的核绑定工程实践。
二、CPU 热插拔状态机架构
2.1 核心状态定义
内核使用 cpuhp_cpu_state 枚举管理每个 CPU 的热插拔生命周期:
// include/linux/cpuhotplug.h
enum cpuhp_cpu_state {
CPUHP_OFFLINE = 0,
CPUHP_PREPARE, // CPU 即将下线,各子系统开始准备
CPUHP_AP_ONLINE, // 目标 CPU 已在运行(启动阶段)
CPUHP_AP_ONLINE_DYN, // 动态分配的上线态
CPUHP_TEARDOWN, // CPU 正在下线清理
// ... 更多静态状态由编译时生成
};
状态转移通过 cpuhp_invoke_callback() 调用每个注册的回调函数,支持两种方向:
- Startup 路径:PREPARE → AP_ONLINE → ... → CPU online
- Teardown 路径:AP_ONLINE → TEARDOWN → ... → CPU offline
2.2 Step 模型与双向链表
热插拔不是单步操作,而是被分解为多个 Step。每个子系统在关心的 Step 注册回调:
// 注册示例:调度器在 CPU 下线前迁移任务
int cpuhp_setup_state(int cpu, const char *name,
int (*startup)(unsigned int cpu),
int (*teardown)(unsigned int cpu));
内核内部维护一个 cpuhp_step 双向链表(hp_hpfs->steps[]),每个 Step 包含:
struct cpuhp_step {
const char *name;
int (*startup)(unsigned int cpu);
int (*teardown)(unsigned int cpu);
bool cant_stop; // 是否不可中止(关键状态)
struct hlist_node list;
};
2.3 静态状态 vs 动态状态
早期内核使用编译时静态状态(CPUHP_*),灵活性差。4.10+ 后引入 cpuhp_setup_state_nocalls() 和 AP_ONLINE_DYN,允许运行时按需分配状态,成为现代驱动的标准做法。
三、CPU 下线完整路径拆解
以 echo 0 > /sys/devices/system/cpu/cpu3/online 为例,追踪内核执行路径:
用户空间
│
▼
device_store_online() [drivers/base/cpu.c]
│
▼
cpu_down() → _cpu_down()
│
▼
cpuhp_invoke_callback(CPUHP_TEARDOWN)
│
├─► stop_machine_cpu_stop() ← 核心:停止目标 CPU
│ │
│ ├─► 目标 CPU 执行 migration_cpu_stop()
│ │ 迁移所有运行中的线程到 other CPUs
│ │
│ ├─► 调用 teardown 回调链:
│ │ ├─► sched_cpu_deactivate() 调度域移除
│ │ ├─► cpuidle_sys teardown CPUIdle 退出
│ │ ├─► hrtimermigration() 高精度定时器迁移
│ │ ├─► rcu_report_dead() RCU 回调迁移
│ │ └─► ...
│ │
│ └─► target CPU 进入 idle()
│
▼
CPUHP_OFFLINE 状态确认
3.1 stop_machine() 的角色
stop_machine() 是热插拔的基石:它让所有其他 CPU 停下来(进入忙等待循环),保证目标 CPU 下线过程中不会有并发访问。
// 内核实现关键路径
int stop_cpu_sync(struct stop_machine_data *data)
{
// 1. 发送 IPI 到目标 CPU,让它进入 migration_cpu_stop()
smp_call_function_single(cpu, migration_cpu_stop, &arg, 1);
// 2. 等待目标 CPU 确认停止
while (!task_cpu(arg->task) == stop_cpu)
cpu_relax();
}
3.2 调度器层回调详解
调度器在热插拔中注册了关键回调:
// kernel/cpu.c 中注册
static int __init cpuhp_cb(struct notifier_block *self,
unsigned long action, void *hcpu)
{
unsigned int cpu = (unsigned long)hcpu;
switch (action) {
case CPUHP_SCHED_DEAD:
sched_exit_cpu(cpu); // 清理 per-cpu 运行队列
break;
case CPUHP_AP_SCHED_DEAD:
rq = cpu_rq(cpu);
raw_spin_lock_irqsave(&rq->lock, flags);
// 迁移剩余任务
rq->balance_callback = &no_balance;
raw_spin_unlock_irqrestore(&rq->lock, flags);
break;
}
}
四、RCU 与热插拔的生死纠缠
4.1 RCU 离线回调机制
RCU 必须处理"正在使用的 CPU 突然下线"的极端场景。内核通过 rcu_report_dead() 机制:
// kernel/rcu.h 注释(简化版)
/*
* 当 CPU 成功下线后,调用 rcu_report_dead() 通知:
* 1. 将 CPU 从 RCU 掩码中清除
* 2. 处理该 CPU 积压的 RCU 回调(通过 rcu_core)
*/
4.2 RCU 热插拔的死锁风险
如果某个 RCU 临界区内尝试下线 CPU(罕见但危险),会触发:
WARNING: possible circular locking dependency detected
CPU0 (热插拔进程) CPU3 (目标 CPU)
----------------------- -----------------
rcu_read_lock() ...
↓ prepare_to_wait()
stop_machine() rcu_read_lock()
→ wait for CPU3 → wait for CPU0
解决方案:热插拔路径禁止在 RCU 读端临界区内发起。内核通过 get_online_cpus() / put_online_cpus() 保护热插拔操作:
// 热插拔操作的保护
get_online_cpus(); // 增加热插拔引用计数,阻止新热插拔
// ... 执行热插拔 ...
put_online_cpus(); // 释放
五、异构计算:CPU 遮蔽与核绑定实战
5.1 isolcpus 与 nohz_full
生产环境中,常需将特定 CPU 从通用调度域中隔离,专用于实时任务或 DPDK 等高性能应用:
# GRUB 参数:隔离 CPU 2-5,启用全动态无tick
GRUB_CMDLINE_LINUX="isolcpus=2-5 nohz_full=2-5 rcu_nocbs=2-5"
5.2 cset shield(cgroup v2 cpuset)
现代替代方案:使用 cpuset.cpus 动态控制:
# 创建专用分区
mkdir /sys/fs/cgroup/partition
echo "2-5" > /sys/fs/cgroup/partition/cpuset.cpus
echo "root" > /sys/fs/cgroup/partition/cpuset.cpus.partition # 启用分区
# 将关键任务移入
echo $PID > /sys/fs/cgroup/partition/cgroup.procs
5.3 CPU 热插拔 + 调度策略组合
在大小核架构(如 ARM big.LITTLE / Intel Hybrid)中,小核可能随时被热插除:
// 监控 CPU 在线状态变更
static int __init monitor_hp_init(void)
{
return cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "my_monitor:online",
cpu_online_callback, cpu_offline_callback);
}
static int cpu_offline_callback(unsigned int cpu)
{
pr_warn("CPU%d 下线!当前活跃 CPU: %d\n",
cpu, cpumask_weight(cpu_online_mask));
// 触发负载均衡策略调整
adjust_sched_domain(cpu);
return 0;
}
六、生产环境工程实践
6.1 CPU 下线失败的诊断
当 CPU 下线卡死时,可查询:
# 查看热插拔状态机当前阻塞位置
cat /sys/devices/system/cpu/cpuN/hotplug/state
# 打印所有注册的热插拔回调
grep -r "cpuhp_setup_state" arch/x86 kernel/ | wc -l
# 检测 D-state 等待
echo w > /proc/sysrq-trigger
dmesg | tail -50 | grep -i "hotplug\|blocked"
6.2 BPF 监控热插拔事件
// BPF 程序:跟踪 CPU 热插拔事件
SEC("tp_btp/cpuhp_enter")
int trace_cpuhp_enter(struct trace_event_raw_cpuhp_enter *ctx)
{
u32 cpu = bpf_get_smp_processor_id();
u32 target = ctx->cpu;
bpf_printk("CPU%d: hp_enter target=%d state=%d\n",
cpu, target, ctx->target);
return 0;
}
6.3 NUMA 场景下的热插拔注意事项
在 NUMA 架构下关闭一个 CPU 可能触发:
- 该 CPU 本地 NUMA 节点的内存页面迁移到其他节点
mempolicy(内存策略)需要重新平衡ksm(内核同页合并)需跳过离线 CPU
# NUMA 感知的热插拔策略
numactl --cpunodebind=0 --membind=0 echo 0 > /sys/devices/system/cpu/cpu8/online
七、内核源码关键路径速查
| 功能 | 文件路径 | 关键函数 |
|---|---|---|
| 热插拔状态机 | kernel/cpu.c | _cpu_down() |
| 调度器回调 | kernel/sched/core.c | sched_cpu_deactivate() |
| RCU 热插拔 | kernel/rcu/tree.c | rcu_report_dead() |
| CPUIdle | drivers/cpuidle/cpuidle.c | cpuidle_install_handler() |
| CPUFreq | drivers/cpufreq/cpufreq.c | cpufreq_offline() |
| 定时器迁移 | kernel/time/timer.c | timers_dead_cpu() |
八、总结
CPU 热插拔是 Linux 内核最复杂的协同机制之一,涉及调度器、RCU、定时器、内存管理、中断系统等多个子系统的精密配合。本文梳理了:
- 状态机模型:Step-based 双向回调,支持静态/动态状态注册
- CPU 下线路径:stop_machine → migration_cpu_stop → teardown 回调链
- RCU 分布式同步:引用计数保护、离线回调、死锁规避
- 异构计算工程:isolcpus/cpuset/热插拔事件监控
- 生产诊断:状态机阻塞定位、BPF 追踪、NUMA 注意事项
掌握这些底层机制,才能在机密计算、实时调度、DPDK 等对 CPU 拓扑敏感的场景中避免踩坑。
参考文档:
- Linux Kernel Documentation:
Documentation/core-api/cpu_hotplug.rst Documentation/admin-guide/cputopology.rst- LWN.net: "CPU hotplug in the Linux kernel" 系列
- Kernel Newbies: cpuhp 状态机图解

发表评论 取消回复