EXECUTION HIERARCHY PANORAMA
CPU 硬件基础
晶体管 → 逻辑门 → ALU/寄存器 → 控制单元 → 时钟单核 → 多核
频率提升瓶颈 → 多核并行 → 缓存一致性 → NUMA超线程 (SMT)
一个核心两套上下文 → 资源复用 → 30%吞吐提升指令执行
取指 → 译码 → 执行 → 访存 → 写回 → 流水线进程
虚拟地址空间 → 页表 → PCB → 上下文切换OS 线程
轻量执行单元 → 线程调度 → 内核态切换 → 锁竞争协程 (Coroutine)
用户态调度 → 极低切换开销 → 百万并发CPU 硬件基础 HARDWARE
晶体管 (Transistor)
CPU的基本构建单元,本质是微型电子开关。现代CPU含数十亿晶体管(Apple M2约200亿,AMD EPYC约500亿)。NAND门需4个晶体管,SRAM一个存储单元需6个。通过3nm/5nm/7nm工艺不断提升集成密度。
逻辑门 → 组合逻辑
晶体管组合成AND/OR/NOT/XOR逻辑门,再构建加法器、多路选择器、译码器等。ALU(算术逻辑单元)由大量加法器和逻辑门构成,负责整数运算与逻辑判断。
寄存器 (Register)
CPU内部最快的存储单元,延迟约0.1-0.3ns。x86-64有16个通用寄存器(RAX,RBX...),ARM64有31个(X0-X30)。寄存器是程序能直接访问的最快存储。
控制单元 (CU)
CPU的"指挥中心",取指令、译码生成控制信号、协调ALU/寄存器/缓存协同工作。通过微操作(μop)将一条指令拆解为多个硬件步骤。现代CU支持乱序执行和分支预测。
单核 → 多核 MULTI-CORE
⚡ 单核时代 (2005前)
靠提升主频获取性能:Pentium 4冲到3.8GHz。但功耗∝频率²(P=αCV²f),3.8GHz功耗约130W,继续提升面临功耗墙和散热墙。Intel NetBurst架构的失败宣告频率路线走到尽头。
🔋 多核时代 (2005后)
同一芯片集成多个核心:Intel Core 2 Duo开启双核。同等功耗下,双核2GHz > 单核4GHz。当今桌面8-16核,服务器64-128核(AMD EPYC 9754达128核/256线程)。
缓存一致性 (MESI)
多核各有私有L1/L2缓存,MESI协议定义4种缓存行状态:Modified、Exclusive、Shared、Invalid。通过总线嗅探(Bus Snooping)实现核心间同步。
NUMA架构
多路服务器每个CPU有本地内存控制器,访问本地内存~100ns,远端~150-200ns。Linux numactl可绑定进程到特定NUMA节点,避免跨节点访问惩罚。
big.LITTLE
ARM首创,Apple M系列采用P-core+E-core混合架构。性能核处理重负载,效率核处理后台任务。Intel 12代+跟进P/E Core。调度器需感知核心类型。
超线程 SMT HYPERTHREADING
核心思想
单条指令执行中,ALU/访存单元常处于空闲(如cache miss等待)。SMT在同一个物理核上维护两套架构状态(寄存器组、PC),让两个硬件线程交替使用闲置执行单元,提高资源利用率。
共享 vs 独占资源
独占:寄存器组、PC指针、栈指针、指令TLB → 每线程一份
共享:ALU、FPU、L1数据缓存、L2/L3缓存 → 两线程争用
因此超线程并非"双核",而是更高效的"单核复用"。
性能收益与限制
典型吞吐提升20%~40%,绝非2倍。整数运算密集型收益高,浮点密集型收益低。单线程性能可能因缓存争用而轻微下降。数据库、Web服务器等IO密集场景收益显著。
| 场景 | 超线程提升 | 原因 |
|---|---|---|
| 数据库查询 | 25~40% | 大量cache miss,另一线程可填充空闲 |
| Web服务器 | 20~35% | IO等待多,SMT填补空窗 |
| 视频编码 | 10~20% | SIMD单元争用,收益有限 |
| 科学计算 | -5~10% | FPU满载,争用反而降低性能 |
指令执行 INSTRUCTION
① 取指 (IF - Instruction Fetch)
PC寄存器指向的内存地址,通过L1指令缓存读取指令字节。x86变长1~15B,ARM64固定4B。分支预测器在此阶段猜测下一条指令地址。
② 译码 (ID - Instruction Decode)
将机器码翻译为μop(微操作)。x86复杂指令被拆解为多个RISC-like μop。如 ADD [mem],EAX 拆为 load→add→store。寄存器重命名也在此阶段完成。
③ 执行 (EX - Execute)
ALU执行算术逻辑,FPU执行浮点,AGU计算内存地址。现代CPU支持乱序执行:操作数就绪即执行。保留站(Reservation Station)负责调度。
④ 访存 (MEM - Memory Access)
Load/Store通过L1D→L2→L3→主存层次读写数据。Cache miss时流水线可能阻塞数十到数百周期。Store Buffer允许写操作异步完成。
⑤ 写回 (WB - Write Back)
结果写入目标寄存器或标志寄存器。按序完成(Retire)保证精确中断和程序语义正确。ROB确保指令虽乱序执行但按序提交。
流水线冒险 (Hazard)
数据冒险:后续指令依赖前序结果 → Forwarding缓解
控制冒险:分支跳转打乱流水线 → 分支预测+推测执行
结构冒险:争用同一硬件 → 增加资源或插气泡
分支预测
if/else等条件跳转占指令20%+。现代CPU使用两级自适应预测器,准确率>97%。预测错误需冲刷流水线(15~20周期惩罚)。Spectre漏洞正是利用推测执行副作用。
ISA: x86 vs ARM
x86-64(CISC,变长1-15B,微码翻译μop) vs ARM64(RISC,定长4B,直接执行)。RISC-V同为RISC。ISA是软硬件契约:OS和编译器面向ISA编程,CPU微架构自由实现。
进程 PROCESS
进程的本质
进程 = 虚拟地址空间 + PCB(进程控制块) + 打开的文件/资源。每个进程拥有独立页表层次(x86-64四级),将虚拟地址翻译为物理地址。进程间内存天然隔离,通信需IPC。
上下文切换代价
进程切换需保存/恢复全部寄存器、切换页表基址(CR3)→刷新TLB、切换内核栈。TLB重建是最大开销,可达数千周期。典型切换耗时1~10μs。
虚拟地址空间布局
x86-64下48位虚拟地址:高地址内核空间(共享)、栈区(向下增长)、堆区(向上增长)、BSS/数据/代码段。ASLR将各区域随机偏移,防止缓冲区溢出攻击。
OS 线程 THREAD
🔄 进程 vs 线程
| 维度 | 进程 | 线程 |
|---|---|---|
| 地址空间 | 独立(隔离) | 共享(同进程内) |
| 创建开销 | ~1ms | ~10μs |
| 切换开销 | 1~10μs | 0.5~5μs |
| 页表切换 | 需要(CR3→刷TLB) | 不需要 |
| 通信 | IPC(管道/Socket等) | 直接读写共享变量 |
| 安全性 | 高(内存隔离) | 低(一线程崩溃全完) |
⚙️ 线程调度机制
Linux线程=进程(都是task_struct),1:1模型——每个用户线程对应一个内核线程。
CFS调度器:红黑树管理可运行线程,按虚拟运行时间排序。时间片通常1~10ms。
切换触发:时间片耗尽、IO阻塞、高优先级抢占、主动yield、信号中断。
内核态切换:保存用户态寄存器→切换内核栈→调度→恢复目标线程。
线程模型演进
1:1(Linux):用户线程=内核线程,简单但有内核开销。
N:1(Green Thread):多用户→一内核,无法利用多核。
M:N(Go goroutine):多用户→多内核,兼顾轻量与并行。
锁与同步
pthread_mutex(futex→内核切换)、spinlock(自旋等)、RW Lock(读共享写独占)、atomic(CAS硬件原子指令,无锁)。错误使用导致死锁/活锁/优先级反转。
协程 Coroutine COROUTINE
协程的本质
用户态调度的执行单元,切换不需要进入内核态。由运行时管理调度。一个OS线程可运行数万协程,切换仅需保存/恢复少量寄存器(约0.2~0.5μs),比线程切换快10~50倍。
协作式 vs 抢占式
协作式:协程主动yield(Python generator、Lua)。简单但一个不yield就阻塞全部。
抢占式:运行时强制挂起(Go 1.14+基于信号抢占)。更安全。
stackful vs stackless
stackful(有栈):每协程有独立调用栈(Go goroutine 2KB~1GB动态增长),可在任意层级yield。
stackless(无栈):编译器转为状态机(Rust async/await、C++20 co_await),零开销但无法深层yield。
Go Goroutine (stackful, M:N)
Rust async/await (stackless, M:N)
| 维度 | 进程 | OS线程 | 协程 |
|---|---|---|---|
| 创建开销 | ~1ms | ~10μs | ~0.3μs |
| 切换开销 | 1~10μs | 0.5~5μs | 0.2~0.5μs |
| 内存占用 | ~10MB(栈+页表) | ~8MB(栈) | ~2KB(goroutine) |
| 最大并发数 | ~数千 | ~数万 | ~数百万 |
| 调度者 | 内核 | 内核 | 用户态运行时 |
| 切换进内核 | 是 | 是 | 否 |
| 多核利用 | ✅ 原生 | ✅ 原生 | ⚠️ 需M:N映射 |