共享内存多处理器的基本概念
5.7.1 把 MIMD 分成了两支:共享内存和消息传递。这一节讲第一支——也是 408 唯一要求展开的那一支。
定义只有一句:
共享内存多处理器(SMP,Shared-Memory Multiprocessor):所有处理器共享同一个物理地址空间,同一个地址在任何处理器上都指向同一个存储单元。
这一句里最要紧的是后半句。 它带来了两个好处和两个麻烦,本节其余部分全在处理这四件事:
| 内容 | 去哪解决 | |
|---|---|---|
| 好处一 | 通信只需读写变量,不必显式收发消息 | —— |
| 好处二 | 程序模型接近单机,易于移植 | —— |
| 麻烦一 | 访问速度可能不均匀 → UMA / NUMA | 本节 |
| 麻烦二 | 私有 Cache 里的副本可能不一致 → Cache 一致性 | 本节 + 3.5.5 |
机制
一、UMA 与 NUMA
按”各处理器访问内存的时间是否相同”划分。
| UMA(均匀存储访问) | NUMA(非均匀存储访问) | |
|---|---|---|
| 访问延迟 | 所有处理器访问任何内存单元时间相同 | 访问本地内存快,访问远程内存慢 |
| 结构 | 处理器与内存之间是对称的(总线或交叉开关) | 内存分散挂在各处理器上,通过互连网络互访 |
| 扩展性 | 差——总线是瓶颈,处理器一多就争不过来 | 好——本地访问不上互连网络 |
| 典型 | 单芯片多核、小规模双路系统 | 大型多路服务器、多插槽系统 |
NUMA 的地址空间仍然是统一的——这一点极容易搞错。“非均匀”说的是访问时间,不是地址空间。 每个处理器都能用同一个地址访问到同一个单元,只是有的快有的慢。
NUMA 对操作系统提出了新要求:调度时应尽量让线程跑在它的数据所在的那个结点上,内存分配也应优先分配本地内存。这又是一处”硬件特性必须由 OS 配合才能生效”的例子,和 5.7.2 超线程需要 OS 知道拓扑是同一类问题。见 OS 2.2.6。
SMP(对称多处理器)通常指 UMA 结构:各处理器地位对等,都能访问全部内存和 I/O,操作系统只有一份、任一处理器都能执行内核代码。
与之相对的是 AMP(非对称多处理器):处理器地位不对等,有主从之分——这个词现在少用,但”大小核”某种意义上是它的回归。
二、Cache 一致性问题
问题从哪来:多核各有私有 L1/L2(5.7.3),同一个内存单元的副本可能同时躺在几个核的 Cache 里。
初始:内存 x = 0,核心 A 和 B 的 Cache 里都缓存了 x = 0
核心 A:x = 1 → A 的 Cache 里 x = 1
核心 B:读 x → B 的 Cache 里还是 0 ← 读到了旧值
这就是一致性问题:同一个地址的多个副本不一致。
注意它是多核特有的问题——单核不会出现,因为只有一份 Cache。3.5.5 已经讨论过写策略(写直达/写回、写分配/非写分配),那里解决的是”Cache 与主存之间”的一致,这里解决的是”Cache 与 Cache 之间”的一致。
两类协议
| 监听(侦听)协议 Snooping | 目录协议 Directory | |
|---|---|---|
| 怎么工作 | 每个 Cache 监听总线上的所有事务,发现别人写了自己缓存的地址就作出反应 | 用一个目录集中记录”每个块被哪些 Cache 缓存了”,写时只通知这些 Cache |
| 依赖 | 共享总线(广播) | 互连网络,不需要广播 |
| 扩展性 | 差——总线带宽是瓶颈 | 好 |
| 适合 | 小规模(多核芯片内) | 大规模(NUMA 多路系统) |
两者的取舍就是”广播 vs 点播”:监听协议简单但每次写都要打扰所有人,目录协议要维护一张表但只通知相关的几个。
两种处理方式
| 写失效(Write Invalidate) | 写更新(Write Update) | |
|---|---|---|
| 做法 | 我要写,先让别人的副本全部失效 | 我写了,把新值广播给所有持有副本的 Cache |
| 总线流量 | 只在第一次写时有一次失效广播 | 每次写都要广播新值 |
| 主流 | ✅ 是主流 | 少用 |
写失效之所以成为主流:一个变量常被同一个核连续写多次,写失效只在第一次付出代价,之后这个块已被独占,后续写不再打扰任何人。写更新则每次都要广播。
三、Cache 一致性 ≠ 存储一致性
这是本节最容易失分的一处辨析,也是最值得想清楚的一处。
| Cache 一致性(Coherence) | 存储一致性(Consistency) | |
|---|---|---|
| 管的是 | 同一个地址的多个副本 | 不同地址的访问在各处理器眼中的顺序 |
| 保证什么 | 对同一个地址,所有处理器最终看到同一个值 | 一个处理器的多次写,在别的处理器看来是什么顺序 |
| 靠什么 | 一致性协议(监听 / 目录) | 存储一致性模型 + 内存屏障指令 |
举例说明第二个为什么是独立的问题:
核心 A: 核心 B:
x = 1; while (flag == 0) ;
flag = 1; print(x); ← 会不会打印出 0?
即使 Cache 一致性完美工作(B 读 flag 一定能读到 1,读 x 也一定能读到最新值),B 仍可能打印 0——因为处理器可能把 A 的两条写调换顺序执行(乱序执行),flag = 1 先于 x = 1 生效。
一致性协议只保证”每个地址各自的副本一致”,不保证”不同地址的写按程序顺序对外可见”。 后者由存储一致性模型规定,需要内存屏障指令来强制。
四、一致性不等于互斥
这是第二个高频陷阱,而且它直接连到操作系统。
Cache 一致性保证你读到的是最新值,但不保证”读-改-写”这个序列不被打断。
两个核同时执行: count = count + 1;
核 A:读 count(=5) 核 B:读 count(=5)
核 A:算 5+1=6 核 B:算 5+1=6
核 A:写 count=6 核 B:写 count=6
结果:count = 6,而不是 7
每一步读到的都是”最新值”,一致性协议毫无问题——但结果还是错的。
互斥需要的是原子性,而原子性靠专门的硬件指令提供:
| 指令 | 作用 |
|---|---|
| TAS(Test-and-Set) | 读出旧值并写入 1,这两步不可分割 |
| CAS(Compare-and-Swap) | 值等于期望值时才写入,比较和写入不可分割 |
| LL/SC(Load-Linked / Store-Conditional) | 中间若有别人写过就失败重来 |
这些指令是 OS 2.3.3 互斥锁和信号量能实现的硬件基础——操作系统所有的同步原语,最终都落在这几条指令上。
所以三个层次要分清:
| 层次 | 解决什么 | 谁提供 |
|---|---|---|
| Cache 一致性 | 同一地址的副本一致 | 硬件协议,自动 |
| 原子指令 | 读-改-写不可分割 | 硬件指令,要显式使用 |
| 互斥 / 同步 | 临界区互斥、条件等待 | 操作系统用原子指令实现 |
下层不能替代上层。 有了一致性不代表有原子性,有了原子指令不代表程序自动正确。
五、共享内存 vs 消息传递:回顾
这一节结束时,把 5.7.1 那张表补完整:
| 共享内存多处理器 | 多计算机(消息传递) | |
|---|---|---|
| 地址空间 | 单一 | 各自独立 |
| 通信 | 读写共享变量(隐式) | send / recv(显式) |
| 需要一致性协议 | ✅ | ❌ |
| 需要同步原语 | ✅ | 通信本身即同步 |
| 编程难度 | 较易,但易出并发 bug | 较难,但bug 更显式 |
| 扩展规模 | 数十到数百核 | 数千到数万结点 |
| 内部再分 | UMA / NUMA | —— |
“共享内存好写但容易错、消息传递难写但错得明显”,这个取舍在分布式系统里一直存在。
边界
边界辨析:UMA / NUMA
- UMA:访问任何内存单元的时间相同;扩展性差
- NUMA:访问本地快、远程慢;扩展性好
NUMA 仍然是共享内存、仍然是统一的地址空间——“非均匀”指的是时间不是空间。
边界辨析:Cache 一致性 / 存储一致性
- Cache 一致性:同一个地址的多个副本的值要一致
- 存储一致性:不同地址的访问在各处理器看来的顺序
一致性协议不能保证跨地址的顺序,那需要内存屏障。
边界辨析:一致性 / 互斥
Cache 一致性不提供互斥。 它保证每次读到最新值,但**“读-改-写”仍可能被打断**。
互斥需要 TAS / CAS / LL-SC 这类原子指令,操作系统再用它们实现锁(OS 2.3.3)。
边界辨析:写失效 / 写更新
- 写失效:让别人的副本失效。只有第一次写付出代价,主流做法
- 写更新:广播新值。每次写都有开销
一道题问”哪种总线流量小”,答写失效。
边界辨析:监听协议 / 目录协议
- 监听:靠总线广播,简单但扩展性差,用于小规模
- 目录:集中记录谁缓存了什么,只点播,用于大规模 NUMA
对照速查
| 说法 | 判断 |
|---|---|
| NUMA 的各处理器有各自独立的地址空间 | ❌ 地址空间统一,只是访问时间不同 |
| SMP 一般指 UMA 结构 | ✅ |
| Cache 一致性问题只出现在多处理器系统 | ✅ 单核只有一份 Cache |
| 写失效协议的总线流量小于写更新 | ✅ |
| 监听协议适合大规模系统 | ❌ 总线是瓶颈,适合小规模 |
| Cache 一致性能保证临界区互斥 | ❌ 需要原子指令 |
| Cache 一致性等同于存储一致性 | ❌ 一个管值一个管序 |
| 消息传递系统需要 Cache 一致性协议 | ❌ 地址空间独立,不存在共享副本 |
| SMP 一般采用偶数路 CPU | ✅ 按王道口径 |
| 层次 | 解决 | 提供者 |
|---|---|---|
| Cache 一致性 | 同址副本一致 | 硬件,自动 |
| 原子指令 | 读-改-写不可分割 | 硬件,显式使用 |
| 锁 / 信号量 | 临界区互斥 | 操作系统 |
考点
- UMA / NUMA 辨析:NUMA 的地址空间是统一的,这是最常见的误判。
- Cache 一致性 vs 存储一致性:一个管值、一个管序。
- 一致性不提供互斥:高频陷阱,要能说出”读-改-写会被打断”。
- 写失效 vs 写更新:写失效流量小、是主流。
- 监听 vs 目录:监听靠广播适合小规模,目录适合大规模。
- 共享内存 vs 消息传递:判据是”同一地址是否同一单元”。
- SMP 偶数路:按王道口径记,同时分清”路 ≠ 核”(5.7.3)。
链接
- 🏠 返回总览:计算机组成原理第 5 章:中央处理器总览
- ⬅️ 上一节:5.7.3 多核处理器的基本概念
- ➡️ 下一章:第 6 章 总线
- 🔗 Cache 一致性详解:3.5.5 Cache 一致性问题
- 🔗 OS 同步原语:OS 2.3.3 互斥锁
- 🔗 OS 多处理机调度:OS 2.2.6 多处理机调度
- 📖 名词库:第 5 章名词库