目录
4.1 指令系统
4.1.1 指令集体系结构
指令集体系结构(Instruction Set Architecture,ISA)作为计算机软硬件交互的关键枢纽,详细规定了计算机处理器所能识别与执行的全部指令及其对应功能。它涵盖操作类型(诸如加法、乘法、数据传输等)、操作数来源与去向(涉及寄存器、内存地址等)、数据类型呈现形式(像整数、浮点数等)、寻址模式(用以确定操作数地址)、可访问内存空间范畴、可用寄存器的数量及特性(包含寄存器位数、用途等),甚至囊括中断机制(用于处理突发事件)、机器状态的定义与转换以及输入 / 输出架构等多方面内容。
不同类型的计算机,从日常使用的个人电脑、大型服务器到嵌入式设备,所采用的 ISA 可能大相径庭。例如,x86 架构凭借丰富指令与强大功能,在个人电脑和服务器领域广泛应用,能够满足复杂计算与多任务处理需求;而 ARM 架构则以低功耗、高性能和灵活设计,在移动设备和嵌入式系统中占据主导,适配资源受限环境。ISA 的存在,让软件开发者得以依据特定硬件平台编写高效程序,充分发挥硬件性能优势。
4.1.2 指令的基本格式
一条完整指令通常由操作码字段和地址码字段构成。操作码明确指令的操作性质与功能,不同指令系统中,其编码方式与代表操作种类各异,少则几十种,多则数百种。地址码字段负责提供操作数或其地址信息,它可以是操作数本身(立即寻址,如 “ADD R1, #5” 中的 “#5”)、内存地址(计算机借此从内存读取操作数)或寄存器编号(指示操作数所在寄存器)。地址码设计与计算机寻址方式紧密相连,合理设计可提升指令执行效率与灵活性。
4.1.3 定长操作码指令格式
定长操作码指令格式是一种常见且易理解的设计。在此格式中,指令字的最高位部分被分配固定数量位来表示操作码。例如,操作码字段固定为 n 位时,依据二进制组合原理,该指令系统最多能表示 2^n 条不同指令,如 n = 6 时,可表示 2^6 = 64 种操作。
定长操作码简化了硬件设计,因操作码长度与位置固定,指令译码器能快速准确识别指令操作类型,提升译码与执行效率。在 32 位或更长字长的计算机中更为常见,可充分利用指令字空间,保证指令功能丰富的同时维持硬件简洁高效。不过,当指令系统需扩展新指令时,固定长度操作码可能无法满足指令种类增长需求。
4.1.4 扩展操作码指令格式
为在有限指令字长下保持丰富指令种类,扩展操作码指令格式应运而生。此格式中,指令操作码字段位数并非固定,而是依指令复杂程度和地址码数量动态调整,且操作码字段分散于指令字不同位置。
通常,扩展操作码使操作码长度随地址码数量减少而增加。简单常用指令,因操作数隐含或地址信息需求少,可分配较短操作码以提高执行效率;复杂指令因需更多地址信息指定操作数,则分配较长操作码。例如,三地址指令操作码可能较短,零地址指令操作码可能较长。
设计扩展操作码指令格式需遵循两条原则:一是短码不能是长码前缀,避免指令译码歧义;二是各指令操作码必须唯一。合理运用扩展操作码,可在满足指令功能前提下缩短指令字长、提高编码效率,但也增加了指令译码与分析难度,对计算机控制器设计要求更高。
4.1.5 指令的操作类型
计算机指令系统中的指令按功能可大致分为以下几类:
- 数据处理指令:用于数据的算术与逻辑运算。算术运算指令包含加法(ADD)、减法(SUB)、乘法(MUL)、除法(DIV)等;逻辑运算指令有与(AND)、或(OR)、非(NOT)、异或(XOR)等,常用于数据筛选、掩码设置。此外,还包括移位指令,如左移(SHL)、右移(SHR),通过二进制位移位可实现快速乘除运算(左移一位相当于乘以 2,右移一位相当于除以 2)及位操作等功能。
- 数据传送指令:负责计算机不同存储单元间的数据传输。常见的有寄存器间数据传送(如 “MOV R1, R2” 将寄存器 R2 值传至 R1)、寄存器与主存储器间数据传送(如 “LOAD R1, [address]” 从内存地址 address 处加载数据到 R1;“STORE [address], R1” 将 R1 数据存储到内存地址 address 处)。数据传送指令在程序中频繁出现,确保数据在不同存储层次间高效流动,为数据处理操作提供数据支持。
- 程序控制指令:用于改变程序执行流程。条件转移指令(如 “BEQ R1, R2, label”,当寄存器 R1 和 R2 值相等时,程序跳转到标签 label 处执行;“BNE R1, R2, label”,当 R1 和 R2 值不等时跳转)依据条件判断结果决定是否跳转;无条件转移指令(如 “JUMP label” 直接跳转)则强制程序跳转。此外,还包括转子程序指令(如 “CALL subroutine” 调用子程序并保存返回地址,以便子程序执行完毕后返回原程序继续执行),用于实现程序模块化设计与代码复用。
- 输入-输出指令:负责计算机与外部设备的数据交互。通过这些指令,计算机可从输入设备(如键盘、鼠标、传感器等)读取数据,或将处理结果输出到输出设备(如显示器、打印机、磁盘等)。不同计算机系统对输入-输出指令的实现方式不同,有的将其包含在数据传送指令类中,通过特定地址空间区分内存与 I/O 设备操作;有的则设计独立的输入-输出指令集,更好地支持外部设备控制与数据传输。
- 状态管理指令:用于计算机系统状态控制与管理。例如,置存储保护指令可设置内存区域访问权限,防止程序非法访问内存,保障系统安全稳定;中断处理指令用于处理计算机运行中的突发事件(如外部设备请求、程序错误等),通过中断机制,计算机暂停当前程序,处理中断事件后再返回原程序继续执行。状态管理指令对维护计算机系统正常运行与资源管理至关重要。
4.2 指令的寻址方式
4.2.1 指令寻址和数据寻址
寻址方式是指令系统的重要概念,分为指令寻址和数据寻址。
指令寻址用于确定下一条待执行指令在内存中的地址,主要有顺序寻址和跳跃寻址两种方式。顺序寻址通过程序计数器(PC)自动确定下一条指令地址,每条指令执行完毕,PC 值自动增加一个指令字长,指向下一条指令内存地址。例如,在指令字长为 4 字节的系统中,当前指令地址为 0x1000,执行后 PC 值变为 0x1004,指向内存地址为 0x1004 的下一条指令,适用于顺序结构程序段。
跳跃寻址通过转移类指令实现,执行转移指令时,根据指令指定条件和目标地址改变 PC 值,使程序跳转到目标地址执行。如条件转移指令 “BEQ R1, R2, label”,当寄存器 R1 和 R2 值相等时,程序跳转到标签 label 对应的地址执行,实现程序条件控制和循环等复杂结构。
数据寻址则是确定指令中操作数地址的方式。计算机执行指令时需获取操作数进行运算或处理,不同数据寻址方式为程序员提供灵活多样的操作数访问手段,满足复杂编程需求。
4.2.2 常见的数据寻址方式
- 直接寻址:指令地址码字段直接给出操作数在内存中的真实地址,如 “LOAD R1, [0x2000]” 将内存地址 0x2000 处的数据加载到寄存器 R1。其优点是寻址过程简单,硬件实现容易,能快速定位操作数;缺点是地址码固定,缺乏灵活性,操作数地址变化时需修改指令,影响程序可维护性与通用性。
- 间接寻址:指令地址码字段表示的是操作数地址的地址,即地址码对应存储单元中的内容才是操作数真实地址。例如 “LOAD R1, [[0x3000]]”,假设内存地址 0x3000 处的值为 0x4000,则实际操作是将内存地址 0x4000 处的数据加载到 R1。间接寻址增加了寻址灵活性,可通过修改间接地址指向内容,在不改变指令情况下访问不同位置操作数,常用于数据结构(如链表、数组)动态访问及程序运行中根据条件动态改变操作数地址,但会带来额外内存访问开销,降低指令执行速度。
- 立即寻址:指令地址码字段直接表示操作数本身,如 “ADD R1, #5” 中 “#5” 为立即数,将寄存器 R1 值与立即数 5 相加。立即寻址在指令执行时无需额外内存访问获取操作数,执行速度相对较快,常用于寄存器初始化赋值及程序中使用固定常数运算。但操作数包含在指令中会增加指令长度(尤其对于较大操作数),且受地址码字段长度限制,只能表示有限范围内常数。
- 变址寻址:指令包含变址寄存器号和位移值,执行时将变址寄存器内容与位移值相加得到操作数地址。例如 “LOAD R1, [R2 + 10]”,将变址寄存器 R2 值加上位移值 10 后的内存地址处的数据加载到 R1。变址寻址适合处理数组、字符串等数据结构,通过改变变址寄存器值可方便访问数组不同元素,无需频繁修改指令地址部分,提高程序通用性与灵活性。许多计算机还支持双变址功能,即通过两个变址寄存器值与位移值相加确定操作数地址,增强寻址灵活性,适用于更复杂数据结构访问需求。
- 相对寻址:以程序计数器(PC)当前值为基准,加上指令地址码字段表示的位移值得到操作数地址。例如,当前 PC 值为 0x1000,指令 “JUMP +20” 表示将 PC 值加上 20,跳转到地址 0x1014 处继续执行程序。相对寻址使程序在内存中的位置可灵活变化,无需修改指令地址部分。当程序在内存中动态加载或重定位时,相对寻址能保证程序正确执行,常用于实现程序中的循环结构和子程序调用中的返回地址计算等场景。
- 基址寻址:将 CPU 中基址寄存器的内容,加上指令格式中的形式地址而形成操作数的有效地址。被引用的专用寄存器含有一个存储器地址,地址字段含有一个相对于该地址的偏移量(通常是无符号的整数),寄存器的引用可以是显式的,也可以是隐式的。基址寻址方式主要用来解决程序的动态定位问题。在多道程序环境下,用户无法决定自己使用的主存区,因而编程时常按(以零为基准地址)相对地址编写,当程序被放入主存时,操作系统根据主存空间情况给基址寄存器赋值,从而将虚地址转化为实地址。例如 “LDR R2, (R3, #0x0C)”,读取 R3 + 0x0C 地址上的存储单元的内容,放入 R2 。该寻址方式常用于查表、数组操作、功能部件寄存器访问等。
- 堆栈寻址:堆栈是一个按特定顺序进行存取的存储区,操作顺序为 “后进先出” 。堆栈寻址是隐含的,它使用一个专门的寄存器(堆栈指针)指向一块存储区域(堆栈),指针所指向的存储单元即是堆栈的栈顶。存储器堆栈可分为向上生长(向高地址方向生长,称为递增堆栈)和向下生长(向低地址方向生长,称为递减堆栈) 。堆栈指针指向最后压入的堆栈的有效数据项,称为满堆栈;堆栈指针指向下一个待压入数据的空位置,称为空堆栈。由此组合出四种类型的堆栈方式:满递增(堆栈向上增长,堆栈指针指向内含有效数据项的最高地址,指令如 LDMFA、STMFA 等)、空递增(堆栈向上增长,堆栈指针指向堆栈上的第一个空位置,指令如 LDMEA、STMEA 等)、满递减(堆栈向下增长,堆栈指针指向内含有效数据项的最低地址,指令如 LDMFD、STMFD 等)、空递减(堆栈向下增长,堆栈指针向堆栈下的第一个空位置,指令如 LDMED、STMED 等)。
- 寄存器寻址:操作数在寄存器中,由指令操作码中的 rrr 三位的值和 PSW 中 RS1 及 RS0 的状态,选中某个工作寄存器区的某个寄存器,然后进行相应的指令操作。指令中可以引用的寄存器包含 8 位寄存器(AH、AL、BH、BL、CH、CL、DH 和 DL 等)、16 位寄存器(AX、BX、CX、DX、SI、DI、SP、BP 和段寄存器等)以及 32 位寄存器(EAX、EBX、ECX、EDX、ESI、EDI、ESP 和 EBP 等)。由于操作数就在寄存器中,不需要访问存储器来取得操作数,因而可以取得较高的运算速度。源和目的操作数都可以是寄存器,例如 “MOV EAX, EBX”“MOV AX, BX”“MOV DH, BL” 等都是源和目的操作数均为寄存器寻址的指令。
- 寄存器间接寻址:在 MCS - 51 单片机中,为区分寄存器寻址和寄存器间接寻址,在寄存器名称前加一个符号 @来表示寄存器间接寻址,如 “mov A, @R0” 。其过程为第一次寻址得到寄存器 R0 的值,这个值是一个地址,再通过这个地址,第二次寻址得到存数器数据,即把寄存器里的内容看成是地址,然后把这个地址里面的内容赋给累加器 A 。8051 规定,采用 R0、R1、DPTR 作为间接寻址寄存器,可寻址片内数据存储器 RAM 的低 128B 单元和片外数据存储器的低 256 单元;采用 DPTR 作间址寄存器,可寻址片外数据存储器的整个 64KB 地址空间,堆栈指针 SP 用于指示堆栈操作的地址,因此,PUSH 和 POP 指令也是寄存器间接寻址。该寻址方式使用指针访问存储器中的数据,指针是包含另一个存储位置地址的双字存储位置,用常数偏移量来寻址多个数组元素时,直接寻址并不实用,此时可使用寄存器作为指针的间接寻址,并控制该寄存器的值。
寻址方式 | 地址码含义 | 操作数获取方式 | 优点 | 缺点 | 应用场景 |
直接寻址 | 操作数在内存中的真实地址 | 直接根据地址码从内存读取 | 寻址简单,硬件易实现,快速定位操作数 | 地址码固定,缺乏灵活性,操作数地址变需改指令 | 操作数地址固定且不常变化场景 |
间接寻址 | 操作数地址的地址 | 先根据地址码从内存读地址,再据此地址读操作数 | 寻址灵活,可动态改变操作数地址 | 额外内存访问开销,降低指令执行速度 | 链表、数组动态访问,依条件改变操作数地址 |
立即寻址 | 操作数本身 | 指令中直接获取操作数 | 执行时无需额外内存访问,速度快 | 操作数含在指令中使指令变长,受地址码字段长度限,常数范围有限 | 寄存器初始化,使用固定常数运算 |
变址寻址 | 变址寄存器内容 + 位移值得到操作数地址 | 计算地址后从内存读取 | 适合数组、字符串处理,改变变址寄存器值可方便访问不同元素 | 需设置变址寄存器 | 数组、字符串等数据结构操作 |
相对寻址 | 程序计数器当前值 + 位移值得到操作数地址 | 计算地址后获取操作数 | 程序在内存位置可变,无需修改指令地址部分 | 仅适用于与 PC 相关的地址计算 | 程序循环结构、子程序调用返回地址计算 |
基址寻址 | 基址寄存器内容 + 形式地址得到操作数有效地址 | 计算地址后从内存读取 | 解决程序动态定位,用于查表、数组操作、功能部件寄存器访问 | 需设置基址寄存器 | 多道程序环境下程序动态定位,特定数据结构操作 |
堆栈寻址 | 通过堆栈指针隐含确定操作数地址 | 按堆栈规则从堆栈存取操作数 | 适合子程序调用、中断处理等需保存和恢复现场场景 | 堆栈操作有特定规则,需注意堆栈溢出 | 子程序调用、中断处理 |
寄存器寻址 | 寄存器编号 | 从寄存器获取操作数 | 运算速度快 | 寄存器数量有限 | 频繁数据处理且数据在寄存器中场景 |
寄存器间接寻址 | 寄存器内容为操作数地址 | 先读寄存器得地址,再据此读操作数 | 适合复杂数据结构访问 | 需两次访问,速度相对慢 | 复杂数据结构(如链表)访问 |
4.3 程序的机器级代码表示
4.3.1 常用汇编指令介绍
汇编语言是面向机器的低级程序设计语言,使用助记符表示机器指令,与机器语言二进制代码一一对应,便于程序员编写与硬件密切相关的程序。以下是常见汇编指令及其功能:
- 数据传送指令:如 MOV(传送数据)指令,格式为 “MOV destination, source”,将源操作数的值传送到目标操作数中。例如 “MOV AX, BX” 将寄存器 BX 值传至 AX;“MOV AX, [1000H]” 将内存地址 1000H 处的数据加载到 AX。
- 算术运算指令:ADD(加法)指令,格式为 “ADD destination, source”,将源操作数与目标操作数相加,结果存储在目标操作数中,如 “ADD AX, BX” 将 AX 和 BX 值相加存于 AX;SUB(减法)指令与之类似,实现减法运算,如 “SUB AX, BX” 将 AX 值减去 BX 值存于 AX。此外,还有 MUL(乘法)、DIV(除法)等算术运算指令。
- 逻辑运算指令:AND(与运算)指令,格式为 “AND destination, source”,对源操作数和目标操作数进行按位与操作,结果存储在目标操作数中,如 “AND AX, BX” 将 AX 和 BX 二进制位进行与运算存于 AX;OR(或运算)指令进行按位或操作,如 “OR AX, BX”;NOT(非运算)指令对操作数进行按位取反操作,如 “NOT AX” 将 AX 二进制位取反。
- 移位指令:SHL(左移)指令,格式为 “SHL destination, count”,将目标操作数向左移动指定位数,低位补 0,如 “SHL AX, 1” 将寄存器 AX 值左移 1 位,相当于 AX 值乘以 2;SHR(右移)指令与之相反,将目标操作数向右移动指定位数,高位补 0,如 “SHR AX, 1” 相当于 AX 值除以 2(对于无符号数)。
- 程序控制指令:JMP(无条件跳转)指令,格式为 “JMP label”,将程序执行流程无条件跳转到指定标签处。
4.3.2 选择语句的机器级表示
在高级语言中,选择语句(如 if - else 语句)用于根据条件决定程序执行路径。在机器级代码中,选择语句通过条件转移指令和比较指令实现。
以简单的 if - else 语句为例,高级语言代码可能如下:
if (a > b) {
c = a;
} else {
c = b;
}
在汇编语言中,可能的实现如下:
CMP a, b ; 比较a和b
JG L1 ; 如果a大于b,跳转到L1
MOV c, b ; 否则,将b赋值给c
JMP L2 ; 跳转到L2结束选择
L1: MOV c, a ; 将a赋值给c
L2: ; 后续代码
这里,CMP 指令比较 a 和 b 的值,JG(大于则跳转)指令根据比较结果决定是否跳转到 L1 处执行将 a 赋值给 c 的操作,若不跳转则执行将 b 赋值给 c 的操作,最后通过 JMP 指令跳转到结束选择的位置继续执行后续代码。
4.3.3 循环语句的机器级表示
循环语句(如 for、while 循环)在高级语言中用于重复执行一段代码。在机器级代码中,循环通常通过条件转移指令和计数器实现。
以 while 循环为例,高级语言代码可能为:
int i = 0;
while (i < 10) {
// 循环体代码
i++;
}
在汇编语言中,可能的实现如下:
MOV i, 0 ; 初始化i为0
L1: CMP i, 10 ; 比较i和10
GE L2 ; 如果i大于等于10,跳转到L2结束循环
; 循环体代码
INC i ; i自增1
JMP L1 ; 跳转到L1继续循环
L2: ; 循环结束后的代码
这里,MOV 指令初始化循环变量 i,CMP 指令比较 i 和 10,GE(大于等于则跳转)指令根据比较结果决定是否跳转到 L2 结束循环,若不跳转则执行循环体代码,INC 指令使 i 自增 1,JMP 指令跳转到 L1 继续下一次循环。
4.3.4 过程调用的机器级表示
过程调用(函数调用)在高级语言中用于实现程序模块化。在机器级代码中,过程调用涉及保存返回地址、传递参数、跳转到子程序执行以及返回原程序等操作。
当调用一个过程时,首先会将返回地址(即调用指令的下一条指令地址)压入堆栈保存,然后将参数传递给子程序(可以通过寄存器或堆栈传递)。接着,通过跳转指令(如 CALL 指令)跳转到子程序的入口地址开始执行。
在子程序执行完毕后,通过 RET 指令从堆栈中弹出返回地址,将程序执行流程返回到原程序调用点继续执行。
例如,在汇编语言中,调用一个名为 subroutine 的子程序可能如下:
PUSH EIP ; 将返回地址压入堆栈,EIP为程序计数器
MOV AX, param1 ; 将参数param1传递给子程序,这里通过寄存器传递
MOV BX, param2 ; 将参数param2传递给子程序
CALL subroutine ; 调用子程序
; 子程序执行完毕后,程序会从这里继续执行
在子程序 subroutine 中,可能的代码如下:
subroutine:
; 子程序代码
POP EIP ; 从堆栈中弹出返回地址,返回原程序
这样,通过一系列的堆栈操作和跳转指令,实现了过程调用在机器级的表示。
4.4 CISC 和 RISC 的基本概念
4.4.1 复杂指令系统计算机 CISC
复杂指令系统计算机(Complex Instruction Set Computer,CISC)在指令设计上倾向于设置大量功能复杂、指令字长不固定的指令。这些指令具备强大功能,能够在一条指令内完成较为复杂的操作,比如一次内存访问、多次算术运算等。例如,某些 CISC 指令可以直接完成数组元素的读取、计算和存储,无需像简单指令那样分步执行多个指令。
CISC 的指令系统丰富多样,指令格式和寻址方式灵活多变,这使得程序员能够依据不同的应用场景和编程需求,选择最合适的指令来实现复杂功能,在高级语言的编译过程中,能够生成相对紧凑、高效的机器代码。然而,由于指令复杂,CISC 计算机的硬件设计难度较大,需要配备复杂的指令译码和执行电路,这不仅增加了硬件成本,还降低了指令的执行速度,尤其是在执行一些简单指令时,也需经过复杂的硬件处理流程,导致整体效率不高。
4.4.2 精简指令系统计算机 RISC
精简指令系统计算机(Reduced Instruction Set Computer,RISC)秉持与 CISC 截然不同的设计理念。
RISC 致力于精简指令系统,以提升计算机性能与执行效率。其核心设计理念为选取使用频率高、功能简单的指令构成指令系统,摒弃复杂指令,同时采用固定长度指令格式,减少指令格式与寻址方式种类,并以硬布线逻辑控制为主,尽量避免或减少微码控制的使用。这种设计使得 CPU 结构更为简单合理,在一个时钟周期内可执行一条甚至多条指令,大大提高了运算速度。
RISC 架构的指令系统相对较小,指令格式规整、简单,常见基本寻址方式仅有 2 - 3 种。指令长度固定,便于流水线操作执行,数据处理指令通常仅对寄存器进行操作,只有加载(LOAD)和存储(STORE)指令能够访问存储器,这有效减少了内存访问次数,显著提高了指令执行效率。由于结构简单,RISC 计算机的硬件设计相对容易,研发成本较低,并且功耗也更低,在对功耗要求严苛的移动设备和嵌入式系统中应用广泛,像 ARM 架构便是 RISC 的典型代表,在手机、平板电脑等移动设备领域占据主导地位。
4.4.3 CISC 和 RISC 的比较
比较维度 | 复杂指令系统计算机(CISC) | 精简指令系统计算机(RISC) |
指令系统 | 指令数量众多,功能复杂,指令字长不固定 | 指令数量较少,功能简单,指令长度固定 |
指令格式与寻址方式 | 灵活多样,指令格式和寻址方式种类丰富 | 相对单一,指令格式和寻址方式种类有限 |
指令执行速度 | 执行复杂指令时可能只需较少指令,但因指令复杂,硬件译码和执行电路复杂,简单指令执行速度慢,整体指令平均执行速度较慢 | 单个指令功能简单,但多数指令能在一个时钟周期内完成,指令执行速度快 |
硬件设计复杂度 | 硬件设计复杂,需配备复杂指令译码和执行电路 | 硬件设计相对简单,易于实现和维护 |
软件编程难度 | 指令丰富,可根据不同应用场景选择合适指令,对程序员要求较高,软件编程相对复杂 | 指令系统精简,编程相对简单,但对编译器要求较高 |
应用场景 | 适用于对指令功能多样性和代码紧凑性要求高,对性能要求相对不敏感的通用计算机系统,如早期个人电脑和大型主机 | 适用于对性能和功耗要求高的场景,如移动设备、嵌入式系统以及要求高速运算的服务器和工作站 |
研发成本 | 因硬件复杂,研发成本高 | 硬件简单,研发成本相对较低 |