从单核到千核:Linux SMP 的“演化史”与工程细节
如果你只关心结论:
SMP = 对称多处理(Symmetric Multi-Processing);Linux 通过“三级缓存一致性→可抢占调度→Per-CPU 变量→RCU→NUMA 亲和→调度域”这一整套组合拳,把 1 核到 4096 核都当成“大号单核”来用。
但魔鬼藏在细节里,请坐稳,我们慢慢拆。
文章目录
- 从单核到千核:Linux SMP 的“演化史”与工程细节
1 为什么需要 SMP?
1965 年,Gordon Moore 画了一条斜线;50 年后,主频撞墙,IPC(每周期指令数)也逼近极限。于是横向扩展——把多个 CPU 核心放在同一主板——成为必然。
操作系统必须回答两个问题:
- 如何让所有 CPU 看到同样的内存?(缓存一致性)
- 如何让所有 CPU 不互相踩脚?(同步、调度)
Linux 的回答是:SMP。对称意味着“任何 CPU 都能运行内核代码、都能处理中断”,而不是像早期主从结构那样“0 号核是管家,其余核只能跑用户态”。
2 硬件地基:从总线嗅探到目录式缓存一致性
2.1 MESI 及其变体
现代 CPU 的 L1/L2 缓存行有四种状态:
- M(odified)
- E(xclusive)
- S(hared)
- I(nvalid)
总线嗅探(snooping)让所有核监听总线,写操作会触发 Invalidate 消息。
例子:核 0 写入地址 0x1234,发现核 1 也有副本,于是广播“请把 0x1234 标成 I”。
2.2 NUMA & 目录式协议
当核数 > 64,总线广播风暴不可接受,于是出现 目录式缓存一致性(Intel QPI、AMD Infinity Fabric)。
每个 NUMA 节点维护一张“目录表”,记录“某缓存行被哪些节点持有”,从而把广播变成点对点。
Linux 用 CONFIG_NUMA
打开 NUMA 感知,启动时通过 ACPI SLIT/SRAT 表建立距离矩阵,调度器据此把进程“粘”在离内存最近的节点。
3 内核启动:从 1 个 CPU 到 n 个 CPU 的芭蕾
3.1 早期 boot:只有 BSP(Bootstrap Processor)
- 通电后,硬件只唤醒 1 个核(BSP),其余核处于“Wait-for-SIPI”状态。
- 内核解压、建立临时页表、切换到长模式(x86_64)。
3.2 唤醒 AP(Application Processor)
start_kernel()
→smp_init()
→native_smp_cpus_done()
- 对每个 AP 调用
__cpu_up()
:- 在 trampoline 页(低 4 MB)放置实模式 AP 入口;
- 发送 INIT-SIPI-SIPI 序列(x86)或
PSCI_CPU_ON
(ARM); - AP 跳转到
secondary_startup_64
,建立 MMU,最终进入start_secondary()
,完成自己的per_cpu
区域初始化。
细节:x86 的 trampoline 代码在
arch/x86/kernel/head_64.S
,ARM 的启动入口在arch/arm64/kernel/head.S
。
4 调度器:让 1000 个进程在 128 个核上跳舞
4.1 runqueue 的进化
- Linux 2.4:全局 runqueue + 大内核锁(BKL),伸缩性差。
- Linux 2.6:O(1) 调度器,Per-CPU runqueue。
- Linux 3.x:CFS(完全公平调度器),红黑树管理 runnable 任务。
- Linux 5.x:EEVDF(最新默认) + SCHED_EXT(BPF 可编程调度器)。
4.2 调度域(sched domain)
调度器把 CPU 组织成层次化拓扑:
Die -> Package -> Core -> SMT
每个层级都有 load balance 算法,通过 tick_balance()
或 nohz_idle_balance()
定期迁移任务,避免“一核有难,七核围观”。
4.3 实时扩展
RT 补丁把 CONFIG_PREEMPT_RT
打开,把自旋锁变成可睡眠的 rt_mutex
,让高优先级任务随时抢占。代价是吞吐量下降 5-10%。
5 同步原语:自旋锁、信号量、mutex、RCU,到底谁保护谁?
5.1 自旋锁(spinlock_t)
- 在持锁期间禁止抢占(
preempt_disable()
)。 - 实现:
arch_spin_lock()
使用ticket lock
(公平)或qspinlock
(MCS 队列锁,NUMA 友好)。
5.2 读写锁 & seqlock
rwlock_t
:读者并发,写者独占。seqlock_t
:写者优先,读者重试(常用于 jiffies)。
5.3 mutex vs semaphore
mutex
只能睡眠,持有者明确;semaphore
可计数,常用于down_read()/up_write()
保护 struct 文件系统。
5.4 RCU(Read-Copy-Update)
- 读者零开销(仅关抢占),写者复制后异步回收。
- 实现:
call_rcu()
把回调挂到每 CPU 的rcu_data
,下一次 grace period(GP)后回收。 - 关键 API:
rcu_read_lock()
/rcu_read_unlock()
synchronize_rcu()
- 场景:路由表、链表遍历,DPDK 也偷师 RCU。
6 内存管理:Per-CPU 变量、slab、NUMA 节点与页着色
6.1 Per-CPU 变量
- 编译器把
DEFINE_PER_CPU(int, foo)
展开为段.data..percpu
,每个 CPU 一份副本,避免 false sharing。 - 访问:
this_cpu_ptr(&foo)
在 x86 上用%gs
段寄存器偏移。
6.2 slab 分配器
- 原始 slab → slub(目前默认)。
- Per-CPU cache:
kmem_cache_cpu
直接分配,无锁路径。 - NUMA 亲和:
kmem_cache_node
维护 node-local partial 列表。
6.3 页迁移 & 页着色
migrate_pages()
可在 NUMA 节点间迁移匿名页,减少远程内存访问。- 页着色:把物理页地址哈希到不同 L3 slice,避免多核冲突(Intel CAT 扩展)。
7 中断、软中断与 NAPI:IPI 如何让 CPU 之间“打电话”
- IPI(Inter-Processor Interrupt):x86 用
APIC
, ARM 用GIC
。smp_send_reschedule(cpu)
:让某核重新调度。flush_tlb_others()
:广播 TLB shootdown。
- 软中断(softirq):
- 10 个向量:HI_SOFTIRQ, TIMER_SOFTIRQ, NET_RX_SOFTIRQ…
- ksoftirqd 每 CPU 一个守护线程,防止软中断饥饿。
- NAPI:网卡硬中断触发后,关闭中断,改为轮询,减少跨核 IPI 风暴。
8 工具箱:/proc、perf、schedtrace、bpftrace 怎么看 SMP?
工具 | 说明 | 示例 |
---|---|---|
lscpu |
查看拓扑 | lscpu -e |
/proc/sched_debug |
调度器内部状态 | cat /proc/sched_debug | less |
perf stat -a |
全核 PMU 计数 | perf stat -a -e cache-misses ./workload |
bpftrace -e 'profile:hz=99 { @[cpu] = count(); }' |
看核负载分布 | |
taskset -c 0-3 ./a.out |
绑核运行 |
9 未来:CXL、chiplet 与可扩展性的终局之战
- CXL:把内存池化,NUMA 距离进一步拉大,Linux 需要新的“内存热插拔/故障隔离”子系统。
- chiplet:一个 socket 内出现 16 个小芯片,缓存一致性走向“die-to-die”链路,Linux 需把调度域切得更细。
- Rust in kernel:下一代同步原语可能用
lock_api
+crossbeam
的思想,减少 data race。
10 小结:一张思维导图
SMP
├── 硬件
│ ├── 缓存一致性(MESI/目录)
│ └── NUMA
├── 启动
│ ├── BSP → AP
│ └── trampoline
├── 调度
│ ├── CFS/RT/EEVDF
│ └── sched domain
├── 同步
│ ├── spinlock/mutex
│ └── RCU
├── 内存
│ ├── Per-CPU 变量
│ └── slab/NUMA
├── 中断
│ ├── IPI
│ └── softirq/NAPI
└── 工具
├── perf
└── bpftrace
Linux 的 SMP 不是一蹴而就,而是 30 年迭代的“活化石”。
今天,你在笔记本上跑make -j16
或者在 4096 核服务器上跑 Spark,背后都是同一套代码。
下次再听到“多核优化”,不妨想想:从缓存行到调度器,从 RCU 到 NUMA,Linux 早已把能踩的坑都踩了。
研究学习不易,点赞易。
工作生活不易,收藏易,点收藏不迷茫 :)