指令系统

指令集体系结构

机器指令(简称指令)是指示计算机执行某种操作的命令。

一台计算机的所有指令的集合构成该机的指令系统(指令集)

指令系统是指令集体系结构(ISA, Instruction Set Architecture)中最核心的部分,ISA 完整定义了软件和硬件之间的接口,是机器语言或汇编语言程序员所应熟悉的。

ISA 规定的内容主要包括:

  1. 指令的格式,寻址方式,操作类型,以及每种操作对应的操作数的相应规定。
  2. 操作数的类型,操作数寻址方式,以及大/小端存放方式
  3. 程序可访问的寄存器编号、个数和位数存储空间的大小编址方式
  4. 指令执行过程的控制方式等,包括程序计数器、条件码定义等。

ISA 规定了机器级程序的格式,机器语言或汇编语言程序员必须对机器的 ISA 非常熟悉。

指令字长

指令字长是指一条指令所包含的二进制代码的位数,其取决于操作码的长度地址码的长度地址码的个数

指令字长与机器字长没有固定的关系,它既可以等于机器字长,又可以大于或小于机器字长。但为了硬件设计方便,指令字长一般取字节或存储字长的整数倍,而不一定都和存储字长一样大。

通常根据指令长度可以将指令分为:

  • 单字长指令:指令长度等于机器字长的指令。
  • 半字长指令:指令长度等于半个机器字长的指令。
  • 双字长指令:指令长度等于两个机器字长的指令。

Tip

指令长度的不同会导致取指令时间开销的不同。例如,单字长指令只需访存 1 次就能将指令完整取出;而双字长指令则需访存 2 次才能完整取出,耗费 2 个存取周期。

在一个指令系统中,

  • 定长指令字结构:所有指令的长度都是相等的。定字长指令的执行速度快,控制简单。
  • 变长指令字结构:各种指令的长度随指令功能而异。

因为主存一般是按字节编址的,所以指令字长通常为字节的整数倍

指令的格式

指令的基本格式

Tip

现代计算机都采用字节编址方式,即一个内存单元只能存放一字节的信息。一个操作数(如 char、int、float、double)可能是 8 位、16 位、32 位或 64 位等,因此可能占用 1 个、2 个、4 个或 8 个内存单元。也就是说,一个操作数可能有多个内存地址对应。

有两种不同的地址指定方式:大端方式和小端方式。

  • 大端方式:指令中给出的地址是操作数最高有效字节(MSB)所在的地址。
  • 小端方式:指令中给出的地址是操作数最低有效字节(LSB)所在的地址。

总之就是选择 MSB 和 LSB 中的较小的值

零地址指令

零地址的运算类指令仅用在堆栈计算机中

Tip

堆栈指令的访存次数,取决于采用的是软堆栈还是硬堆栈。

  • 若是软堆栈(堆栈区由内存实现),则对于双目运算需要访问 4 次内存:取指令、取源数 1、取源数 2、存结果。
  • 若是硬堆栈(堆栈区由寄存器实现),则只需在取指令时访问一次内存。

一地址指令

二地址指令

三地址指令

四地址指令

定长操作码指令格式

指令系统中所有指令的操作码长度都相同

一般 n 位操作码字段的指令系统最大能够表示 2^n^ 条指令。

  • 优点:简化计算机硬件设计,提高指令译码和识别速度 很有利。当计算机字长为 32 位或更长时,这是常规用法。
  • 缺点:指令数量增加时会占用更多固定位,留给表示操作数地址的位数受限。

扩展操作码指令格式

为了在指令字长有限的前提下仍保持比较丰富的指令种类,可采取可变长度操作码,即指令系统中各指令的操作码长度可变

最常见的变长操作码方法是扩展操作码,它使操作码的长度随地址码的减少而增加,不同地址数的指令可具有不同长度的操作码,从而在满足需要的前提下,有效地缩短指令字长。

  • 优点:指令字长有限的前提下仍保持比较丰富的指令种类。
  • 缺点:加了指令译码和分析的难度,使控制器的设计复杂化。

除这种安排外,还有其他多种扩展方法,如形成 15 条三地址指令、12 条二地址指令、63 条一地址指令和 16 条零地址指令,共 106 条指令。

通常情况下,对使用频率较高的指令分配较短的操作码对使用频率较低的指令分配较长的操作码,从而尽可能减少指令译码和分析的时间。

指令的操作类型

Tip

调用指令和转移指令的区别:执行调用指令时必须保存下一条指令的地址(返回地址),当子程序执行结束时,根据返回地址返回到主程序继续执行;而转移指令则不返回执行。

