指令集种类

指令体系结构

中优先级
指令体系结构 偶尔在选择题中考查,要理解 ISA 的语义边界,和微架构的区别。

指令体系结构(Instruction Set Architecture,ISA)是处理器对软件所提供的抽象机器规范,也是软件与硬件之间的接口

ISA 规定的是:

软件能够观察到的处理器行为,以及软件与处理器交互时必须遵守的规则。

例如,ISA 会规定处理器有哪些指令、有哪些软件可见的寄存器、指令如何编码、操作数如何指定、访存和异常等行为具有什么语义。编译器、操作系统以及其他系统软件可以依据 ISA 生成和执行程序,而不需要了解处理器内部具体采用什么实现方式。

ISA 主要规定什么?

  1. 指令集及其语义 规定处理器支持哪些指令,以及每条指令执行后应该产生什么可观察的结果,例如算术运算、逻辑运算、跳转、加载和存储等。

  2. 指令格式与编码 规定指令如何编码,包括操作码、操作数等字段的组织方式。

  3. 软件可见的寄存器 规定有哪些寄存器、寄存器的功能以及程序如何访问它们。例如通用寄存器、程序计数器以及状态/控制寄存器等。

  4. 操作数与寻址方式 规定指令如何指定操作数,例如立即数、寄存器、内存地址等。

  5. 数据类型及其表示 规定处理器指令所支持的数据类型及其相关操作,例如整数、浮点数以及向量数据等。

  6. 内存访问与内存模型 规定处理器访问内存时的软件可观察行为,例如内存访问的规则、字节序、原子操作以及多处理器环境下的内存一致性/顺序语义等。

  7. 异常、中断以及特权机制 ISA 通常会规定异常和中断相关的机器状态变化,以及特权级、系统寄存器等处理器机制,使操作系统能够控制和管理处理器。

ISA 最重要的边界:规定“做什么”,不规定“怎么做”

ISA 是一种抽象接口

例如,ISA 可以规定:

执行一条 ADD 指令后,目标寄存器的值应该等于两个操作数之和。

但是它通常不规定处理器内部究竟:

  • 使用多少级流水线;
  • 是否采用乱序执行;
  • 是否进行寄存器重命名;
  • 有多少个 ALU;
  • Cache 有多大;
  • 分支预测器采用什么算法;
  • 指令经过哪些内部执行阶段。

这些属于 微架构(Microarchitecture)

因此可以记住:

ISA 规定“机器应该表现成什么样”;微架构规定“这个机器具体怎么实现”。

同一个 ISA 可以由完全不同的微架构实现。例如不同厂商的处理器可以实现相同的 x86-64 ISA,但内部的流水线、Cache、执行单元、分支预测和乱序执行机制可以完全不同。只要对软件呈现出符合 ISA 规定的行为,它们就可以运行相同的 ISA 程序。

ISA、操作系统和应用程序的关系

可以从抽象层次理解:

ISA、操作系统与应用程序的抽象层次关系从上到下依次为应用程序、操作系统、ISA、微架构、数字逻辑/电路五层,箭头表示层层依赖与接口关系,ISA 是操作系统与硬件之间的统一机器接口。应用程序用户程序、库、运行时操作系统进程、虚拟内存、文件系统、I/O 抽象ISA(指令集架构)统一机器接口:指令、特权机制、异常/中断微架构流水线、乱序执行、缓存、分支预测数字逻辑 / 电路门电路、触发器、时序逻辑

ISA 为操作系统提供了一个统一的机器接口。操作系统利用 ISA 提供的指令、特权机制、异常/中断机制等管理处理器和硬件,并进一步向应用程序提供进程、虚拟内存、文件系统、I/O 等更高层次的抽象。

复杂和精简指令集

💡 低优先级
复杂和精简指令集也是在选择题偶尔考查,了解两者的差异,看到选项能选出来就行。

CISC (Complex Instruction Set Computer)和 RISC (Reduced Instruction Set Computer)是两种不同的计算机体系结构设计哲学,它们在 指令集架构执行方式 上有显著的差异。

