计算机组成原理
计算机组成原理
艾伦·克莱门茨(Alan Clements)著,沈立、王苏峰、肖晓强译,机械工业出版社 2017 年 3 月第 1 版;对应 2014 年英文版 Computer Organization and Architecture: Themes and Variations 的选编内容。本文核对日期:2026-08-10。
先确定这本中文版究竟讲了什么
作者在前言中用一句很克制的话概括目标:“本书将阐述计算机是如何工作的。”版权页的内容简介进一步说明,全书从计算机的组成与体系结构、数据表示与运算出发,进入 ISA(Instruction Set Architecture,指令集体系结构),再讨论面向多媒体的指令扩展、控制器和流水线。
通俗地说,这本书回答的不是“怎样组装一台电脑”,而是下面这一串彼此依赖的问题:
- 软件里的变量、条件和函数,落到硬件上究竟变成了什么?
- CPU 为什么只靠少量简单操作就能运行任意程序?
- 同一套指令为何能由不同处理器实现,而且性能差异巨大?
- 处理器怎样用并行、流水和预测提高吞吐,又为何会被存储器、分支和功耗拖慢?
书中的核心对象是“计算机系统”,不只是 CPU。作者明确指出,系统包括读取并执行程序的 CPU、保存程序和数据的存储系统,以及让 CPU 与显示、网络和其他外设通信的子系统。只提高 CPU 而不改善数据供给,就只是让 CPU 更早开始等待。
版本边界:中文纸质版是英文原书的选编
这点很容易被忽略,却会直接影响对全书完整性的判断。译者序说明:
- 本书第 1~6 章分别对应英文原书第 1 章、第 2 章前 8 节、第 3~5 章和第 7 章。
- 原书的存储器、Cache、虚拟存储、总线和 I/O 被另编为《计算机存储与外设》。因此本书第 1 章只建立“存储层次”的概念,不负责完整展开。
- 原书 2.9~2.11 节以及第 6、8、13 章的数字逻辑、性能评价、多发射处理器和处理器级并行没有收入本纸质版。
所以,本文严格覆盖这册中文书的前言、三部分六章和参考文献;遇到被拆分或删节的主题会指出边界,不用书外知识冒充原书内容。
从程序语义走向硅片内部:全书逻辑图
三部分的关系不是三个互不相干的专题,而是一条由外向内的路径:第一部分建立抽象和数据规则;第二部分说明软件能看到的机器接口;第三部分把接口落实为数据通路、控制信号与流水线。
几种设计路线放在一起看
“计算机组成原理”不是可以被另一项单一技术替代的产品,更有意义的比较是:面对“指令如何取得操作数、如何编码、如何高效执行”这一问题,不同体系结构作出了怎样的取舍。
| 路线 | 操作数主要位置 | 指令形态 | 优点 | 代价与典型场景 |
|---|---|---|---|---|
| 栈式/零地址 | 栈顶 | 操作数隐含 | 字节码紧凑、解释器简单 | 随机访问弱、需要频繁压栈出栈;JVM 字节码是典型抽象机 |
| 累加器/单地址 | 累加器与存储器 | 一个显式地址 | 硬件与编码简单 | 中间值反复访存,指令数增多;早期小型处理器常见 |
| 寄存器—存储器(传统 CISC) | 一个寄存器、一个内存操作数 | 常为变长、寻址丰富 | 代码密度高、兼容历史软件 | 译码和流水化更复杂;x86 是代表 |
| Load/Store(经典 RISC) | 算术只操作寄存器 | 通常定长、字段规整 | 译码简单,利于流水、并行和编译器调度 | 装入/存储指令更多,固定宽度可能牺牲代码密度;ARM、MIPS、RISC-V 属于此思路 |
| 压缩 RISC | 仍为 Load/Store | 16/32 位混合或压缩子集 | 缩小程序与取指带宽 | 编码空间和可用寄存器受限;Thumb、MIPS16、RISC-V C |
| SIMD/向量扩展 | 宽向量寄存器 | 一条指令处理多元素 | 大幅提高规则数据并行吞吐 | 受分支、数据依赖、尾部元素和内存带宽限制;SSE/AVX、NEON/SVE |
结论不是“RISC 必胜”或“CISC 过时”。现代 x86 会把复杂指令译成内部微操作,现代 ARM 也加入了大量专用扩展;二者在实现层趋同,但软件可见的 ISA、兼容成本、代码密度和生态仍不同。书中反复出现的真正主线,是简单、性能、代码密度、可编译性和向后兼容之间的平衡。
六章解决了哪些问题
| 顺序 | 标题 | 核心内容 | 问题与解决思路 |
|---|---|---|---|
| 前言、译者序与导读 | 为什么要学体系结构 | 区分 EE、ECE、CS 的关注点,说明 ARM 教学平台和中文版裁剪范围 | 用“主题与变化”而非某一颗芯片串联硬件、编译器和系统软件 |
| 第一章 | 计算机系统体系结构 | 系统、体系结构与组成;历史;存储程序;存储层次和总线 | 从“最长连续数字串”问题反推机器必需的状态、存储、运算与条件转移 |
| 第二章 | 计算机算术 | 位模式、进制、补码、乘除、IEEE 浮点与误差 | 用统一二进制载体表示不同信息,并在范围、精度、成本之间折中 |
| 第三章 | 体系结构与组成 | ISA 三要素;ARM 寄存器、数据处理、分支、寻址、子程序、栈与端序 | 把取指—执行抽象落实为程序员可以使用的机器接口 |
| 第四章 | 指令集体系结构——广度和深度 | 栈帧、特权与异常、MIPS、复杂寻址、Thumb/MIPS16、变长指令 | 比较丰富指令与规整指令、代码密度与译码成本、硬件便利与编译器可用性 |
| 第五章 | 计算机体系结构与多媒体 | 图像处理、压缩、MMX/SSE、SIMD 与 ARM 多媒体指令 | 识别多媒体的“同一操作作用于大量短数据”特征,用数据级并行提高吞吐 |
| 第六章 | 处理器控制 | 微程序与组合控制、数据通路、流水线、冒险、分支预测和 BTB | 将 ISA 译成微操作并重叠执行,再用旁路、暂停和预测处理依赖与控制变化 |
| 参考文献 | 论述来源 | 分支预测、流水线与 ISA 研究文献 | 提供书中历史数据和设计判断的原始出处 |
第一阶段:从计算问题建立机器模型
第一章 计算机系统体系结构
1.1 什么是计算机系统体系结构
作者先纠正一个常见偷换:芯片、微处理器、CPU 和计算机系统不是同义词。
- CPU(Central Processing Unit):真正取指和执行程序的中央处理单元。
- 微处理器(microprocessor):集成在单个硅片上的 CPU。
- 计算机系统(computer system):CPU、存储层次、I/O 与互连构成的整体。
- Cache:保存近期或常用数据的高速小容量存储,目的是缩短平均访问时间。
系统视角带来第一个性能原则:吞吐取决于最慢且无法被隐藏的环节。处理器进步快于主存和外存时,就形成“存储墙”;后来即使 SSD 改善了外存延迟,CPU 与 DRAM 的鸿沟依旧需要多级 Cache、预取和并发访存来缓解。
1.2 体系结构和组成
书中把体系结构定义为程序员看到的抽象机器,把组成定义为这个抽象的具体实现。今天更常用 微体系结构(microarchitecture) 表示“组成”。一套 ISA 可以由顺序执行小核、乱序超标量大核或模拟器实现,软件仍看到相同的寄存器和指令语义。
一个很有用的判断方法是:
- 改动若要求重新编译或改变机器码语义,多半属于 ISA。
- 改动若只改变延迟、吞吐、Cache 大小或流水线深度,多半属于微体系结构。
- 两者并非互不影响。ISA 的变长编码、条件执行和精确异常都会约束译码与流水线;实现成本反过来也会促使 ISA 演化。
1.2.1 计算机系统和技术强调同一抽象能用不同器件和数据通路宽度实现。例如程序员看到 32 位操作,并不保证内部所有总线都一次传完 32 位。1.2.2 计算机体系结构在计算机科学中的地位则把底层与编译器、操作系统和高级语言连接起来:不了解调用、内存和指令代价,就很难解释高级程序为何具有某种性能行为。
1.3 计算机的发展:需求、器件与兼容性的共同结果
本节不是年代罗列,而是说明设计从来不是纯技术最优:奇思妙想、商业成本、制造能力和既有软件会共同决定路线。
- 1.3.1 机械计算机:以齿轮和机械状态表示数,证明“自动执行规则”早于电子器件。
- 1.3.2 机电式计算机:继电器让控制可电气化,但机械触点限制速度和可靠性。
- 1.3.3 早期电子计算机:真空管提高开关速度,同时带来体积、功耗和故障率问题。
- 1.3.4 微机和 PC 革命:集成电路和微处理器把计算从机构设备变成个人工具。
- 1.3.5 摩尔定律和进步的历程:晶体管密度的长期趋势提供更多资源,但它是经验观察,不是永远保证性能翻倍的物理定律。
- 1.3.6 存储技术发展:容量提升与访问延迟改善并不同步,层次化存储因此成为系统结构问题。
- 1.3.7 普适计算:计算进入汽车、家电和移动设备后,连接性、成本和能效与峰值性能同样重要。
- 1.3.8 多媒体计算机:声音、图像和视频把工作负载从少量标量运算推向大规模规则数据处理,为第五章埋下伏笔。
【当前补充】书中仍以“摩尔定律推动单核性能”为主要历史背景。后来频率提升受功耗和散热约束,增长更多转向多核、宽向量和专用加速器。晶体管更多,并不自动等于单线程更快。
1.4 从最长游程问题反推存储程序计算机
原书不用一张既成 CPU 框图开场,而是要求找出数字串 23277366664792221 的最大连续游程,答案为 4。逐个扫描时只需保留:当前数字、上一数字、当前长度、历史最大长度和当前位置;若当前数字相同就递增长度,否则结束旧游程并开始新游程。
1.4.1 问题描述确定输入与目标;1.4.2 解决方法把行为画成“仍在同一游程/进入新游程”的状态转换;1.4.3 构造一个算法把状态变化排成步骤;1.4.4 计算机需要通过什么来解决问题由步骤归纳出移动、装入、保存、算术、测试和分支;1.4.5 存储器为程序、输入和中间状态提供带地址的单元。
下面是依据书中思路重写的伪代码,不是逐字复制:
current = input[0]
run = 1
maximum = 1
for each value in input[1..end]:
if value == current:
run = run + 1
else:
maximum = max(maximum, run)
current = value
run = 1
maximum = max(maximum, run) // 别漏掉结尾仍在增长的游程它揭示了通用计算机的关键:复杂任务并不要求每个问题都有专用硬件,只要求一组足以组合的基本操作和根据结果改变控制流的能力。
1.5 存储程序概念
程序和数据都被编码为位模式,放在可寻址存储器中;某串位究竟是指令还是数据,由使用它的上下文决定。原书的基本循环可整理为:
PC 指向第一条指令
repeat forever:
IR = memory[PC] // 取指
PC = PC + 指令长度
decoded = decode(IR) // 译码
execute(decoded) // 可能继续访存并更新状态- PC(Program Counter):程序计数器,保存下一条待取指令的地址;它本质上是指针。
- IR(Instruction Register):指令寄存器,保存当前译码的指令。
- RTL(Register Transfer Language):寄存器传送语言,用符号描述“何时把哪个值送到哪里”,不是可运行的编程语言。
- opcode:操作码,说明做什么;其余字段描述寄存器、立即数或地址。
现代核可能预取、并行乃至乱序执行,但必须在架构可见层面维持与程序语义一致的结果;这就是“实现可以复杂,抽象必须稳定”。
1.6 系统概览:层次与互连
1.6.1 存储层次用“越近越快、越小越贵”的层次弥合 CPU 与大容量存储的差距。寄存器、Cache、主存和辅助存储不是简单替代关系,而是通过局部性协作。1.6.2 总线则承载地址、数据和控制信息;共享总线必须解决谁在何时驱动线路、读写方向和设备仲裁。
原书还按操作数位置区分存储器—存储器、寄存器—存储器和寄存器—寄存器(Load/Store)机器。这种分类会直接影响访存次数、编码字段和编译器生成代码的方式。
1.7 现代计算:性能之外还有能效
章末把全书的几条约束收拢起来:性能、能耗、连接性、存储墙和功耗墙。移动设备要求一小块电池同时支撑视频、网络与计算;服务器则受散热和总拥有成本制约。因而“更快”不能只理解为更高频率,还可能来自更低数据移动、更强并行、更好的局部性或更合适的专用单元。
本章的局限也很清楚:它只建立 Cache、主存、总线和 I/O 的位置,完整机制在配套的《计算机存储与外设》中,而不在本册。
第二章 计算机算术
2.1 数据不是位模式自带的含义
2.1.1 位与字节说明 bit 只有两种状态,byte 通常为 8 bit;字长则是处理器自然处理的数据宽度。2.1.2 位模式进一步强调,同一串 01000001 可以按协议解释为整数 65、ASCII 字符 A、颜色分量或指令字段。硬件存的是模式,类型和上下文赋予含义。
- bit(binary digit):二进制位。
- byte:字节,现代通用系统通常为 8 位。
- word:字,ISA 自然处理的一组位,不等同于固定的 16 位。
- encoding:编码,位模式与语义对象之间的约定。
2.2~2.3 位置记数法与二进制运算
2.2 数字和 2.2.1 位置记数法用
[
(d_nd_{n-1}\ldots d_0.d_{-1}\ldots)_b=\sum_i d_i b^i
]
统一十进制、二进制和十六进制。十六进制每位对应 4 个二进制位,适合阅读地址、掩码和机器码;它没有改变底层数据,只是更紧凑的书写形式。
2.3 二进制运算的规则与十进制相同,只是基数变成 2。真正重要的边界是固定字长:加法最高位进位可能丢失;0.1 在二进制中是无限循环小数;BCD 虽能逐位保留十进制数字,却牺牲编码和算术效率。书中还指出十进制算术在金融处理器上并未消失,这提醒我们“非主流”不等于“没有适用场景”。
2.4 有符号整数:为什么补码成为主流
2.4.1 符号及值表示法直观地用最高位表示正负,但产生 +0 和 -0,加减也要额外处理符号。2.4.2 二进制补码运算把 (n) 位数解释为范围 ([-2{n-1},2-1]),负数可由按位取反再加 1 得到;同一个加法器便能统一处理加法和减法。
需要分清两类状态:
- carry(进位)主要描述无符号结果超出位宽。
- overflow(溢出)描述有符号真值超出可表示范围。例如两个正数相加得到符号位为 1,就是有符号溢出。
- biased/excess representation(移码)把指数加固定偏置后存储,便于比较和表示正负指数;它常见于浮点指数,不是通用整数的主要表示。
【当前补充】C/C++ 中无符号整数按模 (2^n) 回绕;有符号溢出在语言层不是“可靠地补码回绕”,优化器可以假定它不发生。硬件行为和语言语义不能混为一谈。
2.5 乘除法:移位、部分积和速度折中
2.5.1 移位运算说明左移通常对应乘 2,逻辑右移向高位补 0,算术右移复制符号位。右移负数时不能简单补 0,但具体舍入方向仍受 ISA 和语言定义约束。
2.5.2 无符号二进制乘法逐位检查乘数,生成移位后的部分积再求和。2.5.3 快速乘法用并行部分积归约、重编码等方法换取更高硬件成本。2.5.4 除法反复移位、试减并生成商位;书中随后用牛顿—拉夫森迭代说明,也可以先逼近倒数,再把除法变成乘法。
这体现了组成层的典型权衡:ISA 只承诺“给出乘法结果”,实现可以是小面积的多周期移位加法器,也可以是高吞吐乘法阵列。
2.6~2.7 IEEE 浮点数与运算流水
2.6 浮点数把数写成符号、有效数和指数,扩大可表示范围;2.6.1 IEEE 浮点数给出统一格式、特殊值和舍入规则。以 binary32 为例:1 位符号、8 位指数、23 位 fraction;规格化数还有隐含的最高有效位。
[
(-1)^s \times 1.f \times 2^{e-bias}
]
- significand:有效数,旧文献常称 mantissa(尾数)。
- normal/subnormal:规格化数/次正规数;后者让接近 0 的表示逐渐下溢,而非突然归零。
- NaN(Not a Number):无有效数值结果,例如
0/0;通常会在后续运算中传播。 - infinity:正负无穷,可由有限非零数除以零或溢出产生。
- ULP(Unit in the Last Place):末位单位,用来衡量浮点间距和误差。
2.7 浮点运算展示加法的真实步骤:拆字段、比较指数、右移较小有效数以对阶、相加/相减、规格化、舍入、检查异常。乘法则主要是有效数相乘、指数相加,再规格化和舍入。浮点加法不满足数学实数上的结合律,因为每一步都可能舍入。
【纠正与补充】原书说不同芯片可能给出不同浮点结果,这在当年的编译器、扩展精度和指令差异语境下有道理,但不能理解为 IEEE 754 操作本身没有规定。相同格式、输入、操作和舍入模式下,基本操作有明确结果;差异常来自 FMA 是否融合、表达式重排、中间精度、非正规数处理和数学库算法。当前 IEEE 页面列出的现行修订是 IEEE 754-2019。
2.8 程序员必须理解误差怎样被放大
2.8.1 浮点运算中的误差传播讨论吸收、消去和累计误差。两个很接近的大数相减会丢失大量有效位;一个很小的数加到很大的数上可能完全不起作用。书中用爱国者导弹计时误差说明:单步误差很小,不代表长时间累计后仍可忽略。
#include <math.h>
#include <stdio.h>
int main(void) {
double x = 5.9995998e2;
double y = 1.0002010e2;
double direct = x * x - y * y;
double factored = (x + y) * (x - y);
printf("direct = %.17g\nfactored = %.17g\n", direct, factored);
}这两个表达式在实数代数上相等,在有限精度中却走过不同的舍入路径。解决方案不是“全部改成 double”这么简单,而是选择数值稳定的公式、缩放输入、补偿求和,并为误差而非字符串相等设置测试容差。
2.8.2 生成数学函数介绍查表、多项式/级数与迭代逼近。平方根的牛顿迭代为:
[
y_{k+1}=\frac{1}{2}\left(y_k+\frac{x}{y_k}\right)
]
从 y0 = 1 计算 (\sqrt{2}),少数几次迭代就能快速接近真值。代价是仍需考虑初值、停止条件、舍入和异常输入。
第二阶段:建立并比较软件可见的机器接口
第三章 体系结构与组成
3.1 再看存储程序计算机:这一次落实到数据通路
本章用寄存器、ALU、存储器和总线解释信息怎样流动。3.1.1 扩展处理器:常量处理加入立即数路径;3.1.2 扩展处理器:流控制加入条件码、多路选择器和分支目标,使 PC 能在顺序地址与目标地址之间选择。
- ALU(Arithmetic Logic Unit):算术逻辑单元。
- MAR(Memory Address Register):存储器地址寄存器,指出访问哪个单元。
- MBR/MDR(Memory Buffer/Data Register):保存从存储器读出或即将写入的数据。
- N/Z/C/V:负、零、进位、溢出条件标志。
- multiplexer:多路选择器,在多个输入中按控制信号选择一个。
这一模型会在第六章再次出现:第三章从 ISA 使用者看它,第六章从控制器设计者看它。
3.2 ISA 的三块拼图
原书将 ISA 归纳为寄存器集、寻址方式和指令格式。
- 3.2.1 寄存器:规定数量、宽度、通用/专用角色和哪些状态对软件可见。
- 3.2.2 寻址方式概述:规定立即数、寄存器、基址加偏移、PC 相对等如何形成有效地址。
- 3.2.3 指令格式:在有限位数中分配操作码、寄存器号、立即数和控制字段。
- 3.2.4 操作码与指令:编码空间越给某一类字段,其他字段就越受限;不存在“字段全都更宽”而不付出指令长度的方案。
书中依次比较零地址栈机、单地址累加器机、“一个半地址”的寄存器—存储器机和多寄存器 Load/Store 机。栈机省操作数字段,Load/Store 机则用更多寄存器和规整格式换流水化便利。
3.3 ARM 指令集体系结构
原书选 ARM 的原因是结构规整、工具丰富,并且当时已广泛用于移动设备。这里的主角主要是 32 位 A32 ARM 指令集,不是今天服务器和手机普遍使用的 AArch64/A64。
3.3.1 ARM 寄存器集介绍 r0~r15,其中 r13 常作 SP、r14 作 LR、r15 作 PC;不同异常模式还可能拥有分组寄存器。3.3.2 ARM 指令集强调 Load/Store、三操作数、条件码以及把移位器放在第二操作数路径上。
【当前补充】AArch64 使用 x0~x30(或相应的 32 位 w0~w30)、独立 SP 与程序计数语义,不能把书中 r15 = PC、普遍条件执行和“读取 PC 得当前地址加 8”直接迁移过去。学习本章应抓设计思想,编写现代代码则查 A64 手册。
3.4 ARM 汇编语言与伪指令
3.4.1 ARM 程序结构给出 label opcode operands ; comment 的基本格式;3.4.2 汇编器的实际考虑因素说明源程序还需要代码段、入口和数据定义;3.4.3 伪指令说明汇编器可以把方便的写法展开成一条或多条真实指令。
书中的 A32 风格示例可整理为:
loop:
SUBS r1, r1, #1 @ r1--,同时更新 N/Z/C/V
ADD r2, r2, #4 @ 不带 S,不破坏刚才的标志
MUL r3, r4, r5
BNE loop @ Z == 0 时继续这段代码的重点不在语法,而在“标志是隐式依赖”:SUBS 产生条件码,BNE 消费条件码,中间指令必须避免覆盖它。现代乱序核还必须在内部跟踪这种依赖。
书中 LDR rN, =constant、ADR 等属于伪指令或地址生成便利写法,具体展开取决于常量大小、代码位置和工具链。不要把伪指令的表面形式当成 ISA 保证。
3.5 数据处理:算术、位操作、移位与编码约束
3.5.1 算术指令覆盖加减、带进位运算和比较;3.5.2 位操作用 AND/OR/XOR/BIC 实现掩码、置位和清位;3.5.3 移位操作展示 ARM 能在一条数据处理指令中对第二操作数移位,例如:
ADD r0, r0, r1, LSL #2 @ r0 = r0 + r1 * 4这非常适合数组下标和乘常数。3.5.4 指令编码再解释为什么能力仍有边界:32 位指令必须同时容纳条件、操作码、寄存器和移位/立即数字段,立即数并非任意 32 位值。
3.6 流控制:顺序语义与条件执行
3.6.1 无条件分支直接替换控制流;3.6.2 条件分支读取标志;3.6.3 测试与比较指令只更新标志而不保留算术结果;3.6.4 分支与循环结构把高级语言的 if、while 和 for 降低成比较与跳转;3.6.5 条件执行让 A32 的多数指令带条件后缀,在短小分支中避免刷新流水线。
条件执行并非免费:被判为 false 的指令仍需取指和译码,长条件块还会浪费执行带宽。AArch64 不再让几乎所有指令都带条件字段,而更多使用条件选择、条件比较和普通分支;这正是 ISA 随微体系结构权衡变化的例子。
3.7 寻址方式:把数据结构映射为地址计算
- 3.7.1 立即数寻址:值在指令中,省去数据访存但受编码宽度限制。
- 3.7.2 寄存器间接寻址:寄存器保存地址,适合指针。
- 3.7.3 带偏移量的寄存器间接寻址:
base + offset,适合结构字段和数组元素。 - 3.7.4 自动前索引:先更新地址再访问。
- 3.7.5 自动后索引:先访问再更新地址,适合流式遍历。
- 3.7.6 PC 相对寻址:以当前位置为基准形成地址,有利于位置无关代码。
- 3.7.7 load/store 编码:编码字段决定偏移范围、索引方向和可用寄存器。
寻址方式越丰富,单条代码可能越短;但硬件地址生成、异常处理和编译器选择也越复杂。第四章会用 68K 的存储器间接寻址展示这种复杂度的极端。
3.8~3.10 子程序与栈
3.8 子程序调用与返回要求同时完成跳转和保存返回地址;3.8.1 ARM 对子程序的支持使用带链接分支把返回地址写入 LR;3.8.2 条件子程序调用将条件语义与调用结合。
3.9 ARM 代码实例不是独立的新机制,而是连续练习:
- 3.9.1~3.9.3用条件值、移位和掩码完成绝对值、字节拼接与字节逆转。
- 3.9.4~3.9.7用移位加减实现乘 (2^n\pm1)、组合多条件、寻找单指令写法并构造多段程序。
- 3.9.8~3.9.11把位操作用于十六进制字符转换、输出和横幅打印,从纯计算走到可观察的程序行为。
3.10 子程序与栈说明 LR 只能直接保存一层返回地址。3.10.1 调用与返回在进入非叶函数前保存需要保留的寄存器和 LR;3.10.2 子程序嵌套用栈支持任意调用深度;3.10.3 叶子程序不再调用别人,常可直接使用 LR 返回而免去栈帧。
这里必须补上书中示例之外的现实约束:哪些寄存器由调用者保存、哪些由被调用者保存、参数和返回值放哪里,都由 ABI(Application Binary Interface,应用二进制接口)规定,而不是任由每个函数选择。
3.11 数据大小、端序与块移动
3.11.1 数据组织与端格式区分大端和小端:多字节值的最低有效字节放在低地址还是高地址。3.11.2 数据组织和 ARM讨论字节、半字、字访问、对齐与符号扩展。3.11.3 块移动指令一次装入/保存多个寄存器,A32 常用它实现压栈和出栈。
- endianness只规定字节顺序,不改变寄存器中数值的位权。
- alignment要求或偏好数据地址是其大小的倍数;未对齐访问可能变慢、拆成多次访问或在某些环境触发异常。
- sign extension/zero extension:把窄值扩宽时复制符号位或补 0。
3.12 整合
最后一节把输入、数值转换、子程序、栈和输出放进一个完整程序。它的价值不是让读者背旧版 ARM 汇编,而是验证一条迁移链:高级任务 → 数据布局 → 调用约定 → 指令选择 → 寄存器和内存状态变化。
本章的局限是版本性很强。A32 的教学价值仍在,但现代 Arm 软件、工具链和操作系统普遍围绕 AArch64;书中的 ARMASM 指示符、系统调用示例和旧 PC 语义都应视作历史目标平台的材料。
第四章 指令集体系结构——广度和深度
第三章沿 ARM 纵向走了一遍;本章横向比较栈、异常、MIPS、复杂寻址和不同指令长度,追问“为什么 ISA 会长成不同样子”。
4.1 数据存储和栈:高级语言的运行时骨架
4.1.1 存储和栈把栈从“保存返回地址”扩展为过程运行时数据结构;4.1.2 通过栈传递参数说明寄存器不够、可变参数或调用边界需要稳定布局时,参数也可进入栈。
典型栈帧包含:返回地址、旧帧指针、被调用者保存的寄存器、局部变量和溢出到栈的参数。SP 指向当前栈顶,FP 为本次调用提供相对稳定的基准。优化器可以省略 FP、合并栈帧甚至内联函数,所以“每个 C 函数必有固定栈帧”并不成立。
高地址
┌─────────────────┐
│ 调用者参数/旧帧 │
├─────────────────┤ ← FP(若使用)
│ 返回地址与保存值 │
├─────────────────┤
│ 局部变量/临时值 │
└─────────────────┘ ← SP
低地址(以下降栈为例)- stack frame:一次函数调用在栈上占用的区域。
- calling convention:规定参数、返回值、寄存器保存责任、栈对齐和符号命名。
- unwinding:沿调用链恢复上一帧,用于异常、调试和栈回溯。
4.2 特权模式和异常
用户程序不能直接执行所有系统操作,否则一个错误指针就可能破坏内核或其他进程。处理器因此提供用户/特权模式和受控入口。
- exception(异常):改变正常控制流的事件总称,可由当前指令、处理器状态或外部设备引发。
- interrupt(中断):通常来自外部且与当前指令异步。
- trap/system call(陷阱/系统调用):程序有意请求特权服务的同步入口。
- precise exception(精确异常):异常点之前的指令已完成,之后的指令没有架构可见效果,便于恢复。
原书用 ARM 的 CPSR、异常模式和分组 LR/SP 解释:进入异常时必须保存返回位置和旧状态,切换到受保护的处理环境,处理后原子地恢复状态与 PC。不同异常的返回地址可能需要调整,不能机械地把普通 return 套上去。
【当前补充】AArch64 用异常级别 EL0~EL3、SPSR_ELx、ELR_ELx 和 ERET 表达相同的基本需求,寄存器名和入口模型已不同。理解“保存上下文—提升权限—定位原因—恢复”比背 A32 返回指令更耐久。
4.3 MIPS:用另一种 RISC 检查 ARM 结论
MIPS 由 John Hennessy 团队推动,是字段规整、32 个通用寄存器、Load/Store 思路鲜明的经典 RISC。4.3.1 MIPS 数据处理指令展示固定 32 位编码、寄存器操作和 16 位立即数的取舍。
书中的数组循环对比表明,两者能生成很相似的算法:MIPS 依靠更多规整寄存器和简单基址偏移;ARM 用自动索引与“移位后的第二操作数”压缩常见地址操作。谁“更好”不能脱离具体实现、编译器、内存系统、功耗和软件生态只数指令条数。
# MIPS 风格:y[i] = x[i] * 8
loop:
lw $t0, 0($a0)
sll $t0, $t0, 3
sw $t0, 0($a1)
addiu $a0, $a0, 4
addiu $a1, $a1, 4
addiu $t1, $t1, -1
bne $t1, $zero, loop这是依据书中循环整理的教学片段;真实代码还受延迟槽(旧 MIPS)、伪指令展开和 ABI 约束。
4.4 数据处理与数据传送:专用指令是否值得
本节故意离开 ARM,考察交换、双精度移位、压缩/解压、边界测试、位字段和循环指令。
- 4.4.1 不可见的交换指令讨论寄存器重命名或内部路径可消除某些表面数据搬移。
- 4.4.2 双精度移位让两个寄存器拼成更宽操作数,适合多字精度运算。
- 4.4.3 压缩和解压缩指令重排窄元素,为多媒体数据准备布局。
- 4.4.4 边界测试把上下界检查合并,可能服务图形、饱和或安全检查。
- 4.4.5 位字段数据抽取/插入任意位段,适合设备寄存器和协议字段。
- 4.4.6 循环考察“硬件循环”能否减少计数与分支开销。
评价一条专用指令要问四件事:常见负载是否真的频繁使用?编译器能否稳定识别?它是否缩短关键路径或只缩短汇编文本?引入的译码、验证和上下文保存成本是多少?书中对早期“手册上看起来很强但很少用”的指令持谨慎态度。
4.5 存储器间接寻址:表达力与可实现性的冲突
寄存器间接是 memory[register];存储器间接则先从内存取出另一个指针,再访问最终操作数,即“指向指针的指针”。它能用一条 68K 指令描述复杂表和记录:基址、索引、比例、内外偏移甚至位字段都塞进同一条指令。
书中给出的结论很有迁移性:一条指令参数多并不自动更快。多级依赖访存无法并行,可能跨页并产生异常,编译器也很难命中复杂模式。拆成规整的 load、地址加法和最终访问,反而更利于调度、乱序执行和精确定位故障。
4.6 压缩代码、RISC、Thumb 和 MIPS16
固定 32 位 RISC 易于取指译码,却会让简单指令也占 4 字节。嵌入式设备的存储容量和取指带宽昂贵,于是出现压缩子集。
- 4.6.1 Thumb:以更短编码表达常用 ARM 操作,早期功能/寄存器范围受限;后来的 Thumb-2 混合 16/32 位编码。
- 4.6.2 MIPS16:为 MIPS 提供 16 位紧凑形式,利用较小寄存器子集和受限立即数换密度。
压缩不仅节约磁盘或 Flash,还能让同一条 Cache line 容纳更多指令,降低取指带宽;代价是译码边界、寄存器选择与立即数范围更复杂。
【当前补充】相同思想延续在 RISC-V C 扩展中。AArch64 的 A64 指令固定为 32 位,不是 Thumb-2 的延续;现代 Arm 的 A-profile 可以同时涉及 A64、A32/T32 支持,但具体可用状态由架构版本与实现决定。
4.7 变长指令
x86、68K 和早期 8 位处理器用不同长度容纳无操作数、短立即数和完整地址,获得较高代码密度,却必须先确定边界才能并行译码。书中的假想 8 位机用前缀字节逐级扩展操作码,说明变长编码怎样增长。
现代 x86 证明变长 ISA 完全可以做到高性能,但需要前端预译码、指令边界预测、µop cache 和多级译码器等大量组成层工程。它不是“复杂所以必慢”,而是把复杂性转移到实现,并用兼容生态换取长期价值。
本章总结出的设计原则是:理论上极少指令即可完成通用计算;工程上仍要在性能、代码密度、向后兼容、工具链和市场之间取舍。
第五章 计算机体系结构与多媒体
5.1 高性能计算应用从哪里产生
多媒体把声音、图像、视频和交互整合起来,对容量、带宽、低延迟和吞吐同时施压。原书强调的共同结构是:对大量短数据元素重复少量简单运算。
5.1.1 图像处理用两个例子说明这一点:
- 噪声滤波读取一个像素及其邻域,执行加权、求和和归一化;每个像素都重复同一模板。
- 对比度增强把旧像素按全局最小/最大值线性映射到新范围:
[
p_{new}=(p_{old}-c)\frac{b-a}{d-c}+a
]
图像像素彼此规则,适合并行;但邻域滤波还受数据复用和内存访问模式影响。专用 GPU 的兴起不是单纯因为乘加更快,而是它拥有大量并行执行通道和适合高吞吐访问的存储层次。
本节还讨论图形、数字信号处理、编码与有损压缩。有损压缩利用人类感知不敏感的信息降低数据量;它解决带宽和容量问题,但不可逆,且重复转码会积累损失。
5.2 多媒体为什么让 ISA 再次变复杂
传统 32/64 位标量寄存器一次只处理一个 8 位颜色或 16 位音频样本,浪费其宽度。SIMD 把宽寄存器切成多个 lane,例如一条指令并行完成 8 个字节加法或 4 个 16 位加法。
- SIMD(Single Instruction, Multiple Data):单指令多数据。
- lane:向量中的一个元素通道。
- packed data:把多个窄元素打包进一个宽寄存器。
- saturation arithmetic:饱和算术,溢出时钳位到最大/最小值而非回绕,常用于音视频。
这看似让 RISC/CISC 争论“倒退”,其实是以真实热点为依据的专用复杂化。关键区别不是指令数量,而是新增语义能否用成倍的数据并行回报成本。
5.3 SIMD 处理简介:从 MMX 理解打包计算
MMX 在不改变旧操作系统上下文结构的目标下复用 x87 浮点寄存器的存储,提供 8 个 64 位逻辑寄存器。这样兼容性好,却导致 MMX 与 x87 状态不能自然混用;后续 SSE 使用独立 XMM 寄存器纠正了这个局限。
5.3.1 SIMD 技术的应用包括打包加减、乘加、比较、打包/解包和饱和运算。比较通常生成全 0/全 1 掩码,再由按位逻辑选择结果,从而避免每个元素分支。
以可移植 C 写成的像素亮度增加如下,现代优化编译器在别名、对齐和目标 ISA 信息充分时可以自动向量化:
#include <stddef.h>
#include <stdint.h>
void brighten(uint8_t *restrict dst,
const uint8_t *restrict src,
size_t n,
uint8_t delta) {
for (size_t i = 0; i < n; ++i) {
unsigned value = (unsigned)src[i] + delta;
dst[i] = (uint8_t)(value > 255 ? 255 : value);
}
}restrict 告诉 C 编译器源和目标不重叠;饱和表达式可以映射到某些向量饱和指令。应先检查编译器生成代码和基准数据,再决定是否写平台 intrinsics。
5.4 流扩展和 SIMD 的演化
5.4.1 浮点软件扩展从整数打包走向并行浮点;5.4.2 Intel 的第三层多媒体扩展展示扩展如何逐代增加;5.4.3 SSE3 和 SSE4加入水平运算、点积、混合、字符串等能力;5.4.4 ARM 系列处理器的多媒体指令展示 NEON 的向量 load/store、算术与结构化数据访问。
原书用数组加法说明:向量 load 取多元素,向量 add 同时求和,向量 store 写回,因此循环每次推进多个元素。今天仍要处理四个边界:
n不是向量宽度整数倍时的尾部元素;- 地址未对齐或跨 Cache line/page;
- 前后迭代有真实数据依赖,无法安全并行;
- 算术语义不同,例如回绕、饱和、NaN 和舍入。
【版本对照】MMX 已是教学历史,SSE 也不是 x86 向量能力终点;后续有 AVX/AVX2/AVX-512。Arm 侧从固定 128 位 Advanced SIMD(NEON)扩展到长度可伸缩的 SVE/SVE2,并出现面向矩阵处理的 SME。更宽不必然更快:降频、功耗、数据搬移和可用执行端口都可能成为瓶颈。
【进一步扩展】GPU 适合线程级大规模规则并行,NPU/张量单元适合矩阵乘累加,CPU SIMD 适合低延迟、分支较多且与通用代码紧密耦合的向量片段。它们是异构协作,不是简单替代。
第三阶段:在处理器内部实现并加速 ISA
第六章 处理器控制
6.1 通用数字处理器与控制器
数据通路包含寄存器、总线、ALU、多路选择器和存储接口;控制器根据当前指令、时序阶段和状态生成控制信号,让数据按正确路径移动。
6.1.1 微程序把一条机器指令解释成保存在控制存储器中的微指令序列。例如“MAR 取 PC、发起存储器读、MBR 送 IR、PC 递增”都是微操作。微程序易修改、适合复杂 ISA,但多一层控制存储访问。
6.1.2 生成微操作讨论水平/垂直微指令、字段编码和下一微地址。另一条路线是组合逻辑硬连线控制:直接由操作码、状态和周期生成信号,延迟可更低,但设计和修改成本更高。
- micro-operation:一个时钟阶段内完成的基本寄存器传送或 ALU 动作。
- microcode:把机器指令展开为微操作序列的控制程序;与“微处理器”不是同一个“微”。
- control store:保存微程序的控制存储器。
- hardwired control:用组合/时序逻辑直接生成控制信号。
现代处理器常混合使用:常见简单指令走快速译码,复杂或修复性流程由微码辅助。因此“RISC 一定硬布线、CISC 一定微程序”只是过度简化。
6.2 RISC 的组成
6.2.1 寄存器—寄存器数据通路让寄存器文件同时读出源操作数,ALU 运算后写回目的寄存器;load/store 再连接地址生成和数据存储器。6.2.2 单周期直通计算机的控制尝试在一个长时钟周期内完成一条指令。
单周期设计概念清楚,但时钟周期必须覆盖最慢指令的完整路径,简单指令也被迫等待;资源还可能需要复制以避免同周期冲突。它适合作教学基线,不是高性能实现的终点。
6.3 流水线:提高吞吐,不是缩短单条延迟
流水线像生产线一样把取指、译码、执行、访存、写回分段,在不同阶段同时处理多条指令。6.3.1 加速比的理想模型是:若分成 (k) 个平衡阶段,大量指令的吞吐最多接近提高 (k) 倍;单条指令还要经过全部阶段,延迟通常不会缩短。
若阶段组合逻辑延迟为 (t_i),流水寄存器开销为 (t_r),时钟周期受
[
T_{clk}\ge \max(t_i)+t_r
]
限制。不平衡阶段、寄存器开销和冒险都会让实际加速低于理想值。
6.3.2 实现流水线在阶段间加入寄存器,锁存数据和控制信息。控制信号必须跟着对应指令向后传,不能只传操作数。load 的数据较晚到达,会成为经典依赖难点。
6.3.3 冒险解释流水线不能永远满载:
- 结构冒险:两阶段同时争用一个硬件资源,例如单端口存储器。
- 数据冒险:后一指令依赖前一指令尚未产生或提交的结果;顺序五级流水最常见 RAW(Read After Write)。
- 控制冒险:分支、异常和返回改变下一 PC,已取入的错误路径指令要作废。
- forwarding/bypassing(前递/旁路):结果不等写回寄存器,直接送到需要它的后级。
- stall/bubble(暂停/气泡):依赖无法旁路时冻结部分流水线,插入不做有效工作的周期。
书中给出直观算例:若 20% 指令是分支、其中 80% 会跳转,每次错误路径损失 4 周期,那么平均附加 CPI 为 0.2 × 0.8 × 4 = 0.64,理想 CPI 1 会退化到 1.64。它说明少数控制指令足以吃掉大量流水收益。
6.4 分支与分支开销
6.4.1 分支方向区分 taken/not taken,循环末尾的向后分支往往多次 taken、最后一次 not taken。6.4.2 流水线中分支的影响取决于分支在哪一阶段解析;越晚知道方向和目标,错误路径越长。6.4.3 分支开销还包括无条件跳转、调用、返回、异常和中断带来的取指重定向。
6.4.4 延迟分支让分支后一条(或数条)指令无论是否跳转都执行,由编译器把有用工作填进延迟槽。它曾适合短而固定的流水线,但书中也指出约有相当比例只能填 NOP,且把实现细节暴露进 ISA。
【当前补充】现代深流水、乱序和多发射核不适合固定延迟槽;MIPS 新版本和 RISC-V 等设计不再以架构延迟槽作为主要方案。阅读旧汇编时仍必须尊重其语义,写新架构代码则不要模仿。
6.5 静态分支预测
静态预测可以总猜 taken/not taken、按方向猜、按操作码统计,或由编译器给出提示。循环回边“向后猜跳转、向前猜不跳转”通常比固定策略合理,但它无法适应运行时输入和相位变化。
预测正确时继续供给流水线;错误时清空投机路径并从正确 PC 重启。收益取决于:分支频率、预测准确率、错误代价和目标地址是否及时可用,不能只报一个准确率。
6.6 动态分支预测
动态预测用过去行为猜未来。单个位会在循环退出时翻转,下一次进入循环又错一次;两位饱和计数器需要连续两次相反结果才改变强预测方向,对偶发异常更稳。
6.6.1 BTB(Branch Target Buffer,分支目标缓冲)缓存分支 PC、目标地址和预测相关信息。方向预测器回答“跳不跳”,BTB 回答“跳到哪”;方向猜对但目标拿不到,前端仍可能停顿。返回地址栈还可专门预测函数返回。
6.6.2 两级分支预测用局部或全局历史索引模式表,捕捉“该分支最近行为”或“多个分支之间的相关性”。tournament predictor 再学习局部预测器和全局预测器哪个对当前分支更可靠。容量、别名冲突和访问时延同样重要,预测器并非越复杂越好。
【当前补充:性能机制也是安全边界】现代核会沿预测路径投机执行,再在结果错误时撤销架构状态;但 Cache 等微架构痕迹可能保留,形成 Spectre 一类侧信道。解决方案涉及软件屏障、索引掩码、编译器缓解和硬件设计,通常要付出性能成本。原书讨论的是性能,不包含这一 2018 年后成为焦点的安全维度。
本章到这里完成了闭环:第一章的“取指—执行”简单循环没有被否定,而是被实现成内部高度并行、对外仍保持顺序语义的处理器。
在今天搭建一个可复现的 AArch64 观察环境
下面是【当前补充】,目标不是复刻书中旧 ARMASM 截图,而是在 Windows 11 + WSL2 中编译、运行并反汇编现代 A64 程序。命令在 Ubuntu 24.04/22.04 的常见包名下成立;若使用其他发行版,请按其包管理器调整。
1. 安装 WSL 和 Ubuntu
以管理员 PowerShell 执行:
wsl --install -d Ubuntu按提示重启,首次启动 Ubuntu 时创建 Linux 用户。然后确认:
wsl --status
wsl -l -v2. 安装交叉工具链和用户态 QEMU
在 Ubuntu 终端执行:
sudo apt update
sudo apt install -y gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu qemu-user make gdb-multiarch核对工具可用:
aarch64-linux-gnu-gcc --version
qemu-aarch64 --version3. 编写最小 A64 Linux 程序
保存为 hello-a64.s:
.section .rodata
msg:
.ascii "hello, AArch64\n"
len = . - msg
.section .text
.global _start
_start:
mov x0, #1 // fd = stdout
adr x1, msg // buffer
mov x2, #len // byte count
mov x8, #64 // Linux AArch64 write
svc #0
mov x0, #0 // exit status
mov x8, #93 // Linux AArch64 exit
svc #0这里 x0~x2 传系统调用参数,x8 放系统调用号,svc 触发受控异常进入内核;它正好把第三章的寄存器/指令与第四章的特权入口连起来。
4. 汇编、链接、运行与反汇编
aarch64-linux-gnu-as -o hello-a64.o hello-a64.s
aarch64-linux-gnu-ld -o hello-a64 hello-a64.o
qemu-aarch64 ./hello-a64
aarch64-linux-gnu-objdump -d -s hello-a64期望看到 hello, AArch64,并在反汇编中看到固定 32 位 A64 指令。若出现 Exec format error,说明直接让 x86-64 内核执行了 AArch64 ELF,应通过 qemu-aarch64 运行;若链接器报告架构不匹配,检查是否误用了宿主 as/ld。
5. 用 C 观察编译器如何选择指令
保存为 sum.c:
#include <stddef.h>
long sum(const int *a, size_t n) {
long total = 0;
for (size_t i = 0; i < n; ++i) total += a[i];
return total;
}生成不同优化级别的汇编:
aarch64-linux-gnu-gcc -O0 -S -o sum-O0.s sum.c
aarch64-linux-gnu-gcc -O2 -S -o sum-O2.s sum.c
aarch64-linux-gnu-gcc -O3 -march=armv8-a+simd -S -o sum-O3.s sum.c
diff -u sum-O0.s sum-O2.s || true观察循环控制、load、地址递增和寄存器分配;再检查 -O3 是否向量化。不要仅以“指令更少”判断更快,应结合目标 CPU、数据规模和基准测试。
成书之后,主流体系结构怎样继续演进
| 原书重点 | 今天继续有效的思想 | 主要变化 |
|---|---|---|
| A32 ARM 教学 ISA | Load/Store、寄存器、条件码、寻址和 ABI | 现代通用 Arm 软件以 AArch64/A64 为主,寄存器、PC、条件执行和异常级别均变化 |
| MIPS 作为第二种 RISC | 固定字段、编译器友好、简单流水 | 教学与新设计中 RISC-V 更活跃,开放规范和扩展机制便于实现与研究 |
| MMX/SSE/NEON | 数据级并行、打包数据、掩码、饱和 | x86 发展到 AVX 系列,Arm 有 SVE/SVE2/SME,GPU/NPU 承担更多吞吐计算 |
| 延迟分支 | 控制相关必须被隐藏或付费 | 主流新 ISA 倾向动态预测与投机,不把固定流水深度暴露成延迟槽 |
| 两级/tournament 预测 | 局部与全局历史、选择器、BTB 仍是基础 | 现代预测器规模和算法更复杂,同时必须面对投机侧信道 |
| 单核性能主线 | 流水、局部性与并行仍关键 | 功耗墙推动多核、异构计算、chiplet 和专用加速器;数据移动常比算术更贵 |
RISC-V 值得作为本书之后的第三个 ISA 样本:它保留规整基础整数 ISA,用标准扩展提供乘除、原子、压缩、向量等功能。它的优势是规范开放、模块化和实现空间大;局限是扩展组合、软件成熟度和商业实现差异需要具体评估。它并不让 ARM/x86 的兼容生态、工具链和成熟微体系结构自动失去价值。
把六章串成一种分析机器的方法
遇到一项陌生处理器技术,可以沿本书的顺序提问:
- 语义层:它要表示什么数据、状态和控制行为?
- ISA 层:软件看到哪些寄存器、指令、地址与异常?
- 编码层:有限指令位怎样分配,牺牲了什么范围或密度?
- 组成层:数据通路和控制器怎样兑现语义?
- 性能层:能否重叠、向量化或预测?瓶颈是依赖、分支、内存还是功耗?
- 系统层:编译器、ABI、操作系统和兼容历史能否真正利用它?
这比背“RISC 指令少、CISC 指令多”更接近工程现实。全书最重要的能力,是把一个软件可见现象沿抽象边界一直追到硬件约束,再从成本和边界条件判断设计是否值得。
来源与进一步核验
原书依据
- Alan Clements,Computer Organization and Architecture: Themes and Variations,Cengage Learning,2014。
- 艾伦·克莱门茨著,沈立、王苏峰、肖晓强译,《计算机组成原理》,机械工业出版社,2017 年 3 月第 1 版,ISBN 978-7-111-55807-1。本文的章节顺序、原书观点、短引文、公式与示例均以该 PDF 的版权页、译者序、前言、目录和六章正文为主。
- 本文中的汇编与 C 片段分为“依据书中思路整理”和“当前补充”;为可读性修正了扫描 OCR 的空格、字母与助记符错误,没有把 OCR 文本当作逐字引文。
当前补充的一手资料(核验于 2026-08-10)
- Arm A-profile architecture:A64/AArch64、A32/T32 与架构演进入口。
- Arm A64 Instruction Set Architecture:现代 A64 指令语义。
- Arm C Language Extensions:NEON/SVE 等编程接口与特性检测。
- Intel Intrinsics Guide:SSE、AVX 与 AVX-512 intrinsic 语义和目标特性。
- RISC-V Unprivileged ISA Specification:基础 ISA 与压缩、向量等标准扩展。
- IEEE 754-2019:现行浮点算术标准页面。
- QEMU User Mode Emulation:跨架构运行 Linux 用户态程序。
- Spectre Attacks:投机执行侧信道的原始论文与机制说明。
书中个别 URL、工具截图和处理器市场数据已经过时;本文保留其历史论证,但没有沿用为今天的安装或选型结论。
