Linux 内核热插拔深度实战:CPU、内存、PCIe 动态资源管理全景

在线弹性伸缩是云原生基础设施的核心能力。本文从内核源码层面拆解 CPU 热插拔、内存热插拔、PCIe 热插拔三大子系统的实现原理,并给出生产环境中的观测、排障与调优指南。


一、为什么需要热插拔?

数据中心运维中,硬件故障、资源扩容、节能降本是持续挑战。传统做法依赖整机重启来变更物理资源配置,这意味着:

  • 服务中断:关键业务(数据库、AI 训练)无法接受分钟级停机
  • 资源利用率低:为了避免重启,通常过度配置(over-provision),平均 CPU 利用率仅 15%-30%
  • 故障恢复慢:坏内存条、故障 CPU 需要上架换件

热插拔技术(Hotplug)让系统在不重启的前提下,动态增减物理资源。Linux 内核在 2.6 时代引入 CPU/内存热插拔,如今已成为云服务器的标配能力。


二、CPU 热插拔

2.1 基本原理

CPU 热插拔本质上是控制调度器(scheduler)何时开始/停止调度任务到某个 CPU,以及该 CPU 上的中断路由。

内核将 CPU 状态分为:

状态 含义
CPU_DOWN_PREPARE 准备下线,停止新任务调度
CPU_DYING 停止中断、迁移任务
CPU_DEAD 完全离线,可物理移除
CPU_UP_PREPARE 准备上线
CPU_UP_CANCELED 上线失败回滚
CPU_ONLINE 在线可用

2.2 操作接口

通过 sysfs 操作 CPU 热插拔:

# 查看在线 CPU
cat /sys/devices/system/cpu/online
# 输出:0-31(32核在线)

# 下线 CPU 15
echo 0 > /sys/devices/system/cpu/cpu15/online

# 上线 CPU 15
echo 1 > /sys/devices/system/cpu/cpu15/online

# 查看 CPU 状态
cat /sys/devices/system/cpu/cpu15/online
# 0 = 离线,1 = 在线

2.3 内核源码流程

CPU 下线的核心调用链(简化版):

echo 0 > .../online
  └─ cpu_down()                    # kernel/cpu.c
       └─ _cpu_down(cpu, 0)
            └─ cpuhp_invoke_callback(CPU_DEAD)
                 └─ 通知调度器停止任务
                 └─ 迁移中断路由
                 └─ 关闭本地 APIC

关键函数 take_cpu_down() 是架构相关的回调(x86 实现于 arch/x86/kernel/smpboot.c),它会:

1. 通知 SMT sibling 同步 L1/L2 cache 状态

2. 关闭本地 APIC timer

3. 跳转到 idle 的 MWAIT 指令进入 C3/C6 低功耗状态

2.4 NUMA 拓扑感知

在 NUMA 系统中,CPU 热迁移还涉及内存节点的本地性变化。当 CPU 下线时,其关联的本地内存可能也需要迁移:

# 查看 CPU 所在 NUMA 节点
cat /sys/devices/system/cpu/cpu15/topology/physical_package_id
# 0

lscpu | grep NUMA
# NUMA node0 CPU(s): 0-15,32-47
# NUMA node1 CPU(s): 16-31,48-63

2.5 实战建议

  • 生产环境注意:离线 CPU 时,per-CPU 变量(per-CPU runqueue 统计、softirq 计数)会被清零,监控工具可能显示锯齿状波动
  • 性能影响:在线/离线 CPU 涉及全局调度器锁争用,建议避开业务峰值
  • qemu/kvm 场景:支持 CPU hot-unplug,配合 virsh setvcpus 使用

三、内存热插拔

3.1 两种机制

Linux 内核提供两种内存热插拔机制:

机制 接口 粒度 典型场景
Memory Hotplug sysfs 内存块(通常 128MB) 物理 DIMM 热插拔
Memory Ballooning virtio-balloon 可变粒度 虚拟机动态调整内存

3.2 sysfs 接口

# 查看内存块状态
ls /sys/devices/system/memory/memory*/

# 查看状态
cat /sys/devices/system/memory/memory52/state
# "online"、"offline"、"going offline"

# 离线内存块
echo offline > /sys/devices/system/memory/memory52/state

# 在线内存块
echo online > /sys/devices/system/memory/memory52/state

# 查看内存块大小
cat /sys/devices/system/memory/memory52/block_size_bytes
# 134217728 = 128MB

3.3 内部流程

内存在线的关键步骤:

/*
 * 简化版 memory_online 流程 (drivers/base/memory.c)
 */
memory_block->state = MEM_GOING_ONLINE;
  └─ memory_block->online_callback()  // 架构实现
       └─ add_memory()                  # 添加物理内存到 buddy
            └─ create_section_mem_map()
            └─ __add_pages()             # 真正添加到 buddy
            └─ online_page()             # 标记 PG_active