指令的寻址方式

寻址方式是指寻找指令或操作数有效地址的方式,即确定本条指令的数据地址及下一条待执行指令的地址的方法。

采用不同寻址方式的目的是为了缩短指令字长,扩大寻址空间,提高编程的灵活性,但这也提高了指令译码的复杂度。

指令寻址

寻找下一条将要执行的指令地址称为指令寻址。

顺序寻址

通过程序计数器 PC 加 1 条指令的长度,自动形成下一条指令的地址

PC 自增的大小与编址方式、指令字长有关。

跳跃寻址

通过转移类指令实现,可以实现程序的无条件转移和条件转移。

跳跃是指由本条指令给出下条指令地址的计算方式,而是否跳跃可能受到状态寄存器的控制。

跳跃的方式分为:

  1. 绝对转移:地址码直接指出转移目标地址。
  2. 相对转移:地址码指出转移目的地址相对于当前 PC 值的偏移量。

由于 CPU 总是根据 PC 的内容去主存取指令的,因此转移指令执行的结果是修改 PC 值,下一条指令仍然通过 PC 给出。

数据寻址

数据寻址是指如何在指令中表示一个操作数的地址,或怎样计算出操作数的地址。

数据寻址的方式较多,为区别各种方式,通常在指令字中设置一个寻址特征字段,用来指明属于哪种寻址方式(其位数决定了寻址方式的种类)。CPU 也是通过这来区分某个寄存器中存放的是操作数还是操作数的地址

指令中的地址码字段并不代表操作数的真实地址,这种地址称为形式地址(A)。形式地址结合寻址方式,可以计算出操作数在存储器中的真实地址,这种地址称为有效地址(EA, effective address)

Tip

(A)表示地址为 A 的数值,A 既可以是寄存器编号,又可以是内存地址。

各常见指令寻址方式的特点和适用情况:

  • 立即寻址:操作数获取便捷,通常用于给寄存器赋初值。
  • 直接寻址:相对于立即寻址,缩短了指令长度。
  • 间接寻址:扩大了寻址范围,便于编制程序,易于完成子程序返回。
  • 寄存器寻址:指令字较短,指令执行速度较快。
  • 寄存器间接寻址:扩大了寻址范围。
  • 基址寻址:扩大了操作数寻址范围,适用于多道程序设计,常用于为程序或数据分配存储空间。
  • 变址寻址:主要用于处理数组问题,适合编制循环程序。
  • 相对寻址:用于控制程序的执行顺序、转移等。

直接寻址

指令字中的形式地址 A 就是操作数的真实地址 EA。

  • 优点:简单,不需要专门计算操作数的地址,指令 执行阶段 仅需访存一次
  • 缺点:A 的位数限制了该指令操作数的寻址范围,操作数的地址不易修改。

Tip

一条指令的执行分为取指令执行指令。取指令需要一次访存。

间接寻址

间接寻址是相对于直接寻址而言的,指令的地址字段给出的不是操作数的真正地址,而是操作数有效地址所在主存单元的地址,也就是操作数地址的地址,即 EA = (A)。

  • 优点:可扩大寻址范围(有效地址 EA 的位数大于形式地址 A 的位数),便于编制程序(用间接寻址可方便地完成子程序返回)。
  • 缺点:指令在执行阶段要多次访存,例如一次间接寻址需 2 次访存;由于执行速度较慢,一般为了扩大寻址范围时,通常采用寄存器间接寻址。

寄存器寻址

与直接寻址的原理一样,只是把访问主存改为访问寄存器,指令的地址字段给出的是操作数所在寄存器的编号,即 EA = Ri,其操作数在由 Ri 所指的寄存器内。

  • 优点:指令执行阶段不用访存,只访问寄存器,执行速度快;寄存器数量远小于内存单元数,所以地址码位数较少,指令字长较短
  • 缺点:寄存器价格昂贵,CPU 的寄存器数量有限。

Note

由于寄存器寻址的指令字长较短,因此想要缩短指令中某个地址段的位数可以使用这种方法。

寄存器间接寻址

这种方式综合了间接寻址和寄存器寻址各自的特点,指令字中的 Ri 所指寄存器给出的不是一个操作数,而是操作数所在主存单元的地址,即 EA = (Ri)。

  • 相比间接寻址,这种方式既扩大了寻址范围,又减少了访存次数在执行阶段仅需访存 1 次
  • 相比寄存器寻址,这种方式在执行阶段需要访存获得操作数。

隐含寻址

不明显地给出操作数的地址,而是隐含操作数的地址。