csic 和 risc 处理器

CISC

  1. 指令集复杂 :CISC 指令集包含大量 复杂的指令,其中一条指令可以执行多种操作,包括 内存访问算术运算逻辑运算 等。
  2. 指令不定长 :CISC 支持多种长度的指令。
  3. 多寻址模式 :CISC 指令通常支持多种 寻址模式,允许直接访问内存,因此可以在一条指令中执行复杂的操作。
  4. 微程序控制 :CISC 计算机通常使用 微程序控制单元,指令解码和执行过程相对复杂。
  5. 复杂硬件 :CISC 处理器通常包括大量的硬件单元,用于支持 复杂的指令集,这使得 CISC 芯片相对较大。

CISC 的典型芯片就是 x86 系列,如 Intel 的 Core i 系列处理器和 AMD 的 Ryzen 系列处理器。

RISC

  1. 指令集精简 :RISC 计算机的指令集更加精简,通常包含较少、更 简单的指令。每条指令只执行一种操作。
  2. 指令定长 :RISC 指令集中所有指令长度相同。
  3. 固定寻址模式 :RISC 指令通常只支持一种或者很少种 寻址模式,鼓励将数据加载到寄存器中后再执行操作。
  4. 硬布线控制 :RISC 计算机使用 硬布线控制单元,指令解码和执行过程较为简单。
  5. 精简硬件 :RISC 处理器通常采用更 精简的硬件,以提高 性能 和降低 成本

RISC 的典型芯片就是 arm 系列,比如苹果的 A 系列和 M 系列处理器。


ax
bx
cx
dx
si
di
sp
bp
cs
ds
es
fs
gs
ss
x0
x1
x2
x3
x8
x9
x10
x11
x16
x17
x18
x19
x24
x25
x26
x27
x4
x5
x6
x7
x12
x13
x14
x15
x20
x21
x22
x23
x28
x29
x30
x31
ADD  4(x2), x3
LW  x4, 4(x2)
ADD  x3, x4, x3
SW  x3, 4(x2)
寄存器数量
指令的内存访问模式
mem[x2+4] ← mem[x2+4] + x3
内存访问模式复杂,可以在
一条指令中对内存读和写
内存访问模式简单,只能在一条指令中
对内存进行 load 或 store
寄存器数量较多
寄存器数量较少
复杂指令集
精简指令集
x86 16位 cpu 8086
的寄存器
RISC
CSIC
x4 ← mem[x2+4]
x3 ← x4 + x3      
x3 → mem[x2+4]

上图体现了 CISCRISC 的差别:RISC 寄存器数量比 CISC 更多,CISC 的访存指令比 RISC 更加复杂(CISC 单条指令完成的工作 RISC 需要多条指令才能完成)。

程序示例

这一节举个实际的例子说明一下 CISCRISC 上的汇编代码区别。

for (i = 0; i < 24; i++)
    for (j = 0; j < 24; j++)
        ...
        a[i][j] = 10;
        ...

对于上述的循环代码段,编译器将其编译为如下的 intel x86 汇编代码(CISC)和 arm mips 汇编代码(RISC):

同一段 C 代码在 CISC (x86) 与 RISC (MIPS) 上的指令对照C 源代码for (i = 0; i < 24; i++)for (j = 0; j < 24; j++)a[i][j] = 10;CISC · Intel x86单条指令可访存 + 复杂寻址mov dword ptr [ebp-8], 0; i = 0jmp loop_chk_imov eax, [ebp-8]; load icmp eax, 24jge end_imov dword ptr [ebp-4], 0; j = 0mov dword ptr [ecx+edx*4+0x422000], 10; 一条指令完成寻址+赋值inc dword ptr [ebp-4]; j++inc dword ptr [ebp-8]; i++特点:复杂寻址模式、变长指令、单指令多步操作RISC · MIPSload/store 架构,寻址需多条指令addi $v0, $zero, 0; i = 0j loop_chk_ilw $v1, 8($fp); load ibge $v1, 24, end_iaddi $a0, $zero, 0; j = 0sll $v1, $v1, 6; i * 64add $v1, $v1, $a0; + jsll $v1, $v1, 2; * 4lui $at, 0x0042; 高 16 位基址ori $v0, $at, 0x2000; 拼出完整地址add $v0, $v1, $v0li $a1, 10 / sw $a1, 0($v0)

