====== 第六章:指令系统与汇编 ====== * [[#6.1 机器指令|6.1 机器指令]] * [[#6.2 寻址方式|6.2 寻址方式]] * [[#6.3 CISC与RISC|6.3 CISC与RISC]] * [[#6.4 汇编语言基础|6.4 汇编语言基础]] * [[#6.5 汇编程序设计|6.5 汇编程序设计]] * [[#6.6 中断与异常处理|6.6 中断与异常处理]] * [[#6.7 练习题|6.7 练习题]] ---- ===== 章节概述 ===== 本章介绍计算机指令系统的基本概念、寻址方式、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指令)