• 授课老师:廖建明老师
  • 教材:《微机原理与接口技术》(第4版). 吴宁,乔亚男主编. 清华大学出版社
  • 参考教材:《汇编语言程序设计》. 廖建明主编.清华大学出版社

# 第一章 微型计算机基础概论

# 计算机的工作原理

计算机中的指令执行过程:取指令 -> 指令译码 -> 读取操作数 -> 执行命令 -> 存放结果

计算机基本组成结构

指令的顺序工作方式和并行流水线工作方式

  • 冯 • 诺依曼计算机的工作原理:
    • 存储程序工作方式
    • 运算器为核心
  • 特点:
    • 存储程序,共享数据,顺序执行;
    • 属于顺序处理机,适于确定的算法和数值处理。
  • 不足:
    • 与存储器间有大量数据交互,对总线要求很高;
    • 执行顺序由程序决定,对大型复杂任务较难处理;
    • 以运算器为核心,处理效率较低; 由PC控制执行顺序,难以进行真正的并行处理。

非冯 • 诺依曼计算机:并行性

# 微机系统组成

  • 微处理器:运算器单元+控制器单元+寄存器单元+内部总线,具有CPU全部功能的大规模集成电路芯片。
  • 微型机:微处理器+内存+I/O接口+系统总线+电源+输入/输出设备+外存设备
  • 微机系统:微型机+系统软件+应用软件

# 第二章 微处理器

# 8086/8088 特点、工作模式

特点:并行流水线、内存空间分段管理、多处理器系统

工作模式:最小(单处理器,不必接总线控制器)/最大(多处理器,需总线控制器)

最小模式下的总线连接示意图

最大模式下的总线连接示意图

工作模式选择:MN/MXMN/\overline{MX}引脚

  • MN/MX=0MN/\overline{MX}=0:最大模式
  • MN/MX=1MN/\overline{MX}=1:最小模式

# 8086/8088 引线及功能

# 地址线和数据线

  • AD0AD7AD0-AD7:低 8 位地址和低 8 位数据信号(分时复用)。传送地址时单向,传送数据时双
  • A8A15A8-A15:8位地址信号
  • A16A19/S3S6A16-A19/S3-S6:高4位地址信号,与状态信号分时复用
  • WR\overline{WR}:写信号
  • RD\overline{RD}:读信号
  • IO/MIO/\overline{M}:访问内存/访问接口
  • DEN\overline{DEN}:低电平有效时,数据总线上数据有效,允许进行读/写操作
  • DT/RDT/\overline{R}(Data Transmit/ Receive):为“1”时 CPU 向存储器或 I/O 传送,否则为反向
  • ALEALE:地址锁存信号,当其为高时表示地址线上地址有效。一般用它将地址锁存到一个锁存器中
  • RESETRESET:复位信号。当其为高时将完成CPU内部复位。复位后CPU内部寄存器的值如下表

复位后 CPU 的内部寄存器状态

例题

  • READYREADY:外部同步控制输入信号,高电平有效(8088 与内存/外设之间在一个总线周期内的时钟配合信号)

# 中断请求和响应信号

  • INTRINTR:可屏蔽中断请求输入端
  • NMINMI: 非屏蔽中断请求输入端
  • INTA\overline{INTA}:中断响应输出端

# 总线保持信号

  • HOLDHOLD:总线保持请求信号输入端。当 CPU 以外的其他设备要求占用总线时,通过该引脚向 CPU 发出请求(外设 -> CPU)
  • HLDAHLDA:总线保持响应信号输出端。CPU 对 HOLD 信号的响应信号(CPU -> 外设)

# 8088 和 8086 CPU 引线的差异

  • 数据总线宽度不同
    • 8088的外部总线宽度是8位,8086为16位。
  • 访问存储器和I/O控制的信号含义不同
    • 8088——IO/M=0IO/\overline{M}=0 表示访问内存;
    • 8086——IO/M=1\overline{IO}/M=1 表示访问内存。

# 8088/8086 内部结构

  • 执行单元 EU
  • 总线接口单元 BIU

8086CPU结构

执行单元 EU

  • 组成:
    • ALU
    • 8 个通用寄存器
    • 1 个标志寄存器
    • EU 部分的控制电路
  • 功能:
    • 指令译码
    • 指令执行
    • 暂存中间运算结果
    • 保存运算结果特征

总线接口单元 BIU

  • 组成:
    • 地址加法器
    • 4 个段寄存器
    • 指令指针 IP
    • 总线控制逻辑
  • 功能:
    • 从内存中取指令到指令队列(指令队列是并行流水线工作的基础)
    • 负责与内存或 I/O 接口之间的数据传送
    • 在执行转移程序时,BIU 清除指令队列,从指定的新地址取指令,并立即传给执行单元执行

# 8088/8086 内部寄存器

  • 16 位寄存器
    • 8个通用寄存器
    • 4个段寄存器
    • 2个控制寄存器

# 通用寄存器

  • 数据寄存器:AX、BX、CX、DX
    • AX 分为 AH:AL,以此类推
    • AX: Add
    • BX: Base 基址寄存器
    • CX: Count 计数
    • DX: Data
      • 在间接寻址的 I/O 指令中存放 I/O 端口地址
      • 在 32 位乘除法运算时,存高 16 位数
  • 地址指针寄存器:SP、BP
    • SP: Stack Pointer 栈顶的偏移地址
    • BP: Base Pointer 基址指针寄存器,访问内存时存放内存单元的偏移地址
      • BX BP 作为通用寄存器,二者均可用于存放数据
      • 作为基址寄存器,用 BX 表示所寻找的数据在数据段;用 BP 则表示数据在堆栈段
  • 变址寄存器 Index Register:SI、DI 存放数据在内存中的地址
    • SI (Source Index):源
    • DI (Destination Index):目标

# 控制寄存器

  • IP (Instruction Pointer):指令指针寄存器,其内容为下一条要执行指令的偏移地址
  • FLAGS:CF、SF、AF、PF、OF、ZF、IF、TF、DF
    • CF (Carry Flag):进位标志位
    • SF (Signal Flag):符号标志位
    • AF (Auxility Flag):辅助 CF。若 Bit3 向 Bit4 有进位(借位),AF=1
    • PF (Parity Flag):奇偶标志位,运算结果的低 8 位中 1 的个数为偶数时 PF=1
    • OF (Overflow Flag):溢出标志位
    • ZF (Zero Flag):零标志位
    • TF (Trap Flag):陷阱标志位,TF=1 使 CPU 处于单步执行指令
    • IF (Interrupt Flag):允许中断标志位
    • DF (Direction Flag):方向标志位。在数据串操作时确定操作的方向。

例题

# 段寄存器

  • 作用:用于存放相应逻辑段的段基地址
  • 8086/8088 内存中逻辑段的类型:代码段、数据段、附加段、堆栈段
  • 8086/8088 内存中逻辑段的数量
    • 最多为 64K 个
    • 程序中同时可以使用4个段,分别由CS、DS、ES和SS四个段寄存器指示。
  • CS (Code Segment):代码段寄存器,存放代码段的段基地址。
  • DS (Data Segment):数据段寄存器,存放数据段的段基地址。
  • ES (Extended Segment):附加段寄存器,存放数据段的段基地址。
  • SS (Stack Segment):堆栈段寄存器,存放堆栈段的段基地址。

段寄存器

# 8088/8086 存储器组织

内存地址分为物理地址、逻辑地址。

物理地址:8086/8088 CPU有 20 根地址线,它可以产生 20 位的地址码,寻址范围为 2202^20,即 1 兆字节空间。

字单元:任何两个相邻字节单元构成,16 bits

  • 子单元地址:字节较小地址
  • 存放规则:小端

例题

  • 32-bit 逻辑地址 = 16-bit 段基地址 :(拼接) 16-bit 段内地址
  • 20-bit 物理地址:16-bit 段基地址 *16+ 16-bit 偏移地址
    • 段首的偏移地址 = 0000H

例题

# 8086/8088 的存储器段结构的特点

  1. 段大小 <= 64KB
  2. 段首地址为一个小节的首地址
  • 小节:每 16 Bytes 为一小节
  • 小节的首地址最低位为 0000
  1. 逻辑段在物理上可能是:可邻接的、间隔的、部分重叠的或完全重叠的
  2. 在任一时刻,一个程序只能访问4个当前段中的内容

例题

# 逻辑地址、物理地址转换

物理地址 = 段基地址 << 4 + 偏移地址

例题

# 堆栈

堆栈:

  • 特定的存储区,访问该存储区一般需要按照专门的规则进行操作
  • 主要用于暂存数据以及在过程调用或处理中断时保存断点信息
  • 一般分为专用堆栈存储器软件堆栈
    • 专用堆栈存储器:按堆栈的工作方式专门设计的存储器
    • 软件堆栈:由程序设计人员用软件在内存中划出的一块存储区作为堆栈来使用。8086/8088采用这种方式。 堆栈组成:
  • 栈底:固定,是堆栈存储区最大地址单元
  • 栈顶:浮动,是最后存入信息的存储单元
  • 栈顶指针 SP:指示栈顶单元
  • 数据在堆栈中以字(16 bits)为单位小端存放
  • 初始化时,SP = 栈底 + 2 = 堆栈长度
  • 堆栈长度 <= 64KB
  • SP 始终表示堆栈段基址与栈顶之间的距离
  • 程序设置多个堆栈段

堆栈

例题

# 8088 系统总线

总线 Bus:是一组导线和相关的控制、驱动电路的集合,它是计算机系统各部件之间传输地址、数据和控制信息的通道。

分类:

  • 地址总线 AB
  • 数据总线 DB
  • 控制总线 CB

最小模式下的系统总线

最小模式下不需要 8288 总线控制器,而最大模式下需要。

  • 8282:锁存器,连接地址线(因为要和数据线复用,所以要配合 ALE 将地址存进锁存器)
  • 8284:时钟发生器
  • 8286:双向总线驱动器,连接(内外部的)数据总线

最大模式下的系统总线

  • 8288:总线控制器,支持 CPU

# *****总线时序、总线周期

  • 时序:CPU各引脚信号在时间上的关系
  • 总线周期:CPU 完成一次访问内存(或I/O接口)操作所需要的时间
    • 一个总线周期至少包括 4 个时钟周期

8088最小模式下的读周期

  • T1上 输出地址
  • T1下 锁地址
  • T2 输出状态,设置 RD\overline{RD}DEN\overline{DEN}
  • T3 输入数据
  • T4 复位,关状态输出

但这个状态在最小模式中没有用,在最大模式中才会用到(

8088最小模式下的写周期

写的区别除了 WR\overline{WR}DT/RDT/\overline{R}、外,还有 AD 线在地址输出结束后必须立即进行数据输出。

# IA-32 微处理器和工作方式

Intel公司将 80286 之后的 80X86 32 位微处理器称为 IA(Intel Architecture)-32 结构(现在是 AMD64)

# IA-32 微处理器历史

应该不是重点,所以就比较略了。

# 80286
  • 实地址模式、虚地址保护模式
  • CPU 被分为:
    • 总线部件 BU
    • 地址部件 AU
    • 执行部件 EU
    • 指令部件 IU
# 80386
  • 实地址模式、保护模式和虚拟 8086 模式
  • CPU 被分为:
    • 总线接口单元 BIU
    • 指令预取单元 IPU
    • 指令译码单元 IDU
    • 执行单元 EU
    • 分段单元 SU
    • 分页单元 PU
  • 分页存储
# 80486
  • 突发传送方式(成块数据传送)
# Pentium
  • 与80X86系列微处理器兼容
  • RISC型超标量结构(处理器包含多个指令单元和指令流水线)
  • 高性能浮点运算器
  • 双重分离式高速缓存(分离指令缓存和数据缓存)
  • 64位数据总线
  • 分支指令预测
  • 常用指令固化与微代码改进(把常用的指令改用硬件实现,而不使用微程序方式)
  • 系统管理方式程序 SMM(电源管理、为操作系统和正在运行的程序提供安全性)

# IA-32 主要寄存器

通用寄存器:8 个 32 位通用寄存器

通用寄存器

指令指针和标志寄存器:

指令指针和标志寄存器

段寄存器和系统地址寄存器:

段寄存器和系统地址寄存器

控制寄存器:

控制寄存器

# IA-32 处理器工作方式

# 实模式
  • 兼容 8086
  • 32条地址线中只有低20条地址线起作用,可寻址1MB的物理地址空间
  • 无多任务处理
# 保护模式
  • 32条地址可寻址4GB的物理存储器空间
  • 支持虚拟存储器功能。每个任务运行可以有16K个段,每个段最大为4GB,一个任务最大可使用64TB虚拟地址空间
  • 程序运行分为4个特权等级,操作系统核心运行在最高特权级0,用户程序运行在最低特权级3
# 虚拟 8086 模式
  • 在虚拟8086方式下,IA-32微处理器总体上是工作在保护虚地址方式,支持多用户多任务操作系统。其中,有的任务可以工作在虚拟8086方式,运行DOS应用程序。
# 保护模式下的存储器访问

![]](/images/aa43a063892450e1db26d246ecfdcb8633f71176b8c300c906f4fc07370f17d3.png)

# 本章小结

本章小结

8088 相关知识:

  • 地址线、数据线、控制线若干
  • 16 位寄存器(各个的中文?)
    • 8个通用寄存器:AX、BX、CX、DX、SP、BP、SI、DI
    • 4个段寄存器:CS、DS、ES、SS
    • 2个控制寄存器:IP、FLAGS (CF、SF、AF、PF、OF、ZF、IF、TF、DF)
  • 存储器
    • 32-bit 逻辑地址 = 16-bit 段基地址 拼接 16-bit 段内地址
    • 20-bit 物理地址:16-bit 段基地址 *16+ 16-bit 偏移地址
  • 堆栈:栈底固定,为地址最大值;以字为单位

# 第三章 指令系统

# 概述(略)

# 寻址方式

这里讲的是 8086 的寻址方式,《计算机组成原理》讲的是 MIPS 的,因此会有区别。

除了立即寻址、寄存器寻址、隐含寻址,其余寻址方式都是得到 偏移地址 以后 +16*段地址 得出,并且这些方式都会加 [ ]

# 立即寻址

立即寻址

# 寄存器寻址

寄存器寻址

# 直接寻址

偏移地址 = 立即数

直接寻址

  • 直接寻址下,存储器操作数的长度由指令中另一个操作数的长度决定。如 MOV [1234H], CX MOV CL, [1234H]
  • 直接寻址方式下,操作数的段地址默认为数据段
    • 但允许段重设,即由指令定义段,说明数据存放在其他逻辑段中
    • MOV AX, ES:[1200H] 指令将 ES:[1200H] 的数存入 AX
    • 这种情况称为段超越,所加的段寄存器叫段前缀

# 寄存器间接寻址

偏移地址 = 寄存器值

寄存器间接寻址

  • “偏移地址”只能来自于间址寄存器(BX, BP, SI, DI)
    • 间址寄存器必须为 16-bit registers
  • “段地址”取决于基址寄存器
    • BX, SI, DI -> DS
    • BP -> SS
    • 但允许段超越

记忆上,就记 BP base pointer 作用类似于 SP stack pointer,都是用来指栈的某个位置。其余都是指 DS。

# 寄存器相对寻址

偏移地址 = 寄存器值 + 立即数

  • 寄存器值同样来自于 BX、BP、SI 或 DI

# 基址-变址寻址

偏移地址 = 基址寄存器值 + 变址寄存器值

  • “段地址”取决于基址寄存器
    • BX -> DS
    • BP -> SS(在计算题中,不要无脑使用 DS 作段地址!!!BP 需要使用 SS)
  • 常用于一维数组

例

# 基址-变址-相对寻址

偏移地址 = 基址寄存器值 + 变址寄存器值 + 立即数

  • 段地址同上
  • 常用于二维数组

例

# 五种寻址方式的总结

五种寻址方式的总结

除立即寻址、寄存器寻址、隐含寻址外,其余都是计算得到偏移地址,然后在 BIU 中的地址加法器运算(+16*基地址)得到物理地址。

# 隐含寻址

指令中隐含了一个或两个操作数的地址,即操作数在默认的地址中。

MUL BL
; AX = AL * BL

# 数据传送指令

# 通用数据传送

注意:该类指令的执行对标志位不产生影响

# 一般数据传送指令 MOV
MOV dest, src
; 读取 src 并保存到 dest

注意事项:

  • 各 FLAGS 一般不作为操作数在指令中出现
  • 两操作数长度必须相同
  • OFFSETSEG 等运算符得到的值可视为立即数
  • 某些方向是不能传输数据的,具体看图

MOV指令的传送方向示意图

可以用两条记忆:

  1. 立即数和 CS 不能做 dest(显然)
  2. 立即数和段寄存器如果想传送到别的段寄存器,必须经过通用寄存器中转。

顺便一提,立即数可以不经过寄存器、直接传入存储器。但由于立即数的长度不定,可能需要显式指明存储器的长度,如 MOV BYTE PTR[BX], 233H。参见 PTR 运算符


例题:判断下列指令的正确性:

MOV  AL, BX             ; 错误,长度不一致
MOV  AX, [SI]05H        ; 对
MOV  [BX][BP], AX       ; 错误,同时使用两个基址寄存器
MOV  DS, 1000H          ; 错误,常数不能直接送段寄存器
MOV  DX, 09H            ; 对,立即数位数不够,会自动补齐
MOV  [1200H], [SI]      ; 错误,dst 和 src 不能同时为存储器
MOV  AX, CS             ; 对
MOV  DS, CS             ; 错误,段寄存器之间不能传送

例:将符号“*”的ASCII码2AH送入内存数据段中以变址指针DI所指的单元再偏移100个字节单元中:

MOV AL, '*'
MOV 100[DI], AL
# 堆栈操作指令 PUSH POP
PUSH OPRD
; 压栈
POP OPRD
; 出栈

注意事项:

  • 堆栈操作以字为单位,故操作数必为 16 位
  • 操作数不能是立即数
  • 操作数可以来自寄存器或存储器
    • 若为存储器操作数,需要声明为字存储单元
  • 不能 POP 到 CS,这是上面的知识
  • PUSH 顺序是从高地址向低地址,看下图

压栈过程

出栈过程

# 交换指令 XCHG
; XCHG REG/MEM, REG/MEM
XCHG  AX, BX
XCHG  [2000], CL

注意事项:

  • 两操作数至少有一个是寄存器操作数
  • 不允许使用段寄存器
# 字位扩展指令 CBW CWD
  • 将带符号数的符号位 (0/1) 扩展 Convert 到高位;
  • 零操作数指令,采用隐含寻址,隐含的操作数为 AX 或者 “AX与DX”
