第 2 章 指令:计算机的语言 (Instructions: Language of the Computer)
核心结论
-
存储程序概念:指令和数据都以数字形式存储在内存中,可被读 / 写 / 修改——这是计算机区别于"硬连线计算器"的核心。
-
LEGv8 操作数:32 个寄存器
X0–X30 + XZR+2³¹内存字;数据必须在寄存器中才能算术运算。 -
二进制补码(§2.4):负数用补码表示,
-x = ~x + 1;无符号数与有符号数共享位模式,靠指令解释区分。 -
指令格式(§2.5):LEGv8 三种格式——R(寄存器-寄存器)、I(立即数)、D(数据传送);每种长度固定 32 位。
-
决策与循环(§2.7):条件分支
B.cond、比较CMP / CMPI;B无条件跳转、BL函数调用。 -
过程调用(§2.8):栈帧保存现场;
X29是 FP(frame pointer)、X30是 LR(link register)。 -
同步原语(§2.11):
LDXR / STXR实现原子读-改-写,是锁和无锁数据结构的基础。
|
本章主旨
本章用 LEGv8(ARM 简化版教学 ISA)介绍指令集——硬件 / 软件接口的核心。理解 ISA 才能理解编译器如何把高级语言翻译为机器指令、CPU 如何执行每条指令、操作系统如何管理进程与线程。后续章节在 ISA 之上展开算术(第 3 章)、CPU 微架构(第 4 章)、内存层级(第 5 章)、并行(第 6 章)。 |
一、核心概念
本章围绕 7 个核心概念展开:从存储程序概念出发,介绍 LEGv8 操作数与有符号 / 无符号数表示,再深入指令格式、决策循环、过程调用,最后给同步原语。
| 概念 | 定义 + 重要性 | 实现提示 |
|---|---|---|
存储程序概念 |
指令与数据都以数字形式存储在内存中;可读、可写、可执行——这是现代计算机的基础。 |
§2.1;1945 年 von Neumann 架构的核心;允许自我修改代码(现代不用了)。 |
LEGv8 操作数 |
32 个 64-bit 寄存器 |
§2.3;算术指令必须用寄存器; |
有符号 / 无符号数 |
二进制补码表示负数;MSB 是符号位(0 正 1 负);无符号与有符号共享位模式,靠指令解释。 |
§2.4; |
指令格式 |
LEGv8 三种格式:R(寄存器-寄存器算术)、I(立即数)、D(数据传送);都 32 位长。 |
§2.5;指令编码 = opcode + 操作数;编译器 / 反汇编器都依赖此编码。 |
决策与循环 |
条件分支 |
§2.7;while / for / if-else 编译为 条件分支 + 跳转。 |
过程调用 |
栈帧保存调用者寄存器( |
§2.8;递归 / 嵌套调用必须正确管理栈帧,否则返回地址 / 寄存器被破坏。 |
同步原语 |
|
§2.11;硬件原子性是操作系统 / 并发编程的根基。 |
二、详细笔记
2.1 存储程序概念 (Stored-Program Concept)
What:指令与数据都以数字形式存储在内存中;内存既可读 / 写数据,又可读 / 写指令。
Why:这是现代计算机区别于早期"硬连线计算器"的核心——程序可被修改、传输、共享。
How:
-
1945 年 von Neumann 架构提出。
-
内存地址统一编址指令与数据。
-
CPU 的 PC(program counter)指向下一条要执行的指令。
|
存储程序的工程意义
|
When:所有现代计算机的基础;OS 用此概念加载 / 运行程序。
Example:从磁盘加载 a.out 到内存 → CPU 从 _start 开始执行 → 跳转到 main → 执行用户代码。
2.2 LEGv8 操作数 (LEGv8 Operands)
What:算术指令的操作数来自 32 个 64-bit 寄存器;数据传送指令访问内存。
Why:寄存器是 CPU 内部最快的存储;所有算术必须先把数据加载到寄存器。
How:
32 个寄存器(§2.3):
-
X0–X7:参数 / 返回值(a0-a7 的 64-bit 形式)。 -
X8:间接结果寄存器(系统调用)。 -
X9–X15:临时寄存器(调用者保存)。 -
X16–X17:链接 / 临时(IP0 / IP1)。 -
X18:平台寄存器(保留)。 -
X19–X28:被调用者保存。 -
X29:帧指针 FP。 -
X30:链接寄存器 LR。 -
XZR:零寄存器(恒为 0)。
内存字寻址(§2.3.2):
-
按字节寻址;64-bit 字 8 字节对齐。
-
2³¹个 64-bit 字 = 16 GB 内存空间。
|
寄存器 vs 内存的工程权衡
|
When:写汇编;编译器优化(寄存器分配 / 内联);性能调优(寄存器溢出 vs 缓存未命中)。
Example:ADD X1, X2, X3 = X2 + X3 → X1(寄存器-寄存器操作,1 个周期)。
2.3 二进制补码 (Two’s Complement)
What:负数用补码表示,-x = ~x + 1(位翻转 + 1);MSB 是符号位(0 正 1 负)。
Why:补码让加减法运算统一(不需要特殊处理符号位);硬件实现简单。
How:
表示范围(§2.4):
-
64-bit 有符号:
[-2⁶³, 2⁶³ - 1]。 -
64-bit 无符号:
[0, 2⁶⁴ - 1]。 -
共享位模式,靠指令区分(
LDURSW= 有符号字,LDURW= 无符号字)。
补码算术:
硬件只做加法;减法 = 加负数;负数 = 补码。
|
补码的边界条件
|
When:写汇编 / 系统编程;debug 溢出 bug;选择有符号 vs 无符号类型(C int vs unsigned int)。
Example:-1 的 64-bit 补码 = 0xFFFFFFFFFFFFFFFF;加 1 得 0x0000000000000000 = 0。
2.4 指令格式 (Instruction Formats)
What:LEGv8 三种指令格式——R / I / D;每种长度固定 32 位。
Why:理解指令编码才能看懂反汇编 / 调试汇编 / 优化指令混合。
How:
三种格式(§2.5):
-
R-format:寄存器-寄存器算术 / 逻辑。
opcode (11) | Rm (5) | shamt (6) | Rn (5) | Rd (5) -
I-format:立即数算术 / 逻辑 / 数据传送(偏移)、分支。
opcode (10) | ALU_immediate (12) | Rn (5) | Rd (5) -
D-format:数据传送(load / store)。
opcode (11) | DT_address (9) | op (2) | Rn (5) | Rt (5)
各字段含义:
-
opcode:指令类型(ADD / SUB / LDUR / …)。
-
Rn / Rm / Rd:源 / 目标寄存器。
-
shamt:移位量(仅 R-format)。
-
ALU_immediate / DT_address:12-bit 立即数(I / D-format)。
|
指令格式的工程权衡
|
When:写汇编;理解反汇编;优化指令密度;ISA 扩展(ARMv8.5 SVE)。
Example:ADD X1, X2, X3 编码为 R-format;ADDI X1, X2, #20 编码为 I-format。
2.5 决策与循环 (Decision & Loop)
What:条件分支 + 跳转实现 if-else / while / for;比较指令设置条件码。
Why:所有控制流编译为分支指令;理解分支指令才能优化条件跳转。
How:
条件分支(§2.7):
-
B.cond(条件分支):cond是 EQ / NE / LT / LE / GT / GE / LO / LS / HI / HS。 -
比较指令:
CMP X1, X2=SUBS XZR, X1, X2(计算 X1 - X2 设置条件码)。 -
CMPI X1, #imm:与立即数比较。
循环模式:
-
while:判断 + 跳转回循环顶。 -
for:初始化 + 判断 + 增量 + 跳转回。 -
if-else:判断 + 条件跳转 + else 分支。
|
分支指令的"分支延迟"
|
When:写汇编;debug 分支 bug;性能优化(减少分支 / 用 SIMD)。
Example:
CMP X1, X0 // X1 - X0
B.LT loop_top // if X1 < X0, jump to loop_top
2.6 过程调用 (Procedure Call)
What:过程调用需要保存 / 恢复调用者寄存器;栈帧用于管理局部变量与寄存器。
Why:递归 / 嵌套调用必须正确管理栈帧;寄存器分配是编译器关键优化。
How:
调用约定(§2.8):
-
调用者保存(caller-saved):
X0–X18——被调用者可以自由修改。 -
被调用者保存(callee-saved):
X19–X28、X29 FP、X30 LR——被调用者必须保留。 -
参数 / 返回值:
X0–X7(参数与返回值)。 -
栈帧:
[FP]= 保存的旧 FP;[FP, #8]= 保存的旧 LR;[FP, #16]= 局部变量。
栈帧布局:
低地址 (栈顶增长方向)
[X29] ← saved X29 (FP)
[X29, #8] ← saved X30 (LR)
[X29, #16] ← local variables
[X29, #24] ← saved callee-saved regs
高地址
|
栈帧调试
|
When:写汇编;debug 段错误;理解编译器栈布局;实现协程 / 异常处理。
Example:BL func 调用 func,LR = PC + 4;func 内部保存 X19–X28、建立栈帧、结束时恢复、RET(= BR X30)。
2.7 同步原语 (Synchronization Primitives)
What:LDXR / STXR 实现原子读-改-写;是多线程同步的硬件基础。
Why:没有原子性,并发程序的"读-改-写"会被其他线程打断,导致数据竞争。
How:
原子交换(§2.11):
LDXR X1, [X2] // 原子读 X2 指向的内存到 X1
STXR X3, X1, [X2] // 尝试写 X1 回 X2;X3 = 0(成功)或 1(失败)
-
LDXR:独占读,标记内存地址为"被独占"。
-
STXR:独占写;只有当地址仍独占时成功,否则失败(X3 = 1)。
-
失败时需重试,构成 CAS(Compare-And-Swap)模式。
|
原子性 vs 锁
|
When:写并发程序(线程同步、锁、无锁队列);debug 数据竞争;实现 OS 内核同步原语。
Example:锁的实现:while (LDXR X1, [lock]; X1 != 0) retry; STXR X3, X1, [lock];。
三、关键图表
视觉图表
非可视化条目
|
非可视化条目(表 / 算法)
|
核心公式对照表
|
核心公式对照表
|
四、思维导图
mindmap
root((第 2 章 指令:计算机的语言))
存储程序
指令也是数字
vonNeumann
程序可修改
LEGv8操作数
32寄存器
内存字节寻址
XZR零寄存器
补码
负数表示
溢出边界
有符号vs无符号
指令格式
RID三格式
32位固定
字段含义
决策循环
Bcond
CMP
ifwhilfor
过程调用
栈帧
calleesaved
X29FPX30LR
同步
LDXRSTXR
原子读写
CAS模式
五、重点与易错点
-
存储程序概念:指令和数据同等对待——程序可被复制 / 修改 / 共享;这是现代计算机的核心。
-
XZR 是硬件零寄存器:用于清零、构造常量、加载基址;比
MOV X0, #0高效。 -
二进制补码溢出:
INT64_MIN的相反数无法表示(有符号);用unsigned比较避免溢出。 -
有符号 vs 无符号比较:
B.LT(有符号)与B.LO(无符号)不同;选错会出现255 > -1这种"反直觉"结果。 -
R / I / D 三种指令格式对应不同操作:算术多用 R-format、立即数用 I-format、内存访问用 D-format。
-
过程调用约定:X0–X7 是参数 / 返回值,X19–X28 是 callee-saved,X29 是 FP,X30 是 LR。混用会导致程序崩溃。
-
栈破坏是常见 bug:数组越界写入可覆盖返回地址("栈溢出攻击");用
-fstack-protector检测。 -
LDXR/STXR 是硬件原子性:是 OS 同步原语(mutex / 信号量)的基础;没有它并发编程无法实现。
-
LEGv8 vs ARMv7/v8:LEGv8 是教学简化版;ARMv8 是商业版(含 SIMD、加密等扩展);x86 用 CISC 编码(可变长度)。
-
跨章衔接:第 3 章在 LEGv8 整数指令上扩展浮点;第 4 章讲 CPU 如何执行每条 LEGv8 指令;第 5 章讲内存层级;第 6 章讲多线程与同步。
-