第 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 / CMPIB 无条件跳转、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 寄存器 X0–X30 + XZR(恒为 0);内存按字节寻址,64-bit 字对齐。

      §2.3;算术指令必须用寄存器;XZR 用于清零与常量。

      有符号 / 无符号数

      二进制补码表示负数;MSB 是符号位(0 正 1 负);无符号与有符号共享位模式,靠指令解释。

      §2.4;LDURSW / LDURB 等指令决定位模式被解释为有符号还是无符号。

      指令格式

      LEGv8 三种格式:R(寄存器-寄存器算术)、I(立即数)、D(数据传送);都 32 位长。

      §2.5;指令编码 = opcode + 操作数;编译器 / 反汇编器都依赖此编码。

      决策与循环

      条件分支 B.cond、无条件 BBL 函数调用;比较指令 CMP / CMPI / TST

      §2.7;while / for / if-else 编译为 条件分支 + 跳转

      过程调用

      栈帧保存调用者寄存器(X0–X7 是参数 / 返回值,X19–X28 是 callee-saved);X29 FP、X30 LR。

      §2.8;递归 / 嵌套调用必须正确管理栈帧,否则返回地址 / 寄存器被破坏。

      同步原语

      LDXR / STXR 实现原子读-改-写;是多线程同步、锁、无锁数据结构的基础。

      §2.11;硬件原子性是操作系统 / 并发编程的根基。

      二、详细笔记

      2.1 存储程序概念 (Stored-Program Concept)

      What:指令与数据都以数字形式存储在内存中;内存既可读 / 写数据,又可读 / 写指令。

      Why:这是现代计算机区别于早期"硬连线计算器"的核心——程序可被修改、传输、共享。

      How

      • 1945 年 von Neumann 架构提出。

      • 内存地址统一编址指令与数据。

      • CPU 的 PC(program counter)指向下一条要执行的指令。

      存储程序的工程意义
      • 软件 = 数据 + 指令,可被复制 / 修改 / 共享。

      • 同一硬件可运行不同程序(PC 启动 vs 游戏 vs 浏览器)。

      • 现代 OS 用"代码段只读 / 写时复制"防止自我修改(防止恶意软件)。

      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 内存的工程权衡
      • 寄存器:访问 1 个周期(最快)、数量少(32 个)。

      • 内存:访问 100+ 周期(受缓存命中影响)、容量大(GB 级)。

      • 编译器优化关键:减少内存访问、最大化寄存器使用(寄存器分配)。

      When:写汇编;编译器优化(寄存器分配 / 内联);性能调优(寄存器溢出 vs 缓存未命中)。

      ExampleADD 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 = 无符号字)。

      补码算术:

      \[\text{ADD} : a + b \pmod{2^{64}} \end{bmatrix}\]

      硬件只做加法;减法 = 加负数;负数 = 补码。

      补码的边界条件
      • INT64_MIN = -2⁶³,其相反数 2⁶³ 超出有符号范围——溢出!

      • 比较指令区分 B.LT(有符号)与 B.LO(无符号)——避免溢出错误。

      • 数组下标用无符号(有符号下标可能为负);温度 / 速度等物理量用有符号。

      When:写汇编 / 系统编程;debug 溢出 bug;选择有符号 vs 无符号类型(C int vs unsigned int)。

      Example-1 的 64-bit 补码 = 0xFFFFFFFFFFFFFFFF;加 10x0000000000000000 = 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)。

      指令格式的工程权衡
      • 固定 32 位长度:硬件解码简单(无需可变长度)。

      • 字段安排反映常用操作(R-format 强调 3 寄存器;D-format 强调 1 寄存器 + 内存地址)。

      • 立即数 12 位 = 4 KB 偏移;超过此范围需用 MOVZ/MOVK 构造 16/32/48-bit 常量。

      When:写汇编;理解反汇编;优化指令密度;ISA 扩展(ARMv8.5 SVE)。

      ExampleADD 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 分支。

      分支指令的"分支延迟"
      • 早期 CPU:分支需等条件码就绪(约 5-10 个周期)。

      • 现代 CPU:分支预测(§4 章深入)+ 乱序执行——大部分分支"零开销"。

      • 错误预测 仍有 10-20 周期代价;尽量减少分支、用 CMOV 替代。

      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–X28X29 FPX30 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
      高地址
      栈帧调试
      • 栈破坏是 C 程序崩溃的常见原因(数组越界写入)。

      • backtrace / gdb 命令显示栈帧链。

      • -fstack-protector 编译选项可检测栈破坏。

      When:写汇编;debug 段错误;理解编译器栈布局;实现协程 / 异常处理。

      ExampleBL func 调用 func,LR = PC + 4;func 内部保存 X19–X28、建立栈帧、结束时恢复、RET(= BR X30)。

      2.7 同步原语 (Synchronization Primitives)

      WhatLDXR / 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 锁
      • 硬件原子性(LDXR/STXR):单条指令内不可中断。

      • 锁(mutex):操作系统提供,但底层也用原子指令。

      • 无锁数据结构:用 CAS 实现,无需锁但代码复杂。

      When:写并发程序(线程同步、锁、无锁队列);debug 数据竞争;实现 OS 内核同步原语。

      Example:锁的实现:while (LDXR X1, [lock]; X1 != 0) retry; STXR X3, X1, [lock];

      三、关键图表

      视觉图表

      图 2-1
      Figure 1. 图 2-1:LEGv8 指令集概览
      图 2-2
      Figure 2. 图 2-2:32 个寄存器布局
      图 2-3
      Figure 3. 图 2-3:LEGv8 指令格式(R / I / D)
      图 2-4
      Figure 4. 图 2-4:二进制补码转换
      图 2-5
      Figure 5. 图 2-5:栈帧布局
      图 2-6
      Figure 6. 图 2-6:while 循环编译为分支指令

      非可视化条目

      非可视化条目(表 / 算法)
      编号 内容摘要

      表 2.1

      LEGv8 寄存器约定(参数 / callee-saved / 特殊用途)。

      表 2.2

      LEGv8 指令集(算术 / 数据传送 / 逻辑 / 分支 / 同步)。

      式 2-1 至 2-5

      二进制补码运算、有符号 / 无符号转换。

      列表 2.1-2.x

      C 代码 → LEGv8 汇编示例(factorial / 数组求和 / 排序)。

      表 2.3

      LEGv8 / ARMv7 / MIPS / x86 指令对比(编码 / 寄存器数 / 寻址模式)。

      核心公式对照表

      核心公式对照表
      概念 公式

      二进制补码

      \(-x = \sim x + 1\)

      有符号 64-bit 范围

      \(-2^{63}, 2^{63} - 1\)

      无符号 64-bit 范围

      \(0, 2^{64} - 1\)

      R-format 字段

      latexmath:[\text{opcode (11)}

      Rm (5)

      shamt (6)

      Rn (5)

      Rd (5)]

      I-format 字段

      latexmath:[\text{opcode (10)}

      \text{immediate (12)}

      Rn (5)

      Rd (5)]

      D-format 字段

      latexmath:[\text{opcode (11)}

      \text{address (9)}

      op (2)

      Rn (5)

      四、思维导图

      mindmap
        root((第 2 章 指令:计算机的语言))
          存储程序
            指令也是数字
            vonNeumann
            程序可修改
          LEGv8操作数
            32寄存器
            内存字节寻址
            XZR零寄存器
          补码
            负数表示
            溢出边界
            有符号vs无符号
          指令格式
            RID三格式
            32位固定
            字段含义
          决策循环
            Bcond
            CMP
            ifwhilfor
          过程调用
            栈帧
            calleesaved
            X29FPX30LR
          同步
            LDXRSTXR
            原子读写
            CAS模式

      五、重点与易错点

      1. 存储程序概念:指令和数据同等对待——程序可被复制 / 修改 / 共享;这是现代计算机的核心。

      2. XZR 是硬件零寄存器:用于清零、构造常量、加载基址;比 MOV X0, #0 高效。

      3. 二进制补码溢出INT64_MIN 的相反数无法表示(有符号);用 unsigned 比较避免溢出。

      4. 有符号 vs 无符号比较B.LT(有符号)与 B.LO(无符号)不同;选错会出现 255 > -1 这种"反直觉"结果。

      5. 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 章讲多线程与同步。