关键的疑难问题在于:内存下线前必须先迁移页面。

# 生产环境挂载 hugetlbfs 后大页内存无法 offline
# 排查:是否有进程持有大页
cat /proc/meminfo | grep HugePages_
# HugePages_Total: 1024
# HugePages_Free:    0   ← 被全部占用,无法 offline

3.4 自映射(self-mapping)与 vmemmap

x86_64 内核使用 direct mapping(线性映射区)访问物理内存。当热插新增物理内存时,内核需要扩展:

  • vmemmap 稀疏内存映射:记录 struct page 的虚拟地址
  • direct mapping:将新物理页加入 PAGE_OFFSET 开始的线性映射区
  • zone 扩展:更新 zone 的 spanned_pages / present_pages
# 查看 zone 状态
cat /proc/zoneinfo grep -E "Node|zone|spanned|present|managed"

3.5 生产故障排查

内存离线失败通常有以下原因:

错误码 含义 解决方案
-EBUSY 内存被固定(pinned) 检查是否有内核模块持有、是否有 MAP_FIXED 映射
-EINVAL 内存已离线 无需处理
-ENOMEM 无法迁移页面 检查碎片化、NUMA 绑定
-EAGAIN 正在尝试 稍后重试

四、PCIe 热插拔

4.1 硬件基础

PCIe 热插拔依赖硬件支持:

  • Attention Button:物理按钮通知控制器拔出请求
  • Power Indicator / Attention Indicator:LED 状态指示
  • Presence Detect 信号线:检测插卡存在
  • Power Controller:控制插槽电源

4.2 内核驱动

Linux PCI 子系统(drivers/pci/hotplug/)提供多种热插拔驱动:

驱动 适用
pciehp 标准 PCIe 热插拔控制器
shpchp 兼容 SHPC 标准(PCI/PCIe)
acpiphp ACPI 方式的热插拔(服务器常用)
rpaphp IBM Power 平台
pnv_php IBM PowerNV 平台

4.3 操作接口

# 查看 PCI 桥插槽
ls /sys/bus/pci/slots/

# 查看插槽状态
cat /sys/bus/pci/slots/0-1/power
cat /sys/bus/pci/slots/0-1/attention

# 上电
echo 1 > /sys/bus/pci/slots/0-1/power

# 下电
echo 0 > /sys/bus/pci/slots/0-1/power

4.4 拔出流程

用户按下 Attention Button 后:

Attention Button Pressed
  └─ pciehp_handle_button_press()
       └─ pcie_handle_button_press()  # pciehp
            └─ 通知用户态
            └─ pciehp_disable_slot()
                 └─ 卸载绑定的驱动
                 └─ 释放 BAR 资源
                 └─ 关闭插槽电源

4.5 NVMe SSD 热插拔实践

服务器环境中最常见的热插拔是 NVMe U.2/U.3 SSD:

# 列出 NVMe 设备
nvme list

# 识别新插入的 NVMe
dmesg | tail
# nvme nvme1: pci function 0000:82:00.0
# nvme nvme1: 31 model ...

# 安全拔出流程(关键!)
# 1. 卸载文件系统
umount /data

# 2. 解除块设备绑定(通知多路径)
echo 'remove' > /sys/bus/pci/devices/0000:82:00.0/driver/unbind
# 或
echo 1 > /sys/bus/pci/devices/0000:82:00.0/remove

# 3. 确认移除
dmesg | tail
# nvme nvme1: Shutdown timeout set to 10 seconds

4.6 SR-IOV 热插拔与 PF/VF 联动

SR-IOV(Single Root I/O Virtualization)场景下,VF(Virtual Function)的热插拔会影响 PF(Physical Function):

# 查看 PF 的 VF 数量
cat /sys/bus/pci/devices/0000:3b:00.0/sriov_numvfs

# 减少 VF 数量(如 DPDK 场景需要释放 VF)
echo 0 > /sys/bus/pci/devices/0000:3b:00.0/sriov_numvfs

# 增加 VF 数量
echo 8 > /sys/bus/pci/devices/0000:3b:00.0/sriov_numvfs

# 注意:减少 VF 时,已绑定到 vm 的 VF 会触发错误
# 必须先关闭对应虚拟机再调整

五、ACPI / 设备树与上层层通知

5.1 ACPI 热插拔事件

服务器平台通过 ACPI(Advanced Configuration and Power Interface)标准传递热插拔事件:

# 通过 acpid 监听事件
acpi_listen
# 输出示例:
# cpuinden00 00000080 00000001   ← CPU 插入事件
# meminden34 00000080 00000000 00000081  ← 内存事件