下面给出原始的汇编代码片段以供对照:

for (i = 0; i < 24; i++)
1  00401072  C7 45 F8 00 00 00 00      mov     dword ptr [ebp-8], 0   ; i = 0,初始化 i 变量
2  00401079  EB 09                     jmp     00401084h              ; 跳转到循环条件检查
3  0040107B  8B 55 F8                  mov     eax, [ebp-8]           ; 读取 i 的值到 eax
    ... ... ...
7  00401088  7D 32                     jge     004010BCh              ; 若 i >= 24,跳出循环

    for (j = 0; j < 64; j++)
8  0040108A  C7 45 FC 00 00 00 00      mov     dword ptr [ebp-4], 0   ; j = 0,初始化 j 变量
        ... ... ...
        a[i][j] = 10;
        ... ... ...
19 004010AE  C7 84 82 00 20 42 00 0A 00 00 00
                                       mov     dword ptr [ecx+edx*4+00422000h], 0Ah
                                                                      ; 存储 10 到 a[i][j],计算地址:
                                                                      ; ecx = i, edx = j
                                                                      ; a[i][j] = 10
20 ... ...
for (i = 0; i < 24; i++)
1  00401000  20020000      addi  $v0, $zero, 0       # i = 0
2  00401004  08004004      j     0x00401010          # 跳转到循环条件检查
3  00401008  8C430008      lw    $v1, 8($fp)         # 读取 i
    ... ... ...
7  00401010  1C600018      bge   $v1, 24, 0x00401040 # if (i >= 24) 跳出循环
    for (j = 0; j < 64; j++)
8  00401014  20040000      addi  $a0, $zero, 0       # j = 0
        ... ... ...
        a[i][j] = 10;
17 00401028  00031880      sll   $v1, $v1, 6         # i × 64
18 0040102C  00641820      add   $v1, $v1, $a0       # i × 64 + j
19 00401030  00031880      sll   $v1, $v1, 2         # (i × 64 + j) × 4
20 00401034  3C010042      lui   $at, 0x0042         # 加载基地址高 16 位
21 00401038  34222000      ori   $v0, $at, 0x2000    # 完整基地址 0x00422000
22 0040103C  00621020      add   $v0, $v1, $v0       # 计算目标地址
23 00401040  2405000A      li    $a1, 10             # 10 存入 $a1
24 00401044  AC450000      sw    $a1, 0($v0)         # a[i][j] = 10
25 ... ...

对比

CISCRISC 的主要区别如下表所示:

特性CISCRISC
指令集复杂,指令数量多精简,指令数量少
指令长度不定长固定长度
寻址模式多种寻址模式较少寻址模式
控制方式微程序控制硬布线控制
硬件复杂度复杂精简
优点功能强大,一条指令可完成复杂操作性能高,功耗低
缺点硬件复杂,指令执行效率相对较低功能相对简单,复杂操作需要多条指令完成

不同位数指令

AH
AL
AX
(16 bits)
EAX
(32 bits)
RAX
(64 bits)

8086(16 位 intel 处理器)再到后来的 32 位 以及 64 位 的 CPU,差异主要体现在以下几个方面:

  1. 寄存器位数命名 不同:32 位寄存器有 E 前缀,64 位寄存器有 R 前缀。
  2. 指令集 不同:32 位处理器相比 16 位处理器有着更加复杂的指令集,64 位处理器的指令集也比 32 位更加复杂。

下表中给出了不同位数寄存器的对比,注意每个寄存器的开头都与一个 单次 相对应,在最初 8086 的设计中这是代表一种语义。

