- 授课老师:廖建明老师
- 教材:《微机原理与接口技术》(第4版). 吴宁,乔亚男主编. 清华大学出版社
- 参考教材:《汇编语言程序设计》. 廖建明主编.清华大学出版社
# 第一章 微型计算机基础概论
# 计算机的工作原理
计算机中的指令执行过程:取指令 -> 指令译码 -> 读取操作数 -> 执行命令 -> 存放结果
指令的顺序工作方式和并行流水线工作方式
- 冯 • 诺依曼计算机的工作原理:
- 存储程序工作方式
- 运算器为核心
- 特点:
- 存储程序,共享数据,顺序执行;
- 属于顺序处理机,适于确定的算法和数值处理。
- 不足:
- 与存储器间有大量数据交互,对总线要求很高;
- 执行顺序由程序决定,对大型复杂任务较难处理;
- 以运算器为核心,处理效率较低; 由PC控制执行顺序,难以进行真正的并行处理。
非冯 • 诺依曼计算机:并行性
# 微机系统组成
- 微处理器:运算器单元+控制器单元+寄存器单元+内部总线,具有CPU全部功能的大规模集成电路芯片。
- 微型机:微处理器+内存+I/O接口+系统总线+电源+输入/输出设备+外存设备
- 微机系统:微型机+系统软件+应用软件
# 第二章 微处理器
# 8086/8088 特点、工作模式
特点:并行流水线、内存空间分段管理、多处理器系统
工作模式:最小(单处理器,不必接总线控制器)/最大(多处理器,需总线控制器)
工作模式选择:引脚
- :最大模式
- :最小模式
# 8086/8088 引线及功能
# 地址线和数据线
- :低 8 位地址和低 8 位数据信号(分时复用)。传送地址时单向,传送数据时双
- :8位地址信号
- :高4位地址信号,与状态信号分时复用
- :写信号
- :读信号
- :访问内存/访问接口
- :低电平有效时,数据总线上数据有效,允许进行读/写操作
- (Data Transmit/ Receive):为“1”时 CPU 向存储器或 I/O 传送,否则为反向
- :地址锁存信号,当其为高时表示地址线上地址有效。一般用它将地址锁存到一个锁存器中
- :复位信号。当其为高时将完成CPU内部复位。复位后CPU内部寄存器的值如下表
- :外部同步控制输入信号,高电平有效(8088 与内存/外设之间在一个总线周期内的时钟配合信号)
# 中断请求和响应信号
- :可屏蔽中断请求输入端
- : 非屏蔽中断请求输入端
- :中断响应输出端
# 总线保持信号
- :总线保持请求信号输入端。当 CPU 以外的其他设备要求占用总线时,通过该引脚向 CPU 发出请求(外设 -> CPU)
- :总线保持响应信号输出端。CPU 对 HOLD 信号的响应信号(CPU -> 外设)
# 8088 和 8086 CPU 引线的差异
- 数据总线宽度不同
- 8088的外部总线宽度是8位,8086为16位。
- 访问存储器和I/O控制的信号含义不同
- 8088—— 表示访问内存;
- 8086—— 表示访问内存。
# 8088/8086 内部结构
- 执行单元 EU
- 总线接口单元 BIU
执行单元 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 位数
- AX 分为
- 地址指针寄存器: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
- Bit 号从 0 开始,用中文描述就是第四位向第五位有进位。
- **在 16 位加法中,AF 仍然是 Bit3 向 Bit4 进位的结果。**详见 assembly - how to set auxiliary flag for 16bits binary addition - Stack Overflow (opens new window)
- 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 位的地址码,寻址范围为 ,即 1 兆字节空间。
字单元:任何两个相邻字节单元构成,16 bits
- 子单元地址:字节较小地址
- 存放规则:小端
- 32-bit 逻辑地址 = 16-bit 段基地址
:
(拼接) 16-bit 段内地址 - 20-bit 物理地址:16-bit 段基地址
*16+
16-bit 偏移地址- 段首的偏移地址 =
0000H
- 段首的偏移地址 =
# 8086/8088 的存储器段结构的特点
- 段大小 <= 64KB
- 段首地址为一个小节的首地址
- 小节:每 16 Bytes 为一小节
- 小节的首地址最低位为
0000
- 逻辑段在物理上可能是:可邻接的、间隔的、部分重叠的或完全重叠的
- 在任一时刻,一个程序只能访问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 个时钟周期
- T1上 输出地址
- T1下 锁地址
- T2 输出状态,设置 、
- T3 输入数据
- T4 复位,关状态输出
但这个状态在最小模式中没有用,在最大模式中才会用到(
写的区别除了 、、外,还有 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 偏移地址
- 32-bit 逻辑地址 = 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
注意事项:
可以用两条记忆:
- 立即数和
CS
不能做dest
(显然) - 立即数和段寄存器如果想传送到别的段寄存器,必须经过通用寄存器中转。
顺便一提,立即数可以不经过寄存器、直接传入存储器。但由于立即数的长度不定,可能需要显式指明存储器的长度,如 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 AL,86H
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 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
不访问存储器,而下面的 LDS
、LES
要访问存储器。
# 装入地址指针指令 LDS LES
LDS DEST, SRC
LES DEST, SRC
作用:把 SRC
存储单元开始的 4 个字节单元的内容送入 DEST
通用寄存器和段寄存器 DS
(LDS指令)或 ES
(LES指令)
- 低 16 位送
DEST
,一般是送SI
或DI
- 高 15 位送
DS
/ES
执行后,SI=0020H
,DS=5030H
。
# 标志位操作指令
# LAHF SAHF
- LAHF (Load Flags to AH):将 FLAGS 的低 8 位装入 AH
- 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 指令基本相同:
- 源操作数可以是通用寄存器、存储单元或立即数
- 目的操作数只能是通用寄存器或存储单元,不能是立即数
- 二者不能同时来自存储器。
# 带符号加法 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 字节,首地址为 M1
和 M2
。
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位
- 一个乘数和计算结果使用隐含寻址,隐含的是存放被乘数的累加寄存器
AL
或AX
,及存放结果的AX
或DX
- 乘法只影响
OF
、CF
;若运算结果的高半部分是无效数值,则OF=CF=0
,否则OF=CF=1
- 考虑 8 位乘 8 位,结果仍可以用 8 位存储的情况,此时
OF=CF=0
- 对于无符号乘法,当且仅当高半部分为全 0,
OF=CF=1
- 对于有符号乘法,当且仅当高半部分为低半部分的符号扩展,
OF=CF=1
- 若有符号乘法结果为
00000000 11111111
,高半部分不是符号扩展,CF=OF=0
。若在后续步骤只看后半部分,会导致原来的正值被(错误地)识别为负值
- 考虑 8 位乘 8 位,结果仍可以用 8 位存储的情况,此时
# 无符号乘法 MUL
MUL OPRD
; 字节运算:AX = AL * OPRD
; 字运算: DX:AX = AX * OPRD
OPRD
不能是立即数- 若结果高半部(
AH
或DX
)是全 0(不是有效数值),则CF=OF=0
,否则CF=OF=1
# 带符号乘法 IMUL
IMUL
(sIgned MULtiply) 除了操作数是带符号外,其余与 MUL
指令相同。
IMUL OPRD
- 若结果高半部(
AH
或DX
)是低半部的符号扩展(不是有效数值),则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
= 余数
- 执行:
简而言之,就是高位存余数,低位存商。记忆的方法,可以想:平时更常用除法而不是求模,低位存储的结果就可以直接进行下一步运算。
除法指令常和 CBW
或 CWD
配合使用。
# BCD 码调整指令
# 六条指令
- DAA
Decimal Adjust after Addition
,Decimal
即每四位表示一个 BCD 码,又称压缩型、组合型 - AAA
ASCII Adjust after Addition
,ASCII
即每八位表示一个 BCD 码,又称非压缩型、非组合型
剩下的就自己看 PPT 吧。
# 逻辑运算和移位指令
# 逻辑运算
- 逻辑运算指令对操作数的要求大多与MOV指令相同
NOT
运算指令要求操作数不能是立即数- 除
NOT
运算指令外,其余指令的执行都只会影响OF
和CF
(使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
,但:
- 前者会使
OF
=CF
=0 - 前者的字节码更短
- 前者在旧的架构上速度会更快(新架构就没必要了)
- 参见 xor ax, ax when loading segment register - Stack Overflow (opens new window)
# 测试(与) 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
使用 AND
后 CMP
:
AND AL, 2AH
CMP AL, 2AH
JNZ WAIT
使用 AND
后 XOR
:
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
CL
是 CX
寄存器的低 8 位。
二者实际上就是一条指令,都是最低位补 0,最高位移到 CF
。
# 算术右移 SAR 逻辑右移 SHR
; 算术右移指令,视为有符号数
SAR OPRD, 1
SAR OPRD, CL
; 逻辑右移指令,视为无符号数
SHR OPRD, 1
SHR OPRD, CL
移出的数送 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)重设。
- 每次:
- 只处理串中的一个单元(字或字节)
- 这些指令执行结束后,都会按
DF
决定的方向自动修改SI
和/或DI
,使其指向下一个单元(按指令可分别处理字单元和字节单元)
- 地址修改方向由 DF 标志位决定:
DF = 0
=> 增地址方向DF = 1
=> 减地址方向
- 指令前面可加上自动重复前缀,实现自动重复执行串操作,重复执行次数由
CX
指定
# 重复前缀
重复前缀:重复执行给定指令,每执行一次后,自动使 CX-1=>CX
,直至 CX=0
或其他条件
- 无条件重复
REP
=> 若CX≠0
则重复
- 条件重复
REPE
相等重复 => 若CX≠0
且ZF=0
则重复REPZ
为零重复(上一指令的别名)REPNE
不相等重复 => 若CX≠0
且ZF≠0
则重复REPNZ
不为零重复(上一指令的别名) 注意:重复前缀本身不改变标志位
# 串操作指令流程
左边部分是串操作的初始化,右边的虚线框部分由串操作指令完成。
# 串传送指令 MOVS
MOVS OPRD1(ES:DI), OPRD2(DS:SI)
MOVSB
MOVSW
- 第一种格式中,
OPRD1
为目标串地址,OPRD2
为源串地址(类似于MOV
)。两串的段地址允许使用默认值(ES
、DS
),源串也允许段重设 - 二、三种格式下隐含了操作数地址(目标串
ES:DI
、源串DS:SI
) MOVSB
一次完成一个 Byte 的传送,MOVSW
一次完成一个 Word 的传送- 执行结束后,按
DF
决定的方向自动修改SI
和DI
,使其指向下一个单元
例题:用串传送指令实现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
为目标串(这和SUB
、CMP
相同) - 和
CMP
相同的是,执行OPRD1-OPRD2
,不改变操作数, 只改变标志位 - 串比较指令常与条件重复前缀连用
- 指令的执行不改变操作数,仅影响标志位
- 执行结束后,按
DF
决定的方向自动修改SI
和DI
,使其指向下一个单元
例:比较两组(200个字节)对应数据,找出第一个不同数据放入AL,其地址放入BX
LEA SI, MEM1
LEA DI, MEM2
MOV CX, 200
CLD
REPE CMPSB ; 指令执行结束,可能是找到不同,也可能是完全相同
JZ STOP
DEC SI ; 注意!
MOV AL,[SI]
MOV BX,SI
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
的状态
- 确定
- 源串和目标串指针分别为
SI
和DI
- 串长度值必须由
CX
给出 - 注意重复前缀的使用方法
- 传送类指令前加无条件重复前缀
- 串比较类指令前加条件重复前缀,但前缀不影响
ZF
状态
# 程序控制指令
# 程序的执行方向
- 程序控制类指令的本质:控制程序的执行顺序
- 决定程序执行方向的因素:
CS
,IP
- 下条指令地址:
CS:[IP]
- 修改
CS
,程序转向另一个代码段执行 - 修改
IP
,程序转向本代码段内另一处执行
# 转移指令 JMP
- 转移指令修改
CS
和IP
的值,实现程序转移 - 分为无条件转移指令和有条件转移指令
# 无条件转移指令
JMP OPRD
# 段内转移
- 段内转移:目标地址为 16 位,赋值给
IP
- 段内直接转移:
OPRD/Label
为立即数,IP <= IP + Label
(Label
被汇编为 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 = Label
(Label
被汇编为 32 位地址) - 段间间接寻址:转移的目标地址由指令中的 32 位存储器单元给出
例:
JMP FAR Label ; 段内直接转移
; CS:IP = Label
JMP DWORD PTR[BX] ; 段内间接寻址
; IP = [BX]
; CS = [BX+1]
例 2:
MOV SI,1122H
MOV WORD PTR[SI],0120H
ADD SI,2
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的个数是否为偶数 |
没有类似指令判断 AF 或 DF 的状态,只有上面五个 | ||
JA/JAE/JB/JBE | 判断CF 或CF+ZF 的状态 | 常用于无符号数的大小比较 |
JG/JGE/JL/JLE | 判断SF 、OF 和ZF 的状态 | 常用于带符号数的大小比较 |
有关 JA
JG
等的含义和用法请读者自行百度 / Google。
例题:统计内存数据段中以 TABLE 为首地址的 100 个 8 位有符号数中正数、负数和零的个数。
# 循环控制指令
- 循环范围:以当前
IP
为中心的-128~+127
范围内循环 - 循环次数:由
CX
寄存器指定 - 循环指令:
LOOP
LOOPZ
LOOPNZ
# 无条件循环指令 LOOP
LOOP LABEL
- 操作:
CX-1 => CX
CX ≠ 0
则转LABEL
,否则执行下条指令
也就是说,当 CX = 1
时执行 LOOP
则不会跳转,而是顺序执行。
# 有条件循环指令 LOOPZ LOOPNZ(略)
LOOPZ
和 LOOPNZ
# 过程调用指令 CALL
- 用于调用一个子过程
- 子过程由程序员预先设计并装入内存
- 子过程执行结束后要返回原调用处
- 重要概念:入口地址、断点,如图
调用指令的执行过程:
- 保护断点:将断点(调用指令的下一条指令的地址)压栈
- 获取子过程的入口地址(子过程第一条指令的地址)
- 执行子过程,含相应参数的保存及恢复
- 恢复断点,返回原程序(将断点地址由堆栈弹出)
分为:
- 段内直接调用
- 段内间接调用
- 段间直接调用
- 段间间接调用
# 段内调用 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
指令的下一条指令的CS
、IP
压栈 - 由
n*4
得到存放中断向量的地址 - 将中断向量(中断服务程序入口地址)送
CS
和IP
寄存器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
- 中断服务程序的最后一条指令,执行:
- 恢复断点
- 恢复标志寄存器内容
# 处理器控制指令
主要分为:
- 对标志位的操作
- 对标志位操作都是无操作数指令
- 可操作的标志位有
CF
、IF
和DF
- 与外部设备的同步
常见指令 | 作用 |
---|---|
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 中各寄存器名(如
AX
、CS
等) - 指令助记符(如
MOV
、ADD
) - 伪指令符(如
SEGMENT
、DB
) - 表达式中的运算符(如
GE
、EQ
) - 属性操作符(如
PTR
、OFFSET
等)
# 汇编语言数据
- 数据:指令和伪指令语句中的操作数
- 常用的数据形式有:常数、变量和标号
- 一个数据由数值和属性(比如是字节数据还是字数据)两部分构成
# 常数
常数:经过汇编后其值已完全确定,并且在程序运行过程中不会改变。
# 常数的表示
- 二进制数:以字母
B
结尾,如01001001B
- 八进制数:以字母
O
或Q
结尾,如631Q
254O
- 十进制数:以字母
D
结尾,或者没有结尾字母。如2007D
、2007
- 十六进制数:以字母
H
结尾,如3FEH
- 如果常数的第一个数符为字母,为了与标识符区别,必须在其前面冠以数字
0
,如0F000H
为 16 位常数
- 如果常数的第一个数符为字母,为了与标识符区别,必须在其前面冠以数字
- 实数:如
2.134E+10
。汇编源程序时会把实数转换为 4 字节、8 字节或 10 字节的二进制数形式存放。 - 字符串常数:用引号(单引号或双引号)括起来的一个或多个字符,其值为这些字符的ASCII码
- 如
'ABC'
存储为41H 42H 43H
- 其中
41H
在低地址,43H
在高地址,类似于 C 语言的char *
- 如
# 常数的作用
- 作指令语句的源操作数
- 在指令语句中作位移量
- 在数据定义伪指令中使用
; 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
# 变量
变量:用来表示存放数据的存储单元,这些数据在程序运行期间可以被改变。
程序中以变量名的形式来访问变量。变量名就是存放数据的存储单元地址。
# 变量的定义
定义变量:给变量在内存中分配一定的存储单元。也就是给这个存储单元赋与一个符号名,即变量名,同时还要将这些存储单元预置初置。
定义变量使用数据定义伪指令 DB
、DW
、DD
、DQ
和 DT
等。
格式:
VAR_DATA SEGMENT
DATA1 DB 12H
; 变量名 + 数据定义伪指令 + 初值
DATA2 DB 20H, 30H
DATA3 DW 5678H
VAR_DATA ENDS
# 变量的属性
- 段属性(逻辑段):上例
DATA1
DATA2
DATA3
均在VAR_DATA
逻辑段 - 偏移量属性(偏移地址):上例
DATA1
DATA2
DATA3
的偏移量分别为 0, 1, 3 - 类型属性,如下:
类型 | 中文 | 变量长度 |
---|---|---|
DB | Define Byte | 1 字节 |
DW | Define Word | 2 字节 |
DD | Define Doubleword | 4 字节 |
DQ | Define Quadword | 8 字节 |
DT | Define Tenbytes | 10 字节 |
# 变量的预置
- 数值表达式
?
表达式:表示预置任意内容(未赋初值)- 字符串表达式
DUP
表达式
# 数值表达式
; 1. 数值表达式
DATA1 DB 32, 30H
; DATA1 的内容为 32(20H)
; DATA1+1 单元内容为30H
# ? 表达式
DA-BYTE DB ?, ?, ?
; 表示让汇编程序分配三个字节存储单元
; 这些存储单元的内容的值为任意(未赋初值)
# 字符串表达式
- 字符串长度不超过 255 个字符
- 使用
DB
伪指令,会对每个字符分配一个字节单元,从左到右将各字符的 ASCII 码以地址递增的顺序依次存放 - 使用
DW
伪指令,可以给两个字符组成的字符串分配两个字节存储单元 - 但需要注意的是,两个字符的存放顺序是前一个字符放在高地址,后一字符放低地址单元
- 使用
DD
伪指令,只能给两个字符组成的字符串分配 4 个字节单元 - 两个字符存放在较低地址的两个字节单元中,存放顺序与
DW
伪指令相同 - 而较高地址的两个字节单元存放 0
DW
和DD
伪指令不能用两个以上字符构成的字符串赋初值
; 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 字节
DUP
和 DW
组合时,每个 重复内容
占用两字节,例如:
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
加在一条指令的前面,它就是该指令在内存的存放地址的符号表示,也就是指令地址的别名 - 标号主要用在程序中需要改变程序的执行顺序时,用来标记转移的目的地
- 下面代码的
LAB
和NEXT
即为标号
MOV CX, 100
LAB: MOV AX, BX
; ...
LOOP LAB
JNE NEXT ;不为零转移
; ...
NEXT: ; ...
# 标号的三个属性
- 段属性
SEG
它表示该标号所代表的地址在哪个逻辑段中,即段基值。
- 偏移量属性
OFFSET
它表示该标号所代表的地址在段内与段起点间的字节数,即地址的偏移量。
- 距离属性(也叫类型属性)
它表示该标号可以被段内还是段间的指令调用。
NEAR
(近):用作段内转移,即只能是与该标号所指指令同在一个逻辑段的其它指令才能使用它FAR
(远):可以被非本段的转移和调用指令使用
标号的距离属性可以有两种方法来指定:
(1) 隐含方式
SUB1: MOV AX, 30H
SUB1
默认为 NEAR。
(2) 用 LABEL
伪指令给标号指定距离属性
标号名 LABEL NEAR/FAR
该语句要与指令语句连用,如下:
SUB1_FAR LABEL FAR
SUB1: MOV AX, 30H
上文中,SUB1_FAR
与 SUB1
两个标号具有相同的逻辑地址。但 SUB1
只能被本段调用,SUB1_FAR
可以被其它段的指令调用。
# LABEL 定义变量属性
LABEL
伪指令还可以用于定义变量的属性,即改变一个变量的属性,如把字变量的高低字节作为字节变量来处理。
DATA_BYTE LABEL BYTE
DATA_WORD DW 20H DUP(?)
DATA_BYTE
与DATA_WORD
具有相同的段基址和偏移量DATA_BYTE
可以被用来存取一个字节数据,而DATA_WORD
则不能
# 符号定义语句
符号定义语句将常数或表达式等形式用某个指定的符号来表示。在 8086/8088 汇编语言中有两种符号定义语句,分别为等值语句 EQU
和等号语句 =
。
# 等值语句 EQU
- 格式:
符号名 EQU 表达式
- 功能:用符号名来表示
EQU
右边的表达式。后面的程序中一旦出现该符号名,汇编程序将把它替换成该表达式。 - 类似于 C 的
#define
表达式
:
- 常数或数值表达式
COUNT EQU 5
NUM EQU COUNT+5
- 地址表达式
ADR1 EQU DS:[BP+14]
- 变量名、寄存器名或指令助记符
CREG EQU CX ; 在后面的程序使用CREG就是使用CX
CBD EQU DAA ; DAA为十进制调整指令
注意,同一符号不能用 EQU
重复定义。
# 等号语句 =
格式:符号名=表达式
等号语句与等值语句具有类似的作用,二者的区别是:
- 等号语句可以对一个符号进行多次定义
- 等号语句不能为助记符定义别名(诸如
CBD=DAA
是错误的)
等值语句与等号语句都不会为符号分配存储单元。所定义的符号没有段、偏离量和类型等属性。
# 表达式与运算符
表达式是指令或伪指令语句操作数的常见形式。它由常数、变量、标号等通过操作运算符连接而成。
任何表达式的值在程序被汇编的过程中进行计算确定,而不是到程序运行时才计算。
8086/8088宏汇编语言中的操作运算符非常丰富,可以分为以下五类。
- 算术运算符
- 逻辑运算符
- 关系运算符
- 数值返回运算符
- 属性修改运算符
# 算术运算符
包含 +
、-
、*
、 /
、MOD
、SHL
、SHR
、[ ]
。
- 运算符
+
和-
也可作单目运算符,表示数的正负 - 使用
+
、-
、*
和/
运算符时,参加运算的数和运算结果都是整数 /
运算为取商的整数部分,而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
SHR
和SHL
为逻辑移位运算符
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
- 下标运算符
[ ]
具有相加的作用
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 AX,0FH 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 1,2
K2 DW 3,4
; ...
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]
; 正确
另外,OFFSET
和 SEG
的返回值可认为是立即数,不能直接 MOV
进 DS
、ES
(见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 10H,20H,30H,40H
K3 DW 20H DUP (0,1,2 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 地址表达式
- 作用: 将地址表达式所指定的标号、变量或用其它形式表示的存储器地址的类型属性临时修改为“类型”所指的值
- 类型:
BYTE
、WORD
、DWORD
、NEAR
或FAR
- 这种修改是临时的,只在含有该运算符的语句内有效,类似于 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
运算符不能分离寄存器、存储器单元或变量中的内容。- 分离变量的段基址、偏移量时,需要配合
SEG
和OFFSET
。这是符合“变量在伪指令中是偏移地址,在指令中是存储器中内容”的。
; 设 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
伪指令 SEGMENT
和 ENDS
用于定义一个逻辑段,分别表示定义的开始与结束。
段名 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 |
定位类型为 PAGE
和 PARA
时,段的起始边界直接选用段基址,即它们是重合的。定位类型为 DWORD
、WORD
和 BYTE
时,段的起始边界与段基址可能不同。
# 组合类型
组合类型说明符用来指定段与段之间的连接关系和定位。
默认
:未指定组合类型,表示本段与其它段无连接关系- 在装入内存时,本段有自己的物理段,因此有自己的段基址
PUBLIC
:将该逻辑段接在前一逻辑段后面,形成新逻辑段- 在满足定位类型的前提下,将与该段同名的段邻接在一起,形成一个新的逻辑段,共用一个段基址。段内的所有偏移量调整为相对于新逻辑段的段基址
COMMON
:产生一个覆盖段- 在多个模块连接时,把该段与其它也用
COMMON
说明的同名段诉汇编程序,在处理置成相同的段基址,这样可达到共享同一存储区。共享存储区的长度由同名段中最大的段确定
- 在多个模块连接时,把该段与其它也用
STACK
:将该段作为栈使用- 把所有同名段连接成一个连续段,且系统自动对
SS
段寄存器初始化为该连续段的段基址,并初始化堆栈指针SP
- 用户程序中应至少有一个段用
STACK
说明,否则需要用户程序自己初始化SS
和SP
。
- 把所有同名段连接成一个连续段,且系统自动对
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
- 在段定义中选用了
PARA
说明,则该段起始单元与前面已分配存储单元之间可能存在一些未使用的空白 CODE
段的组合类型为MEMORY
,因此被装入在其它段之后(最高地址)
# 段寻址伪指令 ASSUME
ASSUME的作用是告诉汇编程序,在处理源程序时,定义的段与哪个段寄存器关联。
ASSUME并不设置各个段寄存器的具体内容,段寄存器的值是在程序运行时设定的。所以,一般需要同时使用:
ASSUME DS:段名
MOV AX, 段名
MOV DS, AX
一般格式:ASSUME 段寄存器名: 段名, 段寄存器名:段名......
- 段寄存器名:
CS
,DS
,ES
或SS
- 段名:用
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
语句设定。
# 段寄存器的装入
从上面可以看出,要让一个段寄存器真正地指向某个逻辑段,一般需要两个步骤:
- 将段基值装入到该段寄存器
- 将段和段基址建立关联(常用
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
建立ES
和DATA2
的联系 - 使用
MOV AX,DATA2
MOV ES,AX
将DATA2
段基址装入ES
如果已经将段基址装入了 ES
,也可以临时指明段前缀:
MOV ES:DBYTE2[2], AL
# SS 的装入
在段的组合类型中提到,若将一个段声明为 STACK
,系统会自动初始化 SS
和 SP
寄存器为对应的值。
因此,SS
的装入有两种方法:
- 手动使用
MOV
装入SS
和SP
,然后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_STACK
,SP=TOP=40H
- 定义段时使用
STACK
作为组合类型(系统自动装入SS
和SP
),然后ASSUME SS:段名
STACK1 SEGMENT PARA STACK
DB 40H DUP(?)
STACK1 ENDS
CODE SEGMENT
ASSUME CS:CODE, SS:STACK1
上述例子中,SS=STACK1
,SP=40H
# CS
CPU在执行指令之前根据CS和IP的内容来从内存中取指令,即必须在程序执行之前装入CS和IP的值。因此,CS和IP的初始值就不能用可执行语句来装入。
装入CS和IP一般有下面两种情况。
- 程序加载到主存时,由系统软件给CS和IP赋予初始值
结束伪指令格式:END 起始地址
- 起始地址: 是一个标号或表达式,它必须是程序中第一条指令语句前所加的标号,如前文的
END MAIN
- 汇编源程序必须以END伪指令结束
- END伪指令指示源程序结束并指定程序运行时的第一条指令的地址(起始地址)
- 起始地址段基址和偏移量被分别装入CS和IP中
CODE SEGMENT
ASSUME CS:CODE,......
START:
; ...
CODE ENDS
END START
- 程序运行期间,当执行某些指令时,CPU自动修改CS和IP,使它们指向新的代码段。
- 执行段间过程调用CALL和段间返回指令RET;
- 执行段间无条件转移指令JMP;
- 响应中断及中断返回指令;
- 执行硬件复位操作。
# 总结
- 修改 DS、ES:
MOV
+ASSUME
MOV
+ 临时指明段基址ES:段名
- 修改 SS:
- 声明段为
STACK
+ASSUME
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
中,有关的参量送指定的寄存器。
三步骤:
- 送入口参量到指定的寄存器
- 送功能号到 AH
- 执行
INT 21H
# 带显示的键盘输入(1号功能)
- 该功能子程序将等待键盘输入,直到按下一个键
- 将字符的ASCII码送入AL寄存器,并在屏幕上显示该字符
- 如果是Ctrl-C组合键,则停止程序运行
- 该功能调用无入口参量
MOV AH,01H
INT 21H
# 不带显示的键盘输入(8号功能)
- 该功能调用与1号功能的作用相似,区别是8号功能将不显示输入的字符
MOV AH,8
INT 21H
# 不带显示的键盘字符输入(7号功能)
该功能与8号功能相似,但对Ctrl-C组合键和TAB制表键无反应。
MOV AH,7
INT 21H
# 字符串输入(0AH号功能)
- 该功能调用可实现从键盘输入字符串,其长度 <= 255个字符
- 调用前,应在内存中建立输入缓冲区
- 缓冲区第一个字节是可输入的最大字符数+1
- 第二个字节是系统在调用该功能时,自动填入的实际输入的字符个数
- 从第三个字节开始存放输入字符的ASCII码(见后面例子)
- 当用户输入回车键时,结束输入,并将回车键的ASCII码
0DH
作为最后一个字符送入缓冲区。但它不计入实际输入字符个数
入口参量:DS
和 DX
寄存器分别装入输入缓冲区的段基值和偏移量
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 AH,2
INT 21H
# 字符打印(5号功能)
该功能将字符送入打印机接口,实现单个字符的打印操作。
入口参量:DL
装入打印字符的ASCII码
MOV DL,'A'
MOV AH,5
INT 21H
# 字符串显示(9号功能)
该功能实现将一个字符串显示到屏幕上。
入口参数:
- 将待显示的字符串存放在一个数据缓冲区,字符串以符号“$”作为结束标志。
- 将字符串的首址的段基值和偏移量分别送入
DS
和DX
中
CHAR DB 'This is a test.', 0AH, 0DH, '$'
; ......
MOV DX, OFFSET CHAR
MOV AH, 9
INT 21H
# 直接输入输出(6号功能)
该功能可以实现键盘输入,也可以实现屏幕显示操作。两种操作通过 DL
的内容确定。
(DL)=00~0FEH
时,输出DL
对应的字符。
MOV DL,24H ; $的ASCII码为24H
MOV AH,06
INT 21H ; 输出 $
(DL)=FFH
时 ,从键盘输入字符
该功能不等待键盘输入,而是从键盘缓冲区中读取。读取的字符ASCII码送入AL中,如果缓冲区为空,则标志位ZF=1。
WAIT: MOV DL,0FFH
MOV AH,6
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 AH,4CH
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,需要做以下三个操作:
- 将用户程序编制成一个过程,类型为
FAR
- 将PSP的起始逻辑地址压栈,即将
INT 20H
指令的地址压栈 - 在用户程序结尾处,使用一条
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 17,5,40,0,67,12,34,78,32,10
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 扩充了寄存器结构,采用了多种存储器管理方式 ,因此其寻址方式有了较大的增强。
- 允许使用 32 位的通用寄存器作寄存器间接寻址。例如:
MOV DX, [EBX]
- 所有的32位通用寄存器均可作为基址寄存器使用;除
ESP
外的32位通用寄存器都能作为变址寄存器使用。
例如:
MOV EDX, [EDX+16]
MOV AX, ADDR[EBP]
MOV EBX, [ESI][EAX]
- 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
类似于 LDS
、LES
(DEFG
还行)
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 时,允许使用立即数作为操作数。
例如,SAL AX, 2
- 新增了
SHLD
与SHRD
指令
SHLD DEST, SRC, OPTR
; 对 DEST 左移 OPTR 位,移出的位依次进入 CF 标志位,DEST 空出的位由 SRC 的高位顺序移入。移位结束后SRC保持不变
SHRD DEST, SRC, OPTR
; 对 DEST 右移 OPTR 位,移出的位依次进入 CF 标志位,DEST 空出的位由 SRC 的低位顺序移入。移位结束后,SRC保持不变
8086 的 RCL
RCR
只能移一次,空出的位由 CF
补。
# 堆栈操作指令
- PUSH允许立即数入栈
PUSH 0ABCDH ;将16位立即数入栈
PUSH 0ABCD0000H ;将32位立即数入栈
- 新增两条PUSH指令
PUSHA
指令将8个16位通用寄存器AX、BX、CX、DX、SP、BP、SI与DI
一次性入栈。
PUSHD
指令将8个32位通用寄存器EAX、EBX、ECX、EDX、ESP、EBP、ESI与EDI
一次性入栈。
似乎是为了函数调用时保存所有寄存器值?
- 新增两条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多了一个使用类型。
使用类型: 有USE16
和USE32
两种取值,用来定义段寻址方式
- 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 端口与内存统一编址
- 指令及控制信号统一
- 内存地址资源减少
# I/O 端口独立编址
- 内存地址资源充分利用
- 能够应用于端口的指令较少
# 8088/8086 的 I/O 端口编址
- 采用I/O端口独立编址方式(但地址线与存储器共用)
- 地址线上的地址信号用()来区分
- I/O操作只使用20根地址线中的16根:A15~A0
- 可寻址的I/O端口数为64K(65536)个
- I/O地址范围为
0
~FFFFH
# I/O 地址译码
# 全地址译码与部分地址译码
一个接口电路中可以有一个或多个端口。
- 全地址译码:
- 全部16位I/O地址信号参与译码
- 当接口中只有一个端口时, 16位地址线应全部参与译码,译码输出直接选择该端口中;
- 当接口中有多个端口时,则16位地址线的高位参与译码(决定接口的基地址),低位直接输入到接口芯片,用于寻址接口中要访问的端口。
- 部分地址译码:
- 仅用部分地址(比如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 表示任意状态,图中无需接入;后两位表示片内地址
最上面表示 和 至少有一个是 0(有效)时芯片有效,若都是 1 则芯片无效。
# 接口的基本构成
- AB (Address Bus)
- DB (Data Bus)
- CB (Control Bus)
- 数据输入/输出寄存器:暂存输入/输出的数据
- 命令寄存器:存放控制命令(设定接口功能、工作参数和工作方式)
- 状态寄存器:保存外设当前状态,以供CPU读取
# 接口的类型及特点
- 按传输信息的方向分类:
- 输入接口
- 输出接口
- 按传输信息的类型分类:
- 数字接口
- 模拟接口
- 按传输信息的方式分类:
- 并行接口
- 串行接口
接口特点:
- 输入接口:
- 要求对数据具有控制能力(允许数据送到数据线)
- 常用三态门实现
- 输出接口:
- 要求对数据具有锁存能力(接收后保持数据不变)
- 常用锁存器实现
# 简单接口电路
# 三态门接口及 74LS244
三态:高电平、低电平、高阻态
74LS244:
- 含8个三态门的集成电路芯片
- 在外设具有数据保持能力时用来输入接口数据
例题:编程判断图中的开关状态,若全闭合则转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 触发器
74LS373和74LS374:
- 三态输出的8 D触发器,并具有对数据的控制能力。
- 既可以做输入接口,也可以做输出接口。
不懂
例题:输出接口地址是多少?
不懂+1
# I/O接口综合应用例题(重要!)
- 根据 4 个开关的状态控制 7 段数码管的显示(输入:4位开关 输出:显示符号与输出数据对应表中的 7 位)
- 当4个开关的状态分别为
0000
~1111
时,在7段数码管上对应显示'0'~'F' - 设显示接口的地址为
F0H
,开关接口地址为F1H
解:下面是总线连接图。
- 四个开关 K0~K3 连接 74LS244,输入到 CPU
- 汇编程序将 D0~D3 转为上表中的 8 位值
- 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)
其中前三种是程序控制方式。
# 无条件传送
要求外设总是处于准备好状态。
优点:
- 软件及接口硬件简单
缺点:
- 只适用于简单外设,适应范围较窄
例:读取开关的状态;当开关闭合时,输出编码使发光二极管亮。
# 查询工作方式
- 仅当条件满足时才能进行数据传送;
- 每满足一次条件一般只进行一次数据传送。
- 适用场合:
- 外设并不总是处于“准备好”状态
- 对传送速率和效率要求不高
- 工作条件:
- 外设应提供设备状态信息
- 接口应具备状态端口
上述流程图可能出现一直等待外设,导致死机的情况。
例:数据输出
- 外设状态端口地址为
03FBH
,bit5
为状态标志(=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控制器发出“DMA传送请求”信号 DREQ;
- DMA控制器收到请求后,向CPU发出“总线请求”信号HOLD;
- CPU在完成当前总线周期后会立即发出HLDA信号,对HOLD信号进行响应;
- DMA控制器收到HLDA信号后,就开始控制总线,并向外设发出DMA响应信号DACK。
DMA控制方式工作过程例:从外设向内存传送若干字节数据
- DMAC向I/O接口发出读信号;
- 向地址总线上发出存储器的地址;
- 发出存储器写信号和AEN信号;
- 传送数据并自动修改地址和字节计数器
- 判断是否需要重复传送操作;
- 若数据传送完,DMA控制器撤销发往CPU的HOLD信号;
- CPU检测到HOLD失效后,则撤销HLDA信号,并在下一时钟周期重新开始控制总线。
DMA工作方式:
- 周期窃取:每个DMA周期只传送一个字节或一个字就立即释放总线。
- 数据块传送:DMAC在申请到总线后,将一块数据传送完后才释放总线,而不管中间DREQ是否有效。
DMA控制方式的优缺点:
- 优点:数据传输由DMA硬件来控制,数据直接在内存和外设之间交换,可以达到很高的传输速率。
- 缺点:控制复杂,硬件成本相对较高。
# 中断技术
# 基本概念
CPU执行程序时,发生了某种随机的事件(外部或内部),引起CPU暂时中断正在运行的程序,转去执行一段特殊的服务程序(称为中断服务程序或中断处理程序),以处理该事件,该事件处理完后又返回被中断的程序继续执行,这一过程称为中断。
中断源:引起CPU中断的事件,发出中断请求的来源。
分类
- 内部中断
- 异常中断:异常事件引起
- 软件中断:中断指令引起
- 外部中断
- 可屏蔽中断:
INTR
中断 - 非屏蔽中断:
NMI
中断
- 可屏蔽中断:
引入中断的原因:
- 提高数据传输率;
- 避免了CPU不断检测外设状态的过程,提高了CPU的利用率。
- 实现对特殊事件的实时响应。
# 中断处理的一般过程
- 中断请求
- 中断源识别及中断判优
- 中断响应
- 中断处理(服务)
- 中断返回
# 中断请求
- 包括 NMI、INTR
- 中断请求信号应保持到中断被处理为止;
- CPU响应中断后,中断请求信号应及时撤销。
# 中断源识别
- 软件查询法:在中断处理程序中查找中断源
- 中断矢量法—硬件识别
- 由中断源提供中断类型号,CPU根据类型号确定中断源。
- 中断源识别及中断判优(确定先响应哪个中断请求)由硬件系统完成
# 中断判优
当有多个中断源同时提出请求时,需要确定首先响应哪一个中断源。
优先级法则:
- 同时出现或等待的多个中断源,优先级最高的被响应。
- 低优先级的中断程序允许被高优先级的中断源所中断(可能出现中断嵌套)
- 也可以设置成禁止中断嵌套
软件判优:顺序查询中断请求,先查询的先服务,即先被查询的中断源优先级别高
硬件判优:链式判优、并行判优(中断向量法)
看不懂图。
# 中断响应
中断响应包含以下几个操作:
- 向中断源发出中断响应信号
- 关中断
- 保护硬件现场:将FLAGS压入堆栈
- 保护断点:将CS、IP压入堆栈
- 获得中断服务程序入口地址
这些步骤都是由硬件系统完成。
# 中断处理
执行中断服务程序
中断服务程序的特点:
- 中断服务程序要定义为“远过程”
- 结束时要用
IRET
指令返回
中断服务程序完成的工作:
- 保护软件现场(参数)
- 开中断
STI
--允许中断嵌套 - 中断处理—具体的处理
- 关中断
CLI
- 恢复软件现场
- 中断返回
IRET
# 中断返回
执行 IRET
指令,包括下面的操作:
- 使 IP、CS 和 FLAGS 从堆栈弹出
- 开中断
# 8088/8086中断系统
在8086/8088所有的中断源都统一分配了不同的类型号
# 内部中断
中断类型 | 功能 |
---|---|
类型0 | 除数为0中断例行程序 |
类型1 | 单步 |
类型2 | 非屏蔽中断,NMI |
类型3 | 设置断点 |
类型4 | 溢出处理中断,INTO 指令 |
类型10H | 显示设备中断 |
类型20H | 程序结束中断 |
类型21H | DOS系统功能调用功能程序 |
# 外部中断
- 非屏蔽中断
- NMI 引脚上出现上升沿触发
- 不受标志位IF的限制,即不可以屏蔽。
- 类型号:2
- 可屏蔽中断
- INTR引脚输入,高电平有效
- 受标志位IF的限制
- 类型号:08H~0FH 70H~77H
# 中断向量表
- 存放各类中断的中断服务程序的入口地址;
- 每个入口占用4 Bytes,低字为段内偏移,高字为段基址;
- 表的地址位于内存的00000H~003FFH,大小为1KB,共256个入口。
# 中断向量表 IVT
8086/8088中断向量表位于内存最低1KB
# 中断向量表的初始化
- 系统启动时已经把默认的中断向量写入IVT
- 用户需将自定义的中断服务程序入口地址放入向量表
- 注意点:
- 向量表所在的段基址=
0
- 存放中断服务程序入口的单元的偏移地址=
n*4
- 向量表所在的段基址=
例:将中断类型码为48H的服务程序入口地址放入向量表
TIMER PROC FAR
; ...
; ...
IRET
TIMER ENDP
; 下面的程序用MOV指令将类型码为48H的中断服务程序TIMER的中断向量放入向量表
MOV AX,0000H
MOV DS,AX
MOV SI,0120H ; 48H*4
MOV BX,OFFSET TIMER
MOV [SI],BX
MOV BX,SEG TIMER
MOV [SI+2],BX
# 可屏蔽中断的类型号的获取时序
对可屏蔽中断的响应需要两个总线周期:
# 8088内部中断与NMI中断
特点:
- 无 总线周期
- 中断类型码固定或由指令给出
# 8086的中断响应和处理流程
# IA-32的中断模式
# 中断控制器 8259A 介绍
# 一. 8259A 的功能与结构
# 8259A 的主要功能
- 记录 8 个中断源的中断请求。
- 确定是否响应中断请求,并确定优先的中断请求并响应。
- CPU 响应中断时向 CPU 发送中断类型号。
# 8259A 的内部结构
三个寄存器的英文分别是 Interrupt Request Register
(IRR), In-Service Register
(ISR), Interrupt Mask Register
(IMR)。这三个的功能一定要记住,当然要是记住了英文就很好记了。
# 8259A 的工作原理
- 当IR0~IR7中的一条或多条请求线变高时,将相应的IRR位置1。
- 根据中断服务寄存器(ISR)和中断屏蔽寄存器(IMR)的内容,找出未被屏蔽的最高优先权的中断请求,向CPU发中断请求信号INT。
- CPU响应中断时,送回应答信号 脉冲。
- 8259A接到CPU发的第一个 脉冲时,把ISR中与最高优先级请求信号对应的位置1,并把IRR中的相应位复位。
- 在8259A接到第二个 脉冲时向CPU发送中断类型码。如果是在AEOI(自动结束中断)方式,在这个脉冲结束时复位ISR的相应位。在其他方式下,要在中断服务程序结束时通过发EOI命令来复位ISR相应位。
# 8259A 的外部特性
# 二. 8259A 的工作方式
下面的部分应该只用对名字留一个印象,具体是什么功能、怎么工作,应该不重要。
# 1. 优先级管理方式
# 1) 中断嵌套方式
- 普通全嵌套方式
- 这是最常用最基本的工作方式,8259A初始化后为该方式。
- 当一个中断正被处理时,只有比它优先级更高的中断请求才会被响应。
- 特殊全嵌套方式
- 它与普通全嵌套方式的区别是:允许同级中断进行嵌套
- 只允许主片8259A使用特殊全嵌套方式,以实现从片中的高低优先级之间的嵌套。
以上两种嵌套方式中各中断源的优先级顺序是固定的,加电时优先级从高到低顺序为:IR0、IR1、IR2、IR3、IR4、IR5、IR6、IR7。也可以重设和循环优先级。
# 2) 优先级变化方式
- 优先级固定方式
- 各中断请求的优先级固定不变,8259A加电后IR0最高,IR7最低。
- 优先级循环方式
- 优先队列是变化的,一个中断源得到中断服务后,它的优先级自动降为最低。
- 按照加电时的优先级顺序进行优先级循环称为优先级自动循环方式。改变初始优先级顺序后的循环,称为优先级特殊循环方式。
- 优先级循环方式适合于系统中各个中断源级别相当,能够得到均衡的服务。
# 2.中断源的屏蔽方式
- 常规屏蔽方式
- 8259A的每个中断请求输入端都可通过对应的IMR位的设置被屏蔽。IMR某位为“1”表示屏蔽对应的中断请求。
- 特殊屏蔽方式
- 使正在处理的中断所对应的IMR位置1,并使对应的ISR位清零,这样任何优先级的中断都可得到响应。
- 主要用在中断服务程序中需要动态地改变系统的优先级结构的情况。
- 例如,在执行中断服务程序的某一部分时,需要禁止比本中断优先级低的其他中断请求,而在执行另一部分时,又希望开放这些中断请求。
看不懂
# 3.中断结束的处理方式
中断结束 (End of Interrupt, EOI)
- 自动中断结束方式
- 它是最简单的中断结束方式。
- 系统进入中断过程,在第二个INTA脉冲的后沿,8259A将当前处理的中断所对应的ISR位清零。
- 当系统正在为某外设进行中断服务,但在8259A的ISR中却没有对应位指示,故该方式只能用于非嵌套方式处理。
- 在初始化时由初始化命令字ICW4的AEOI位置1来设置这种方式。
- 正常中断结束方式
- 它用在两种全嵌套方式下,当CPU向8259A发出中断结束命令时,8259A将ISR中优先级最高的位复位(即当前正在进行的中断服务结束)。
- 这种结束方式的操作很简单,通过向8259A的偶地址端口输出一个操作命令字OCW2来发EOI命令。
- 特殊中断结束方式
- 用这种方式结束中断时,在程序中要发一条特殊中断结束命令,指出当前中断服务寄存器ISR中的哪一位将被清除。
- 它通过向8259A的偶地址端口输出一个操作命令字OCW2,其中的L2、L1、L0这三位指出了对ISR中的哪一位进行清除。
- 该方式用于中断优先级顺序会改变的特殊全嵌套方式(两种优先级循环方式),无法判断ISR的哪位是当前处理的中断。
对于多片8259A级联情况,如果不是自动中断结束方式,在中断服务程序的最后需要发两次EOI命令,分别清除从片中的ISR位和主片中的ISR位。
对于采用特殊嵌套方式的多片级联,从片中可能嵌套有多个中断源,应先向从片发EOI命令,然后读ISR,检查还有无为1的位,如无则才能向主片发EOI命令。
这说的是人话吗(逃
# 4.连接系统总线的方式
- 缓冲方式
- 在很多片8259A级联的大系统中,8259A需要通过总线驱动器和数据总线相连,这就是缓冲方式。
- 在缓冲方式下,8259A的SP/EN端和总线驱动器的允许端相连, SP/EN端输出的低电平可作为总线驱动器的控制信号。
- 非缓冲方式
- 当系统中只有单片8259A或有少量几片8259A级联时,一般将8259A直接与数据总线相连,这种方式就称为非缓冲方式。
- 8259A的SP/EN端作为输入端,在单片8259A系统中, SP/EN端接高电平;在多片系统,主片的SP/EN 端接高电平,从片的SP/EN端接低电平。
# 5.中断触发方式
- 电平触发方式
- 把中断请求输入端的高电平作为中断请求信号。这时高电平信号不能持续太久,否则一次中断请求可能会被多次响应。
- 边沿触发方式
- 8259A将中断请求输入端IRi出现的上升沿作为中断请求信号。该中断请求得到触发后可以一直保持高电平。
真心看不懂。
# 三. 8259A 的初始化命令字和初始化流程
下面的图考试应该会给吧。应该就是依葫芦画瓢的事情了。
- 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。
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写入偶地址。
8259A初始化后缺省的状态是全部屏蔽位=0(允许中断)
例:若要屏蔽IR5、IR4和IR1引脚上的中断,而让其余的中断得到允许。试确定其中断屏蔽操作命令字。
OCW1为:00110010
或 32H
。
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。
有三个功能:
- 设置特殊中断屏蔽方式:D6D5=11为设置,10为清除.
- 查询中断请求:使P=1先写到8259A,再对该地址读入,得到中断状态字节(见下图)。
- I=1,表示IR0~IR7中有中断请求,R2R1R0表示其中最高优先级的编号(IRi);
- I=0,表示无中断请求产生。
- 读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的初始化:
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. 串行通信
- 定义:数据逐位顺序传送。
- 数据一位一位通过同一通信线进行传输。
- 发送设备将几位并行数据转换成串行方式,再逐位传输到接收设备。在接收端将数据从串行方式转换成并行方式。
- 数据传送方式:
- 双工:两根数据传输线,能够同时发送和接收
- 半双工:单根数据传输线,不能同时发送和接收
- 单工:单根数据传输线只用作发送或只用作接收
- 通信方式:
- 同步通信:通信时,发送方和接收方的时钟频率和相位保持一致,收发双方采用同一个时钟信号来定时。每两个字符间的时间间隔固定。
- 异步通信:收发双方没有统一的时钟来定时和同步,每两个字符间的时间间隔不固定。
- 传输速率:
- 在通讯中,用波特率来描述数据的传输速率。即每秒钟传送的二进制位数,简写为bps。
# 7.2 可编程计数/定时器芯片 8253
- 定时/计数器的应用
- 生产线上统计产品的数目----计数器
- 系统的动态存储器刷新----定时器
- 系统时钟计时----定时器
- 扬声器的频率源----定时器
- 常用的定时方法
- 软件定时:优点是节省硬件;缺点是执行程序期间CPU一直被占用,降低了CPU效率
- 硬件定时:要用额外的硬件—计数/定时器,但可提高CPU的利用率
# 8253 基本功能
- 具有三个相互独立的16位计数器,也叫通道。
- 每个通道都可设定以6种工作方式之一进行计数/定时
- 每个计数器都可设为按二进制或BCD码计数
- 具有计数和定时功能,都是基于减1计数方式工作。
- 计数器减为0后,产生输出信号,有的方式可自动装入初值重新计数。
- 在减1过程中,随时都可由CPU读取计数器的当前值。
# 8253 结构
# 8253的控制字
8253工作前需要通过控制字进行设置,每个计数器(通道)都要单独写控制字,端口地址都为同一个地址。
似乎挺好理解的,比 8259 的控制字好理解多了。
虽然我还是不会背
# 8253的工作方式
8253共有6种工作方式:方式0~方式5。
8253各工作方式的共同点:
- 控制字写入计数器时,所有控制逻辑立即复位,输出端OUT进入初始态;
- 写入计数初值后,要经过一个时钟周期后计数器才开始计数;
- 在时钟脉冲CLK的上升沿门控信号GATE被采样;
- 计数器的计数时间点是CLK的下降沿。
# 方式0—计数结束中断
下面六个方式,主要是看图说话,图比字形象的多。
- 写入控制字后OUT变低;
- 写入计数初值后的一个CLK的下降沿,计数初值被装入计数器,然后在每个CLK的下降沿做减1计数;
- 当计数到0时OUT输出变为高电平;
- GATE为高电平时,计数器工作,为低电平时停止计数; 在计数过程中若重新写了新的计数初值,则按新值重新工作; 每写一次计数初值只计数一个周期。
# 方式1—可重复触发的单稳态触发器(硬件触发)
- 写入控制字后,OUT变为高电平;
- 写入计数初值后,当GATE从低变高后的一个CLK下降沿装入初值,OUT变为低。然后对每个CLK下降沿做减1计数,计到0时OUT变高。
- 若计数结束后GATE又出现上跳,则重新装入计数初值,重新开始计数。
- 若计数过程中GATE又出现上跳,则重新装入计数初值,重新开始计数,本次OUT周期宽度加长。
- 负脉冲宽度=计数初值 X CLK周期。
# 方式2—频率发生器(软件或硬件触发)
软件触发:保持GATE为高
- 写控制字后,OUT变为高电平;
- 写计数初值后的一个CLK的下降沿,初值被装入计数器,然后对每个CLK的下降沿减1计数;
- 计数期间OUT保持为高,当计数到1时OUT输出宽度为1个CLK周期的负脉冲,然后重新装入计数初值开始计数。
- 若计数中重写计数初值,则下次计数周期才会以新值开始计数。
硬件触发:写控制字和计数初值时GATE为低,当其变高后的下一个CLK下降沿计数器装入初值,后面每个CLK下降沿计数。
- 方式2为自动装入计数初值的重复计数器。
# 方式3—方波发生器(软件或硬件触发)
- 方式3与方式2相似,只是OUT输出是一个占空比为1:1的方波;
- 若计数初值为偶数,则OUT输出是高低电平对称的方波;
- 若计数初值为奇数,则OUT输出不对称,前面的高比后面的低多1个CLK周期,即近似方波。
- 方式3与方式2一样也可由硬件触发,即GATE从低变高后启动。
# 方式4—软件触发选通
- 写入控制字输出变高,写入计数值后的一个CLK下降沿装入初值,然后对每个CLK下降沿计数;
- 计数到0时输出1个CLK周期宽度的负脉冲;
- 若计数中重写计数初值,则下一个CLK下降沿被装入,然后按此初值计数;
- 若计数中GATE变低将停止计数,当其变高时继续计数;
- 计数初值一次写入只计数一个周期,类似软件触发的方式2的一个周期,但负脉冲的出现会延后一个周期。
# 方式5—硬件触发选通
- 写入控制字后,OUT变高;
- 写入计数值后,当GATE出现上升沿后的一个CLK下降沿装入初值,然后对每个CLK下降沿计数,计数到0时OUT输出一个CLK周期的负脉冲。
- 在计数中若GATE变低后再变高,将重新启动一次计数周期。
- 在计数中若写入新计数值,本次计数周期不受影响。
- 一次GATE触发只计数一个周期,即类似硬件触发的方式2的一个周期,但负脉冲的出现会延后一个周期。
# 方式比较
# 总结
- 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 的初始化编程
下面这段话很重要!
初始化编程的顺序为:
- 对某一指定计数器,先写入控制字,再写入计数初始值。
- 计数初值写入的格式和顺序必须按控制字D5和D4规定的格式写入。
注意:所有通道的控制字都写入同一个控制端口,而计数初值则要写入指定计数器对应的端口。
可以结合下面的例子食用:
更复杂的例子就不一贴出了。
# 考试重点
编一个小的接口应用程序
8253 8255 8259(内容有点多,但考试不会考太难)考工作方式、工作模式,不会设计
8253 定时计数器 8255 应用题
给一组数,找非零数的个数,15 分,框架 12 分
不考的内容(已经老师确认) 1.第五章存储器系统 2.第六章8237 3.第七章8251
# 错题
# 三四章
- 对段寄存器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
# 六七八章 接口部分
- 8086/8088复位后,初始化执行的物理地址是
0FFFF0H
- 当中断处理程序准备执行第一条指令时,
IF=0, TF=0
- 8259A中,特殊全嵌套优先级方式一般用于
多片8259A中的主片中
- DAC0803工作在
两级缓存
方式下时,可在转换输出模拟信号的同时可接收下一个数字信号 - IRET指令会向8259A发送EOI信息,以结束当前的中断
×
- 8086/8088中地址引脚与其它信号复用,所以地址信息需要锁存
√
- 8255A中的端口C置1/0方式使得端口C适合配合端口A、B做控制位使用。
√
- 是否响应软件指令INT引发的中断受IF位的影响。
×
- CPU和外设之间的数据传送方式有
无条件传送
、查询方式
、中断方式
、DMA方式
- 硬件中断分为
可屏蔽中断
、非屏蔽中断
- 8259A工作的优先级方式有
全嵌套
、特殊嵌套
、优先级自动循环
、优先级特殊循环
# 后记
最后考试考了很多的原题。选填题全是前四章的内容,简单题是前四章和后三章各一半,最后解答题一个是前四章的,三个是接口部分,其中两个是老师发的练习题的原题/简化。
隔壁网安就乱划重点 + 没有原题。表扬一下汇编。