内核侧的 ACPI 热插拔通知链路:

ACPI Notify(0x80) 
  └─ acpi_bus_notify()
       └─ acpi_device_hotplug()
            └─ 解析 _EJ0(Eject)/_OST(OST 状态)方法
            └─ 调用通用 device core 热插拔链

5.2 设备树(Device Tree)热插拔

嵌入式/LoongArch/RISC-V 平台使用设备树传递热插拔:

/* 设备树中定义热插拔节点 */
@memory@20000000 {
    device_type = "memory";
    reg = <0x20000000 0x4000000>;  /* 起始地址 512MB, 大小 64MB */
    status = "okay";
    hotpluggable;
};

热插拔时,内核调用 of_add_memory() / of_remove_memory()。


六、生产环境最佳实践

6.1 观测工具

# 查看 CPU 在线状态
lscpu | grep -i "on-line\|off-line"
# On-line CPU(s) list:   0-15,32-47
# Off-line CPU(s) list:  16-31,48-63

# 查看内存块
lsmem
# ADDR-RANGE          STATE     REMOVABLE  BLOCK
# 0x0000000000000000-0x00000001fffffff offline  0  yes
# 0x0000000200000000-0x00000003fffffff online   1  yes

# 查看 PCI 拓扑
lspci -tv
# └─08:00.0-Xilinx Corporation Device

6.2 性能陷阱

陷阱 原因 解决方案
CPU 下线后 numactr 仍统计 残留统计信息 清除 /sys 对应节点
内存下线失败 -EBUSY DMA buffer 或内核模块 pin 住 重启前 echo 3 > /proc/sys/vm/drop_caches
PCIe 电源时序异常 固件未合规 AC 掉电延迟 更新 BIOS/iLO/iDRAC
内存热插 NUMA 失衡 新内存被分配到错误节点 使用 mempolicy 或 libnumactl 显式绑定

6.3 容器/Kubernetes 协同

在 Kubernetes 集群中,热插拔与 kubelet 联动:

# Kubelet 配置 --system-reserved
kubelet --system-reserved=cpu=1,memory=2Gi \
        --kube-reserved=cpu=500m,memory=1Gi \
        --eviction-hard=memory.available<500Mi

# 当物理内存热移除时,kubelet 自动驱逐 Pod

6.4 与云管平台的融合方案

公有云(AWS Nitro、阿里云神龙)使用自研热插拔框架:

  • AWS Nitro:脱卡热拔插通过 Nitro Card Enclave 安全释放
  • 阿里云 ACM:通过 memory_block_state sysfs 实现无感缩容
  • OpenStack:Nova 调用 libvirt virsh setmem/virsh setvcpus

七、工程速查表

┌─────────────────────────────────────────────────────────────────┐
│  Linux 热插拔工程速查                                            │
├─────────────────────────────────────────────────────────────────┤
│  CPU 在线    echo 1 > /sys/devices/system/cpu/cpuN/online        │
│  CPU 离线    echo 0 > /sys/devices/system/cpu/cpuN/online        │
│  内存在线    echo online > /sys/devices/system/memory/memN/state │
│  内存离线    echo offline > /sys/devices/system/memory/memN/state│
│  PCI 上电    echo 1 > /sys/bus/pci/slots/X/power                │
│  PCI 下电    echo 0 > /sys/bus/pci/slots/X/power                │
├─────────────────────────────────────────────────────────────────┤
│  lscpu | grep -i on/off   查看 CPU 在线状态                      │
│  lsmem                     查看内存块状态                        │
│  dmesg \| tail             查看热插拔日志                         │
│  acpi_listen               ACPI 事件监听                         │
├─────────────────────────────────────────────────────────────────┤
│  注意:PCI 设备必须先卸载驱动再下电,避免 DMA 残留                  │
│  注意:内存 offline 前需迁移页面,碎片化严重时可能失败              │
│  注意:KVM 环境需先关机再减少 VF 数量                            │
└─────────────────────────────────────────────────────────────────┘

八、总结

Linux 热插拔是基础设施弹性的终极武器,但各子系统的实现复杂度远超日常使用:

  • CPU 热插拔涉及调度器、中断控制器、APIC 状态机
  • 内存热插拔要求 buddy 分配器线性扩展与页面迁移
  • PCIe 热插拔依赖硬件控制器、ACPI 固件、驱动卸载链

在生产环境中,热插拔失败 90% 的原因不是内核不支持,而是资源仍被持有(DMA pin、大页锁定、中断亲和绑定)。排查时始终从 dmesg 和 -EBUSY 错误码入手,先理清谁在用手上的资源。

掌握热插拔不是炫技,而是在刀片上让硬件"活"起来——这是云原生时代对基础软件工程师的硬核要求。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部
0.394004s