第六章:指令系统与汇编
章节概述
本章介绍计算机指令系统的基本概念、寻址方式、CISC与RISC架构的区别,以及汇编语言程序设计基础。通过本章学习,理解计算机如何执行指令,掌握汇编语言的基本语法和程序设计方法。
学习目标:
- 理解指令的基本格式和组成部分
- 掌握各种寻址方式的原理和应用场景
- 理解CISC与RISC架构的特点和区别
- 掌握x86汇编语言的基本指令和语法
- 能够编写简单的汇编程序
- 理解中断和异常处理机制
本章重点:
- 指令格式和指令类型
- 各种寻址方式的特点和应用
- CISC与RISC的区别
- 汇编语言基本指令和伪指令
- 汇编程序设计方法
本章难点:
- 复杂寻址方式的理解和应用
- 汇编程序设计中的寄存器管理
- 指令执行过程分析
- 中断处理机制
6.1 机器指令
机器指令是计算机能直接识别和执行的二进制代码,是计算机程序的基本单位。指令系统(指令集)是计算机体系结构的核心,决定了计算机的基本功能和编程方式。
6.1.1 指令格式
机器指令是计算机能直接识别和执行的二进制代码,是计算机程序的基本单位。每条指令通常包含两部分:
- 操作码(Opcode):指明指令要执行的操作类型,如加法、减法、数据传送、跳转等
- 地址码(Address):指明操作数的地址或立即数,以及运算结果的存放地址
指令格式示意图: ┌─────────────────┬─────────────────────────┐ │ 操作码(OP) │ 地址码(A) │ ├─────────────────┼─────────────────────────┤ │ 指明操作类型 │ 指明操作数位置 │ │ 如:加、减、 │ 如:寄存器号、 │ │ 传送、跳转等 │ 内存地址、立即数 │ └─────────────────┴─────────────────────────┘ 典型指令格式(三地址指令): ├─────────┼─────────┼─────────┼─────────┤ │ 操作码 │ 目的地址 │ 源地址1 │ 源地址2 │ │ 8位 │ 8位 │ 8位 │ 8位 │ └─────────┴─────────┴─────────┴─────────┘
根据地址码字段的数量,指令可分为:
- 零地址指令:只有操作码,无地址码。如空操作指令NOP、停机指令HALT
- 一地址指令:只有一个地址码。如取反、移位等单操作数指令
- 二地址指令:两个地址码。如ADD R1, R2(R1 = R1 + R2)
- 三地址指令:三个地址码。如ADD R1, R2, R3(R1 = R2 + R3)
6.1.2 指令长度
根据指令长度是否固定,可分为:
定长指令:
- 所有指令长度相同,通常为32位或64位
- 译码简单,执行速度快
- 便于流水线实现
- 如:RISC处理器(ARM、MIPS、RISC-V)通常采用定长指令
变长指令:
- 指令长度根据操作需要变化,从1字节到15字节不等
- 节省存储空间,代码密度高
- 译码复杂,影响流水线效率
- 如:x86采用变长指令(1-15字节)
指令长度示例: 定长指令(32位): ├──────────┼──────────┼──────────┼──────────┤ │ 操作码 │ 源寄存器 │ 目的寄存器│ 偏移量 │ │ 8位 │ 5位 │ 5位 │ 14位 │ └──────────┴──────────┴──────────┴──────────┘ 变长指令(x86示例): 单字节指令:[操作码8位] 如:NOP (0x90) 双字节指令:[操作码8位][操作数8位] 如:PUSH imm8 三字节指令:[操作码8位][操作数16位] 如:MOV AX, imm16 四字节及以上:[前缀][操作码][ModR/M][SIB][位移][立即数]
6.1.3 指令类型
现代计算机的指令系统通常包含以下几类指令:
数据传送指令:
- MOV:数据传送,在寄存器、内存之间移动数据
- PUSH/POP:堆栈操作,用于保存和恢复寄存器值
- LEA:取有效地址(Load Effective Address)
- LDS/LES:取段地址到DS/ES寄存器
算术运算指令:
- ADD/SUB:加/减法运算
- ADC/SBB:带进位加法/带借位减法
- MUL/IMUL:无符号/有符号乘法
- DIV/IDIV:无符号/有符号除法
- INC/DEC:增1/减1运算
- NEG:求补运算(取负数)
- CMP:比较运算(执行减法但不保存结果,只影响标志位)
逻辑运算指令:
- AND/OR/XOR:与/或/异或运算
- NOT:非运算(取反)
- TEST:测试运算(执行AND但不保存结果,只影响标志位)
- SHL/SHR:逻辑左移/右移
- SAL/SAR:算术左移/右移
- ROL/ROR:循环左移/右移
- RCL/RCR:带进位循环左移/右移
控制转移指令:
- JMP:无条件跳转
- JZ/JNZ:条件跳转(为零/非零跳转)
- JE/JNE:条件跳转(相等/不相等跳转)
- JG/JL/JGE/JLE:有符号数比较跳转(大于/小于/大于等于/小于等于)
- JA/JB/JAE/JBE:无符号数比较跳转(高于/低于/高于等于/低于等于)
- CALL/RET:子程序调用/返回
- INT/IRET:中断调用/返回
- LOOP/LOOPE/LOOPNE:循环控制指令
输入输出指令:
- IN/OUT:端口输入/输出(x86架构)
- 用于与外部设备进行数据交换
处理器控制指令:
- NOP:空操作
- HLT:停机
- CLI/STI:清除/设置中断允许标志
- CLC/STC:清除/设置进位标志
指令类型应用示例: ; 数据传送 MOV AX, BX ; AX = BX MOV [SI], AX ; 将AX存入SI指向的内存 PUSH AX ; AX入栈 POP BX ; 出栈到BX ; 算术运算 ADD AX, 10 ; AX = AX + 10 SUB BX, CX ; BX = BX - CX MUL BL ; AX = AL × BL (无符号) IMUL CX ; DX:AX = AX × CX (有符号) DIV BL ; AL = AX ÷ BL的商, AH = 余数 INC AX ; AX = AX + 1 DEC BX ; BX = BX - 1 NEG AX ; AX = -AX CMP AX, BX ; 比较AX和BX(设置标志位) ; 逻辑运算 AND AX, 0FFH ; AX = AX AND 00FFH OR BX, CX ; BX = BX OR CX XOR AX, AX ; AX = 0(清零常用技巧) NOT BX ; BX = 按位取反 SHL AX, 1 ; AX左移1位(相当于乘以2) SHR BX, CL ; BX右移CL位 ; 控制转移 JMP LABEL ; 无条件跳转到LABEL JZ ZERO_LABEL ; 如果ZF=1,跳转 JNZ NONZERO_LABEL ; 如果ZF=0,跳转 CALL SUBROUTINE ; 调用子程序 RET ; 从子程序返回 INT 21H ; 调用DOS中断 IRET ; 从中断返回
6.1.4 指令执行过程
一条指令的执行通常包括以下几个阶段:
指令执行周期:
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ 取指 │ -> │ 译码 │ -> │ 执行 │ -> │ 访存 │ -> │ 写回 │
│ (IF) │ │ (ID) │ │ (EX) │ │ (MEM) │ │ (WB) │
└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
从PC指向的 解析操作码 ALU执行 读/写内存 将结果写
地址取指令 和操作数 运算 数据 回寄存器
- 取指(Fetch):根据程序计数器PC的值,从内存中读取指令
- 译码(Decode):解析指令的操作码和地址码,确定要执行的操作
- 执行(Execute):ALU执行算术或逻辑运算
- 访存(Memory):如果需要,从内存读取或写入数据
- 写回(Write Back):将运算结果写回寄存器或内存
- 更新PC:PC指向下一条指令
6.2 寻址方式
寻址方式是指确定操作数地址的方法。不同的寻址方式提供了访问操作数的灵活性,是指令系统设计的重要组成部分。
6.2.1 立即寻址
操作数直接包含在指令中,作为指令的一部分。
立即寻址示例: MOV AX, 100 ; 将立即数100送入AX寄存器 ADD BX, 50 ; BX = BX + 50 MOV CX, 0FF00H ; CX = 0FF00H 指令执行前:AX = ? 指令执行后:AX = 100 指令格式示意: ├─────────┼─────────┼──────────┤ │ 操作码 │ 寄存器 │ 立即数 │ │ MOV │ AX │ 100 │ └─────────┴─────────┴──────────┘
特点:
- 无需访问内存,执行速度最快
- 操作数范围受指令长度限制
- 不能用于目的操作数(不能给立即数赋值)
- 常用于赋初值、加减常数等操作
6.2.2 直接寻址
指令中直接给出操作数的内存有效地址。
直接寻址示例: MOV AX, [2000H] ; 将地址2000H处的字数据送入AX MOV [3000H], BX ; 将BX内容存入地址3000H处 MOV AL, DATA ; 将DATA变量(地址)处的字节送入AL 内存布局: 地址 内容 2000H 34H 2001H 12H 执行后:AX = 1234H(假设小端存储) 指令格式示意: ├─────────┼─────────┼──────────────┤ │ 操作码 │ 寄存器 │ 直接地址 │ │ MOV │ AX │ 2000H │ └─────────┴─────────┴──────────────┘
特点:
- 需要访问内存,速度较立即寻址慢
- 地址范围受指令长度限制
- 适用于访问固定的内存变量
6.2.3 寄存器寻址
操作数在寄存器中,指令中给出寄存器编号。
寄存器寻址示例: MOV AX, BX ; 将BX内容送入AX ADD CX, DX ; CX = CX + DX INC AX ; AX = AX + 1 执行前:BX = 1234H 执行后:AX = 1234H 指令格式示意: ├─────────┼─────────┼─────────┤ │ 操作码 │ 目的寄存器│ 源寄存器 │ │ MOV │ AX │ BX │ └─────────┴─────────┴─────────┘
特点:
- 无需访问内存,速度快
- 受寄存器数量限制(通常8-32个通用寄存器)
- 是最常用的寻址方式
6.2.4 寄存器间接寻址
寄存器中存放的是操作数的内存地址,而非操作数本身。
寄存器间接寻址示例:
MOV AX, [BX] ; 将BX指向的内存单元内容送入AX
MOV [SI], CX ; 将CX存入SI指向的内存单元
MOV DL, [DI] ; DL = [DI]
假设:BX = 2000H
[2000H] = 56H
[2001H] = 78H
执行后:AX = 7856H
指令格式示意:
├─────────┼─────────┼─────────┤
│ 操作码 │ 寄存器 │ 间接寄存器│
│ MOV │ AX │ [BX] │
└─────────┴─────────┴─────────┘
特点:
- 需要访问内存
- 可以实现指针功能,访问动态地址
- 常用于数组遍历、链表操作等
6.2.5 变址寻址
操作数地址 = 变址寄存器内容 + 位移量(偏移量)。
变址寻址示例:
MOV AX, [SI+10] ; 地址 = SI + 10
MOV AX, ARRAY[DI] ; 地址 = ARRAY的地址 + DI
MOV TABLE[BX], CX ; [TABLE的地址 + BX] = CX
常用于数组访问:
SI/DI作为数组索引,位移量作为数组起始地址
数组访问示意:
数组首地址 = ARRAY (1000H)
├─────┼─────┼─────┼─────┼─────┐
│ A │ B │ C │ D │ E │
│[1000] [1001] [1002] [1003] [1004]
└─────┴─────┴─────┴─────┴─────┘
▲
SI=2
MOV AL, ARRAY[SI] ; AL = 'C' (地址1002H)
特点:
- 适合数组、表格等结构化数据的访问
- 通过修改变址寄存器,可以方便地遍历数组
- x86中SI、DI、BX可作为变址寄存器
6.2.6 基址寻址
操作数地址 = 基址寄存器内容 + 位移量。
基址寻址示例:
MOV AX, [BX+20] ; 地址 = BX + 20
MOV AX, [BP-4] ; 地址 = BP - 4(访问栈帧中的局部变量)
MOV [BP+6], DX ; [BP+6] = DX
常用于访问结构体或栈帧中的局部变量:
BP通常指向栈帧基址,位移量指向参数或局部变量
栈帧结构示意:
高地址 │ 参数n │ BP+2n
│ ... │
│ 参数1 │ BP+4
│ 返回地址 │ BP+2
├─────────┤
BP-> │ 旧BP值 │ BP
│ 局部变量1 │ BP-2
│ 局部变量2 │ BP-4
低地址 │ ... │
MOV AX, [BP+4] ; 取第一个参数
MOV [BP-2], BX ; 存局部变量
特点:
- 常用于访问栈帧中的数据
- x86中BX、BP可作为基址寄存器
- BP默认使用SS段寄存器(栈段),BX默认使用DS段寄存器
6.2.7 基址变址寻址
操作数地址 = 基址寄存器 + 变址寄存器 + 位移量。
基址变址寻址示例:
MOV AX, [BX+SI+10] ; 地址 = BX + SI + 10
MOV [BP+DI-4], CX ; 地址 = BP + DI - 4
MOV DX, ARRAY[BX][SI] ; 地址 = ARRAY地址 + BX + SI
常用于二维数组访问:
BX = 行偏移(每行长度 × 行号)
SI = 列偏移(列号)
位移量 = 数组起始地址
二维数组布局(3行4列):
列0 列1 列2 列3
行0 [a00] [a01] [a02] [a03]
行1 [a10] [a11] [a12] [a13]
行2 [a20] [a21] [a22] [a23]
访问a[i][j]:地址 = 基址 + i×每行字节数 + j
6.2.8 相对寻址
转移地址 = 当前PC值 + 位移量。主要用于条件跳转和循环控制。
相对寻址示例: JMP LABEL ; 跳转到LABEL处 JZ NEXT ; 如果为零,跳转到NEXT LOOP START ; CX减1,如果不为零则跳转到START 位移量范围: - 短跳转(Short Jump):-128到+127字节 - 近跳转(Near Jump):-32768到+32767字节 - 远跳转(Far Jump):跨段跳转 代码示意: 地址 指令 0100H MOV AX, 0 0103H MOV CX, 10 0106H ADD AX, CX <-┐ 0108H LOOP 0106H ---┘ ; 相对位移 = 0106H - 010AH = -4 010AH MOV SUM, AX
特点:
- 代码可重定位(位置无关代码)
- 位移量是相对于当前指令地址的偏移
- 节省指令长度,不需要完整的绝对地址
6.2.9 寻址方式比较
| 寻址方式 | 操作数位置 | 访问速度 | 主要用途 | 示例 |
| 立即寻址 | 指令中 | 最快 | 赋常数、加减常数 | MOV AX, 100 |
| 寄存器寻址 | 寄存器 | 快 | 频繁使用的数据 | MOV AX, BX |
| 直接寻址 | 内存 | 较慢 | 访问固定地址变量 | MOV AX, [1000H] |
| 寄存器间接寻址 | 内存 | 较慢 | 指针操作 | MOV AX, [BX] |
| 变址寻址 | 内存 | 较慢 | 数组访问 | MOV AX, [SI+10] |
| 基址寻址 | 内存 | 较慢 | 结构体、栈帧访问 | MOV AX, [BP+4] |
| 基址变址寻址 | 内存 | 较慢 | 二维数组 | MOV AX, [BX+SI] |
| 相对寻址 | 内存 | 较慢 | 程序转移 | JMP LABEL |
6.3 CISC与RISC
6.3.1 CISC(复杂指令集计算机)
CISC特点:
- 指令系统庞大,指令数量多(通常数百条)
- 指令长度不固定
- 指令格式多样
- 寻址方式复杂(多种寻址方式)
- 指令执行周期不同(有的1周期,有的几百周期)
- 采用微程序控制
- 内存访问指令多样(不仅Load/Store)
代表处理器:Intel x86系列(8086、Pentium、Core i系列、Xeon)
设计思想:
- 用硬件实现复杂指令,减少软件负担
- 减少程序代码量(代码密度高)
- 接近高级语言的操作(如字符串操作、循环控制)
CISC指令示例(x86):
REP MOVSB ; 重复传送字符串,一条指令完成循环
ENTER 8, 0 ; 建立栈帧,包含多条微操作
LEAVE ; 释放栈帧
CMPSB ; 比较字符串
SCASB ; 扫描字符串
LOOP LABEL ; 循环控制(CX减1,不为零则跳转)
复杂指令的微程序实现:
REP MOVSB 实际执行:
while (CX != 0) {
[DI] = [SI]; // 传送字节
SI++; DI++; // 修改指针
CX--; // 计数器减1
}
CISC的优缺点:
- 优点:代码密度高,节省内存;编译器设计相对简单
- 缺点:硬件复杂,功耗高;流水线设计困难;部分复杂指令使用频率低
6.3.2 RISC(精简指令集计算机)
RISC特点:
- 指令数量少(通常几十条基本指令)
- 指令长度固定(32位或64位)
- 指令格式规整(简化译码)
- 寻址方式简单(通常3-5种)
- 大多数指令单周期执行
- 采用硬布线控制(速度快)
- 只有Load/Store指令访问内存
- 通用寄存器数量多(通常32个)
代表处理器:ARM、MIPS、RISC-V、SPARC、PowerPC
设计思想:
- 简化硬件设计,提高时钟频率
- 通过编译优化提高指令级并行性
- 80/20法则:20%的简单指令占程序执行的80%
RISC指令示例(MIPS): # C代码:a = b + c; # 假设a、b、c分别存放在栈偏移8、0、4处 LW $t0, 0($sp) # 从内存加载b到$t0 (Load Word) LW $t1, 4($sp) # 从内存加载c到$t1 ADD $t2, $t0, $t1 # $t2 = $t0 + $t1 SW $t2, 8($sp) # 将结果存入a (Store Word) # 特点:只有LW/SW访问内存,运算都在寄存器间进行
RISC的优缺点:
- 优点:硬件简单,功耗低;流水线效率高;易于实现超标量;编译优化空间大
- 缺点:代码密度较低(程序体积较大);某些操作需要多条指令
6.3.3 CISC与RISC比较
| 特性 | CISC | RISC |
| 指令数量 | 多(数百条) | 少(几十条) |
| 指令长度 | 变长 | 定长 |
| 指令周期 | 不固定(1-数百周期) | 大多数单周期 |
| 寻址方式 | 复杂多样(10+种) | 简单(3-5种) |
| 内存访问 | 多种指令可访问内存 | 只有Load/Store |
| 寄存器数量 | 较少(8-16个) | 较多(32个) |
| 控制方式 | 微程序控制 | 硬布线控制 |
| 流水线实现 | 较复杂 | 较简单 |
| 代码密度 | 高 | 较低 |
| 功耗 | 较高 | 较低 |
| 设计复杂度 | 硬件复杂 | 编译器复杂 |
发展趋势:
- 现代处理器融合两种技术(RISC内核,CISC接口)
- x86处理器(如Intel Core系列)内部将CISC指令译码为类RISC的微操作(μops)
- ARM处理器增加部分复杂指令提高代码密度(Thumb指令集)
- 两者界限逐渐模糊,取长补短
6.4 汇编语言基础
6.4.1 汇编语言概述
汇编语言是机器语言的符号化表示,使用助记符代替二进制操作码,使用标号和符号代替地址。
汇编语言的特点:
- 与机器语言一一对应,是低级语言
- 直接控制硬件,效率高
- 可移植性差(与特定处理器绑定)
- 编程效率低,但执行效率高
汇编语言的组成:
- 指令语句:可执行语句,产生机器码(如MOV、ADD、JMP)
- 伪指令语句:指导汇编器工作,不产生机器码(如DB、DW、SEGMENT)
- 宏指令:用户自定义的指令序列,用于代码复用
6.4.2 汇编语言格式
典型的汇编语句格式:
[标号:] 操作码 [操作数] [;注释]
示例:
START: MOV AX, DATA ; 初始化数据段寄存器
ADD AX, BX ; AX = AX + BX
JMP LOOP1 ; 跳转到LOOP1
; 标号START表示该指令的地址,可用于跳转
; 操作码MOV、ADD、JMP指明操作类型
; 操作数指定参与运算的数据
; 分号后面是注释,不影响程序执行
语句组成:
- 标号:可选,代表指令的符号地址,用于跳转和调用
- 操作码:必需的,指定操作类型(指令助记符或伪指令)
- 操作数:指令需要的操作数,可以是0-3个
- 注释:可选,以分号开头,用于说明代码功能
6.4.3 常用伪指令
段定义伪指令:
SEGMENT/ENDS 定义段的开始和结束
ASSUME 设定段寄存器与段的对应关系
示例:
DATA SEGMENT ; 数据段开始
VAR1 DB 10 ; 定义变量
DATA ENDS ; 数据段结束
CODE SEGMENT ; 代码段开始
ASSUME CS:CODE, DS:DATA ; 设定CS指向CODE段,DS指向DATA段
...
CODE ENDS ; 代码段结束
数据定义伪指令:
DB 定义字节(8位)
DW 定义字(16位)
DD 定义双字(32位)
DQ 定义四字(64位)
DT 定义十字节(80位,用于BCD码)
示例:
DATA SEGMENT
VAR1 DB 10 ; 定义字节变量,初值为10
VAR2 DW 1000H ; 定义字变量,初值为1000H
VAR3 DD 12345678H ; 定义双字变量
VAR4 DB 'HELLO' ; 定义字符串,每个字符占一个字节
ARRAY DB 10 DUP(0) ; 定义10个字节的数组,初值都为0
TABLE DW 5 DUP(1, 2) ; 定义10个字的数组:1,2,1,2,1,2,1,2,1,2
BUF DB 100 DUP(?) ; 定义100字节的缓冲区,未初始化
DATA ENDS
其他伪指令:
ORG 设定程序起始地址
END 程序结束标记,可指定入口点
EQU 常量定义(等价)
PROC/ENDP 过程(子程序)定义
PUBLIC/EXTRN 声明公共符号/外部符号
示例:
ORG 100H ; 从地址100H开始存放代码
MAX EQU 100 ; 定义常量MAX=100
PI EQU 3.14159 ; 定义常量PI
MAIN PROC NEAR ; 定义过程MAIN,NEAR表示近过程
...
RET
MAIN ENDP
END MAIN ; 程序结束,入口点为MAIN
6.4.4 x86常用指令详解
数据传送指令:
MOV 传送数据(寄存器、内存之间) MOVSX 带符号扩展传送 MOVZX 带零扩展传送 XCHG 交换两个操作数的值 PUSH 将数据压入堆栈 POP 从堆栈弹出数据 LEA 取有效地址(Load Effective Address) LDS/LES 取段地址到DS/ES寄存器 PUSHF/POPF 标志寄存器入栈/出栈 示例: MOV AX, BX ; AX = BX MOV AL, [SI] ; AL = [SI] MOV [DI], DX ; [DI] = DX MOVSX EAX, AX ; EAX = 符号扩展的AX(16位扩展到32位) MOVZX EBX, BL ; EBX = 零扩展的BL(8位扩展到32位) XCHG AX, BX ; AX和BX交换 PUSH AX ; SP = SP - 2, [SP] = AX POP BX ; BX = [SP], SP = SP + 2 LEA SI, ARRAY ; SI = ARRAY的地址(不是内容)
算术运算指令:
ADD 加法 ADC 带进位加法(用于多字节加法) SUB 减法 SBB 带借位减法(用于多字节减法) MUL 无符号乘法 IMUL 有符号乘法 DIV 无符号除法 IDIV 有符号除法 INC 加1 DEC 减1 NEG 求负(取补码) CMP 比较(执行减法但不保存结果,只影响标志位) 示例: ADD AX, BX ; AX = AX + BX ADC DX, 0 ; DX = DX + 0 + CF(带进位加法) SUB AX, 10 ; AX = AX - 10 SBB DX, 0 ; DX = DX - 0 - CF(带借位减法) MUL BL ; AX = AL × BL(无符号乘法) IMUL CX ; DX:AX = AX × CX(有符号乘法,16位×16位=32位) DIV BL ; AL = AX ÷ BL的商, AH = 余数(无符号除法) IDIV BX ; AX = DX:AX ÷ BX的商, DX = 余数(有符号除法) INC AX ; AX = AX + 1(不影响CF) DEC BX ; BX = BX - 1(不影响CF) NEG AX ; AX = -AX(按位取反后加1) CMP AX, BX ; 比较AX和BX(AX - BX,只影响标志位)
逻辑运算指令:
AND 逻辑与 OR 逻辑或 XOR 逻辑异或 NOT 逻辑非(取反) TEST 测试(执行AND但不保存结果,只影响标志位) SHL/SAL 逻辑/算术左移(低位补0) SHR 逻辑右移(高位补0) SAR 算术右移(高位补符号位) ROL 循环左移 ROR 循环右移 RCL 带进位循环左移 RCR 带进位循环右移 示例: AND AX, 0FFH ; AX = AX AND 00FFH(屏蔽高8位) OR BL, 80H ; BL = BL OR 80H(设置最高位为1) XOR AX, AX ; AX = 0(清零,比MOV AX, 0更快) NOT BX ; BX = 按位取反 TEST AL, 80H ; 测试AL的最高位是否为1(设置ZF) SHL AX, 1 ; AX左移1位,相当于AX × 2 SHR BX, CL ; BX右移CL位 SAR AX, 2 ; AX算术右移2位(保持符号位) ROL AX, 1 ; AX循环左移1位(最高位移到最低位和CF) RCL AX, 1 ; AX带进位循环左移1位
控制转移指令:
JMP 无条件跳转 JE/JZ 等于/为零跳转(ZF=1) JNE/JNZ 不等于/非零跳转(ZF=0) JG/JNLE 大于跳转(有符号,ZF=0且SF=OF) JGE/JNL 大于等于跳转(有符号,SF=OF) JL/JNGE 小于跳转(有符号,SF≠OF) JLE/JNG 小于等于跳转(有符号,ZF=1或SF≠OF) JA/JNBE 高于跳转(无符号,CF=0且ZF=0) JAE/JNB 高于等于跳转(无符号,CF=0) JB/JNAE 低于跳转(无符号,CF=1) JBE/JNA 低于等于跳转(无符号,CF=1或ZF=1) JC/JNC 有/无进位跳转 JO/JNO 溢出/无溢出跳转 JS/JNS 为负/为正跳转 CALL 调用子程序 RET 从子程序返回 INT 中断调用 IRET 从中断返回 LOOP 循环(CX减1,不为零则跳转) LOOPE/LOOPZ 等于/为零循环 LOOPNE/LOOPNZ 不等于/非零循环 示例: JMP LABEL ; 无条件跳转到LABEL JZ ZERO_LABEL ; 如果ZF=1(结果为0),跳转 JNZ NONZERO_LABEL ; 如果ZF=0(结果非0),跳转 JG GREATER_LABEL ; 有符号数比较,大于则跳转 JA ABOVE_LABEL ; 无符号数比较,高于则跳转 CALL SUBROUTINE ; 调用子程序(PUSH IP, JMP) RET ; 从子程序返回(POP IP) INT 21H ; 调用DOS中断21H IRET ; 从中断返回(恢复FLAGS、CS、IP)
6.5 汇编程序设计
6.5.1 程序基本结构
一个完整的汇编程序通常包括数据段、代码段和堆栈段的定义。
; 完整汇编程序示例:显示"Hello World!"
DATA SEGMENT
MSG DB 'Hello World!', '$' ; 定义字符串,$表示结束符
COUNT DW 10 ; 定义字变量
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE, DS:DATA ; 设定段寄存器
START:
MOV AX, DATA
MOV DS, AX ; 初始化数据段寄存器
LEA DX, MSG ; DX指向字符串
MOV AH, 09H ; DOS功能号:显示字符串
INT 21H ; 调用DOS中断
MOV AH, 4CH ; DOS功能号:返回DOS
INT 21H ; 调用DOS中断
CODE ENDS
END START ; 程序结束,入口点为START
程序结构说明:
- 数据段(DATA SEGMENT):存放变量、常量、字符串等数据
- 代码段(CODE SEGMENT):存放程序指令
- 堆栈段(STACK SEGMENT):存放临时数据和返回地址
- 初始化:程序开始时需要初始化DS寄存器
- 中断调用:使用INT指令调用DOS功能
6.5.2 分支程序设计
分支程序通过条件判断实现不同的执行路径。
; 比较两个数的大小,将大数存入MAX
DATA SEGMENT
NUM1 DW 50
NUM2 DW 30
MAX DW ?
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE, DS:DATA
START:
MOV AX, DATA
MOV DS, AX
MOV AX, NUM1
CMP AX, NUM2 ; 比较NUM1和NUM2
JGE LARGER ; 如果NUM1 >= NUM2,跳转到LARGER
MOV AX, NUM2 ; 否则,AX = NUM2
LARGER:
MOV MAX, AX ; MAX = 较大数
MOV AH, 4CH
INT 21H
CODE ENDS
END START
多分支结构:
; 根据AL的值(0-2)跳转到不同处理分支
CMP AL, 0
JE CASE_0
CMP AL, 1
JE CASE_1
CMP AL, 2
JE CASE_2
JMP DEFAULT_CASE
CASE_0:
; 处理情况0
JMP END_SWITCH
CASE_1:
; 处理情况1
JMP END_SWITCH
CASE_2:
; 处理情况2
JMP END_SWITCH
DEFAULT_CASE:
; 默认处理
END_SWITCH:
6.5.3 循环程序设计
循环程序用于重复执行一段代码。
; 计算1到100的累加和
DATA SEGMENT
SUM DW ?
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE, DS:DATA
START:
MOV AX, DATA
MOV DS, AX
MOV CX, 100 ; 循环计数器,从100到1
MOV AX, 0 ; 累加器清零
LOOP1:
ADD AX, CX ; AX = AX + CX
DEC CX ; 计数器减1
JNZ LOOP1 ; 如果不为零,继续循环
MOV SUM, AX ; 保存结果(结果为5050)
MOV AH, 4CH
INT 21H
CODE ENDS
END START
LOOP指令循环:
; 使用LOOP指令实现循环
MOV CX, 10 ; 循环10次
NEXT:
; 循环体代码
LOOP NEXT ; CX减1,如果不为零则跳转到NEXT
; 带条件的循环(LOOPE/LOOPNE)
MOV CX, 100
MOV SI, 0
SEARCH:
CMP BUF[SI], AL ; 比较
LOOPE SEARCH ; 相等且CX≠0时继续循环
嵌套循环:
; 嵌套循环示例:延时程序
MOV CX, 1000 ; 外层循环次数
OUTER_LOOP:
PUSH CX ; 保存外层计数器
MOV CX, 1000 ; 内层循环次数
INNER_LOOP:
NOP ; 空操作
LOOP INNER_LOOP
POP CX ; 恢复外层计数器
LOOP OUTER_LOOP
6.5.4 子程序设计
子程序(过程/函数)用于封装可复用的代码块。
; 子程序示例:计算阶乘
DATA SEGMENT
N DW 5
RESULT DW ?
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE, DS:DATA
; 阶乘子程序
; 入口:AX = n
; 出口:AX = n!
; 使用递归实现
FACT PROC NEAR
CMP AX, 1
JLE BASE_CASE ; 如果n <= 1,返回1
PUSH AX ; 保存n
DEC AX ; AX = n-1
CALL FACT ; 递归调用
POP BX ; 恢复n到BX
MUL BX ; AX = AX * BX = (n-1)! * n
JMP DONE
BASE_CASE:
MOV AX, 1
DONE:
RET
FACT ENDP
START:
MOV AX, DATA
MOV DS, AX
MOV AX, N
CALL FACT
MOV RESULT, AX ; 保存5! = 120
MOV AH, 4CH
INT 21H
CODE ENDS
END START
参数传递方法:
; 方法1:寄存器传递参数
; 入口:AX = 参数1, BX = 参数2
; 出口:AX = 返回值
ADD_PROC PROC
ADD AX, BX
RET
ADD_PROC ENDP
; 方法2:堆栈传递参数
; 调用前:PUSH 参数2, PUSH 参数1
ADD_PROC2 PROC
PUSH BP
MOV BP, SP
MOV AX, [BP+4] ; 取参数1
MOV BX, [BP+6] ; 取参数2
ADD AX, BX
POP BP
RET 4 ; 返回并清理4字节参数
ADD_PROC2 ENDP
; 调用示例:
PUSH 30 ; 参数2
PUSH 20 ; 参数1
CALL ADD_PROC2 ; AX = 50
6.5.5 数组和字符串操作
; 数组操作示例:查找最大值
DATA SEGMENT
ARRAY DW 23, 56, 12, 89, 34, 78, 45, 67
COUNT EQU 8
MAX DW ?
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE, DS:DATA
START:
MOV AX, DATA
MOV DS, AX
LEA SI, ARRAY ; SI指向数组
MOV CX, COUNT ; 元素个数
MOV AX, [SI] ; 假设第一个元素最大
DEC CX ; 剩余元素数
ADD SI, 2 ; 指向下一个元素
FIND_MAX:
CMP AX, [SI] ; 比较当前最大值与下一个元素
JGE NEXT_ELEM ; 如果AX >= [SI],跳过
MOV AX, [SI] ; 更新最大值
NEXT_ELEM:
ADD SI, 2 ; 指向下一个元素
LOOP FIND_MAX
MOV MAX, AX ; 保存最大值
MOV AH, 4CH
INT 21H
CODE ENDS
END START
字符串操作指令:
; x86字符串操作指令(配合重复前缀使用)
MOVSB ; 传送字节:[DI] = [SI], SI±1, DI±1
MOVSW ; 传送字:[DI] = [SI], SI±2, DI±2
CMPSB ; 比较字节:[SI] - [DI]
SCASB ; 扫描字节:AL - [DI]
LODSB ; 加载字节:AL = [SI]
STOSB ; 存储字节:[DI] = AL
; 重复前缀
REP ; 重复CX次
REPE/REPZ ; 相等/为零时重复(CX≠0且ZF=1)
REPNE/REPNZ ; 不相等/非零时重复(CX≠0且ZF=0)
; 示例:复制字符串
LEA SI, SOURCE
LEA DI, DEST
MOV CX, 100
CLD ; 清除方向标志(DF=0,地址递增)
REP MOVSB ; 复制100字节
; 示例:查找字符
LEA DI, STRING
MOV AL, '$' ; 查找结束符
MOV CX, 100
CLD
REPNE SCASB ; 查找字符
6.6 中断与异常处理
6.6.1 中断的基本概念
中断是指在程序执行过程中,由于某种事件发生而暂停当前程序的执行,转去执行相应的中断处理程序,处理完毕后再返回原程序继续执行。
中断的作用:
- 实现CPU与I/O设备的并行工作
- 处理突发事件(如电源故障)
- 实现多任务调度
- 提供系统调用接口
中断的分类:
- 硬件中断:由外部设备产生
- 可屏蔽中断(INTR):可通过IF标志屏蔽
- 非屏蔽中断(NMI):用于紧急情况,不可屏蔽
- 软件中断:由INT指令产生,用于系统调用
- 异常:CPU执行过程中产生的错误(如除零、页错误)
中断处理流程:
正常程序执行: A -> B -> C -> D -> E
↓
中断发生(如按键)
↓
保存现场(FLAGS、CS、IP)
↓
执行中断处理程序
↓
恢复现场
↓
返回:-> D -> E
6.6.2 中断向量表
中断向量表是存放中断处理程序入口地址的表格。在x86实模式下,位于内存最低1KB(地址00000H-003FFH)。
每个中断向量占4字节:
- 低2字节:IP(偏移地址)
- 高2字节:CS(段地址)
中断向量表示意: 地址 中断号 说明 00000H 0 除零错误 00004H 1 单步中断 00008H 2 非屏蔽中断 0000CH 3 断点中断 00010H 4 溢出中断 00014H 5 打印屏幕 00080H 20H DOS中断 00084H 21H DOS系统调用(最常用) 中断向量表结构(每个表项4字节): ├─────────┼─────────┤ │ IP偏移 │ CS段址 │ │ 16位 │ 16位 │ └─────────┴─────────┘
常用DOS中断(INT 21H):
| 功能号(AH) | 功能 | 入口参数 | 出口参数 |
| 01H | 键盘输入并回显 | 无 | AL = 输入字符 |
| 02H | 显示输出 | DL = 输出字符 | 无 |
| 09H | 显示字符串 | DS:DX = 字符串地址($结尾) | 无 |
| 0AH | 缓冲输入 | DS:DX = 缓冲区地址 | 缓冲区填充 |
| 4CH | 返回DOS | AL = 返回码 | 无 |
6.6.3 中断处理程序设计
; 自定义中断处理程序示例
CODE SEGMENT
ASSUME CS:CODE, DS:CODE
; 中断处理程序:显示一个字符'A'
MY_INT PROC FAR
PUSH AX ; 保存寄存器
PUSH DX
MOV DL, 'A'
MOV AH, 02H
INT 21H ; 显示字符
POP DX ; 恢复寄存器
POP AX
IRET ; 中断返回
MY_INT ENDP
; 设置中断向量
MOV AX, 0
MOV ES, AX ; ES = 0(中断向量表段)
MOV BX, 4 * 60H ; 中断号60H的向量地址
MOV ES:[BX], OFFSET MY_INT ; 设置IP
MOV ES:[BX+2], CS ; 设置CS
; 调用自定义中断
INT 60H
CODE ENDS
END
6.7 练习题
一、选择题
1. 一条指令通常由( )和地址码两部分组成。 A. 操作数 B. 操作码 C. 指令码 D. 地址
2. 在立即寻址方式中,操作数存放在( ) A. 寄存器 B. 内存单元 C. 指令中 D. 栈中
3. RISC的特点不包括( ) A. 指令数量少 B. 指令长度固定 C. 采用微程序控制 D. 只有Load/Store访问内存
4. 下列指令中,属于数据传送指令的是( ) A. ADD B. MOV C. JMP D. CMP
5. 汇编语言中,用于定义字数据的伪指令是( ) A. DB B. DW C. DD D. DQ
6. 在基址寻址中,有效地址等于( ) A. 基址寄存器内容 B. 基址寄存器内容 + 位移量 C. 变址寄存器内容 D. 指令中的地址
7. 执行CALL指令时,CPU首先将( )压入堆栈 A. 段地址 B. 偏移地址 C. 标志寄存器 D. 下一条指令地址
8. 下列哪种寻址方式执行速度最快( ) A. 立即寻址 B. 直接寻址 C. 寄存器寻址 D. 寄存器间接寻址
9. x86处理器采用( )指令集架构 A. CISC B. RISC C. VLIW D. EPIC
10. 中断向量表在实模式下位于内存的( ) A. 高端地址 B. 低端地址(00000H-003FFH) C. 中间地址 D. 由程序指定
二、填空题
1. 寻址方式是指确定_______地址的方法。
2. CISC是_______的缩写,RISC是_______的缩写。
3. 在x86汇编中,MOV AX, [BX]采用的是_______寻址方式。
4. 汇编语言的语句由_______、_______、_______和注释组成。
5. 伪指令_______用于定义字节数据,_______用于定义字数据。
6. 指令执行周期通常包括取指、_______、_______、访存和写回五个阶段。
7. x86处理器有_______个通用寄存器(16位模式下)。
8. LOOP指令使用_______寄存器作为计数器。
9. 在堆栈操作中,PUSH指令使SP_______(增加/减少),POP指令使SP_______(增加/减少)。
10. IRET指令用于从_______返回。
三、简答题
1. 简述立即寻址、直接寻址和寄存器寻址的区别。
2. 比较CISC和RISC的主要特点。
3. 什么是汇编语言?它与机器语言和高级语言有什么区别?
4. 简述子程序调用和返回的过程。
5. 什么是中断?中断处理的基本过程是什么?
6. 简述MESI协议中的四种状态。
四、程序设计题
1. 编写汇编程序,计算数组中10个元素的累加和,并求平均值。
2. 编写汇编程序,实现两个16位无符号数的乘法,结果存入32位变量。
3. 编写汇编程序,从键盘输入一个字符串,统计其中字母的个数。
4. 编写子程序,实现两个双字(32位)数的加法。
参考答案:
一、选择题:1.B 2.C 3.C 4.B 5.B 6.B 7.D 8.C 9.A 10.B
二、填空题:
1. 操作数 2. 复杂指令集计算机、精简指令集计算机 3. 寄存器间接 4. 标号、操作码、操作数 5. DB、DW 6. 译码、执行 7. 8(AX, BX, CX, DX, SP, BP, SI, DI) 8. CX 9. 减少、增加 10. 中断
三、简答题:
1. 立即寻址的操作数在指令中,执行最快但只能用于源操作数;直接寻址的操作数在内存中,指令中包含操作数的内存地址;寄存器寻址的操作数在寄存器中,执行速度快,是最常用的寻址方式。
2. CISC指令数量多、长度可变、寻址方式复杂、采用微程序控制;RISC指令数量少、长度固定、寻址方式简单、采用硬布线控制、只有Load/Store访问内存。
3. 汇编语言是机器语言的符号化表示,用助记符代替二进制操作码。与机器语言相比更易读写,与高级语言相比更接近硬件,执行效率高但可移植性差。
4. CALL指令将返回地址压入堆栈,然后跳转到子程序;子程序执行RET指令时,从堆栈弹出返回地址并跳转回去。
5. 中断是程序执行过程中因某种事件暂停当前程序,转去执行中断处理程序,完成后返回原程序。过程包括:中断请求、中断响应、保存现场、执行处理程序、恢复现场、中断返回。
6. M(Modified):已修改,独占;E(Exclusive):独占,未修改;S(Shared):共享;I(Invalid):无效。
四、程序设计题:
1. 略(参考6.5.3循环程序示例,增加除法求平均) 2. 使用MUL指令,16位×16位=32位结果,高16位在DX,低16位在AX 3. 使用INT 21H功能0AH输入字符串,遍历判断每个字符是否在'A'-'Z'或'a'-'z'范围内 4. 先加低16位,再带进位加高16位(使用ADC指令)