例如,单地址的指令格式就隐含约定第二个操作数由累加器(ACC)提供,指令中只明显指出第一个操作数的地址。因此,累加器(ACC)对单地址指令格式来说是隐含寻址。

  • 优点:有利于缩短指令字长,可以简化地址结构。
  • 缺点:需增加存储操作数或隐含地址的硬件。

立即寻址

指令字中的地址字段指出的不是操作数的地址,而是操作数本身,也称立即数,采用补码表示

图中#表示立即寻址特征,A 就是操作数。

  • 优点:指令在执行阶段不访存指令执行速度最快
  • 缺点:A 的位数限制了立即数的范围。

偏移寻址

Tip

设基址/变址/PC 的值为 B,若题目显式说明形式地址 A 是补码表示,则计算 EA 时有两种方法(存疑):

  1. 将补码 A 扩展到与 B 位数相同,然后相加,忽略溢出。

    • C000 0000H + FFFF FF00H = 1 BFFF FF00H(加上补码,有溢出)
    • 但 FF00H = -0100H,C000 0000H - 0100H = BFFF FF00H(与原码进行加减,无溢出)

  2. 将补码 A 转化为原码,然后再根据补码 A 的符号数进行加减。

    • F000 0000H + (-00EEH) = EFFF FF12H(与原码进行加减,无溢出)
    • 但 FF12H 扩展后是 FFFF FF12H,F000 0000 + FFFF FF12H = 1 EFFF FF12H(加上补码,有溢出)

基址寻址

基址寻址是指将基址寄存器(BR, base address register) 的内容加上指令字中的形式地址 A 而形成操作数的有效地址,即 EA = (BR) + A。

其中基址寄存器既可采用专用寄存器,又可指定某个通用寄存器作为基址寄存器。

基址寄存器是面向操作系统的,其内容由操作系统或管理程序确定主要用于解决程序逻辑空间与存储器物理空间的无关性

在程序执行过程中,基址寄存器的内容不变(作为基地址),形式地址可变(作为偏移量)采用通用寄存器作为基址寄存器时,可由用户决定哪个寄存器作为基址寄存器,但其内容仍由操作系统确定。

优点:可以扩大寻址范围(基址寄存器的位数大于形式地址 A 的位数);用户不必考虑自己的程序存于主存的具体位置,因此有利于多道程序设计,并可用于编制浮动程序,但偏移量(形式地址 A)的位数较短。

基址寄存器对于汇编程序员是可见的。

变址寻址

变址寻址是指将变址寄存器 (IX, index register) 的内容加上指令字中的形式地址 A 而形成操作数的有效地址,即 EA = (IX) + A,其中 IX 为变址寄存器(专用),也可用通用寄存器作为变址寄存器

变址寄存器是面向用户的,在程序执行过程中,变址寄存器的内容可由用户改变(作为偏移量),形式地址 A 不变(作为基地址)。

优点:可扩大寻址范围(变址寄存器的位数大于形式地址 A 的位数);偏移量(变址寄存器 IX)的位数足以表示整个存储空间。

变址寻址与基址寻址本质上的区别

  • 基址寻址面向系统,主要用于为多道程序或数据分配存储空间,因此基址寄存器的内容通常由操作系统或管理程序确定,在程序的执行过程中其值不可变,而指令字中的 A 是可变的。
  • 变址寻址面向用户,主要用于处理数组问题,在变址寻址中,变址寄存器的内容由用户设定,在程序执行过程中其值可变,而指令字中的 A 是不可变的。

相对寻址

相对寻址是把 PC 的内容加上指令格式中的形式地址 A 而形成操作数的有效地址,即 EA = (PC) + A,其中 A 是相对于当前 PC 值的偏移量(即相对于下一条要执行的指令的地址),可正可负,补码表示

A 的位数决定操作数的寻址范围。

Note

对于转移指令 JMPA,若指令的地址为 X,且占 2B,则在取出该指令后,PC 的值会增 2,即 (PC)=X+2,这样在执行完该指令后,会自动跳转到 X+2+A 的地址继续执行。

优点:操作数的地址不是固定的,它随 PC 值的变化而变化,且与指令地址之间总是相差一个固定的偏移量,因此便于程序浮动

相对寻址广泛应用于转移指令

堆栈寻址

堆栈是存储器(或寄存器组)中一块特定的、按后进先出(LIFO)原则管理的存储区,该存储区中读/写单元的地址是用一个特定寄存器给出的,该寄存器称为堆栈指针(SP, Stack Pointer)。