CBW
; 将 AL 的符号位扩展到 AH (Convert Byte to Word)
; 若 AL 最高位=1,则执行后 AH = FFH
; 若 AL 最高位=0,则执行后 AH = 00H
CWD
; 将 AX 符号位扩展到 DX (Convert Word to Doubleword)
; 若 AX 最高位=1,则执行后 DX = FFFFH
; 若 AX 最高位=0,则执行后 DX = 0000H

判断以下指令执行结果:

MOV AL, 44H
CBW
; AX = 0044H

MOV AX, 0AFDEH
CWD
; DX = 0000H

MOV  AL86H
CBW
; AX = FF86H

# 输入输出指令 IN OUT

  • 专门面向I/O端口操作的指令
  • 输入指令:IN acc, PORT
  • 输出指令:OUT PORT, acc
  • PORT 为端口地址,acc 为累加寄存器 AL 或 AX

可以认为指令的第一个参数是目的地址 dest,这和 MOV 保持一致。

PORT 的寻址方式:

  • 直接寻址
    • 端口地址为 8 位时,指令中直接给出 8 位端口地址
    • 可寻址 256 个端口
  • 间接寻址
    • 端口地址为 16 位时,指令中的端口地址必须由 DX 指定
    • 可寻址 64K 个端口

例:

IN AX, 80H

MOV DX, 2400H
IN AL, DX

OUT 35H, AX

# 地址传送指令 LEA LDS LES

# 取偏移地址指令 LEA

LEA (Load Effective Address):将一个存储单元的 16 位偏移地址取出送 16 位通用寄存器(常为间址寄存器)。

LEA REG, MEM

类似于 MOV,但 MOV 取的是存储器值,而 LEA 取的是存储器的偏移地址。

MOV 和 LEA 对比

MOV SI, DATA1
; 这里的 DATA1 是变量
; SI = DATA1 中的内容

LEA SI, DATA1
; SI = DATA1 的偏移地址

MOV BX, [BX]
; BX = BX 作为偏移地址对应的值

LEA BX, [BX]
; BX 不变

变量的知识见变量

看起来 LEA 有点蠢,但其是有存在意义的:当寻址方式比较复杂(如基址-变址-相对寻址),LEA 能一行获取其地址,但 MOV 指令则不行,因为 MOV What's the purpose of the LEA instruction? - Stack Overflow (opens new window)

一道简单的例题:将数据段中首地址为 MEM1 的 50 字节的数据传送到同一逻辑段首地址为 MEM2 的区域存放。

流程图

代码如下:

      LEA SI, MEM1
      LEA DI, MEM2
      MOV CL, 50
NEXT: MOV AL, [SI]
      MOV [DI], AL
      INC SI
      INC DI
      DEC CL
      JNZ NEXT
      HLT           ; halt,暂停执行

眼睛:我没学会 脑子:我也没学会

看不懂就下来再看看。

LEA 不访问存储器,而下面的 LDSLES 要访问存储器。

# 装入地址指针指令 LDS LES
LDS DEST, SRC
LES DEST, SRC

作用:把 SRC 存储单元开始的 4 个字节单元的内容送入 DEST 通用寄存器和段寄存器 DS(LDS指令)或 ES(LES指令)

  • 低 16 位送 DEST,一般是送 SIDI
  • 高 15 位送 DS/ES

例题

执行后,SI=0020HDS=5030H

# 标志位操作指令

# LAHF SAHF
  • LAHF (Load Flags to AH):将 FLAGS 的低 8 位装入 AH

LAHF

  • SAHF (Save Flags to AH):执行与LAHF相反的操作
# PUSHF POPF
  • PUSHF:将 FLAGS 压栈
  • POPF:将栈顶弹出给 FLAGS

# 算术运算类指令

# 标志位影响

这类指令的执行大多对状态标志位会产生影响。

参考:http://www5.zzu.edu.cn/qwfw/info/1044/2483.htm
教材上并没有写 AND 指令会改变 SF 等,然后练习题考了hhhhh,真离谱

影响标志位 影响六个 (CF, SF, AF, ZF, PF, OF) 影响五个 (不影响 CF) 影响五个 (不影响 AF) 影响两个 (CF, OF) 影响一个 (CF) 不影响
指令 ADD/ADC/SUB/SBB/NEG/CMP, CMPS/SCAS INC/DEC AND/OR/XOR/TEST, SAL/SAR/SHL/SHR MUL/IMUL ROL/ROR/RCL/RCR DIV/IDIV NOT, MOV, IN/OUT

# 加法

加法指令对操作数的要求与 MOV 指令基本相同:

  1. 源操作数可以是通用寄存器、存储单元或立即数
  2. 目的操作数只能是通用寄存器或存储单元,不能是立即数
  3. 二者不能同时来自存储器。
# 带符号加法 ADD
ADD OPRD1, OPRD2
; OPRD1 = OPRD1 + OPRD2

目的地址依旧是第一个,类似于 MOV

  • ADD 指令的执行对全部 6 个状态标志位都产生影响

例:

MOV AL, 78H
ADD AL, 99H
; 试写出指令执行后的结果

结果及标志位

# 带进位加法 ADC

ADC (ADd with Carry) 可用于实现多字节数(大数)相加。

ADC OPRD1, OPRD2
; OPRD1 = CF + OPRD1 + OPRD2

例:求两个大数的和,两个数的长度为 20 字节,首地址为 M1M2

      LEA SI, M1
      LEA DI, M2
      MOV CX, 20
      CLC           ; 使 CF = 0
NEXT: MOV AL, [SI]
      ADC [DI], AL
      INC SI        ; SI、DI 自增
      INC DI
      DEC CX        ; CX 自减
      JNZ NEXT      ; Jump if(当运算结果) Not Zero
      HLT
# 自增 INC
INC OPRD
; OPRD = OPRD + 1
  • OPRD 可以来自存储器,不能来自段寄存器或立即数
  • INC 指令执行不影响 CF 标志,只影响其他五个

为什么 INC(以及 DEC)不影响 CF 标志呢?据 Stack Overflow (opens new window),这并不是为了节省成本,而是为了循环的 i++ 等不会影响到 CF 标志位。上段 ADC 处举的例子就很好的说明了这一点:如果 INC 改变了 CF,程序必须在每次完成 ADC 后存储 CF 的状态,否则就会被 INC 覆盖,代码会麻烦得多。

# 减法

# 带符号减法 SUB
SUB OPRD1, OPRD2
; OPRD1 = OPRD1 - OPRD2
  • SUB 指令的执行对全部 6 个状态标志位都产生影响(同 ADD
# 带借位减法 SBB

类似于 ADC

SBB OPRD1, OPRD2
; OPRD1 = OPRD1 - OPRD2 - CF

SBB (SuBtraction with Borrow):指令格式、对操作数的要求、对标志位的影响与 SUB 指令完全一样

# 自减 DEC

类似于 INC

DEC OPRD
; OPRD = OPRD-1
  • OPRD 不能是段寄存器或立即数
  • DEC 不影响 CF

例 1:实现一个计数循环程序,下面的代码正确吗?

     MOV AL, 10H
LOP: DEC AL
     JNC LOP ; Jump if Not CF
; 错误! DEC 不影响 CF
; 程序会执行一次后停止(如果原来 CF = 1)
; 或死循环 (如果原来 CF = 0)

例 2 是一个两层的嵌套循环:

       MOV BL, 2
NEXT1: MOV CX, 0FFFFH
NEXT2: DEC CX
       JNZ NEXT2
       DEC BL
       JNZ NEXT1
       HLT
# 求补 NEG
  • NEG 将影响六个标志位
  • 当且仅当操作数为 0 时,CF = 0,结果不变(仍为 0)
  • 当且仅当字节操作数为 -128 (80H) 或字操作数为 -32768 (8000H) 时,OF = 1,结果不变(仍为 -128 或 -32768)
# 比较 CMP

只影响六个标志位,不会存储结果。常接 JNZ JNC 等语句实现条件跳转。

两个数大小比较:

CMP AX, BX 无符号数 有符号数
AX=BX ZF=0 ZF=0
AX>BX CF=0, ZF=0 OF=SF, ZF=0
AX<BX CF=1, ZF=0 OF!=SF, ZF=0
可相关判断指令 JA JAE JB JBE JG JGE JL JLE

例题:在 20 个无符号数中找出最大的数,并将其存放在 MAX 单元中。

      LEA BX, MAX
      LEA SI, BUF
      MOV CL, 20
      MOV AL, [SI]   ; AL 保存目前的最大值
NEXT: INC SI
      CMP Al, [SI]
      JNC GOON       ; CF=0 转移
      MOV AL, [SI]   ; 将更大的 [SI] 放进 AL
GOON: DEC CL
      JNZ NEXT
      MOV [BX], AL
      HLT

# 乘法

  • 运算结果长度是乘数的两倍,即 8位->16位16位->32位
  • 一个乘数和计算结果使用隐含寻址,隐含的是存放被乘数的累加寄存器 ALAX,及存放结果的 AXDX
  • 乘法只影响 OFCF;若运算结果的高半部分是无效数值,则 OF=CF=0,否则 OF=CF=1
    • 考虑 8 位乘 8 位,结果仍可以用 8 位存储的情况,此时 OF=CF=0
    • 对于无符号乘法,当且仅当高半部分为全 0, OF=CF=1
    • 对于有符号乘法,当且仅当高半部分为低半部分的符号扩展,OF=CF=1
    • 若有符号乘法结果为 00000000 11111111,高半部分不是符号扩展,CF=OF=0。若在后续步骤只看后半部分,会导致原来的正值被(错误地)识别为负值
# 无符号乘法 MUL
MUL OPRD
; 字节运算:AX = AL * OPRD
; 字运算:  DX:AX = AX * OPRD
  • OPRD 不能是立即数
  • 若结果高半部(AHDX)是全 0(不是有效数值),则 CF=OF=0,否则 CF=OF=1
# 带符号乘法 IMUL

IMUL (sIgned MULtiply) 除了操作数是带符号外,其余与 MUL 指令相同。

IMUL OPRD
  • 若结果高半部(AHDX)是低半部的符号扩展(不是有效数值),则 CF=OF=0,否则 CF=OF=1

# 除法

; 无符号除法   
DIV OPRD

; 有符号除法
IDIV OPRD
  • 指令要求被除数是除数的双倍字长
  • OPRD 是 8 bits
    • 执行:AX/OPRD
    • AL = 商
    • AH = 余数
  • OPRD 是 16 bits
    • 执行:DX:AX/OPRD
    • AX = 商
    • DX = 余数

简而言之,就是高位存余数,低位存商。记忆的方法,可以想:平时更常用除法而不是求模,低位存储的结果就可以直接进行下一步运算。

除法指令常和 CBWCWD 配合使用。

# BCD 码调整指令

# 六条指令
  • DAA Decimal Adjust after AdditionDecimal 即每四位表示一个 BCD 码,又称压缩型、组合型
  • AAA ASCII Adjust after AdditionASCII 即每八位表示一个 BCD 码,又称非压缩型、非组合型

剩下的就自己看 PPT 吧。

AAA

DAA

AAS

DAS

AAM

AAD

# 逻辑运算和移位指令

# 逻辑运算

  • 逻辑运算指令对操作数的要求大多与MOV指令相同
  • NOT 运算指令要求操作数不能是立即数
  • NOT 运算指令外,其余指令的执行都只会影响 OFCF(使 OF=CF=0)。NOT 指令不不影响标志位
# 与 AND

语法上类似于 ADD

AND  OPRD1, OPRD2
; 两操作数按位相“与”,结果送目标地址 OPRD1

例:

; 实现两操作数按位相与的运算
AND  BL, [SI]

; 使目标操作数的某些位不变,某些位清零
AND  AL, 0FH

; 在操作数不变的 情况下使 CF 和 OF 清零
AND  AX, AX

例 2:从地址为 3F8H 端口中读入一个字节数,如果该数 bit1 位为 1,则将 DATA 为首地址的一个字输出到 38FH 端口,否则就不能进行数据传送。

      LEA SI, DATA
      MOV DX, 3F8H
WAIT: IN AL, DX
      AND AL, 02H
      JZ WAIT       ; ZF=1转移
      MOV DX, 38FH
      MOV AX, [SI]
      OUT DX, AX
# 或 OR
OR OPRD1, OPRD2
; 两操作数按位相“或”,结果送目标地址 OPRD1

例题:

; 实现两操作数 相“或”的运算
OR AX, [DI]
; 使某些位不变,某些位置“1”
OR CL, 0FH
; 在不改变操作数的情况下使 OF=CF=0
OR AX, AX
# 非 NOT
NOT OPRD
; 操作数按位取反再送回原地址
  • 操作数不能是立即数
  • 对标志位无影响

例:

NOT BYTE PTR[BX]

这里的 BYTE PTR 是强制转换,见PTR 运算符

# 异或 XOR
XOR OPRD1, OPRD2
; 两操作数按位相“异或”,结果送目标地址 OPRD1

例:

XOR BL, 80H  ; 将 BL 的最高位变反
XOR AX, AX   ; 将 AX 清零

注意,XOR AX, AX 的效果等价于 MOV AX, 0,但:

# 测试(与) TEST
TEST OPRD1, OPRD2
; 执行“与”运算,运算的结果影响标志位,但不送回目标地址
; 常用于测试某些位的状态

例题:从地址为 3F8H 的端口中读入一个字节数,当该数的 bit1, bit3, bit5 位同时为 1 时,则从 38FH 端口将 DATA 为首地址的一个字输出,否则就从端口重新输入。

      LEA  SI, DATA
      MOV  DX, 3F8H
WAIT: IN  AL, DX
      ; ...
      ; ...
      MOV  DX, 38FH
      MOV  AX, [SI]
      OUT  DX, AX

; ... 部分的代码可以有三个版本:

使用三次 TEST

TEST AL, 02H
JZ WAIT       ; ZF=1转移
TEST AL, 08H
JZ WAIT
TEST AL, 20H
JZ WAIT

使用 ANDCMP

AND AL, 2AH
CMP AL, 2AH
JNZ WAIT

使用 ANDXOR

AND AL, 2AH
XOR AL, 2AH
JNZ WAIT

# 移位指令

# 算术左移 SAL 逻辑左移 SHL
; 算术左移 (Shift Arithmetic Left) 指令,视为有符号数
SAL OPRD, 1
SAL OPRD, CL

; 逻辑左移指令,视为无符号数
SHL OPRD, 1
SHL OPRD, CL

CLCX 寄存器的低 8 位。

SAL 和 SHL

二者实际上就是一条指令,都是最低位补 0,最高位移到 CF

# 算术右移 SAR 逻辑右移 SHR
; 算术右移指令,视为有符号数
SAR OPRD, 1
SAR OPRD, CL

; 逻辑右移指令,视为无符号数   
SHR OPRD, 1
SHR OPRD, CL

SAR 和 SHR

移出的数送 CF,移入 0 (算术右移)或符号位(逻辑右移)。

# 不带 CF 的循环移位 ROL ROR

RO 取自 Rotate

二者都是在原数上循环移位,同时移出的数送 CF。见下。

# 带 CF 的循环移位 RCL RCR

二者都是 CF 作为移入的数,移出的数再送 CF。

循环移位

循环移位可用于:

  • 用于对某些位状态的测试
  • 高位部分和低位部分的交换
  • 与非循环移位指令一起组成32位或更长字长数的移位

例:对从存储单元 M 开始的三字数据执行左移一位。

SAL M, 1
RCL M+2, 1
RCL M+4, 1

示例

例 2:将 1000H 开始存放的 4 个压缩 BCD 码转换为 ASCII 码存放到 3000H 开始的单元中去。

      MOV SI, 1000H
      MOV DI, 3000H
      MOV CX, 4
Next: MOV AL, [SI]
      MOV BL, AL      ; 由于每个字节有 2 个 BCD 码,需处理两次,故备份一个在 BL
      AND AL, 0FH
      OR  AL, 30H     ; 处理 AL 的后四位
      MOV [DI], AL
      INC DI          ; 目的地地址 ++
      MOV AL, BL
      PUSH CX         ; 对 CX (用于计数剩余字节)备份
      MOV CL, 4       ; 此处的 CL 为右移次数
      SHR AL, CL      ; 处理 AL 的前四位
      OR  AL, 30H
      MOV [DI], AL
      INC DI          ; 目的地地址 ++
      INC SI          ; 源地址 ++
      POP CX
      DEC CX
      JNZ Next
      HLT

# 串操作指令

  • 针对数据块或字符串的操作
  • 实现存储器到存储器的数据传送(前面的所有命令都不能)

串操作指令及其功能:(<=>表示比较,<- 表示数据存储)

  • 串传送 MOVS:存储器 -> 存储器
  • 串比较 CMPS:存储器 <=> 存储器
  • 串扫描 SCAS:存储器 <=> AL/AX
  • 串装入 LODS:存储器 -> AL/AX
  • 串送存 STOS:存储器 <- AL/AX

# 特点

  • 源串地址由 DS:[SI] 提供,目的串由 ES:[DI] 提供
  • 源串允许段(DS)重设,目的串不允许段(ES)重设。
  • 每次:
    1. 只处理串中的一个单元(字或字节)
    2. 这些指令执行结束后,都会按 DF 决定的方向自动修改 SI 和/或 DI,使其指向下一个单元(按指令可分别处理字单元和字节单元)
  • 地址修改方向由 DF 标志位决定:
    • DF = 0 => 增地址方向
    • DF = 1 => 减地址方向
  • 指令前面可加上自动重复前缀,实现自动重复执行串操作,重复执行次数由 CX 指定

# 重复前缀

重复前缀:重复执行给定指令,每执行一次后,自动使 CX-1=>CX,直至 CX=0 或其他条件

  • 无条件重复
    • REP => 若 CX≠0 则重复
  • 条件重复
    • REPE 相等重复 => 若 CX≠0ZF=0 则重复
    • REPZ 为零重复(上一指令的别名)
    • REPNE 不相等重复 => 若 CX≠0ZF≠0 则重复
    • REPNZ 不为零重复(上一指令的别名) 注意:重复前缀本身不改变标志位

# 串操作指令流程

串操作指令流程(以传送操作为例)

左边部分是串操作的初始化,右边的虚线框部分由串操作指令完成。

# 串传送指令 MOVS

MOVS OPRD1(ES:DI), OPRD2(DS:SI)
MOVSB
MOVSW
  • 第一种格式中,OPRD1 为目标串地址,OPRD2 为源串地址(类似于 MOV)。两串的段地址允许使用默认值(ESDS),源串也允许段重设
  • 二、三种格式下隐含了操作数地址(目标串 ES:DI、源串 DS:SI
  • MOVSB 一次完成一个 Byte 的传送,MOVSW 一次完成一个 Word 的传送
  • 执行结束后,按 DF 决定的方向自动修改 SIDI,使其指向下一个单元

例题:用串传送指令实现200个字节数据的传送:

LEA  SI, MEM1
LEA  DI, MEM2
MOV  CX, 200
CLD              ; Clear Direction Flag: DF = 0
REP  MOVSB
HLT

# 串比较指令 CMPS

CMPS OPRD1(DS:SI), OPRD2(ES:DI)
CMPSB
CMPSW
  • MOVS 相同的是,后两个命令默认目标串 ES:DI、源串 DS:SI
  • MOVS 不同的是,OPRD1 为源串,OPRD2 为目标串(这和 SUBCMP 相同)
  • CMP 相同的是,执行 OPRD1-OPRD2,不改变操作数, 只改变标志位
  • 串比较指令常与条件重复前缀连用
  • 指令的执行不改变操作数,仅影响标志位
  • 执行结束后,按 DF 决定的方向自动修改 SIDI,使其指向下一个单元

例:比较两组(200个字节)对应数据,找出第一个不同数据放入AL,其地址放入BX

      LEA  SI, MEM1
      LEA  DI, MEM2
      MOV  CX, 200
      CLD
      REPE CMPSB     ; 指令执行结束,可能是找到不同,也可能是完全相同
      JZ   STOP
      DEC  SI        ; 注意!
      MOV  AL[SI]
      MOV  BXSI
STOP: HLT

上述第 7 行的 DEC SI 说明:当前 REPE CMPSB 条件不满足时,也会执行 SI++ DI++ CX--,然后停止执行指令。

# 串扫描指令 SCAS

SCAS  OPRD(ES:DI)
SCASB
SCASW
  • 执行 AX (或 AL) - OPRD,结果不保存,只影响标志寄存器
  • 常用于在指定存储区域中寻找关键字
  • OPRD 为目标串,默认为 ES:SI
  • 执行结束后,按 DF 决定的方向自动修改 DI,使其指向下一个单元

# 串装入指令 LODS

LODS  OPRD(DS:SI)
LODSB              ; 存入 AL
LODSW              ; 存入 AX
  • 用于将内存某个区域的数据串依次装入累加寄存器 AX/AL,以便进行处理
  • 不影响标志位
  • LODS 指令加重复前缀无意义(等价于 LOAD 最后一个有效值)
  • 执行结束后,按 DF 决定的方向自动修改 SI,使其指向下一个单元(这即是和 MOV AL, [SI] 的最大区别)

# 串存储指令 STOS

STOS  OPRD(ES:DI)
STOSB              ; 将 AL 存入内存
STOSW              ; 将 AX 存入内存

配合重复前缀,常用于将内存某个区域置同样的值。

# 串操作指令注意事项

注意事项:

  • 需要定义附加段
    • 目标操作数必须在附加段
  • 需要设置数据的操作方向
    • 确定 DF 的状态
  • 源串和目标串指针分别为 SIDI
  • 串长度值必须由 CX 给出
  • 注意重复前缀的使用方法
    • 传送类指令前加无条件重复前缀
    • 串比较类指令前加条件重复前缀,但前缀不影响 ZF 状态

# 程序控制指令

# 程序的执行方向

  • 程序控制类指令的本质:控制程序的执行顺序
  • 决定程序执行方向的因素:CSIP
  • 下条指令地址:CS:[IP]
  • 修改 CS,程序转向另一个代码段执行
  • 修改 IP,程序转向本代码段内另一处执行

# 转移指令 JMP

  • 转移指令修改 CSIP 的值,实现程序转移
  • 分为无条件转移指令和有条件转移指令
# 无条件转移指令
JMP  OPRD
# 段内转移
  • 段内转移:目标地址为 16 位,赋值给 IP
  • 段内直接转移:OPRD/Label立即数IP <= IP + LabelLabel 被汇编为 8/16 位位移量)
  • 段内间接转移:OPRD寄存器或存储器IP <= OPRD

例题:

JMP  Next        ; Next 为标号
; 执行后 IP 到 Next 所在指令的地址

JMP  BX          ;  BX  = 1200H
; 执行后 IP = 1200H

JMP  [BX]        ; [BX] = 4312H
; 执行后 IP = 4312H。

关于 Label 的详细介绍可见标号

# 段间转移 JMP FAR
  • 段间转移:目标地址为 32 位,赋值给 CS:IP
  • 段内直接转移:OPRD/Label立即数CS:IP = LabelLabel 被汇编为 32 位地址)
  • 段间间接寻址:转移的目标地址由指令中的 32 位存储器单元给出