寄存器16 位32 位64 位类型
AccumulatorAXEAXRAXGeneral
BaseBXEBXRBXGeneral
CounterCXECXRCXGeneral
DataDXEDXRDXGeneral
Source IndexSIESIRSIPointer
Destination IndexDIEDIRDIPointer
Base PointerBPEBPRBPPointer
Stack PointerSPESPRSPPointer
Instruction PointerIPEIPRIPPointer
Code SegmentCS--Segment
Data SegmentDS--Segment

AT&T 与 Intel 汇编格式

在 x86 汇编语言中,常见的语法格式有 Intel 格式AT&T 格式。二者使用的是相同的 x86 指令,只是在操作数顺序、寄存器表示和内存寻址写法等方面有所不同。

Intelmoveax,ebx; 目的 ← 源AT&Tmovl%ebx,%eax; 源 → 目的特性Intel 格式AT&T 格式操作数顺序目的, 源mov eax, ebx源, 目的movl %ebx, %eax寄存器eax ebx ecx直接写寄存器名%eax %ebx %ecx前缀%立即数10 0x20直接写数值$10 $0x20前缀$内存操作数[ebx + ecx*4 + 8]方括号 [ ]8(%ebx, %ecx, 4)圆括号 ( ),偏移量前置操作数长度由寄存器名决定eax→32位, ax→16位指令后缀 b/w/l/qmovb / movw / movl / movq注释符号; 分号# 井号内存寻址格式对照Intel[基址+变址×比例+偏移][ebx+ecx×4+8]EA = (EBX) + (ECX) ×4+8两种格式计算结果相同,仅写法不同AT&T偏移(基址,变址,比例)8(%ebx,%ecx,4)

408 考试通常采用较接近 Intel 格式的表示方法,但也应能够辨认 AT&T 格式,尤其需要注意两者的 源操作数和目的操作数顺序相反

Intel 格式


# 寄存器访问
mov     eax,1
mov     ebx,0ffh
# 内存访问
mov     eax,[ebx]
mov     eax,[ebx+3]

AT&T 格式


# 寄存器访问
movl    $1,%eax
movl    $0xff,%ebx
# 内存访问
movl    (%ebx),%eax
movl    3(%ebx),%eax

例如,下面两条指令含义相同,都是将 ebx 的内容送入 eax

mov eax, ebx        // Intel:目的操作数,源操作数
movl %ebx, %eax     # AT&T:源操作数,目的操作数
特性Intel 格式AT&T 格式
操作数顺序目的操作数,源操作数源操作数,目的操作数
寄存器eaxebx%eax%ebx
立即数100x20$10$0x20
内存操作数使用方括号 []使用圆括号 ()
操作数长度通常根据寄存器或声明判断常在指令后添加长度后缀

操作数长度后缀

AT&T 格式通常在指令助记符后添加后缀,表示操作数的长度:

后缀长度示例
b8 位,bytemovb
w16 位,wordmovw
l32 位,longmovl
q64 位,quad wordmovq

例如:

movl $1, %eax

其中 l 表示本条指令操作的是 32 位数据

内存寻址格式

Intel 格式的常见 寻址方式 为:

[基址寄存器 + 变址寄存器 × 比例因子 + 偏移量]

AT&T 格式对应写为:

偏移量 (基址寄存器,变址寄存器,比例因子)

例如:

mov eax, [ebx + ecx * 4 + 8]     // Intel
movl 8(%ebx, %ecx, 4), %eax      # AT&T

二者均表示从下面的内存地址读取一个 32 位数据,并送入 eax

其中 (EA) 表示有效地址。

注意

考试辨认重点

遇到 AT&T 格式指令时,可以按照以下顺序判断:

  1. 看操作数顺序:左边是源操作数,右边是目的操作数
  2. 看到 %,说明是寄存器。
  3. 看到 $,说明是立即数。
  4. 根据 b、w、l、q 判断操作数长度。