堆栈可分为硬堆栈和软堆栈两种:

  1. 硬堆栈:使用寄存器作为堆栈,硬堆栈的成本较高,不适合做大容量的堆栈。
  2. 软堆栈:从主存中划出一段区域来做堆栈,这种方法是最合算且最常用的。

在采用堆栈结构的计算机中,大部分指令表面上都表现为无操作数指令的形式,因为操作数地址都隐含使用了 SP。因此在读/写堆栈的前后都伴有自动完成对 SP 的加减操作

程序的机器级代码表示

常用汇编指令

相关寄存器

x86 处理器中有 8 个 32 位的通用寄存器。为了向后兼容,EAX、EBX、ECX 和 EDX 的高两位字节和低两位字节可以独立使用,E 表示 Extended,表示 32 位的寄存器。

例如,EAX 的低两位字节称为 AX,而 AX 的高低字节又可分别作为两个 8 位寄存器,分别称为 AH 和 AL。

除 EBP 和 ESP 外,其他几个寄存器的用法是比较灵活的。

汇编指令格式

一般有两种不同的汇编格式:AT&T 格式和 Intel 格式(统考要求掌握的是 Intel 格式)。

常用指令

汇编指令通常可分为数据传送指令、算术和逻辑运算指令和控制流指令,下面以 Intel 格式为例,介绍一些常用的指令。

数据传送指令

  1. mov 指令

    将第二个操作数(寄存器的内容、内存中的内容或常数值)复制到第一个操作数(寄存器或内存)。

    双操作数指令的两个操作数不能都是内存,即 mov 指令不能用于直接从内存复制到内存,若需在内存之间复制,可先从内存复制到一个寄存器,再从这个寄存器复制到内存。

  2. push 指令

    将操作数压入内存的栈,常用于函数调用。

    ESP 是栈顶,入栈前先将 ESP 值减 4,然后将操作数压入 ESP 指示的地址

    栈中元素固定是 32 位

    栈增长方向与内存地址增长方向相反

  3. pop 指令

    push 指令相反,pop 指令执行的是出栈工作,出栈前先将 ESP 指示的地址中的内容出栈,然后将 ESP 值加 4

算术和逻辑运算指令

Note

若乘法操作溢出,则编译器置溢出标志 OF = 1,以使 CPU 调用溢出异常处理程序。

控制流指令

选择语句的机器级表示

Tip

在 Intelx86 处理器中程序计数器 PC(ProgramCounter)通常被称为 IP(Instruction Pointer)。

循环语句的机器级表示

过程调用的机器级表示

访问栈帧

每个过程都有自己的栈区,称为栈帧,因此,一个栈由若干栈帧组成,寄存器 EBP 指示栈帧的起始位置,寄存器 ESP 指示栈顶,栈从高地址向低地址增长

过程执行时,ESP 会随着数据的入栈而动态变化,而 EBP 固定不变。当前栈帧的范围在 EBP 和 ESP 指向的区域之间。

切换栈帧

https://www.bilibili.com/video/BV1ps4y1d73V?p=58

传递参数

https://www.bilibili.com/video/BV1ps4y1d73V?p=59

CISC 和 RISC

复杂指令系统计算机 (CISC): Complex Instruction Set Computer

精简指令系统计算机 (RISC): Reduced Instruction Set Computer

  • CISC 的主要特点如下
    1. 指令系统复杂庞大,指令数目一般为 200 条以上。
    2. 指令的长度不固定,指令格式多,寻址方式多。
    3. 可以访存的指令不受限制
    4. 各种指令使用频度相差很大。
    5. 各种指令执行时间相差很大,大多数指令需多个时钟周期才能完成。
    6. 控制器大多数采用微程序控制。有些指令非常复杂,以至于无法采用硬连线控制。
    7. 难以用优化编译生成高效的目标代码程序。
  • RISC 的主要特点如下
    1. 选取使用频率最高的一些简单指令,复杂指令的功能由简单指令的组合来实现。
    2. 指令长度固定,指令格式种类少,寻址方式种类少。
    3. 只有 LOAD/STORE(取数/存数)指令访存,其余指令的操作都在寄存器之间进行。
    4. CPU 中通用寄存器的数量相当多。
    5. 一定采用指令流水线技术,大部分指令在一个时钟周期内完成。
    6. 硬布线控制为主不用或少用微程序控制
    7. 特别重视编译优化工作,以减少程序执行时间。

从指令系统兼容性看,CISC 大多能实现软件兼容,即高档机包含了低档机的全部指令,并可加以扩充。但 RISC 简化了指令系统,指令条数少,格式也不同于老机器,因此大多数 RISC 机不能与老机器兼容。