例:

JMP  FAR Label      ; 段内直接转移
; CS:IP = Label

JMP  DWORD PTR[BX]  ; 段内间接寻址
; IP = [BX]
; CS = [BX+1]

例 2:

MOV  SI1122H
MOV  WORD PTR[SI]0120H
ADD  SI2
MOV  WORD PTR[SI]0122H
JMP DWORD PTR[SI-2]
; CS = 0122H, IP = 0120H

注意第二行,将立即数存入 [SI] 指明的内存时,必须使用 BYTE/WORD PTR 显式指明立即数的长度是 8 位还是 16 位,因为立即数和 [SI] 的长度都是不一定的。寄存器值存入内存则不需要,因为寄存器隐式地给出了长度。参见 PTR 运算符

# 条件转移指令
  • 在满足一定条件下,程序转移到目标地址继续执行
  • 条件转移指令均为直接寻址的段内短转移,即转移的位移量为 8 位补码表示,范围为:-128~+127 IP=IP+位移量

常见条件指令的应用:

指令 条件 应用
JC/JNC 判断CF的状态 常用于比大小
JZ/JNZ 判断ZF的状态 常用于循环体的结束判断
JO/JNO 判断OF的状态 常用于有符号数溢出的判断
JS/JNS 判断SF的状态
JP/JNP 判断PF的状态 用于判断运算结果低8位中1的个数是否为偶数
没有类似指令判断 AFDF 的状态,只有上面五个
JA/JAE/JB/JBE 判断CFCF+ZF的状态 常用于无符号数的大小比较
JG/JGE/JL/JLE 判断SFOFZF的状态 常用于带符号数的大小比较

有关 JA JG 等的含义和用法请读者自行百度 / Google。

例题:统计内存数据段中以 TABLE 为首地址的 100 个 8 位有符号数中正数、负数和零的个数。

例题程序

# 循环控制指令

  • 循环范围:以当前 IP 为中心的 -128~+127 范围内循环
  • 循环次数:由 CX 寄存器指定
  • 循环指令:LOOP LOOPZ LOOPNZ
# 无条件循环指令 LOOP
LOOP  LABEL
  • 操作:
  1. CX-1 => CX
  2. CX ≠ 0 则转 LABEL,否则执行下条指令

也就是说,当 CX = 1 时执行 LOOP 则不会跳转,而是顺序执行。

# 有条件循环指令 LOOPZ LOOPNZ(略)

LOOPZLOOPNZ

# 过程调用指令 CALL

  • 用于调用一个子过程
  • 子过程由程序员预先设计并装入内存
  • 子过程执行结束后要返回原调用处
  • 重要概念:入口地址、断点,如图

入口地址和断点

调用指令的执行过程:

  1. 保护断点:将断点(调用指令的下一条指令的地址)压栈
  2. 获取子过程的入口地址(子过程第一条指令的地址)
  3. 执行子过程,含相应参数的保存及恢复
  4. 恢复断点,返回原程序(将断点地址由堆栈弹出)

分为:

  • 段内直接调用
  • 段内间接调用
  • 段间直接调用
  • 段间间接调用
# 段内调用 CALL NEAR

由于被调用程序与调用程序在同一代码段,调用前只需保护断点的偏移地址。

CALL  (NEAR)  PROC

执行过程:

  • 断点压栈
  • PROC => IP

例:

CALL  TIMER           ; 直接调用

CALL  WORD  PTR[SI]   ; 间接调用,指明长度为 16 位,是段内调用
; 执行后 IP = [SI], CS 不变
# 段间调用 CALL FAR

由于子过程与原调用程序不在同一代码段,调用前需保护断点的段基地址和偏移地址。

先将 CS 压栈,再压入 IP

格式:

CALL  FAR  PROC

例:

CALL  FAR  TIMER

CALL  DWORD  PTR[SI]    ; 指明长度为 32 位,是段间调用
; 执行后 IP = [SI]
; CS = [SI+2]
# 返回指令 RET
RET
  • 从堆栈中弹出断点地址,返回原程序
  • 一般位于子程序的最后
  • 在格式上不区分段内或段间

# 中断控制指令

# 中断和过程调用的区别
  • 中断是随机事件或异常事件引起,调用则是事先已在程序中安排好
  • 响应中断请求不仅要保护断点地址,还要保护 FLAGS 内容
  • 调用指令在指令中直接给出子程序入口地址
  • 中断指令只给出中断向量码,入口地址则在中断向量码指向的中断向量表(内存单元)中
# 中断指令 INT

中断指令,又称作软中断指令

INT  n
; n*4 为中断服务程序入口的偏移地址(段地址 = DS)
; 8086 的中断向量表存放在内存最低的 1K 单元
# 中断指令的执行过程
  • FLAGS 压入堆栈
  • INT 指令的下一条指令的 CSIP 压栈
  • n*4 得到存放中断向量的地址
  • 将中断向量(中断服务程序入口地址)送 CSIP 寄存器
    • CS = DS:[n*4]
    • IP = DS:[n*4+2]
  • 转入中断服务程序

下图中,左图为中断后的堆栈段,右图为数据段和代码段。

中断指令的执行过程

例:

[0084H] = 1123H, [0086H] = 2000H。执行 INT 21H 后,

  • IP = [21H*4] = 1123H
  • CS = [21H*4+2] = 2000H
  • 下一条指令在 21123H
# 溢出中断指令 INTO

INTO 指令通常安排在有符号数加减运算指令之后判断是否发生溢出

INTO
  • 指令执行时检查 OF 标志:
    • OF=1,则启动一个类型为4的中断过程,即相当于执行指令: INT 4
    • OF=0,不做任何操作执行下一条指令
# 中断返回指令 IRET
IRET
  • 中断服务程序的最后一条指令,执行:
    1. 恢复断点
    2. 恢复标志寄存器内容

# 处理器控制指令

主要分为:

  • 对标志位的操作
    • 对标志位操作都是无操作数指令
    • 可操作的标志位有 CFIFDF
  • 与外部设备的同步
常见指令 作用
CLC (clear CF) 清除进位标志
STC (set CF) 置1进位标志
CMC (complement CF) 进位标志取反
CLD 清除方向标志
STD 置1方向标志
CLI 清除中断标志
STI 置1中断标志

# 第四章 汇编语言程序设计

# 汇编语言语句种类及其格式

汇编语言语句分为指令语句和伪指令语句。

# 指令语句

每一条指令语句在汇编时都要产生一个可供CPU执行的机器目标代码,它又叫可执行语句。

指令语句的一般格式

  • 指令助记符和操作数两个字段就是上一章——指令系统介绍的指令
  • 标号是可选字段,后面必须跟 :
    • 主要用于控制程序执行顺序
  • 注释字段为可选项,以分号 ; 开始
    • 它不会产生机器目标代码,不影响程序的功能
    • 注释可以加在指令的后面,也可以是整个语句行
LABEL1: ADD  AX, BX  ;功能为AX<=(AX)+(BX)
; 后面的程序段将完成一次对存储器的访问

# 伪指令语句

  • 伪指令语句又叫命令语句,是指示性语句
  • 伪指令本身不产生自己的机器目标代码,它指示汇编程序对其后面的指令语句和伪指令语句如何处理

伪指令语句的一般格式

# 标识符

指令语句中的标号和伪指令语句中的符号名统称为标识符,它由若干个字符构成。

标识符构成规则:

  • 字符的个数为 1-31 个
  • 可以使用字母、数字、@_?
  • 不能以数字开头
  • 不能使用系统专用的保留字

# 保留字

  • CPU 中各寄存器名(如 AXCS 等)
  • 指令助记符(如MOVADD
  • 伪指令符(如SEGMENTDB
  • 表达式中的运算符(如GEEQ
  • 属性操作符(如PTROFFSET等)

# 汇编语言数据

  • 数据:指令和伪指令语句中的操作数
  • 常用的数据形式有:常数变量标号
  • 一个数据由数值属性(比如是字节数据还是字数据)两部分构成

# 常数

常数:经过汇编后其值已完全确定,并且在程序运行过程中不会改变。

# 常数的表示
  • 二进制数:以字母 B 结尾,如 01001001B
  • 八进制数:以字母 OQ 结尾,如631Q 254O
  • 十进制数:以字母 D 结尾,或者没有结尾字母。如 2007D2007
  • 十六进制数:以字母 H 结尾,如 3FEH
    • 如果常数的第一个数符为字母,为了与标识符区别,必须在其前面冠以数字 0,如0F000H 为 16 位常数
  • 实数:如 2.134E+10。汇编源程序时会把实数转换为 4 字节、8 字节或 10 字节的二进制数形式存放。
  • 字符串常数:用引号(单引号或双引号)括起来的一个或多个字符,其值为这些字符的ASCII码
    • 'ABC' 存储为 41H 42H 43H
    • 其中 41H 在低地址,43H 在高地址,类似于 C 语言的 char *
# 常数的作用
  1. 作指令语句的源操作数
  2. 在指令语句中作位移量
  3. 在数据定义伪指令中使用
; 1. 作指令语句的源操作数
MOV  AX,  0B2F0H
ADD  AH,  64H

; 2. 在指令语句中作位移量
MOV  BX, 32H [SI]
MOV  0ABH [BX], CX
ADC  DX, 1234H[BP][DI]

; 3. 在数据定义伪指令中使用
DB   10H
DW   3210H

# 变量

变量:用来表示存放数据的存储单元,这些数据在程序运行期间可以被改变。

程序中以变量名的形式来访问变量。变量名就是存放数据的存储单元地址

# 变量的定义

定义变量:给变量在内存中分配一定的存储单元。也就是给这个存储单元赋与一个符号名,即变量名,同时还要将这些存储单元预置初置。

定义变量使用数据定义伪指令 DBDWDDDQDT 等。

格式:

VAR_DATA SEGMENT
DATA1 DB 12H
; 变量名 + 数据定义伪指令 + 初值
DATA2 DB 20H, 30H
DATA3 DW 5678H
VAR_DATA ENDS
# 变量的属性
  1. 段属性(逻辑段):上例 DATA1 DATA2 DATA3 均在 VAR_DATA 逻辑段
  2. 偏移量属性(偏移地址):上例 DATA1 DATA2 DATA3 的偏移量分别为 0, 1, 3
  3. 类型属性,如下:
类型 中文 变量长度
DB Define Byte 1 字节
DW Define Word 2 字节
DD Define Doubleword 4 字节
DQ Define Quadword 8 字节
DT Define Tenbytes 10 字节
# 变量的预置
  1. 数值表达式
  2. ? 表达式:表示预置任意内容(未赋初值)
  3. 字符串表达式
  4. DUP 表达式
# 数值表达式
; 1. 数值表达式
DATA1  DB  32, 30H
; DATA1 的内容为 32(20H)
; DATA1+1 单元内容为30H
# ? 表达式
DA-BYTE DB ?, ?, ?
; 表示让汇编程序分配三个字节存储单元
; 这些存储单元的内容的值为任意(未赋初值)
# 字符串表达式
  • 字符串长度不超过 255 个字符
  • 使用 DB 伪指令,会对每个字符分配一个字节单元,从左到右将各字符的 ASCII 码以地址递增的顺序依次存放
  • 使用 DW 伪指令,可以给两个字符组成的字符串分配两个字节存储单元
  • 但需要注意的是,两个字符的存放顺序是前一个字符放在高地址,后一字符放低地址单元
  • 使用 DD 伪指令,只能给两个字符组成的字符串分配 4 个字节单元
  • 两个字符存放在较低地址的两个字节单元中,存放顺序与 DW 伪指令相同
  • 而较高地址的两个字节单元存放 0
  • DWDD 伪指令不能用两个以上字符构成的字符串赋初值
; 3. 字符串表达式
STRING1  DB  'ABCDEF'
; 低地址到高地址依次为 'A' 'B' 'C' 'D' 'E' 'F'

STRING2 DW 'AB', 'CD', 'EF'
; 低地址到高地址依次为 'B' 'A' 'D' 'C' 'F' 'E'

STRING3 DD, 'AB', 'CD'
; 低地址到高地址依次为 'B' 'A' 0 0 'D' 'C' 0 0
# DUP 表达式

DUP 称为重复数据操作符。其格式为:变量名 数据定义伪指令 重复次数 DUP(重复内容)

如:

DATA_A DB 10H DUP(?)
; 分配 16 个字节单元,不赋初值

DATA_B DB 20H DUP('AB')
; 分配 20H * 2 = 64 个字节单元,其内容为 'ABABAB'...

嵌套的 DUP

DATA_C DB 10H DUP(4 DUP(2), 7)
; 重复 10H 个数字序列(2、2、2、2、7),共占用 10H*5 = 50H 字节

DUPDW 组合时,每个 重复内容 占用两字节,例如:

DATA  DW 5, 2 DUP(1, 2 DUP(1))

DW 所指向的内存单元(每字节)为 5 0 1 0 1 0 1 0 1 0 1 0 1 0,共占用 14 字节。

# 地址表达式

该地址表达式为一变量名(或标号名),那么:

  • 用伪指令 DW 定义则是用它的偏移量来初始化变量
  • 用伪指令 DD 定义则是用它的段基值和偏移量来初始化变量,且段基值存放在高字单元,偏移量存放在低字单元
; 设以下变量的段基址为 0915H,NUM 的偏移地址为 0004H
NUM   DB 75H
ARRAY DW 20H DUP(0)

ADR1  DW NUM         ; DW 时取 NUM 的偏移地址
; ADR1 = 0004H

ADR2  DD NUM         ; DD 时取 NUM 的偏移地址(存入低字)和段基址(存入)
; ADR2 = 0915 0004H

ADR3  DW ARRAY[2]
; ADR3 = 0007H

注意:变量不能出现在 DB 语句的右边,因为变量的偏移地址为 16 位,与 DB 定义的变量长度不匹配。

语句运行后的内存

注意,ARRAY[2] 这样的形式,等价于 OFFSET(ARRAY)+2和 C 语言不同的是,[2] 不会根据是 DB 还是 DW 而使用不同的偏移量,而是一律偏移 2!可详见算术运算符

# 以上表达式组成的序列

如下:

NUM DB 2 DUP(1), 2 DUP(2, 'B'), '123', 1, 2, 3
; NUM 从低地址到高地址分别为 1 1 2 'B' 2 'B' '1' '2' '3' 1 2 3
# 变量的使用

在指令语句中,直接引用变量名就是对其存储单元的内容进行存取

DA1  DB 0FEH
DA2  DW 52ACH
DA3  DW 0

MOV  AL, DA1   ; 等价于 MOV  AL, 0FEH
MOV  BX, DA2   ; 等价于 MOV  BX, 52ACH
MOV  DA3, BX   ; 将 DA3 变量赋值 52ACH,注意二者大小需要对应
; 变量是可以被重复赋值的
  • 伪指令语句中,DATA2 DB DATA1 为取 DATA1 地址
  • 指令语句中,MOV AL, DATA1 为取 DATA1 存储的值

# 符号和变量对比

符号 变量
可变与否 类似于常量,不可变 可变
定义 EQU= DB DW
地址空间 有段基址、偏移地址

# 标号

  • 标号 label 加在一条指令的前面,它就是该指令在内存的存放地址的符号表示,也就是指令地址的别名
  • 标号主要用在程序中需要改变程序的执行顺序时,用来标记转移的目的地
  • 下面代码的 LABNEXT 即为标号
      MOV  CX, 100
LAB:  MOV  AX, BX
      ; ...
      LOOP LAB
      JNE NEXT       ;不为零转移
      ; ...
NEXT: ; ...
# 标号的三个属性
  1. 段属性 SEG

它表示该标号所代表的地址在哪个逻辑段中,即段基值。

  1. 偏移量属性 OFFSET

它表示该标号所代表的地址在段内与段起点间的字节数,即地址的偏移量。

  1. 距离属性(也叫类型属性)

它表示该标号可以被段内还是段间的指令调用。

  • NEAR(近):用作段内转移,即只能是与该标号所指指令同在一个逻辑段的其它指令才能使用它
  • FAR(远):可以被非本段的转移和调用指令使用

标号的距离属性可以有两种方法来指定:

(1) 隐含方式

SUB1: MOV  AX, 30H

SUB1 默认为 NEAR。

(2) 用 LABEL 伪指令给标号指定距离属性

标号名 LABEL NEAR/FAR

该语句要与指令语句连用,如下:

SUB1_FAR LABEL FAR
SUB1: MOV  AX, 30H

上文中,SUB1_FARSUB1 两个标号具有相同的逻辑地址。但 SUB1 只能被本段调用,SUB1_FAR 可以被其它段的指令调用。

# LABEL 定义变量属性

LABEL 伪指令还可以用于定义变量的属性,即改变一个变量的属性,如把字变量的高低字节作为字节变量来处理。

DATA_BYTE  LABEL  BYTE
DATA_WORD  DW  20H  DUP(?)
  • DATA_BYTEDATA_WORD 具有相同的段基址和偏移量
  • DATA_BYTE 可以被用来存取一个字节数据,而 DATA_WORD 则不能

# 符号定义语句

符号定义语句将常数或表达式等形式用某个指定的符号来表示。在 8086/8088 汇编语言中有两种符号定义语句,分别为等值语句 EQU 和等号语句 =

# 等值语句 EQU

  • 格式:符号名 EQU 表达式
  • 功能:用符号名来表示 EQU 右边的表达式。后面的程序中一旦出现该符号名,汇编程序将把它替换成该表达式。
  • 类似于 C 的 #define

表达式

  1. 常数或数值表达式
COUNT  EQU  5
NUM    EQU  COUNT+5
  1. 地址表达式
ADR1   EQU  DS:[BP+14]
  1. 变量名、寄存器名或指令助记符
CREG  EQU  CX   ; 在后面的程序使用CREG就是使用CX
CBD   EQU  DAA  ; DAA为十进制调整指令

注意,同一符号不能用 EQU 重复定义。

# 等号语句 =

格式:符号名=表达式

等号语句与等值语句具有类似的作用,二者的区别是:

  • 等号语句可以对一个符号进行多次定义
  • 等号语句不能为助记符定义别名(诸如 CBD=DAA 是错误的)

等值语句与等号语句都不会为符号分配存储单元。所定义的符号没有段、偏离量和类型等属性。

# 表达式与运算符

表达式是指令或伪指令语句操作数的常见形式。它由常数、变量、标号等通过操作运算符连接而成。

任何表达式的值在程序被汇编的过程中进行计算确定,而不是到程序运行时才计算。

8086/8088宏汇编语言中的操作运算符非常丰富,可以分为以下五类。

  • 算术运算符
  • 逻辑运算符
  • 关系运算符
  • 数值返回运算符
  • 属性修改运算符

# 算术运算符

包含 +-*/MODSHLSHR[ ]

  1. 运算符 +- 也可作单目运算符,表示数的正负
  2. 使用 +-*/ 运算符时,参加运算的数和运算结果都是整数
  3. / 运算为取商的整数部分,而 MOD 运算取除法运算的余数
NUM=15*8       ; NUM = 120
NUM=NUM/7      ; NUM = 17
NUM=NUM MOD 3  ; NUM = 2
NUM=NUM+5      ; NUM = 7
NUM=-NUM-3     ; NUM = -10
NUM=-NUM-NUM   ; NUM = 20
  1. SHRSHL 为逻辑移位运算符

SHR 为右移,左边移出来的空位用 0 补入。 SHL 为左移,右边移出来的空位用 0 补入。

移位运算符(如上)与移位指令区别:

  • 移位运算符的操作对象只能是某一具体的数(常数),在汇编(编译)时完成移位操作
  • 而移位指令是对一个寄存器或存储单元内容,在程序运行时执行移位操作
NUM=11011011B

MOV  AX, NUM SHL 1   ; AX=1 1011 0110B
MOV  BX, NUM SHR 2   ; BX= 11 0110B
ADD  DX, NUM SHR 6   ; DX = 11B
  1. 下标运算符 [ ] 具有相加的作用

MOV AX,DA_WORD[20H] 等价于 MOV AX,DA_WORD+20H

不过,需要注意的是汇编的 [ ] 和 C 语言不同:

C 语言中,如果变量 DA_WORD 为 2 字节,DA_WORD[20H] 为 DA_WORD 偏移 20H*2 字节后的内容;而汇编中只是普通的加法,DA_WORD[20H] 为 DA_WORD 偏移 20H 字节后的内容。

以下语句均等价:

MOV  AX, ARRAY[BX][SI]  ; 基址变址寻址
MOV  AX, ARRAY[BX+SI]
MOV  AX, [ARRAY+BX][SI]
MOV  AX, [ARRAY+SI][BX]
MOV  AX, [ARRAY+BX+SI]

以下为错误语句:

MOV  AX,ARRAY+BX+SI
MOV  AX,ARRAY+BX[SI]
MOV  AX,ARRAY+DA_WORD

# 逻辑运算符

逻辑运算符有NOT、AND、OR和XOR等四个,它们执行的都是按位逻辑运算。

MOV  AX,NOT 0F0H       ; 0FF0FH
MOV  AL, NOT 0F0H       ; 0FH
MOV  BL, 55H AND 0F0H   ; 50H
MOV  BH, 55H OR 0F0H    ; 0F5H
MOV  CL, 55H XOR 0F0H   ; 0A5H

# 关系运算符

  • 关系运算符包括:EQ(等于)、NE(不等于)、LT(小于,less than)、 LE(小于等于)、GT(大于)、 GE(大于等于)
  • 关系运算符用来比较两个表达式的大小。比较的两个表达式必须同为常数同一逻辑段中的变量
  • 若是常量的比较,则按无符号数进行比较;若是变量的比较,则比较它们的偏移量的大小。
  • 关系运算的结果只能是全 1)或全 0
MOV  AX0FH EQ 1111B  ; AX = 0FFFF
MOV  BX, 0FH NE 1111B  ; BX = 0

VAR  DW NUM LT 0ABH    ; 若符号常量 NUM 值小于 0ABH,则 DW=0FFFFH;否则为 0

# 数值返回运算符

该类运算符有 5 个,它们可以将变量或标号的某些特征值或存储单元地址的一部分提取出来。

# SEG 运算符

取变量或标号所在段的段基址。

; 设 DATA 逻辑段段基址为 1FFEH
DATA  SEGMENT
K1  DW  12
K2  DW  34
; ...
MOV  AX, SEG  K1
MOV  BX, SEG  K2

两条 MOV 指令将被汇编为:

MOV  AX, 1FFEH
MOV  BX, 1FFEH
# OFFSET 运算符

该运算符的作用是取变量或标号在段内的偏移地址。

DATA SEGMENT
VAR1  DB  20H DUP(0)
VAR2  DW  5A49H
ADDR  DW  VAR2         ; ADDR=20H
; ...
MOV  BX,VAR2          ; BX=5A49H
MOV  SI, OFFSET VAR2   ; SI=20H
MOV  DI, ADDR          ; DI=20H
MOV  BP,OFFSET ADDR   ; BP=22H

获取变量的偏移量还可以用指令 LEA

需要注意的是:

MOV SI, OFFSET DATA[BX]
; 错误,个人猜测是因为 OFFSET 只能取最简单的 *变量* 的偏移地址

LEA SI, DATA[BX]
; 正确

另外,OFFSETSEG 的返回值可认为是立即数,不能直接 MOVDSES(见MOV 指令)。解决办法是经过 AX 中转。

# TYPE 运算符

作用:取变量或标号的类型属性,并用数字形式表示。对变量来说就是取它的字节长度。

类型 返回值
BYTE 1
WORD 2
DWORD 4
QWORD 8
TBYTE 10
NEAR 标号 -1
FAR 标号 -2
V1   DB   'ABCDE'
V2   DW   1234H, 5678H
V3   DD   V2
; ...
MOV  AL,  TYPE  V1     ; AL = 1
MOV  CL,  TYPE  V2     ; CL = 2
MOV  CH,  TYPE  V3     ; CH = 4
# LENGTH 运算符
  • 该运算符用于取变量的长度
  • 如果变量是用重复数据操作符 DUP 说明的,则 LENGTH 运算取最外层 DUP的重复值。
  • 如果没有用 DUP 说明,则 LENGTH 运算返回值总是 1
K1  DB   10H DUP(0)
K2  DB   10H20H30H40H
K3  DW   20H DUP (012 DUP(0))
K4  DB   'ABCDEFGH'
; ...
MOV  AL,  LENGTH   K1   ; (AL)=10H
MOV  BL,  LENGTH   K2   ; (BL)=1
MOV  CX,  LENGTH   K3   ; (CX)=20H
MOV  DX,  LENGTH   K4   ; (DX)=1

从上例可以看出,LENGTH 并不能求得字符串变量的长度,还是需要$ 符号

# SIZE 运算符
  • 该运算符只能用于变量
  • SIZE = LENGTH * TYPE
MOV  AL,SIZE  K1     ; (AL)=10H
MOV  BL,SIZE  K2     ; (BL)=1
MOV  CL,SIZE  K3     ; (CL)=20H*2=40H
MOV  DL,SIZE  K4     ; (DL)=1

# 属性修改运算符

这一类运算符用来对变量、标号或存储器操作数的类型属性进行修改或指定。

# PTR 运算符
  • 格式:类型 PTR 地址表达式
  • 作用: 将地址表达式所指定的标号、变量或用其它形式表示的存储器地址的类型属性临时修改为“类型”所指的值
  • 类型:BYTEWORDDWORDNEARFAR
  • 这种修改是临时的,只在含有该运算符的语句内有效,类似于 C 语言 (char)i 强制转换。
DA_BYTE  DB   20H  DUP(0)
DA_WORD  DW   30H  DUP(0)
; ...
MOV  AX,  WORD PTR  DA_BYTE[10]
ADD  BYTE PTR  DA_WORD[20], BL
INC  BYTE PTR  [BX]
SUB  WORD PTR  [SI], 100
JMP  FAR  PTR  SUB1 ; 指明SUB1不是本段中的地址

MOV  WORD PTR[SI]0120H

注意最后一行,将立即数存入 [SI] 指明的内存时,必须使用 BYTE/WORD PTR 显式指明立即数的长度是 8 位还是 16 位,因为立即数和 [SI] 的长度都是不一定的。寄存器值存入内存则不需要,因为寄存器隐式地给出了长度。

# HIGH/LOW 运算符

格式:HIGH 表达式 LOW 表达式

用来将表达式的值分离出高字节和低字节。

  • 如果表达式为一个常量,则将其分离成高 8 位和低 8 位
  • 如果表达式是一个地址(段基值或偏移量)时,则分离出它的高字节和低字节
  • HIGH/LOW 运算符不能分离寄存器、存储器单元或变量中的内容
  • 分离变量的段基址、偏移量时,需要配合 SEGOFFSET。这是符合“变量在伪指令中是偏移地址,在指令中是存储器中内容”的。
; 设 DATA 段的段基址为 0926H
DATA  SEGMENT
CONST EQU 0ABCDH
DA1   DB  10H DUP(0)
DA2   DW  20H DUP(0)
DATA  ENDS
; ...
MOV   AH, HIGH CONST         ; AH = 0ABH
MOV   AL, LOW  CONST         ; AL = 0CDH
MOV   BH, HIGH (OFFSET DA1)  ; BH = 00H
MOV   BL, LOW  (OFFSET DA2)  ; BL = 10H
MOV   CH, HIGH (SEG DA1)     ; CH = 09H
MOV   CL, LOW  (SEG DA2)     ; CL = 26H

# 运算符的优先级

优先级别 运算符
(最高)1 LENGTH, SIZE, ( )
2 PTR, OFFSET, SEG, TYPE, THIS
3 HIGH, LOW
4 *, /, MOD, SHR, SHL
5 +, `-
6 EQ, NE, LT, LE, GT, GE
7 NOT
8 AND
(最低)9 OR, XOR

相同优先级别的操作,从左到右进行。

# 程序的段结构

8086/8088 将内存按逻辑段进行管理,不同的逻辑段可以用来存放不同目的的内容。

在程序中使用四个段寄存器CS,DS,ES和SS来访问它们。

在源程序中,使用伪指令来定义和使用这些逻辑段。

# 段定义伪指令 SEGMENT

伪指令 SEGMENTENDS 用于定义一个逻辑段,分别表示定义的开始与结束。

段名  SEGMENT [定位类型] [组合类型] ['类别名']
; ...
; ...
段名  ENDS
# 段名

段名:由用户定义。

# 定义类型

定位类型:段的起始数据边界,即第一个可存放数据的位置(不是段基地址)。

定位类型 含义 段的起始边界
PAGE 该段从一个页面的边界开始 xxxx xxxx xxxx 0000 0000
PARA(默认) 该段从一个小节的边界开始 xxxx xxxx xxxx xxxx 0000
DWORD 该段从一个双字的边界开始 xxxx xxxx xxxx xxxx xx00
WORD 该段从一个偶数字节地址开始 xxxx xxxx xxxx xxxx xxx0
BYTE 该段起始数据单元地址可以是任一地址值 xxxx xxxx xxxx xxxx xxxx

定位类型为 PAGEPARA 时,段的起始边界直接选用段基址,即它们是重合的。定位类型为 DWORDWORDBYTE 时,段的起始边界与段基址可能不同。

# 组合类型

组合类型说明符用来指定段与段之间的连接关系和定位。

  • 默认:未指定组合类型,表示本段与其它段无连接关系
    • 在装入内存时,本段有自己的物理段,因此有自己的段基址
  • PUBLIC:将该逻辑段接在前一逻辑段后面,形成新逻辑段
    • 在满足定位类型的前提下,将与该段同名的段邻接在一起,形成一个新的逻辑段,共用一个段基址。段内的所有偏移量调整为相对于新逻辑段的段基址
  • COMMON:产生一个覆盖段
    • 在多个模块连接时,把该段与其它也用 COMMON 说明的同名段诉汇编程序,在处理置成相同的段基址,这样可达到共享同一存储区。共享存储区的长度由同名段中最大的段确定
  • STACK:将该段作为栈使用
    • 把所有同名段连接成一个连续段,且系统自动对 SS 段寄存器初始化为该连续段的段基址,并初始化堆栈指针 SP
    • 用户程序中应至少有一个段用 STACK 说明,否则需要用户程序自己初始化 SSSP
  • AT 表达式:手动指定段基址
    • 表示本段可定位在表达式所指示的小节边界上
    • 表达式的值就是段基值
  • MEMORY:本段在存储器中应定位在所有其它段之后的最高地址上
    • 如果有多个用 MEMORY 说明的段,则只处理第一个用 MEMORY 说明的段,其余的被视为 COMMON
# 类别名

类别名为某一个段或几个相同类型段设定类型名称。

系统在进行连接处理时,把类别名相同的段存放在相邻的存储区,但段的划分与使用仍按原来的设定。

类别名必须用单引号引起来。所用字符串可任意选定,但它不能使用程序中的标号、变量名或其它定义的符号。

# 示例
STACK1   SEGMENT  PARA  STACK  'STACK0'
;......
STACK1   ENDS

DATA1    SEGMENT  PARA  'DATA'
;......
DATA1    ENDS

STACK2   SEGMENT  PARA  'STACK0'
;......
STACK2   ENDS

CODE     SEGMENT  PARA  MEMORY
         ASSUME  CS:CODE,DS:DATA1,SS:STACK1
MAIN:
;......
CODE     ENDS
DATA2    SEGMENT  BYTE  'DATA'
;......
DATA2    ENDS
         END  MAIN

该程序经LINK程序连接处理后装入内存的示意图

  • 在段定义中选用了 PARA 说明,则该段起始单元与前面已分配存储单元之间可能存在一些未使用的空白
  • CODE 段的组合类型为 MEMORY,因此被装入在其它段之后(最高地址)

# 段寻址伪指令 ASSUME

ASSUME的作用是告诉汇编程序,在处理源程序时,定义的段与哪个段寄存器关联。

ASSUME并不设置各个段寄存器的具体内容,段寄存器的值是在程序运行时设定的。所以,一般需要同时使用:

ASSUME DS:段名

MOV AX, 段名
MOV DS, AX

一般格式:ASSUME 段寄存器名: 段名, 段寄存器名:段名......

  • 段寄存器名:CS, DS, ESSS
  • 段名:用 SEGMENT/ENDS 定义。
DATA1  SEGMENT
VAR1   DB  12H
DATA1  ENDS

DATA2  SEGMENT
VAR2   DB  34H
DATA2  ENDS

CODE   SEGMENT
VAR3   DB  56H
       ASSUME  CS:CODE, DS:DATA1, ES:DATA2
START:
       ; .....
       INC  VAR1
       INC  VAR2
       INC  VAR3
       ; ......
CODE   ENDS
       END START

例如上面程序中的 3 条 INC 指令:

  • 第 1 条 INC 指令要访问的变量 VAR1 在逻辑段 DATA1 中,由于一般操作数寻址隐含使用的是 DS,而 DATA1 又正是与 DS 对应,所以指令 INC VAR1 就可以直接汇编成目标代码 FE 06 0000
  • 第 2 条 INC 指令要访问的变量 VAR2 在逻辑段 DATA2 中,而 DATA2 是与 ES 对应的,要正确执行这条指令必须使用段前缀(即用 ES 替代 DS),因此汇编程序在汇编这条指令时就自动产生一个段前缀标记代码 26,所以指令 INC VAR2 在这个程序中汇编的目标代码为 26 FE 06 0000
  • 与第 2条 INC 指令类似,第 3 条 INC 指令要访问的变量 VAR3 在逻辑段 CODE 中,指令汇编时自动产生一个段前缀标记代码 2E 表示用 CS 替代 DS,所以指令 INC VAR3 汇编的目标代码为 2E FE 06 0000

可以使用关键字 NOTHING 将前面的设置删除。

ASSUME  ES:NOTHING    ; 删除前面对 ES 与某个定义段的关联
ASSUME  NOTHING       ; 删除全部 4 个段寄存器的设置

但是 ASSUME 的存在意义依然有点奇怪:DS 被改为段基址以后,想知道 DS 和谁被关联了,读一下 DS 不就行了吗,为什么还要 ASSUME 呢?

搜索了一下,大概明白了:

ASSUME 是给汇编程序看的,而 MOV AX, DATA MOV DS, AX 是给 CPU 看的。

汇编的时候,代码还没在 CPU 里跑,自然是没法读取 DS 的,所以要通过 ASSUME 语句设定。

# 段寄存器的装入

从上面可以看出,要让一个段寄存器真正地指向某个逻辑段,一般需要两个步骤:

  1. 将段基值装入到该段寄存器
  2. 将段和段基址建立关联(常用 ASSUME 语句实现)
# DS 和 ES 的装入

下面是一个错误示范:

DATA1   SEGMENT
DBYTE1  DB  12H
DATA1   ENDS
DATA2   SEGMENT
DBYTE2  DB 14H DUP(?)
DATA2   ENDS

CODE    SEGMENT
        ASSUME  CS:CODE, DS:DATA1
START:  MOV  AX,DATA1
        MOV  DS,AX
        MOV  AX,DATA2
        MOV  ES,AX
        MOV  AL,DBYTE1    ; 正确
        MOV  DBYTE2[2],AL ; 错误,因为 ASSUME 指令中未指定 ES 与 DATA2 关联
CODE   ENDS

使用逻辑段前需要进行两个步骤:

  • 使用 ASSUME ES: DATA2 建立 ESDATA2 的联系
  • 使用 MOV AX,DATA2 MOV ES,AXDATA2 段基址装入 ES

如果已经将段基址装入了 ES,也可以临时指明段前缀:

MOV  ES:DBYTE2[2],  AL
# SS 的装入

段的组合类型中提到,若将一个段声明为 STACK,系统会自动初始化 SSSP 寄存器为对应的值。

因此,SS 的装入有两种方法:

  1. 手动使用 MOV 装入 SSSP,然后 ASSUME SS:段名
DATA_STACK   SEGMENT
        DB   40H DUP(?)
       TOP   LABEL  WORD
DATA_STACK   ENDS

CODE         SEGMENT
       MOV   AX,DATA_STACK
       MOV   SS,AX
       MOV   SP,OFFSET TOP

上述例子中,SS=DATA_STACKSP=TOP=40H

  1. 定义段时使用 STACK 作为组合类型(系统自动装入 SSSP),然后 ASSUME SS:段名
STACK1  SEGMENT  PARA  STACK
        DB  40H DUP(?)
STACK1  ENDS

CODE    SEGMENT
        ASSUME  CS:CODE, SS:STACK1

上述例子中,SS=STACK1SP=40H

# CS

CPU在执行指令之前根据CS和IP的内容来从内存中取指令,即必须在程序执行之前装入CS和IP的值。因此,CS和IP的初始值就不能用可执行语句来装入。

装入CS和IP一般有下面两种情况。

  1. 程序加载到主存时,由系统软件给CS和IP赋予初始值

结束伪指令格式:END 起始地址

  • 起始地址: 是一个标号或表达式,它必须是程序中第一条指令语句前所加的标号,如前文的 END MAIN
  • 汇编源程序必须以END伪指令结束
  • END伪指令指示源程序结束并指定程序运行时的第一条指令的地址(起始地址)
  • 起始地址段基址和偏移量被分别装入CS和IP中
CODE   SEGMENT
       ASSUME  CS:CODE,......
START:
; ...
CODE   ENDS
       END START
  1. 程序运行期间,当执行某些指令时,CPU自动修改CS和IP,使它们指向新的代码段。
  • 执行段间过程调用CALL和段间返回指令RET;
  • 执行段间无条件转移指令JMP;
  • 响应中断及中断返回指令;
  • 执行硬件复位操作。
# 总结
  • 修改 DS、ES:
    1. MOV + ASSUME
    2. MOV + 临时指明段基址 ES:段名
  • 修改 SS:
    1. 声明段为 STACK + ASSUME
    2. MOV + ASSUME

# 过程定义伪指令 PROC

在程序设计过程中,常常将具有一定功能的程序段设计成一个子程序。在MASM宏汇编语言中,用过程 (PROCEDURE) 来构造子程序。

过程名   PROC  [NEAR/FAR]
        RET
过程名   ENDP
  • 过程名如同标号,具有段、偏移量和距离三个属性
  • 距离属性使用NEAR和FAR来指定,默认为NEAR

每一个过程中必须包含有返回指令RET,它控制CPU从过程中返回到调用该过程的主程序。

# 当前位置计数器 $ 与定位伪指令 ORG

汇编程序在汇编源程序时,每遇到一个逻辑段,就要为其设置一个位置计数器,用来记录该逻辑段中定义的每一个数据或每一条指令在逻辑段中的相对位置。

可以使用 $ 获取位置计数器的值。$ 也被称为当前位置计数器。$ 在使用上完全类似变量的使用。

可以使用 ORG 改变位置计数器的值。

DATA1     SEGMENT
          ORG  30H                ; 跳到 30H,即保留段的前 30H 字节
          DB1  DB  12H,34H        ; DB1 偏移量为 30H
          ORG  $+20H              ; $ = 32H,跳到 52H,即保留段的 32H~51H 字节
          STRING  DB  'ABCDEFGHI' ; STRING 偏移量为 52H
          COUNT   EQU   $-STRING  ; COUNT = 9,即 STRING 的长度
          DB2  DW  $              ; DB2 = $ = 自己的偏移量
          DB3  DB   $+20H         ; 错误,访问到了段外的内容
DATA1     ENDS

CODE      SEGMENT
          ASSUME  CS:CODE, ......
          ORG  12H
START:    MOV  AX,DATA
          MOV  DS,AX
CODE      ENDS
          END START

# DOS 功能调用

  • DOS操作系统为程序设计人员提供了可以直接调用的功能子程序
  • 调用这些子程序可以实现从键盘输入数据,将数据送显示器显示,打印机打印,以及磁盘操作等功能
  • 调用DOS功能需要用软中断指令 INT 21H,并在执行该指令之前,将调用的功能号送入寄存器 AH 中,有关的参量送指定的寄存器。

三步骤:

  1. 送入口参量到指定的寄存器
  2. 送功能号到 AH
  3. 执行 INT 21H

# 带显示的键盘输入(1号功能)

  • 该功能子程序将等待键盘输入,直到按下一个键
  • 将字符的ASCII码送入AL寄存器,并在屏幕上显示该字符
  • 如果是Ctrl-C组合键,则停止程序运行
  • 该功能调用无入口参量
MOV  AH01H
INT  21H  

# 不带显示的键盘输入(8号功能)

  • 该功能调用与1号功能的作用相似,区别是8号功能将不显示输入的字符
MOV  AH8
INT  21H

# 不带显示的键盘字符输入(7号功能)

该功能与8号功能相似,但对Ctrl-C组合键和TAB制表键无反应。

MOV AH7
INT   21H

# 字符串输入(0AH号功能)

  • 该功能调用可实现从键盘输入字符串,其长度 <= 255个字符
  • 调用前,应在内存中建立输入缓冲区
    • 缓冲区第一个字节是可输入的最大字符数+1
    • 第二个字节是系统在调用该功能时,自动填入的实际输入的字符个数
    • 从第三个字节开始存放输入字符的ASCII码(见后面例子)
  • 当用户输入回车键时,结束输入,并将回车键的ASCII码 0DH 作为最后一个字符送入缓冲区。但它不计入实际输入字符个数

入口参量:DSDX 寄存器分别装入输入缓冲区的段基值和偏移量

CHAR_BUF DB 31H       ;缓冲区的最大长度
         DB 0         ;存实际输入字符数
         DB 31H DUP(0);输入缓冲区
         ; ......
MOV DX,SEG CHAR_BUF   ;如果DS已经指向CHAR_BUF所在
MOV DS,DX             ;数据段,则可以省去这两条指令
MOV DX,OFFSET CHAR_BUF
MOV AH,0AH
INT 21H

# 字符显示(2号功能)

该功能实现在屏幕上显示单个字符。

入口参量:DL 装入要显示字符的ASCII码。

MOV  DL, 'A'
MOV  AH2
INT  21H

# 字符打印(5号功能)

该功能将字符送入打印机接口,实现单个字符的打印操作。

入口参量:DL 装入打印字符的ASCII码

MOV  DL'A'
MOV  AH5
INT  21H

# 字符串显示(9号功能)

该功能实现将一个字符串显示到屏幕上。

入口参数:

  • 将待显示的字符串存放在一个数据缓冲区,字符串以符号“$”作为结束标志。
  • 将字符串的首址的段基值和偏移量分别送入 DSDX
CHAR    DB  'This is a test.', 0AH, 0DH, '$'
; ......
MOV  DX, OFFSET CHAR
MOV  AH, 9
INT  21H

# 直接输入输出(6号功能)

该功能可以实现键盘输入,也可以实现屏幕显示操作。两种操作通过 DL 的内容确定。

  • (DL)=00~0FEH 时,输出 DL 对应的字符。
MOV  DL24H  ; $的ASCII码为24H
MOV AH06  
INT   21H     ; 输出 $
  • (DL)=FFH 时 ,从键盘输入字符

该功能不等待键盘输入,而是从键盘缓冲区中读取。读取的字符ASCII码送入AL中,如果缓冲区为空,则标志位ZF=1。

WAIT: MOV  DL0FFH
      MOV  AH6
      INT  21H
      JZ   WAIT

# 读出系统日期(2AH号功能)

读出的日期信息放入指定的寄存器中:

  • CX:年(1980—2099)
  • DH:月(1—12)
  • DL:日(1—31)
  • AL:星期(0—星期日,1—星期一……)
YEAR     DW  ?
MONTH    DB  ?
DAY      DB  ?
; ......
MOV  AH,2AH
INT  21H
MOV  YEAR,CX
MOV  MONTH,DH
MOV  DAY,DL

# 设置系统日期(2BH号功能)

该功能用来改变计算机CMOS中的系统日期。入口参数:

  • CX<=年号(1980—2099)
  • DH<= 月号(1—12)
  • DL<= 日(1—31)

返回参数在AL中,成功设置,则返回 (AL)=0,否则 (AL)=0FFH

MOV  CX,2000
MOV  DH,11
MOV  DL,2
MOV  AH,2BH
INT  21H
CMP  AL,0
JNE  ERROR ;转出错处理

# 读出系统时间(2CH号功能)

执行该功能将获得系统的当前时间。返回的时间参数存放在指定的寄存器中:

  • CH:小时(0—23)
  • CL:分(0—59)
  • DH:秒(0—59)
  • DL:百分秒(0—99)

# 设置系统时间(2DH号功能)

调用该功能,将设定系统时间。其入口参量为:

  • CH:小时(0—23)
  • CL:分(0—59)
  • DH:秒(0—59)
  • DL:百分秒(0—99)

该功能执行后返回时,如果调用成功,则(AL)=0。否则(AL)=0FFH

# 程序返回操作系统的方法

# 在DOS操作系统下运行

在早期的计算机中使用DOS操作系统,系统运行为单一进程,为了让系统运行完用户的程序后,能够正确地返回到操作系统,需要在程序中加上一些必要的语句。

一般有以下两种方法:

  • 使用DOS系统功能调用实现返回
  • 使用程序段前缀PSP实现返回
# 使用DOS系统功能调用实现返回

执行DOS功能调用4CH,控制用户程序结束,并返回DOS操作系统。

在程序结束时,使用两条指令:

MOV  AH4CH
INT  21H

程序结构如下:

DATA    SEGMENT
        ; ……
DATA    ENDS              ;定义数据段

STACK1  SEGMENT PARA STACK
        DW 20H DUP (0)
STACK1  ENDS              ;定义堆栈段

CODE    SEGMENT
        ASSUME CS:CODE,DS:DATA,SS:STACK1
START:                    ;指令开始地址   
        MOV AX,DATA
        MOV DS,AX         ;初始化DS
        ; ……
        MOV  AH, 4CH
        INT  21H          ;返回DOS操作系统
CODE    ENDS
        END START         ;汇编结束标志
# 使用程序段前缀PSP实现返回
  • DOS系统将一个.EXE文件(可执行文件)装入内存时,在该文件的前面生成一个程序段前缀PSP Program Segment Prefix
  • PSP的长度为100H字节
  • 系统将DS和ES都指向PSP的开始
  • CS指向该程序的代码段,即第一条可执行指令

PSP的开始是一条中断指令 INT 20H,执行该指令将终止用户程序,返回DOS系统。

内存结构

为了使程序执行完后,正确返回DOS,需要做以下三个操作:

  1. 将用户程序编制成一个过程,类型为 FAR
  2. 将PSP的起始逻辑地址压栈,即将 INT 20H 指令的地址压栈
  3. 在用户程序结尾处,使用一条 RET 指令。执行该指令将使保存在堆栈中的PSP的起始地址弹出到CS和IP中
DATA    SEGMENT
        ;……
DATA    ENDS         ;定义数据段

STACK1  SEGMENT PARA STACK
        DW 20H DUP (0)
STACK1  ENDS         ;定义堆栈段

CODE    SEGMENT
        ASSUME CS:CODE,DS:DATA,SS:STACK1
MAIN    PROC  FAR    ;设置为FAR过程
        PUSH  DS     ;为返回操作系统执行INT 20H 指令做准备
        MOV  AX,0
        PUSH  AX     ;立即数不能够作为操作数 
        MOV  AX,DATA
        MOV  DS,AX   ;初始化DS
        ; ......
        RET          ;返回操作系统
MAIN    ENDP
CODE    ENDS
        END MAIN     ;汇编结束标志

# 在Windows操作系统下运行

Windows系统下系统的运行为多进程方式,进程间切换由系统自动完成,即当用户的程序结束后使用 HLT 就自动返回操作系统。

DATA    SEGMENT
        ; ......
DATA    ENDS                ;定义数据段

STACK1  SEGMENT PARA STACK
        DW 20H DUP (0)
STACK1  ENDS                ;定义堆栈段

CODE    SEGMENT
        ASSUME CS:CODE,DS:DATA,SS:STACK1
START:                      ;指令开始地址   
        MOV AX,DATA
        MOV DS,AX           ;初始化DS等
        ; ......
        HLT                 ;结束程序
CODE    ENDS
        END START           ;汇编结束标志

# 汇编语言程序应用实例

# 顺序程序设计实例

例 1:利用学号查学生的数学成绩表

TITLE   TABLE       LOOKUP

DATA    SEGMENT
        TABLE   DB 81, 78, 90, 64, 85, 76, 93, 82, 57, 80
                DB 73, 62, 87, 77, 74, 86, 95, 91, 82, 71
        NUM     DB 8
        MATH    DB ?
DATA    ENDS

STACK1  SEGMENT  PARA  STACK
        DW 20H    DUP(0)
STACK1  ENDS

COSEG   SEGMENT
        ASSUME  CS:COSEG, DS:DATA, SS:STACK1
START:  MOV  AX, DATA
        MOV  DS, AX               ;装入DS
        MOV  BX, OFFSET  TABLE    ;BX指向表首址
        XOR  AH, AH               ;(AH)=0
        MOV  AL, NUM          
        DEC  AL                   ;实际学号是从1开始的
        ADD  BX, AX               ;BX加上学号指向要查的成绩
        MOV  AL,[BX]              ;查到成绩送AL
        MOV  MATH, AL             ;存结果
        MOV  AH, 4CH              ;返回DOS
        INT  21H
COSEG  ENDS
        END   START

# 分支程序实例

# 用比较/测试指令+条件转移指令实现分支
  • CMP DEST,SRC:做减法
  • TEST DEST,SRC:做与运算

例 2:数据段的ARY数组中存放有10个无符号数,试找出其中最大者送入MAX单元。

DATA    SEGMENT
        ARY  DB 175400671234783210
        MAX  DB ?
DATA    ENDS

STACK1  SEGMENT PARA STACK
        DW  20H DUP(0)
STACK1  ENDS

CODE    SEGMENT
        ASSUME DS:DATA CS:CODE SS:STACK1
START:  MOV  AX, DATA
        MOV  DS, AX         ;SI指向ARY的第一个元素
        LEA  SI, ARY
        MOV  CX, 9          ;CX作次数计数器,遍历后九个元素
        MOV  AL, [SI]       ;取第一个元素到AL
LOP:    INC  SI             ;SI指向后一个元素      
        CMP  AL, [SI]       ;比较两个数
        JAE  BIGGER         ;前元素≥后元素转移
        MOV  AL, [SI]       ;取较大数到AL
BIGGER: DEC  CX             ;减1计数
        JNZ  LOP            ;未比较完转回去,否则顺序执行
        MOV  MAX, AL        ;存最大数
        MOV  AH, 4CH
        INT  21H
CODE  ENDS
        END   BEGIN

例 3:编写一程序,实现将存储器中的源数据块传送到目的数据块。

需要注意的是,在存储器中两个数据块的存放有下列情况:两个数据块分离和有部分重叠。

我们采用:当源块首地址 < 目的块首地址时,从数据块末地址开始传送。反之,则从首地址开始传送。

TITLE   DATA BLOCK MOVE

DATA    SEGMENT
        ORG  $+20H
        STRG  DB    'ABCDEFGHIJ'  ; 数据块
        LENG  EQU   $-STRG        ;数据块字节长度
        BLOCK1 DW   STRG          ;源块首址
        BLOCK2 DW   STRG-5        ;目的块首址
DATA    ENDS

STACK1  SEGMENT STACK
        DW   20H DUP(0)
STACK1  ENDS

COSE    SEGMENT 
        ASSUME CS:COSE, DS:DATA, SS:STACK1
BEGIN:  MOV  AX, DATA
        MOV  DS, AX
        MOV  CX, LENG      ;设置计数器初值
        MOV  SI, BLOCK1    ;SI指向源块首址
        MOV  DI, BLOCK2    ;DI指向目的块首址 
        CMP  SI,DI         ;源块首址>目的块首址吗? 
        JA   TOP           ;大于则转到TOP处,否则顺序执行
        ADD  SI,LENG-1     ;SI指向源块末址
        ADD  DI,LENG-1     ;DI指向目的块末址
BOTTOM: MOV  AL[SI]      ;从末址开始传送
        MOV  [DI],  AL
        DEC  SI
        DEC  DI
        DEC  CX
        JNE  BOTTOM
        JMP  END1
TOP:    MOV  AL,[SI]       ;从首址开始传送
        MOV  [DI],AL
        INC  SI
        INC  DI
        DEC  CX
        JNE  TOP
END1:   MOV  AH, 4CH
        INT  21H
COSE    ENDS
        END BEGIN
# 用跳转表形成多路分支

当程序的分支数量较多时,采用跳转表的方法可以使程序长度变短。跳转表有两种构成方法:

  • 跳转表用入口地址构成
  • 跳转表用无条件转移指令构成
# 跳转表用入口地址构成

这种方法将所有入口地址存在一个变量数组中(称为跳转表),跳转前取出对应地址然后直接跳。

例 4:设某程序有10路分支,试根据变量N的值(1~10),将程序转移到其中的一路分支去。

TITLE   JUMP  TABLE  OF  ADDRESS
DATA    SEGMENT
        ATABLE   DW  BRAN1, BRAN2, BRAN3, ..., BRAN10
        N        DB  3
DATA    ENDS

STACK1  SEGMENT  PARA   STACK
        DW       20H DUP (0)
STACK1  ENDS

CODE    SEGMENT
        ASSUME   CS:CODE, DS:DATA, SS:STACK1
START:  MOV  AX, DATA
        MOV  DS, AX
        XOR  AH, AH
        MOV  AL, N
        DEC  AL
        SHL  AL,1
        MOV  BX,OFFSET ATABLE  ;BX指向表首址
        ADD  BX,AX             ;BX指向查表地址                       
        MOV  CX,[BX]           ;将N对应的分支入口地址送到CX中
        JMP  CX                ;转移到N对应的分支入口地址
BRAN1:  ; ...
        JMP      END1
BRAN2:  ; ...
        JMP      END1
BRAN3:  ; ...
        JMP      END1           
BRAN10: ; ...
END1:   MOV  AH,4CH
        INT  21H
CODE    ENDS
        END     START
# 跳转表用无条件转移指令构成

跳转表的每一个项目就是一条无条件转移指令。这时跳转表是代码段中的一段程序。利用 每一条 JMP 指令都是3字节编码,可以在跳转表前计算出目标 JMP 指令的地址,然后跳转。

例 4 的另一种写法。

TITLE   JUMP TABLE OF INSTRUCTION
DATA    SEGMENT
        N  DB  3
DATA  ENDS

STACK1  SEGMENT PARA STACK
        DW  20H DUP(0)
STACK1  ENDS

CODE    SEGMENT
        ASSUME  CS:CODE, DS:DATA, SS:STACK1
START:  MOV  AX,DATA
        MOV  DS,AX
        MOV  BH,0
        MOV  BL,N
        DEC  BL                ;四条指令实现(N-1)*3
        MOV  AL, BL            ;每一条指令都是3字节编码
        SHL  BL, 1
        ADD  BL, AL
        ADD  BX, OFFSET ITABLE ;BX指向查表地址    
        JMP  BX                ;转移到N对应的JMP指令

ITABLE: JMP  BRAN1             ;JMP指令构成的跳转表
        JMP  BRAN2             ;每一条指令都是3字节的编码
        JMP  BRAN3
        ; ...
        ; ...
        JMP  BRAN10

BRAN1:  ; ...
        JMP  END1
BRAN2:  ; ...
        JMP  END1
; ...
; ...
BRAN10: ; ...

END1:   MOV  AH,4CH
        INT  21H
CODE    ENDS
        END  START

# 循环程序实例

# 计数控制循环——循环次数已知

使用 CX 计数即可。

例 5:设有两个数组X和Y,它们都有8个元素,其元素按下标从小到大的顺序存放在数据段中。试编写程序完成下列计算:

Z1=X1+Y1   Z2=X2-Y2    Z3=X3+Y3 
Z4=X4-Y4   Z5=X5-Y5    Z6=X6+Y6
Z7=X7+Y7   Z8=X8-Y8

由于循环体中有“+”和“-”两种可能的运算,通过设置标志0(+)和1(-)来判断,低位表示低下标的运算。八个运算表达式由8位逻辑尺:10011010B来识别。

DATA    SEGMENT
        X       DB  0A2H,7CH,34H,9FH,0F4H,10H,39H,5BH
        Y       DB  14H,05BH,28H,7AH,0EH,13H,46H,2CH
        LEN     EQU $ -Y
        Z       DB  LEN DUP(?)
        LOGR    DB  10011010B       ;设置标志0(+)和1(-)来判断,低位表示低下标的运算
DATA    ENDS

STACK0  SEGMENT PARA STACK
        DW  20H DUP(0)
STACK0  ENDS

COSEG   SEGMENT
        ASSUME CS:COSEG, DS:DATA, SS:STACK0
BEGIN:  MOV  AX, DATA
        MOV  DS, AX
        MOV  CX, LEN        ;初始化计数器
        MOV  SI, 0          ;初始化指针
        MOV  BL, LOGR       ;初始化逻辑尺
LOP:    MOV  AL, X[SI]
        SHR  BL, 1          ;标志位送CF
        JC   SUB1           ;为1,转做减法
        ADD  AL, Y[SI]      ;为0,做加法
        JMP  RES
SUB1:   SUB  AL, Y[SI]
RES:    MOV  Z[SI], AL      ; 存结果
        INC  SI             ; 修改指针
        LOOP LOP
        MOV  AH, 4CH
        INT  21H
COSEG   ENDS
        END BEGIN
# 条件控制循环——循环次数未知

根据条件控制使用 JZ JL JG 等语句进行跳转实现循环。

例 6 编写一程序,将字单元 VARW 中含1的个数(含1的个数是指用二进制表示时,有多少个1)统计出来,存入CONT单元中。

DATA    SEGMENT
        VARW    DW   1101010010001000B
        CONT      DB   ?
DATA    ENDS

STACK1  SEGMENT PARA STACK
        DW  20H DUP(0)
STACK1  ENDS

CODE    SEGMENT
        ASSUME  CS:CODE, DS:DATA, SS:STACK1
BEGIN:  MOV  AX, DATA
        MOV  DS, AX
        MOV  CL, 0                  ;初始值为0,统计1的个数
        MOV  AX, VARW
LOP:    EST  AX, 0FFFFH             ;测试(AX)是否为0
        JZ   END0                   ;为0,循环结束
        JNS  SHIFT                  ;判最高位,为0则转SHIFT
        INC  CL                     ;最高位为1,计数
SHIFT:  SHL  AX, 1
        JMP  LOP
END0:   MOV  CONT, CL ;存结果
        MOV  AH, 4CH
        INT  21H
CODE    ENDS
        END  BEGIN

# IA-32 微处理器的指令与汇编语言结构简介

# 寻址方式

相对 8086 来说,IA-32 扩充了寄存器结构,采用了多种存储器管理方式 ,因此其寻址方式有了较大的增强。

  1. 允许使用 32 位的通用寄存器作寄存器间接寻址。例如:MOV DX, [EBX]
  2. 所有的32位通用寄存器均可作为基址寄存器使用;除 ESP 外的32位通用寄存器都能作为变址寄存器使用。

例如:

MOV  EDX, [EDX+16]
MOV  AX, ADDR[EBP]
MOV  EBX, [ESI][EAX]
  1. 32 位的变址寄存器可以乘上一个比例常数2、4和8,从而形成比例变址寻址方式、基址-比例变址寻址方式和基址-比例变址-位移寻址方式。

寻址方式

上述三种情况可用如图公式表示。

例如:

MOV  EBX, ADDR[ESI*2]      ; 比例变址寻址方式
MOV  EAX, [EDI*4][EDX]     ; 基址比例变址寻址方式
MOV  EBX, [EDI*8][EBP+10]  ; 基址比例变址位移寻址方式

# 扩展的指令

IA-32微处理器对8086/8088的指令进行了扩展或新增了指令。所有指令的操作数可以是8位、16位或32位。

下面列举其中部分新增的指令。

# 数据传送指令
# 符号扩展:MOVSX MOVZX

格式:MOVSX DEST, SRC MOVZX DEST, SRC

  • 两种用法:
    • 8 位寄存器/存储器 -> 16/32 位寄存器
    • 16 位寄存器/存储器 -> 32 位寄存器
  • MOVSX是带符号数扩展指令(符号填充),MOVZX是无符号数扩展指令(零填充)

例如:

MOVSX  ECX, AX  ; 将字扩充到双字
MOVZX  BX, AL   ; 将字节扩充到字
# 取偏移量 LFS LGS LSS

类似于 LDSLESDEFG 还行)

LFS 指令将 32 位的偏移量送目的寄存器,16 位的段选择子送 FS。

其他两条指令类似,只是段寄存器变为GS和SS。

# 算术运算指令

新增了两种乘法指令,格式:

IMUL  DEST, SRC 
; 将(SRC)与(DEST)相乘,将结果放在 DEST 中

IMUL  DEST, SRC1, SRC2
; 将(SRC1)与(SRC2)相乘,将结果放在 DEST 中

新增了两条扩展指令

CWDE
; Convert Word to Doubleword
; 将AX的符号位扩展到EAX的高16位
; 加一个 E 与 CWD (扩展位放在 DX)以示区别

CDQ
; Convert Dooubleword to Quadword
; 将EAX的符号位扩展到EDX的所有32位
# 逻辑运算与移位指令
  1. 对于移位指令,当移位次数大于 1 时,允许使用立即数作为操作数。

例如,SAL AX, 2

  1. 新增了 SHLDSHRD 指令
SHLD  DEST, SRC, OPTR
; 对 DEST 左移 OPTR 位,移出的位依次进入 CF 标志位,DEST 空出的位由 SRC 的高位顺序移入。移位结束后SRC保持不变

SHRD   DEST, SRC, OPTR
; 对 DEST 右移 OPTR 位,移出的位依次进入 CF 标志位,DEST 空出的位由 SRC 的低位顺序移入。移位结束后,SRC保持不变

8086 的 RCL RCR只能移一次,空出的位由 CF 补。

# 堆栈操作指令
  1. PUSH允许立即数入栈
PUSH  0ABCDH      ;将16位立即数入栈
PUSH  0ABCD0000H  ;将32位立即数入栈  
  1. 新增两条PUSH指令

PUSHA 指令将8个16位通用寄存器AX、BX、CX、DX、SP、BP、SI与DI一次性入栈。

PUSHD 指令将8个32位通用寄存器EAX、EBX、ECX、EDX、ESP、EBP、ESI与EDI一次性入栈。

似乎是为了函数调用时保存所有寄存器值?

  1. 新增两条POP指令

与上面对应地:

POPA指令从栈顶弹出8个字数据分别送入AX、BX、CX、DX、SP、BP、SI与DI

POPD指令从栈顶弹出8个双字数据分别送入EAX、EBX、ECX、EDX、ESP、EBP、ESI与EDI

# IA-32 汇编语言程序框架概述

IA-32汇编语言程序设计的方法同前面介绍的16位指令程序设计基本相同。但在结构上有三点主要差异。

  • 在开始处增加了方式选择;
  • 在段定义中增加了使用类型可选项;
  • 可以同时使用6个段。
# 方式选择

方式选择伪指令用来确定微处理器工作模式和当前指令集。

.8086 -选择8086/8088指令集。这是汇编程序默认的方式。 .286 -选择80286实地址方式指令集 .286P -选择80286保护方式 .386 -选择80386非保护方式指令集。 .386P -选择80386保护方式

# 确定段的使用类型属性

IA-32的段定义格式:段名 SEGMENT [定位类型] [组合类型] [类别名] [使用类型],较8086多了一个使用类型。

使用类型: 有USE16USE32两种取值,用来定义段寻址方式

  • USE16:8086/8088实地址方式,段基值16位,偏移量16位,最大段长64KB。该值为缺省值
  • USE32:对应保护方式,段基值(段选择子)16位,偏移量32位,最大段长4GB。
# 例题

例: 统计一个字数组中非负数的个数,结果保存在RESULT单元中。

.386
DATA 	SEGMENT	PARA	USE16
ARRAY	DW   234, -87, 65, 0, 23, -54, 23,44,-54, 0
CNT	EQU	$-ARRAY
RESULT	DW  ?
DATA	ENDS
STACK1	SEGMENT	PARA	STACK USE16	 
                       DW 100 DUP(?)
STACK1	ENDS
CODE     SEGMENT  PARA USE16
           ASSUME CS:CODE, DS:DATA, SS:STACK1     
BEGIN:	MOV	AX, DATA
		MOV	DS, AX
                        LEA	SI, ARRAY
		MOV	CX, CNT/2
ACS:	            LODSW
		BT  AX, 15 ;386新增指令,将AX的第15位送CF 
		JC	NEXT
		INC	RESULT
NEXT:	LOOP	ACS
		MOV	AH, 4CH
		INT	21H
CODE	            ENDS
		END	BEGIN 		

bt是位操作指令:

指令的格式:BT/BTC/BTR/BTS Reg/Mem,Reg/Imm ;80386+
位检测指令是把第一个操作数中某一位的值传送给标志位CF,具体的哪一位由指令的第二操作数来确定。 根据指令中对具体位的处理不同,又分一下几种指令:
BT:把指定的位传送给CF;
BTC:把指定的位传送给CF后,还使该位变反;
BTR:把指定的位传送给CF后,还使该位变为0;
BTS:把指定的位传送给CF后,还使该位变为1;
例如:假设(AX)=1234H,分别执行下面指令。
BT AX, 2 ;指令执行后,CF=1,(AX)=1234h
BTC AX, 6 ;指令执行后,CF=0,(AX)=1274h
BTR AX, 10 ;指令执行后,CF=0,(AX)=1234h
BTS AX, 14 ;指令执行后,CF=0,(AX)=5234h

下面再看一个不像前面学的汇编语言的汇编语言程序:

.386
.model flat, stdcall
;Include文件定义
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
;数据段
. Data
Caption  db    'A MessageBox !',0
Text         db    'Hello, World !', 0
;代码段
.code
start:
        invoke MessageBox,NULL,offset Text, offset Caption,MB_OK
        invoke ExitProcess, NULL
end   start

程序执行结果

# 第六章 输入输出及中断技术

# 输入输出系统

# 概念

输入输出系统:计算机系统中除 CPU 和内存储器之外的部分

I/O系统分为:

  • 输入输出设备
  • 输入输出接口
  • 输入输出软件

特点:

  • 复杂性:输入输出设备、处理器、操作系统的复杂性
  • 异步性:工作速度和时序不一致
  • 实时性:控制的时效性。I/O系统保证处理器对不同设备的请求提供及时服务
  • 与设备无关性:接口的标准化,由操作系统屏蔽了设备的差异。

# I/O 接口系统及接口

有关I/O系统的概念、特点,以及I/O接口的功能等,请参阅教材描述,自行学习。

总体上,I/O接口应具备以下功能:

  • 数据的缓冲与暂存
  • 信号电平与类型的转换
  • 增加信号的驱动能力
  • 对外设进行监测、控制与管理,中断处理

# I/O 端口

  • 端口:接口中的寄存器
  • 端口的主要作用:信息的缓存
  • 端口类型:
    • 数据端口:缓存输入和输出的数据
    • 状态端口:缓存需要输入的外设工作状态
    • 控制端口:缓存由系统输出的各种控制信息

I/O端口


I/O系统中的接口和端口的地址:

  • 每个接口为一个芯片,每个芯片有一个基地址。
  • 端口地址=芯片地址(高位地址)+片内地址

# I/O 端口的编址方式

编址方式:

  1. 与内存统一编址
  2. 独立编址
# I/O 端口与内存统一编址
  • 指令及控制信号统一
  • 内存地址资源减少

I/O端口与内存统一编址

# I/O 端口独立编址
  • 内存地址资源充分利用
  • 能够应用于端口的指令较少

I/O端口独立编址

# 8088/8086 的 I/O 端口编址
  • 采用I/O端口独立编址方式(但地址线与存储器共用)
  • 地址线上的地址信号用IO/MMIO/\overline{M}MIO/M\overline{IO}/M)来区分
  • I/O操作只使用20根地址线中的16根:A15~A0
  • 可寻址的I/O端口数为64K(65536)个
  • I/O地址范围为0~FFFFH

# I/O 地址译码

I/O地址译码

# 全地址译码与部分地址译码

一个接口电路中可以有一个或多个端口。

  1. 全地址译码:
  • 全部16位I/O地址信号参与译码
  • 当接口中只有一个端口时, 16位地址线应全部参与译码,译码输出直接选择该端口中;
  • 当接口中有多个端口时,则16位地址线的高位参与译码(决定接口的基地址),低位直接输入到接口芯片,用于寻址接口中要访问的端口。
  1. 部分地址译码:
  • 仅用部分地址(比如16位中低10位)信号参与译码
  • 含多个端口的接口,最低的几位直接连到接口芯片

I/O系统中,因地址资源丰富,多采用部分地址译码。

看不出来有什么区别。

# I/O地址译码例题

例:某外设接口有 4 个端口,地址为 2F0H——2F3H,由 A15~A2 译码得到,而 A1、A0 用来区分接口中的 4 个端口。试画出该接口与系统的连接图。

题目分析:

  • 寻址端口的地址信号最多为 16bit,题中仅用 12bit 就能表示其地址——故采用部分地址译码
  • 该接口电路中含有4个端口,片内端口寻址需 2 位地址信号,其余 10 位为接口芯片地址,即片选地址信号。

地址范围:

  • xxxx 0010111100 00 (2F0H)
  • ......
  • xxxx 0010111100 11 (2F3H)
  • x 表示任意状态,图中无需接入;后两位表示片内地址

译码电路图

最上面表示 IOR\overline{IOR}IOW\overline{IOW} 至少有一个是 0(有效)时芯片有效,若都是 1 则芯片无效。

# 接口的基本构成

接口的基本构成

  • AB (Address Bus)
  • DB (Data Bus)
  • CB (Control Bus)
  • 数据输入/输出寄存器:暂存输入/输出的数据
  • 命令寄存器:存放控制命令(设定接口功能、工作参数和工作方式)
  • 状态寄存器:保存外设当前状态,以供CPU读取

# 接口的类型及特点

  • 按传输信息的方向分类:
    • 输入接口
    • 输出接口
  • 按传输信息的类型分类:
    • 数字接口
    • 模拟接口
  • 按传输信息的方式分类:
    • 并行接口
    • 串行接口

接口特点:

  • 输入接口:
    • 要求对数据具有控制能力(允许数据送到数据线)
    • 常用三态门实现
  • 输出接口:
    • 要求对数据具有锁存能力(接收后保持数据不变)
    • 常用锁存器实现

# 简单接口电路

# 三态门接口及 74LS244

三态:高电平、低电平、高阻态

三态门接口


74LS244:

  • 含8个三态门的集成电路芯片
  • 在外设具有数据保持能力时用来输入接口数据

74LS244


例题:编程判断图中的开关状态,若全闭合则转NEXT1,否则转NEXT2。

例题图

注意到开关 K0~K7 的另一侧接了地。如果开关闭合,输入 I0~I7 应该为 0。反之为 1。

由图可知对应的地址为:

1000 0111 1111 11xx

83FCH~83FFH

可以任选其中一个地址如 83FCH 作为该接口地址,编程如下:

MOV  DX, 83FCH
IN   AL, DX
AND  AL, 0FFH      ; 闭合为 0
JZ   NEXT1
JMP  NEXT2

# 锁存器接口及 74LS273 74LS373 74LS374

锁存器接口:

  • 通常由 D 触发器构成
  • 特点:
    • 具有对数据的锁存能力
    • 不具备对数据的输出控制能力

74LS273:8 个 D 触发器

74LS273


74LS373和74LS374:

  • 三态输出的8 D触发器,并具有对数据的控制能力。
  • 既可以做输入接口,也可以做输出接口。

74LS374

不懂


例题:输出接口地址是多少?

例题图

不懂+1

# I/O接口综合应用例题(重要!)

  • 根据 4 个开关的状态控制 7 段数码管的显示(输入:4位开关 输出:显示符号与输出数据对应表中的 7 位
  • 当4个开关的状态分别为0000~1111时,在7段数码管上对应显示'0'~'F'
  • 设显示接口的地址为 F0H,开关接口地址为 F1H

数码管结构图

显示符号与输出数据对应表

解:下面是总线连接图。

  1. 四个开关 K0~K3 连接 74LS244,输入到 CPU
  2. 汇编程序将 D0~D3 转为上表中的 8 位值
  3. 8 位值通过 D0~D7 连接 74LS273,输出到数码管

译码器部分有一个 74LS138,它把 3 位信号 (A B C) 转为 Y0~Y7(此问只用到了 Y0 和 Y1)。A3~A7 接入到 74LS138 的使能端。

总线连接图

程序段:

......
Seg7  DB  3FH,06H,
      5BH,4FH,66H,6DH,
      7DH,07H,7FH,67H,77H,
      7CH,39H,5EH,79H,71H
......
    LEA  BX, Seg7
    MOV  AH, 0
GO: IN   AL, 0F1H
    AND  AL, 0FH
    MOV  SI, AX
    MOV  AL, [BX+SI]
    OUT  0F0H, AL
    JMP  GO

# 基本输入/输出方法

基本输入/输出方法有:

  • 无条件传送
  • 查询式传送
  • 中断方式传送
  • 直接存储器存取(DMA)

其中前三种是程序控制方式。

# 无条件传送

要求外设总是处于准备好状态。

优点:

  • 软件及接口硬件简单

缺点:

  • 只适用于简单外设,适应范围较窄

例:读取开关的状态;当开关闭合时,输出编码使发光二极管亮。

# 查询工作方式

  • 仅当条件满足时才能进行数据传送;
  • 每满足一次条件一般只进行一次数据传送。
  • 适用场合:
    • 外设并不总是处于“准备好”状态
    • 对传送速率和效率要求不高
  • 工作条件:
    • 外设应提供设备状态信息
    • 接口应具备状态端口

单个外设的查询工作方式流程图

上述流程图可能出现一直等待外设,导致死机的情况。

单个外设的查询工作方式流程图(改进)

例:数据输出

  • 外设状态端口地址为03FBHbit5 为状态标志(=1忙,=0准备好)
  • 外设数据端口地址为03F8H,CPU 写入数据会使状态标志置1;外设把数据读走后又把它置 0。
  • 试画出其电路图,并将 BUF 中的 100 个字节数据输出。

电路图

      MOV  CL, 100
      LEA  SI, BUF
AGAI: MOV  DX, 03FBH 
NEXT: IN   AL, DX
      TEST AL, 20H
      JNZ  NEXT
      MOV  DX, 03F8H
      MOV  AL, [SI]
      OUT  DX, AL
      INC  SI
      LOOP AGAI

# 多个外设时查询工作方式

  • 优点:软硬件比较简单
  • 缺点:CPU 效率低,数据传送的实时性差,速度较慢

多个外设时查询工作方式

# 中断控制方式

  • 特点:外设在需要时向CPU提出请求,CPU再去为它服务。服务结束后或在外设不需要时,CPU可执行自己的程序。
  • 优点:CPU 效率高,实时性好,速度快。
  • 缺点:程序编制相对较复杂。

# 以上三种I/O方式的共性

  • 信息的传送均需通过CPU
  • 软件: 外设与内存之间的数据传送是通过CPU执行程序来完成的(PIO方式);
  • 硬件:I/O接口和存储器的读写控制信号、地址信号都是由CPU发出的。
  • 缺点:程序的执行速度限定了传送的最大速度

# DMA控制方式

  • 外设直接与存储器进行数据交换 ,CPU不再担当数据传输的中介者;
  • 总线由DMA控制器(DMAC)进行控制(CPU要放弃总线控制权),内存/外设的地址和读写控制信号均由DMAC提供。

DMA控制方式


DMA控制方式的工作过程:

  1. 外设向DMA控制器发出“DMA传送请求”信号 DREQ;
  2. DMA控制器收到请求后,向CPU发出“总线请求”信号HOLD;
  3. CPU在完成当前总线周期后会立即发出HLDA信号,对HOLD信号进行响应;
  4. DMA控制器收到HLDA信号后,就开始控制总线,并向外设发出DMA响应信号DACK。

DMA控制方式工作过程例:从外设向内存传送若干字节数据

  1. DMAC向I/O接口发出读信号;
  2. 向地址总线上发出存储器的地址;
  3. 发出存储器写信号和AEN信号;
  4. 传送数据并自动修改地址和字节计数器
  5. 判断是否需要重复传送操作;
  6. 若数据传送完,DMA控制器撤销发往CPU的HOLD信号;
  7. CPU检测到HOLD失效后,则撤销HLDA信号,并在下一时钟周期重新开始控制总线。

DMA工作方式:

  • 周期窃取:每个DMA周期只传送一个字节或一个字就立即释放总线。
  • 数据块传送:DMAC在申请到总线后,将一块数据传送完后才释放总线,而不管中间DREQ是否有效。

周期窃取的DMA


DMA控制方式的优缺点:

  • 优点:数据传输由DMA硬件来控制,数据直接在内存和外设之间交换,可以达到很高的传输速率。
  • 缺点:控制复杂,硬件成本相对较高。

# 中断技术

# 基本概念

CPU执行程序时,发生了某种随机的事件(外部或内部),引起CPU暂时中断正在运行的程序,转去执行一段特殊的服务程序(称为中断服务程序或中断处理程序),以处理该事件,该事件处理完后又返回被中断的程序继续执行,这一过程称为中断。


中断源:引起CPU中断的事件,发出中断请求的来源。

分类

  • 内部中断
    • 异常中断:异常事件引起
    • 软件中断:中断指令引起
  • 外部中断
    • 可屏蔽中断:INTR 中断
    • 非屏蔽中断:NMI 中断

引入中断的原因:

  • 提高数据传输率;
  • 避免了CPU不断检测外设状态的过程,提高了CPU的利用率。
  • 实现对特殊事件的实时响应。

# 中断处理的一般过程

  1. 中断请求
  2. 中断源识别及中断判优
  3. 中断响应
  4. 中断处理(服务)
  5. 中断返回
# 中断请求
  • 包括 NMI、INTR
  • 中断请求信号应保持到中断被处理为止;
  • CPU响应中断后,中断请求信号应及时撤销。
# 中断源识别
  • 软件查询法:在中断处理程序中查找中断源
  • 中断矢量法—硬件识别
    • 由中断源提供中断类型号,CPU根据类型号确定中断源。
    • 中断源识别及中断判优(确定先响应哪个中断请求)由硬件系统完成
# 中断判优

当有多个中断源同时提出请求时,需要确定首先响应哪一个中断源。

优先级法则:

  • 同时出现或等待的多个中断源,优先级最高的被响应。
  • 低优先级的中断程序允许被高优先级的中断源所中断(可能出现中断嵌套)
  • 也可以设置成禁止中断嵌套

软件判优:顺序查询中断请求,先查询的先服务,即先被查询的中断源优先级别高
硬件判优:链式判优、并行判优(中断向量法)

菊花链逻辑电路

看不懂图。

# 中断响应

中断响应包含以下几个操作:

  1. 向中断源发出中断响应信号 INTA\overline{INTA}
  2. 关中断
  3. 保护硬件现场:将FLAGS压入堆栈
  4. 保护断点:将CS、IP压入堆栈
  5. 获得中断服务程序入口地址

这些步骤都是由硬件系统完成。

# 中断处理

执行中断服务程序

中断服务程序的特点:

  • 中断服务程序要定义为“远过程”
  • 结束时要用 IRET 指令返回

中断服务程序完成的工作:

  1. 保护软件现场(参数)
  2. 开中断 STI--允许中断嵌套
  3. 中断处理—具体的处理
  4. 关中断 CLI
  5. 恢复软件现场
  6. 中断返回 IRET
# 中断返回

执行 IRET 指令,包括下面的操作:

  • 使 IP、CS 和 FLAGS 从堆栈弹出
  • 开中断

外部可屏蔽中断处理过程

# 8088/8086中断系统

在8086/8088所有的中断源都统一分配了不同的类型号

8088/8086中断系统

# 内部中断
中断类型 功能
类型0 除数为0中断例行程序
类型1 单步
类型2 非屏蔽中断,NMI
类型3 设置断点
类型4 溢出处理中断,INTO指令
类型10H 显示设备中断
类型20H 程序结束中断
类型21H DOS系统功能调用功能程序
# 外部中断
  1. 非屏蔽中断
  • NMI 引脚上出现上升沿触发
  • 不受标志位IF的限制,即不可以屏蔽。
  • 类型号:2
  1. 可屏蔽中断
  • INTR引脚输入,高电平有效
  • 受标志位IF的限制
  • 类型号:08H~0FH 70H~77H
# 中断向量表
  • 存放各类中断的中断服务程序的入口地址;
  • 每个入口占用4 Bytes,低字为段内偏移,高字为段基址;
  • 表的地址位于内存的00000H~003FFH,大小为1KB,共256个入口。
# 中断向量表 IVT

8086/8088中断向量表位于内存最低1KB

中断向量表 IVT

# 中断向量表的初始化
  • 系统启动时已经把默认的中断向量写入IVT
  • 用户需将自定义的中断服务程序入口地址放入向量表
  • 注意点:
    • 向量表所在的段基址=0
    • 存放中断服务程序入口的单元的偏移地址=n*4

例:将中断类型码为48H的服务程序入口地址放入向量表

TIMER  PROC  FAR
       ; ...
       ; ...
       IRET
TIMER  ENDP

; 下面的程序用MOV指令将类型码为48H的中断服务程序TIMER的中断向量放入向量表
MOV AX0000H
MOV DSAX
MOV SI0120H          ; 48H*4
MOV BX,OFFSET TIMER
MOV [SI]BX
MOV BX,SEG TIMER 
MOV [SI+2]BX
# 可屏蔽中断的类型号的获取时序

对可屏蔽中断的响应需要两个总线周期:

可屏蔽中断的类型号的获取时序

# 8088内部中断与NMI中断

特点:

  • INTA\overline{INTA} 总线周期
  • 中断类型码固定或由指令给出
# 8086的中断响应和处理流程

8086的中断响应和处理流程

# IA-32的中断模式

picture 29

picture 30

picture 31

picture 32

picture 33

# 中断控制器 8259A 介绍

# 一. 8259A 的功能与结构

# 8259A 的主要功能
  • 记录 8 个中断源的中断请求。
  • 确定是否响应中断请求,并确定优先的中断请求并响应。
  • CPU 响应中断时向 CPU 发送中断类型号。
# 8259A 的内部结构

8259A的内部结构

6-5-p4

6-5-p5

6-5-p6

三个寄存器的英文分别是 Interrupt Request Register (IRR), In-Service Register (ISR), Interrupt Mask Register (IMR)。这三个的功能一定要记住,当然要是记住了英文就很好记了。

# 8259A 的工作原理
  1. 当IR0~IR7中的一条或多条请求线变高时,将相应的IRR位置1。
  2. 根据中断服务寄存器(ISR)和中断屏蔽寄存器(IMR)的内容,找出未被屏蔽的最高优先权的中断请求,向CPU发中断请求信号INT。
  3. CPU响应中断时,送回应答信号 INTA\overline{INTA} 脉冲。
  4. 8259A接到CPU发的第一个 INTA\overline{INTA} 脉冲时,把ISR中与最高优先级请求信号对应的位置1,并把IRR中的相应位复位。
  5. 在8259A接到第二个 INTA\overline{INTA} 脉冲时向CPU发送中断类型码。如果是在AEOI(自动结束中断)方式,在这个脉冲结束时复位ISR的相应位。在其他方式下,要在中断服务程序结束时通过发EOI命令来复位ISR相应位。
# 8259A 的外部特性

8259A与CPU的接口引脚图

8259A与CPU的接口引脚(1)

8259A与CPU的接口引脚(2)

# 二. 8259A 的工作方式

下面的部分应该只用对名字留一个印象,具体是什么功能、怎么工作,应该不重要。

# 1. 优先级管理方式
# 1) 中断嵌套方式
  1. 普通全嵌套方式
  • 这是最常用最基本的工作方式,8259A初始化后为该方式。
  • 当一个中断正被处理时,只有比它优先级更高的中断请求才会被响应。
  1. 特殊全嵌套方式
  • 它与普通全嵌套方式的区别是:允许同级中断进行嵌套
  • 只允许主片8259A使用特殊全嵌套方式,以实现从片中的高低优先级之间的嵌套。

以上两种嵌套方式中各中断源的优先级顺序是固定的,加电时优先级从高到低顺序为:IR0、IR1、IR2、IR3、IR4、IR5、IR6、IR7。也可以重设和循环优先级。

# 2) 优先级变化方式
  1. 优先级固定方式
  • 各中断请求的优先级固定不变,8259A加电后IR0最高,IR7最低。
  1. 优先级循环方式
  • 优先队列是变化的,一个中断源得到中断服务后,它的优先级自动降为最低。
  • 按照加电时的优先级顺序进行优先级循环称为优先级自动循环方式。改变初始优先级顺序后的循环,称为优先级特殊循环方式
  • 优先级循环方式适合于系统中各个中断源级别相当,能够得到均衡的服务。
# 2.中断源的屏蔽方式
  1. 常规屏蔽方式
  • 8259A的每个中断请求输入端都可通过对应的IMR位的设置被屏蔽。IMR某位为“1”表示屏蔽对应的中断请求。
  1. 特殊屏蔽方式
  • 使正在处理的中断所对应的IMR位置1,并使对应的ISR位清零,这样任何优先级的中断都可得到响应。
  • 主要用在中断服务程序中需要动态地改变系统的优先级结构的情况。
  • 例如,在执行中断服务程序的某一部分时,需要禁止比本中断优先级低的其他中断请求,而在执行另一部分时,又希望开放这些中断请求。

看不懂

# 3.中断结束的处理方式

中断结束 (End of Interrupt, EOI)

  1. 自动中断结束方式
  • 它是最简单的中断结束方式。
  • 系统进入中断过程,在第二个INTA脉冲的后沿,8259A将当前处理的中断所对应的ISR位清零。
  • 当系统正在为某外设进行中断服务,但在8259A的ISR中却没有对应位指示,故该方式只能用于非嵌套方式处理。
  • 在初始化时由初始化命令字ICW4的AEOI位置1来设置这种方式。
  1. 正常中断结束方式
  • 它用在两种全嵌套方式下,当CPU向8259A发出中断结束命令时,8259A将ISR中优先级最高的位复位(即当前正在进行的中断服务结束)。
  • 这种结束方式的操作很简单,通过向8259A的偶地址端口输出一个操作命令字OCW2来发EOI命令。
  1. 特殊中断结束方式
  • 用这种方式结束中断时,在程序中要发一条特殊中断结束命令,指出当前中断服务寄存器ISR中的哪一位将被清除。
  • 它通过向8259A的偶地址端口输出一个操作命令字OCW2,其中的L2、L1、L0这三位指出了对ISR中的哪一位进行清除。
  • 该方式用于中断优先级顺序会改变的特殊全嵌套方式(两种优先级循环方式),无法判断ISR的哪位是当前处理的中断。

对于多片8259A级联情况,如果不是自动中断结束方式,在中断服务程序的最后需要发两次EOI命令,分别清除从片中的ISR位和主片中的ISR位。

对于采用特殊嵌套方式的多片级联,从片中可能嵌套有多个中断源,应先向从片发EOI命令,然后读ISR,检查还有无为1的位,如无则才能向主片发EOI命令。

这说的是人话吗(逃

# 4.连接系统总线的方式
  1. 缓冲方式
  • 在很多片8259A级联的大系统中,8259A需要通过总线驱动器和数据总线相连,这就是缓冲方式。
  • 在缓冲方式下,8259A的SP/EN端和总线驱动器的允许端相连, SP/EN端输出的低电平可作为总线驱动器的控制信号。
  1. 非缓冲方式
  • 当系统中只有单片8259A或有少量几片8259A级联时,一般将8259A直接与数据总线相连,这种方式就称为非缓冲方式。
  • 8259A的SP/EN端作为输入端,在单片8259A系统中, SP/EN端接高电平;在多片系统,主片的SP/EN 端接高电平,从片的SP/EN端接低电平。

8259的级联结构

# 5.中断触发方式
  1. 电平触发方式
  • 把中断请求输入端的高电平作为中断请求信号。这时高电平信号不能持续太久,否则一次中断请求可能会被多次响应。
  1. 边沿触发方式
  • 8259A将中断请求输入端IRi出现的上升沿作为中断请求信号。该中断请求得到触发后可以一直保持高电平。

真心看不懂。

# 三. 8259A 的初始化命令字和初始化流程

ICW OCW

下面的图考试应该会给吧。应该就是依葫芦画瓢的事情了。

8259A 读写功能的实现

ICW1

ICW2

  • ICW2是用来设置中断类型码,编程时用ICW2设置中断类型码高5位T7~T3(即D7~D3),而D2~D0的值恒为零。
  • 中断类型码的高5位就是ICW2的高5位,而低3位是由引起中断请求的引脚IR0~IR7决定。
  • 例如:ICW2为20H,则8259A的IR0~IR7对应的8个中断类型码为20H、21H、22H、23H、24H、25H、26H、27H。

ICW3

ICW4

ICW4 说明

ICW4 说明(续)


8259A 的初始化流程:

8259A 的初始化流程


例题:以微型计算机中使用的单片8259A为例,试对其进行初始化设置。在微型计算机中,8259A的ICW1和ICW4的端口地址分别为20H、21H。初始化设置的程序段如下:

MOV	 AL, 13H  ;设置ICW1(中断请求信号采用边沿触发方式;单片;后面要写ICW4)
OUT	 20H, AL  
MOV	 AL, 18H  ;设置ICW2(将中断类型码高5位指定为00011)
OUT	 21H, AL
MOV	 AL, 0DH  ;设置ICW4(用常规全嵌套方式;不用中断自动结束方式;采用缓冲方式;工作于8088/8086系统)
OUT  21H, AL  

应该对照着表就能做叭。

# 四. 8259A 的操作命令字

  • 对8259A用初始化命令字初始化后,就进入工作状态了,准备接受IRi输入的中断请求信号。
  • 在8259A工作期间,可通过操作命令字(OCW)来使它按不同的方式操作。
  • 8259A有3个操作命令字OCW1-OCW3,没有写入顺序和时间要求,可独立使用。
  • OCW1写入奇地址,OCW2和OCW3写入偶地址。

OCW1

8259A初始化后缺省的状态是全部屏蔽位=0(允许中断)


例:若要屏蔽IR5、IR4和IR1引脚上的中断,而让其余的中断得到允许。试确定其中断屏蔽操作命令字。

OCW1为:0011001032H


OCW2

OCW2具有发EOI命令和设置优先级循环方式的两种功能,其中101和111两种情况是结束中断后的系统方式设置。

  • R:优先级方式控制位。
    • 1:循环优先级
    • 0:为固定优先级。
  • SL:指示OCW2中L2~L0位是否有效。
    • 1:有效;
    • 0:无效。
  • EOI:在非自动中断结束方式下的中断结束命令位。
    • 1:发中断结束命令,它使现行中断的ISR位复位;
    • 0:不发出中断结束命令。
  • L2~L0:它有两个作用。
    • (1)设定优先级特殊循环方式时初始的最低优先级序号;
    • (2)在特殊中断结束命令中指明ISR的哪位被复位。

例:若某8259A的OCW2设置为11000011B,试分析此操作命令字所确定的操作方式。

该命令字确定8259A为优先级特殊循环(参见优先级变化方式),将IR3定为最低优先级。因此,系统中优先级从高到低为IR4、IR5、IR6、IR7、IR0、IR1、IR2、IR3。


OCW3

有三个功能:

  1. 设置特殊中断屏蔽方式:D6D5=11为设置,10为清除.
  2. 查询中断请求:使P=1先写到8259A,再对该地址读入,得到中断状态字节(见下图)。
  • I=1,表示IR0~IR7中有中断请求,R2R1R0表示其中最高优先级的编号(IRi);
  • I=0,表示无中断请求产生。
  1. 读8259A的状态
  • 写RR和RIS=10的OCW3到8259A,再读该地址,得到IRR的内容;
  • 写RR和RIS=11的OCW3到8259A,再读该地址,得到ISR的内容;

中断状态字

如果要读IMR,只需要从奇地址端口(A0=1)读8259A即可,与OCW3无关。

# 五. 8259A 的应用举例

两片级联,应该不会考()

设两片8259A级联,提供15级向量中断,CAS2~CAS0作为互连线,从片8259A的INT直接连到主片8259A的IR2上。

  • 端口地址,主片在020H~03FH范围内,实际使用020H和021H两个端口;从片在0A0H~0BFH范围,实际使用0A0H和0A1H两个端口。
  • 主、从片的中断请求信号均采用边沿触发方式。
  • 主片与从片采用一般全嵌套方式,优先级的排列次序为0级最高(主片的IR0),依次为1级(主片的IR1)、2级(主片的IR2,即从片的IR0 ~IR7),然后是3级~7级(主片的IR3~IR7)。
  • 采用非缓冲方式,主片的SP/EN端接+5V,从片的SP/EN端接地。
  • 设定主片的中断号为08H~0FH,从片的中断号为70H~77H。

两个8259A的级联图

对主片8259A的初始化:

INTM00	EQU  	020H       		;主8259A端口0
INTM01	EQU   021H       		;主8259A端口l
……      
MOV  AL, 00010001B;ICWl;  边沿触发,要ICW4,级联方式
                        ;要ICW3
OUT  INTM00, AL
JMP  SHORT $+2 ;延迟=该指令的执行时间,$+2是下条指令
MOV AL, 00001000B	 ;ICW2:设置主片的中断向量,起始
                      ;的中断向量为08H
OUT INTM01, AL
JMP  SHORT $+2
MOV  AL, 00000100B  ;ICW3:主片的IR2接从片8259A的INT  			

OUT  	INTM01, AL
JMP 	SHORT$+2
MOV 	AL, 00000001B   ;ICW4:非总线缓冲,常规全嵌套,
                  ;正常结束中断方式
OUT  	INTM01, AL
JMP  	SHORT$+2

对从片8259A的初始化:

INTS00  EQU  	0A0H       ;从片8259A端口0
INTS01  EQU  	0A1H       ;从片8259A端口1
MOV 	AL, 0001000lB	    ;ICWl:边沿触发,要ICW4;
                        ;级联方式,要ICW3
OUT 	INTS00, Al

JMP 	SHORT  $+2
MOV 	AL, 01110000B 	;ICW2:设置从片的中断向量,
                        ;起始的中断向量为70H
OUT 	INTS01, AL
JMP 	SHORT  $+2
MOV	AL, 00000010B 	;ICW3,设置从片的识别标志,
                        ;即指定连接主片的IR2
OUT 	INTS01, AL
JMP 	SHORT  $+2
MOV 	AL, 00000001B  ;ICW4:非总线缓冲,常规全嵌套,
                      ;正常结束中断方式
OUT 	INTS01, AL
JMP 	SHORT  $+2
MY_INT PROC FAR
PUSH  AX
PUSH  BX
….
STI
<中断服务程序主体>
CLI
….
POP  BX
POP  AX
MOV AL, 20H	; 用OCW2写EOI 命令  , 00100000B
OUT  A0H, AL  ;向从片发EOI命令
MOV AL, 20H
OUT 20H, AL   ; 向主片发EOI命令
IRET	
MY_INT ENDP
CLI 
PUSH DS
XOR AX, AX
MOV DS, AX
MOV BX,  n   ;  中断类型号
MOV CL,2
SHL BX, CL   ;向量表偏移地址=nX4
MOV AX,  OFFSET MY_INT
MOV [BX],  AX
MOV AX,  SEG MY_INT
MOV [BX+2], AX
POP DS
;按上述方法设置用户的各个中断向量
STI

# 第七章 常用数字接口电路

# 7.1 并行通信与串行通信

# 1. 并行通信

  • 定义:能同时传送一个数据的所有位。
  • 一个数据的位数可以是字节或字长。 两个设备之间有必须有多条数据传输线,才能实现多位数据同时传输。

并行通信

  • 并行方式主要用于近距离通信,如计算机内的总线结构
  • 并行通信有简单接口和可编程接口
  • 特点:
    • 传输速度快,处理简单;
    • 适合近距离传送;
    • 所传送信息无固定格式要求。

# 2. 串行通信

  • 定义:数据逐位顺序传送。
  • 数据一位一位通过同一通信线进行传输。
    • 发送设备将几位并行数据转换成串行方式,再逐位传输到接收设备。在接收端将数据从串行方式转换成并行方式。

串行通信

  • 数据传送方式:
    1. 双工:两根数据传输线,能够同时发送和接收
    2. 半双工:单根数据传输线,不能同时发送和接收
    3. 单工:单根数据传输线只用作发送或只用作接收
  • 通信方式:
    1. 同步通信:通信时,发送方和接收方的时钟频率和相位保持一致,收发双方采用同一个时钟信号来定时。每两个字符间的时间间隔固定。
    2. 异步通信:收发双方没有统一的时钟来定时和同步,每两个字符间的时间间隔不固定。
  • 传输速率:
    • 在通讯中,用波特率来描述数据的传输速率。即每秒钟传送的二进制位数,简写为bps。

# 7.2 可编程计数/定时器芯片 8253

  • 定时/计数器的应用
    • 生产线上统计产品的数目----计数器
    • 系统的动态存储器刷新----定时器
    • 系统时钟计时----定时器
    • 扬声器的频率源----定时器
  • 常用的定时方法
    1. 软件定时:优点是节省硬件;缺点是执行程序期间CPU一直被占用,降低了CPU效率
    2. 硬件定时:要用额外的硬件—计数/定时器,但可提高CPU的利用率

# 8253 基本功能

  1. 具有三个相互独立的16位计数器,也叫通道。
  2. 每个通道都可设定以6种工作方式之一进行计数/定时
  3. 每个计数器都可设为按二进制或BCD码计数
  4. 具有计数和定时功能,都是基于减1计数方式工作。
  5. 计数器减为0后,产生输出信号,有的方式可自动装入初值重新计数。
  6. 在减1过程中,随时都可由CPU读取计数器的当前值。

# 8253 结构

8253 引脚

8253 接口

8253 寄存器选择表

8253 内部结构

各计数器的内部结构

# 8253的控制字

8253工作前需要通过控制字进行设置,每个计数器(通道)都要单独写控制字,端口地址都为同一个地址。

8253的控制字

似乎挺好理解的,比 8259 的控制字好理解多了。

虽然我还是不会背

# 8253的工作方式

8253共有6种工作方式:方式0~方式5。

8253各工作方式的共同点:

  1. 控制字写入计数器时,所有控制逻辑立即复位,输出端OUT进入初始态;
  2. 写入计数初值后,要经过一个时钟周期后计数器才开始计数;
  3. 在时钟脉冲CLK的上升沿门控信号GATE被采样;
  4. 计数器的计数时间点是CLK的下降沿
# 方式0—计数结束中断

下面六个方式,主要是看图说话,图比字形象的多。

方式0—计数结束中断

  • 写入控制字后OUT变低;
  • 写入计数初值后的一个CLK的下降沿,计数初值被装入计数器,然后在每个CLK的下降沿做减1计数;
  • 当计数到0时OUT输出变为高电平;
  • GATE为高电平时,计数器工作,为低电平时停止计数; 在计数过程中若重新写了新的计数初值,则按新值重新工作; 每写一次计数初值只计数一个周期。
# 方式1—可重复触发的单稳态触发器(硬件触发)

方式1—可重复触发的单稳态触发器(硬件触发)

  • 写入控制字后,OUT变为高电平;
  • 写入计数初值后,当GATE从低变高后的一个CLK下降沿装入初值,OUT变为低。然后对每个CLK下降沿做减1计数,计到0时OUT变高。
  • 若计数结束后GATE又出现上跳,则重新装入计数初值,重新开始计数。
  • 若计数过程中GATE又出现上跳,则重新装入计数初值,重新开始计数,本次OUT周期宽度加长。
  • 负脉冲宽度=计数初值 X CLK周期。
# 方式2—频率发生器(软件或硬件触发)

方式2—频率发生器(软件或硬件触发)

软件触发:保持GATE为高

  • 写控制字后,OUT变为高电平;
  • 写计数初值后的一个CLK的下降沿,初值被装入计数器,然后对每个CLK的下降沿减1计数;
  • 计数期间OUT保持为高,当计数到1时OUT输出宽度为1个CLK周期的负脉冲,然后重新装入计数初值开始计数。
  • 若计数中重写计数初值,则下次计数周期才会以新值开始计数。

硬件触发:写控制字和计数初值时GATE为低,当其变高后的下一个CLK下降沿计数器装入初值,后面每个CLK下降沿计数。

  • 方式2为自动装入计数初值的重复计数器。
# 方式3—方波发生器(软件或硬件触发)

方式3—方波发生器(软件或硬件触发)

  • 方式3与方式2相似,只是OUT输出是一个占空比为1:1的方波;
  • 若计数初值为偶数,则OUT输出是高低电平对称的方波;
  • 若计数初值为奇数,则OUT输出不对称,前面的高比后面的低多1个CLK周期,即近似方波。
  • 方式3与方式2一样也可由硬件触发,即GATE从低变高后启动。
# 方式4—软件触发选通

方式4—软件触发选通

  • 写入控制字输出变高,写入计数值后的一个CLK下降沿装入初值,然后对每个CLK下降沿计数;
  • 计数到0时输出1个CLK周期宽度的负脉冲;
  • 若计数中重写计数初值,则下一个CLK下降沿被装入,然后按此初值计数;
  • 若计数中GATE变低将停止计数,当其变高时继续计数;
  • 计数初值一次写入只计数一个周期,类似软件触发的方式2的一个周期,但负脉冲的出现会延后一个周期。
# 方式5—硬件触发选通

方式5—硬件触发选通

  • 写入控制字后,OUT变高;
  • 写入计数值后,当GATE出现上升沿后的一个CLK下降沿装入初值,然后对每个CLK下降沿计数,计数到0时OUT输出一个CLK周期的负脉冲。
  • 在计数中若GATE变低后再变高,将重新启动一次计数周期。
  • 在计数中若写入新计数值,本次计数周期不受影响。
  • 一次GATE触发只计数一个周期,即类似硬件触发的方式2的一个周期,但负脉冲的出现会延后一个周期。
# 方式比较

方式 0 与方式 4 的比较(软件控制)

方式 1 与方式 5 的比较(硬件触发)

方式 2 与方式 3 的比较(连续波形输出)

方式 4 与方式 5 的比较(非连续的单脉冲输出)

# 总结
  • 8253的六种工作方式中有软件启动和硬件启动:
    • 软件启动:写入控制字及初值后的第一个CLK下降沿装入初值,下一个CLK下降沿开始减1计数。有方式0,2,3,4。
    • 硬件启动:写入控制字及初值后的一个GATE信号上升沿后的第一个CLK下降沿装入初值,下一个CLK下降沿开始计数。有方式1,2,3和5。
    • 其中方式2和方式3既可软件启动,也可硬件启动。
  • 8253工作方式分为连续波形和非连续波形输出:
    • 连续波形输出:一次启动后,计数到0时则自动装入初值循环工作。有方式2,3
    • 非连续波形输出:一次启动后,计数到0后则结束。有方式0,1,4,5
方式0—计数结束中断 方式1—可重复触发的单稳态触发器(硬件触发) 方式2—频率发生器(软件或硬件触发)
方式3—方波发生器(软件或硬件触发) 方式4—软件触发选通 方式5—硬件触发选通

# 8253 的初始化编程

下面这段话很重要!

初始化编程的顺序为:

  1. 对某一指定计数器,先写入控制字,再写入计数初始值。
  2. 计数初值写入的格式和顺序必须按控制字D5和D4规定的格式写入。

注意:所有通道的控制字都写入同一个控制端口,而计数初值则要写入指定计数器对应的端口。

可以结合下面的例子食用:

例 1

例 2

例 3

例 4-1

例 4-2

例 4-3

例 4-4

更复杂的例子就不一贴出了。

# 考试重点

编一个小的接口应用程序

8253 8255 8259(内容有点多,但考试不会考太难)考工作方式、工作模式,不会设计

8253 定时计数器 8255 应用题

给一组数,找非零数的个数,15 分,框架 12 分


不考的内容(已经老师确认) 1.第五章存储器系统 2.第六章8237 3.第七章8251

# 错题

# 三四章

  1. 对段寄存器CS的装入方式有( 234 ). (1)使用MOV指令 (2) 使用段间转移指令 (3)使用段间子程序调用指令 (4)使用END伪指令

# RCR 填空题

1. 设 (BX)=0C49CH, (CX)=0F002H

ROL  BX, CL
XOR  BH, CH
RCR  BX, CL

上述指令序列执行后,(BX)=() ,CF=()


一定看清 0F 开头的,第一位是 0 还是 F!上面两个都应该是字母开头。

答案是 (BX)=0B89C ,CF=1

# 编程题

试编制一完整源程序,将一个字符串(只包含有字母和数字符)中的小写字母转换为大写,数字符转换成其对应的二进制值。各字符转换后的内容仍然存放在原来的单元中。已知A的ASCII码为41H,a的ASCII码为61H。字符串的定义如下:

DATA  SEGMENT
      STRING  DB  'BTe3F5…….'
DATA  ENDS
DATA    SEGMENT
        STRING  DB  'BTe3F5…….'
        LEN     EQU $-STRING
DATA    ENDS

STACK1  SEGMENG STACK
        DW  20H DUP(?)
STACK1  ENDS

CODE    SEGMENT
        ASSUME CS:CODE, SS:STACK1, DS:DATA
START:  MOV AX, DATA
        MOV DS, AX
        MOV CX, LEN
        LEA SI, STRING
LOP:    MOV AL, [SI]
        CMP AL, 'a'
        JGE LITTLE1
        CMP AL, '9'
        JLE NUMBER1
        JMP NEXT2
LITTLE1:SUB AL, 'a'-'A'
        JMP NEXT1
NUMBER1:SUB AL, '0'
NEXT1:  MOV [SI], AL
NEXT:2  INC SI
        LOOP LOP
        HLT
CODE    ENDS
        END START

# 六七八章 接口部分

  1. 8086/8088复位后,初始化执行的物理地址是 0FFFF0H
  2. 当中断处理程序准备执行第一条指令时,IF=0, TF=0
  3. 8259A中,特殊全嵌套优先级方式一般用于 多片8259A中的主片中
  4. DAC0803工作在 两级缓存 方式下时,可在转换输出模拟信号的同时可接收下一个数字信号
  5. IRET指令会向8259A发送EOI信息,以结束当前的中断 ×
  6. 8086/8088中地址引脚与其它信号复用,所以地址信息需要锁存
  7. 8255A中的端口C置1/0方式使得端口C适合配合端口A、B做控制位使用。
  8. 是否响应软件指令INT引发的中断受IF位的影响。×
  9. CPU和外设之间的数据传送方式有 无条件传送查询方式中断方式DMA方式
  10. 硬件中断分为 可屏蔽中断非屏蔽中断
  11. 8259A工作的优先级方式有全嵌套特殊嵌套、优先级自动循环优先级特殊循环

# 后记

最后考试考了很多的原题。选填题全是前四章的内容,简单题是前四章和后三章各一半,最后解答题一个是前四章的,三个是接口部分,其中两个是老师发的练习题的原题/简化。

隔壁网安就乱划重点 + 没有原题。表扬一下汇编。