汇编语言(王爽·第4版)—— 完整教程与复习笔记
更新于: 2026年10月20日
面向考试 / 期末复习,章节结构与王爽《汇编语言(第4版)》目录完全一致:17 章 + 各章实验/课程设计 + 综合研究 + 附注 1~5。CPU 模型为 8086(16 位、实模式)。文中所有代码示例与计算均已逐一验算;全部内容已对照第 4 版原书逐章核对补全。
这篇讲知识点,适合当参考手册查。想看怎么把代码跑起来,读环境与 14 课实战;想先搞清楚”学它图啥”,读为什么学汇编;考前冲刺,读期末真题与课程项目。示例源码在 GitHub 的
汇编/目录。
目录
- 基础知识
- 寄存器
- 寄存器(内存访问)
- 第一个程序
- [BX]和loop指令
- 包含多个段的程序
- 更灵活的定位内存地址的方法
- 数据处理的两个基本问题
- 转移指令的原理
- CALL和RET指令
- 标志寄存器
- 内中断
- int指令
- 端口
- 外中断
- 直接定址表
- 使用BIOS进行键盘输入和磁盘读写
- 综合研究
- 附注
- 复习速查(本笔记补充)
1. 基础知识
1.1 机器语言
- 机器语言:二进制机器指令的集合,CPU 能直接执行,但人几乎无法直接读写。
- 汇编语言正是在机器语言基础上用符号辅助编写。
1.2 汇编语言的产生
- 机器语言难以记忆、难以查错,于是用助记符(如
mov、add)代表机器指令——这就是汇编语言的产生动机。
1.3 汇编语言的组成
汇编语言由三类指令组成:
- 汇编指令:机器码的助记符,是汇编语言的核心,有对应的机器码;
- 伪指令:没有对应的机器码,由编译器执行(如
segment、end); - 其他符号:如
+、-、*、/,由编译器识别,没有对应机器码。
1.4 存储器
- 存储器:CPU 运行时向其读取/写入数据的部件,由若干存储单元组成。
- 内存的作用:存放 CPU 执行需要的指令和数据。
1.5 指令和数据
- 指令和数据在内存/磁盘中没有区别,都是二进制信息;CPU 在工作时把有的信息当作指令、有的当作数据,区别只在于应用场合。
1.6 存储单元
- 存储器被划分为若干存储单元,每个单元从 0 开始编号,称为地址。
- 8086 一个存储单元 = 1 字节(Byte)= 8 bit;1 字(Word)= 2 字节。
1.7 CPU对存储器的读写
CPU 要读写数据,必须和外部芯片进行 3 类信息交互:
- 地址信息:存储单元的地址(通过地址总线发出);
- 控制信息:读还是写(通过控制总线发出);
- 数据信息:读/写的内容(通过数据总线传送)。
1.8 地址总线
- 地址总线宽度决定 CPU 的寻址能力:N 根地址线 → 可寻址 $2^N$ 个存储单元。
- 8086 有 20 根地址总线 → 寻址 $2^{20}$ = 1 MB 内存空间。
1.9 数据总线
- 数据总线宽度决定 CPU 一次传送多少数据(bit 数)。
- 8086 数据总线 16 根 → 一次传送 2 字节;8088 只有 8 根 → 一次 1 字节。
1.10 控制总线
- 控制总线是一些不同控制线的集合,每根线单独表达一种控制信息。
- 方向不能一概而论:有的由 CPU 发出(如”读”信号),有的由外部芯片发给 CPU(如中断请求线)。
计算例:地址总线宽度为 20,内存地址空间大小 = $2^{20}\ \text{B} = 1048576\ \text{B} = 1\ \text{MB}$。
1.11 内存地址空间(概述)
- 每台 PC 中,内存地址空间的大小受地址总线宽度限制;CPU 对它统一寻址。
1.12 主板
- 主板上有通过总线相连的核心器件:CPU、存储器(RAM)、外围芯片(接口卡)、扩展插槽。
1.13 接口卡
- CPU 不能直接控制外设(显示器、键盘、音箱),而是通过向接口卡上的**寄存器(端口)**发命令间接控制。
- CPU 通过总线向接口卡发送命令,接口卡根据命令控制外设工作。
1.14 各类存储器芯片
| 分类 | 特点 | 举例 |
|---|---|---|
| 随机存储器 RAM | 可读可写,断电丢失 | 主存、显存 |
| 只读存储器 ROM | 只能读,断电不丢 | BIOS ROM(其中存有开机程序) |
1.15 内存地址空间
- 内存地址空间:CPU 将各类存储器都看作一个由若干存储单元组成的逻辑存储器,每个物理存储器在地址空间中占有一段地址区间。
- 8086 PC 机地址空间分配(从低到高):主存储器地址空间 → 显存地址空间 → 各类 ROM 地址空间。对某地址读写,实际上就是对它所归属的物理存储器读写。
2. 寄存器
CPU 的主要部件:运算器进行信息处理、寄存器进行信息存储、控制器控制各器件工作、内部总线在器件之间传送数据。第 1 章的总线相对于 CPU 内部来说是外部总线(连接 CPU 与主板上其他器件)。对汇编程序员来说,CPU 中主要可操作的部件是寄存器——程序员通过改变寄存器中的内容来实现对 CPU 的控制。
2.1 通用寄存器
8086 CPU 有 14 个寄存器(16 位可访问):AX、BX、CX、DX、SI、DI、SP、BP、CS、DS、SS、ES、IP、FLAGS。其中通用寄存器 4 个,通常用于存放一般性数据:
| 16 位寄存器 | 高 8 位 | 低 8 位 |
|---|---|---|
| AX(累加) | AH | AL |
| BX(基址) | BH | BL |
| CX(计数) | CH | CL |
| DX(数据) | DH | DL |
- 为保证兼容,上一代 CPU 中的 8 位寄存器都可独立使用;高 8 位和低 8 位互不影响。
2.2 字在寄存器中的存储
- 一个字存在一个 16 位寄存器中:高字节放高位、低字节放低位。
- 例:AX = 01001110 00100000B = 4E20H,则 AH = 4EH,AL = 20H。
2.3 几条汇编指令
mov ax, 18 ; ax = 18mov ah, 78 ; ah = 78(不影响 AL)add ax, 8 ; ax = ax + 8mov ax, bx ; ax = bxadd ax, bx ; ax = ax + bx- 指令进行两个操作对象位数一致的运算。
- 结果超出寄存器宽度时进位(高位)直接丢弃:
mov al, 98H后add al, 98H,98H+98H = 130H,8 位放不下,AL 中只保留低 8 位 30H(进位在 CF 中,见第 11 章)。 - 汇编中十六进制以字母开头时前面补 0(如
0C0H),否则编译器会把它当符号。
2.4 物理地址
- 8086 有 20 位地址总线(寻址 1MB),但 CPU 内部是 16 位结构——一个 16 位地址无法直接给出 20 位地址,所以需要两个 16 位地址合成物理地址。
2.5 16位结构的CPU
- 16 位结构:运算器一次最多处理 16 位数据、寄存器最大宽度 16 位、寄存器与运算器之间的通路为 16 位。
2.6 8086CPU给出物理地址的方法
$$\textbf{物理地址} = \textbf{段地址} \times 16 + \textbf{偏移地址}$$
- ×16 在二进制中即左移 4 位(十六进制左移 1 位,后面补 0)。
- 例:段地址 123CH、偏移地址 0008H → 物理地址 = 123C0H + 0008H = 123C8H。
2.7 “段地址×16+偏移地址=物理地址”的本质含义
- CPU 用”基础地址(段地址×16)+ 相对于基础地址的偏移地址”给出内存单元的物理地址。
- 两个 16 位地址(段地址、偏移地址)合成了一个 20 位的物理地址。
2.8 段的概念
- “段”并非内存真的被划分,而是 CPU 分段寻址方式带来的逻辑概念;可以根据需要安排若干”段”。
- 一个段的起始地址(段地址×16)一定是 16 的倍数。
- 偏移地址 16 位 → 一个段最长 64 KB($2^{16}$)。
- 同一物理地址可由不同的”段地址:偏移地址 ”组合得到,如 21F60H = 2000H:1F60H = 21F6H:0000H 。
2.9 段寄存器
- 4 个段寄存器:CS、DS、SS、ES。
- 8086 任何时候都要用”段地址:偏移地址 ”定位指令或数据;段地址就存放在段寄存器中。
2.10 CS和IP
- CS(代码段寄存器)+ IP(指令指针寄存器):指示 CPU 当前要读取指令的地址。
- 任意时刻,CPU 将 CS:IP
指向的内容当作指令执行:
- 从 CS×16+IP 单元读取指令,送入指令缓冲器;
- IP = IP + 所读指令长度(指向下一条指令);
- 执行指令,转步骤 1。
- 8086 加电启动或复位后:CS = FFFFH、IP = 0000H,即从 FFFF0H 单元开始执行——那里是 BIOS ROM 中的开机第一条指令。
2.11 修改CS、IP的指令
- 8086 大部分寄存器可用 mov 修改,但 mov 不能用于设置 CS、IP。
- 能修改 CS、IP 的指令统称为转移指令,最简单的是
jmp:
jmp 2AE3:3 ; 同时修改:CS=2AE3H, IP=0003H(段间转移)jmp ax ; 仅修改 IP = ax(段内转移,功能上类比 mov ip, ax)2.12 代码段
- 将一段内存当作代码段,只需把它的段地址放在 CS、把第一条指令的偏移放在 IP,CPU 就会依次执行其中指令。
- 代码段是人为安排的;CPU 只认 CS:IP 。
实验1 查看CPU和内存,用机器指令和汇编指令编程
用 Debug 直接操作 CPU 和内存,Debug 常用命令:
| 命令 | 功能 |
|---|---|
R | 查看/修改寄存器内容(R ax 改 AX) |
D | 查看内存内容(D 段地址:偏移) |
E | 改写内存(E 1000:0 41 42 43) |
U | 将内存中的机器指令翻译成汇编指令(反汇编) |
A | 以汇编指令格式向内存写入机器指令 |
T | 执行一条指令(单步跟踪) |
G | 执行到指定位置(G 0016) |
P | 执行到循环结束/中断返回(遇到 int 指令时不停进中断例程内部) |
Q | 退出 Debug |
实验任务要点:①将给定程序段写入内存逐条执行,观察寄存器变化;②用 3 条指令计算 2⁸(mov ax,1 / add ax,ax / jmp 2000:0003 循环);③查看主板 ROM 中的生产日期(内存 FFF00H~FFFFFH 的某几个单元,ROM 只读、试图修改无效——正好验证 1.15 节”写 ROM 无效”);④向 B8100H 起的显存单元填写数据,屏幕上立即出现现象(写显存 = 显示)。
3. 寄存器(内存访问)
3.1 内存中字的存储
- 小端方式(低对低、高对高——教材称“高高低低”原则):字的低字节存放在低地址单元,高字节存放在高地址单元。
- 例:把 4E20H 存进 20000H 起的字单元,则 (20000H)=20H、(20001H)=4EH;20000H、20001H 合起来构成一个字单元,值为 4E20H。
- 字单元:由两个地址连续的内存单元组成,起始单元为低地址单元。
3.2 DS和[address]
- DS 通常存放要访问数据的段地址;
[address]表示一个内存单元,偏移地址为 address,段地址默认取 DS。
mov bx, 1000Hmov ds, bx ; 注意!8086 不支持 mov ds, 立即数,必须经通用寄存器中转mov al, [0] ; al = (DS×16+0) 中的字节mov [0], al ; 将 al 写入该内存单元3.3 字的传送
- 字的传送一次完成(16 根数据线同时工作):
mov ax, [0]一次读 0、1 两个单元,ax 中高 8 位来自高地址单元、低 8 位来自低地址单元。
3.4 mov、add、sub指令
| 指令形式 | 举例 |
|---|---|
| mov/add/sub 寄存器, 数据 | add ax, 8 |
| mov/add/sub 寄存器, 寄存器 | mov ax, bx |
| mov/add/sub 寄存器, 内存单元 | mov ax, [0] |
| mov/add/sub 内存单元, 寄存器 | add [0], ax |
| mov 段寄存器, 寄存器 | mov ds, bx |
| mov 寄存器, 段寄存器 | mov ax, cs |
⚠️ 不能:
mov ds, 1000H:段寄存器不能直接给立即数,须经通用寄存器中转;- add/sub 不能操作段寄存器;
- 两个内存单元之间不能直接 mov/add/sub;
- 立即数不能作为目的操作数(如
mov 8, ax)。
3.5 数据段
把一段内存当作数据段:将段地址放入 DS,用 [偏移] 访问其中单元即可。数据段也是人为安排。
3.6 栈
- 栈:一种后进先出(LIFO)的结构,8086 提供相关指令以内存的一段空间作为栈。
3.7 CPU提供的栈机制
- SS:SP 指示栈顶元素:SS 存放栈顶的段地址,SP 存放栈顶的偏移地址。
- 8086 的入栈/出栈以字(2 字节)为单位。
push ax 执行过程:
- SP = SP − 2(栈顶指针先向低地址移动);
- 将 ax 内容送入 SS:SP 指向的字单元。
pop ax 执行过程:
- 将 SS:SP 指向的字单元数据送入 ax;
- SP = SP + 2。
- 空栈时,SP 指向栈底(栈空间最高地址单元)的下一个单元。
- 栈增长方向:从高地址向低地址增长。
mov ax, 1000Hmov ss, axmov sp, 0010H ; 栈空间 1000:0000~000FH(16 字节),空栈时 SP=0010H 指向栈底下一单元push ax ; 入栈pop bx ; 出栈执行过程演练(栈空间 1000:0000 ~000FH,初始 SP=0010H):
mov ax, 0123Hmov bx, 2266Hmov cx, 1122Hpush ax ; SP=000EH,(1000:000EH)=0123Hpush bx ; SP=000CH,(1000:000CH)=2266Hpush cx ; SP=000AH,(1000:000AH)=1122Hpop ax ; ax=1122H,SP=000CHpop bx ; bx=2266H,SP=000EHpop cx ; cx=0123H,SP=0010H出栈顺序与入栈相反:ax、bx、cx 的值在入栈前后互换。
3.8 栈顶超界的问题
- push 前 SP 只减 2、pop 后 SP 只加 2,8086 不保证栈操作不会越界,不会因超界而报错。
- 编程者必须自己操心栈的范围,安排足够大的栈空间。
3.9 push、pop指令
可操作对象:寄存器、段寄存器、内存单元。
push ax ; 寄存器push ds ; 段寄存器push [0] ; 内存字单元pop [2] ; 出栈到内存3.10 栈段
- 栈段:把一段内存当作栈段,只需 SS:SP 指向它。
- 栈段最大 64KB:push/pop 只修改 SP,栈顶变化范围 0
FFFFH;从栈空(SP=0)一直压栈到栈满(SP 又回到 0),若再压栈,栈顶将环绕、覆盖原有内容。例:10000H1FFFFH 作栈段(64KB),栈空时 SS=1000H、SP=0。 - 段综述:一段内存可以既是代码段、又是数据段、还可以是栈段,也可以什么都不是——关键只在于 CPU 中寄存器的设置:CS:IP 指向 → 当指令执行;DS 指向 → 当数据访问;SS:SP 指向 → 当栈使用。
实验2 用机器指令和汇编指令编程
- 在 Debug 中用
A命令写汇编指令,体会栈机制与寄存器、内存的读写。 - 重要现象:用
T命令单步执行修改 SS 的指令(如mov ss,ax)时,下一条指令会紧接着被执行(如mov sp,10看似没执行,实际和前一条一起执行了)。原因:CPU 执行完设置 SS 的指令后不响应任何中断(含 Debug 依赖的单步中断),正好为连续设置 SS、SP 提供了时机(呼应 12.12 节)。因此设置栈顶的指令应当连续存放。
4. 第一个程序
4.1 一个源程序从写出到执行的过程
编写(.asm)→ 编译 masm(.obj)→ 连接 link(.exe)→ 加载运行
- 编译:把汇编指令翻译成机器码,生成目标文件 .obj;同时可生成列表文件 .lst。
- 连接:把目标文件组装成可执行文件 .exe;同时可生成映像文件 .map。
4.2 源程序
assume cs:codesg ; 将代码段 codesg 与段寄存器 CS 关联(假设性说明)
codesg segment ; 定义一个段的开始 mov ax, 0123H mov bx, 0456H add ax, bx add ax, ax
mov ax, 4c00H ; 程序返回:AH=4CH(功能号),AL=00H(返回码) int 21H ; 调用 DOS 中断完成返回codesg ends ; 段结束
end ; 程序结束标记(编译器遇 end 结束编译)| 伪指令 | 含义 |
|---|---|
xxx segment / xxx ends | 定义一个段(成对出现) |
end | 标记整个程序结束(编译器遇 end 结束编译) |
assume | ”假设”某段与某段寄存器相关联 |
⚠️ 两个”结束”不是一回事:
-
end是程序结束:汇编期间通知编译器”源程序到此为止”; -
mov ax,4c00H+int 21H是程序返回:运行期间把 CPU 控制权交还给系统。没有它,程序执行完会继续跑进后面的内存。 -
标号:codesg 作为段名,最终被编译处理为一个段地址。
-
语法错误 vs 逻辑错误:编译时被编译器发现的(伪指令拼写错误、段未闭合等)是语法错误;编译通过、运行时才暴露的(如程序没有返回)是逻辑错误。
4.3 编辑源程序
- 用文本编辑器编写 .asm 源文件(纯文本,不含格式)。
4.4 编译
- 用编译器(masm)对源文件编译:生成目标文件 .obj(以及可选列表文件 .lst)。
4.5 连接
- 用连接器(link)连接目标文件:生成可执行文件 .exe(以及可选映像文件 .map)。程序很大时可分为多个源文件分别编译再连接,也可调用库文件。
4.6 以简化的方式进行编译和连接
- 简化方式:在命令行结尾加分号——
masm c:\1;、link 1;,直接生成 .obj / .exe,并自动忽略中间文件(.lst / .crf / .map)的生成。
4.7 1.exe的执行
- 在 DOS 中直接输入可执行文件名,command.com 将其装入内存并执行。
4.8 谁将可执行文件中的程序装载进入内存并使它运行?
- command.com(DOS 的 shell 程序)将 .exe 装入内存:找到一段容量足够的空闲内存 → 建立 PSP(程序段前缀,256 字节) → 在 PSP 之后装入程序。
- 程序加载后:DS 中存放 PSP 的段地址(程序从 PSP 后 256 字节处开始,即程序段地址 = DS+10H),CS:IP 指向程序入口,SS:SP 指向栈顶。
4.9 程序执行过程的跟踪
- Debug 中把 .exe 作为参数载入后跟踪:用
T逐条执行,遇到int 21H用P正常结束返回。 - Debug 加载后 CX 存放程序的长度(机器码字节数,如 15 字节的程序 CX=000FH);PSP 的头两个字节是 CD 20(
int 20H的机器码),可用d ds:0查看。 - 装载与返回链:command 加载 Debug → Debug 加载程序;程序运行结束返回 Debug,
Q退出 Debug 返回 command。
实验3 编程、编译、连接、跟踪
- 完整走一遍”编写源程序 → masm 编译 → link 连接 → Debug 跟踪”流程。
5. [BX]和loop指令
约定符号:
( )表示寄存器或内存单元中的内容,如(ax)、(20000H)、((ds)*16+(bx));idata表示常量。例如mov ax,[bx]描述为(ax)=((ds)*16+(bx));push ax描述为(sp)=(sp)-2、((ss)*16+(sp))=(ax)。完整描述一个内存单元需要两种信息:地址 + 长度(类型)。
5.1 [BX]
[bx]表示内存单元,其偏移地址在 bx 中,段地址默认 DS。
mov bx, 0mov ax, [bx] ; 等价于 mov ax, (DS:0)inc bx ; bx = bx + 1inc 寄存器:加 1;dec 寄存器:减 1(两条指令后面会常用来移动指针)。
5.2 Loop指令
- CPU 执行
loop 标号:- (cx) = (cx) − 1;
- 判断 cx:不为 0 则转至标号处执行;为 0 则向下执行。
- cx 中存放循环次数。
经典例子:计算 $2^{12}$(不用乘法):
mov ax, 2 mov cx, 11 ; 循环 11 次,每次翻倍s: add ax, ax ; ax = ax × 2 loop s ; cx-- ≠0 就跳到 s执行结果:AX = $2^{12}$ = 4096 = 1000H。
5.3 在Debug中跟踪用loop指令实现的循环程序
- 用
G 偏移直接执行到循环外(跳过循环体),避免T逐条执行 12 次;或用P一次执行完 loop。
5.4 Debug和汇编编译器masm对指令的不同处理
| 写法 | Debug(A 命令) | masm 源程序 |
|---|---|---|
mov ax, [0] | 访问内存单元 DS:0 | 被当作 mov ax, 0(立即数)! |
| 访问偏移为 idata 的内存 | [0] | 必须写成 ds:[0],或先把偏移送入 bx 用 [bx] |
在源程序中用 idata 表示偏移地址,要显式写段前缀
ds:[0]。
5.5 loop和[bx]的联合应用
典型套路:累加一段连续内存(ffff:0 ~ffff:b 共 12 个字节单元求和到 dx):
mov ax, 0ffffH mov ds, ax mov bx, 0 ; 偏移指针,从 0 单元开始 mov dx, 0 ; 累加结果 mov cx, 12 ; 12 个单元s: mov al, [bx] mov ah, 0 ; 字节扩展为字,避免 add dx, ax 时误带高位 add dx, ax inc bx loop s5.6 段前缀
- 段前缀:显式给出段地址,如
ds:[bx]、cs:[bx]、ss:[bx]、es:[bx]。 - 出现在访问内存单元的指令中,用于显式指定内存单元的段地址。
5.7 一段安全的空间
- 在一般 PC 机中,0:200H ~0:2FFH 的 256 字节空闲安全,Debug 实验可直接使用;不要随意向未知内存写数据。
5.8 段前缀的使用
- 把数据放到其他段(如代码段、栈段)时,用段前缀访问;没有前缀时默认 DS。
- 经典例:把 ffff:0
ffff:b 的 12 个字节复制到 0:2000:20b (等价于 0020:0 ~0020:b ,选用后者是为了让源和目标的偏移都从 0 开始)。改进:用 ES 存放目标段地址(mov ax,0020H/mov es,ax),循环内mov dl,[bx]+mov es:[bx],dl,避免每次循环重复设置 DS。
实验4 [bx]和loop的使用
- 用 loop 和 [bx] 完成对连续内存单元的访问(如累加、复制)。
6. 包含多个段的程序
6.1 在代码段中使用数据
- 程序取得内存空间的两种方式:加载时由系统分配(在源程序中定义段来说明所需空间,编译连接后随程序一起装入)与执行中向系统申请(本课程不讨论)。
- 程序中定义的数据放在代码段最前面时,要让 CS:IP
跳过数据,用
end 标号指定程序入口:
assume cs:codecode segment dw 0123H,0456H,0789H,0abcH,0defH,0fedH,0cbaH,0987H ; define word,8 个字数据start: ; 程序入口标号 mov bx, 0 mov ax, 0 mov cx, 8s: add ax, cs:[bx] ; 数据在代码段,用 cs 段前缀访问 add bx, 2 loop s
mov ax, 4c00H int 21Hcode endsend start ; end 后标号指示程序从 start 开始执行执行结果:AX = 8 个字之和 = 4BDBH。
dw(define word)定义字型数据;db(define byte)定义字节型数据。
6.2 在代码段中使用栈
用 dw 在段中开辟栈空间,再把 SS:SP
指向它:
dw 0,0,0,0,0,0,0,0 ; 8 个字作为栈空间,占 16 字节start: mov ax, cs mov ss, ax mov sp, 10H ; 空栈 SP 指向栈底(最高单元)下一单元(16)6.3 将数据、代码、栈放入不同的段
- 一个段容量 ≤ 64KB,且数据/代码/栈混放显得混乱;可以定义多个段:
assume cs:code, ds:data, ss:stackdata segment dw 0123H, 0456Hdata endsstack segment dw 0,0,0,0,0,0,0,0stack endscode segmentstart: mov ax, stack mov ss, ax mov sp, 16 mov ax, data mov ds, ax ; 访问 data 段前,先把段地址送 DS ; ...业务指令... mov ax, 4c00H int 21Hcode endsend start关键:
assume只是给编译器的”假设”,CPU 并不会因此自动把段地址送入 DS/SS,段地址必须由指令自己设置;只有 CS:IP 由加载程序根据end 入口标号自动设置。
- 段名 = 标号:指令中的段名(如
mov ax,data)被编译器处理为一个表示段地址的数值,所以同样不能直接写mov ds,data(不能给段寄存器立即数)。段名只存在于源程序中,CPU 并不知道;把哪个段当代码/数据/栈,完全由程序中的具体指令和对 CS:IP 、DS、SS:SP 的设置决定。
实验5 编写、调试具有多个段的程序
- 编写包含数据段、栈段、代码段的程序并调试。
7. 更灵活的定位内存地址的方法
7.1 and和or指令
and:按位与(两 1 才 1),常用于把指定位清零;or:按位或(有 1 就 1),常用于把指定位置 1。
mov al, 01100011Band al, 11011111B ; 第 5 位清零,其余不变 → 01000011Bor al, 00100000B ; 第 5 位置 1,其余不变7.2 关于ASCII码
| 字符 | ASCII(十六进制) |
|---|---|
'0'~'9' | 30H~39H |
'A'~'Z' | 41H~5AH |
'a'~'z' | 61H~7AH |
- 同一字母大小写 ASCII 码相差 20H,区别只在第 5 位(从 0 数)。
7.3 以字符形式给出的数据
- 源程序中字符常量用引号:
db 'unIX'等价于db 75H,6EH,49H,58H。
7.4 大小写转换的问题
- 大写 → 小写:第 5 位置 1(
or xx, 00100000B); - 小写 → 大写:第 5 位清零(
and xx, 11011111B)。
应用:将 data 段中字符串 ‘BaSiC’ 转为大写、‘MinIX’ 转为小写:
assume cs:code, ds:datadata segment db 'BaSiC' db 'MinIX'data endscode segmentstart: mov ax, data mov ds, ax mov bx, 0 mov cx, 5s: mov al, [bx] ; 第一个字符串 (偏移 0~4) and al, 11011111B ; 转大写 mov [bx], al mov al, [bx+5] ; 第二个字符串 (偏移 5~9) or al, 00100000B ; 转小写 mov [bx+5], al inc bx loop s mov ax, 4c00H int 21Hcode endsend start7.5 [bx+idata]
mov ax, [bx+200] ; 偏移地址 = (bx)+200,等价写法:mov ax, 200[bx]mov ax, [bx].2007.6 用[bx+idata]的方式进行数组的处理
- 两个数组分别从
[bx]和[bx+idata]开始,同一 bx 同步定位(如上面 7.4 的例子)。
7.7 SI和DI
- SI(源变址)、DI(目的变址):功能与 bx 相近,但不能拆成两个 8 位寄存器。
mov si, 0mov ax, [si]mov di, 0mov ax, [di+10]- 经典例:把字符串 ‘welcome to masm!’(16 字节)复制到它后面的数据区——
si=0、di=16,字传送 8 次(mov ax,[si]/mov [di],ax/add si,2/add di,2);用[bx+idata]思想可再简化为mov ax,0[si]/mov 16[si],ax,只用一个变址寄存器。
7.8 [bx+si]和[bx+di]
- 用两个变址/基址寄存器表示偏移:
mov ax, [bx+si]=mov ax, [bx][si]。
7.9 [bx+si+idata]和[bx+di+idata]
- 偏移 = 基址 + 变址 + 常数:
mov ax, [bx+si+idata]。
7.10 不同的寻址方式的灵活应用
- 8086 中能用在
[...]里的寄存器只有 bx、bp、si、di(且 bx/bp 不同时出现,si/di 不同时出现):
| 形式 | 名称 | 例子 |
|---|---|---|
[idata] | 直接寻址 | [0] |
[bx] | 寄存器间接寻址 | [bx] |
[bx+idata] | 寄存器相对寻址 | [bx+8] |
[bx+si] | 基址变址寻址 | [bx+si] |
[bx+si+idata] | 相对基址变址寻址 | [bx+si+8] |
- 正确组合:bx/si、bx/di、bp/si、bp/di;错误组合:
[bx+bp]、[si+di]。 - 默认段规则:寻址中出现 bp 时默认段寄存器为 SS;其余默认 DS。可用段前缀改变。
- 二重循环与 cx 冲突:loop 只认 cx,内外两层循环共用 cx 会互相覆盖。演进方案:①用另一个寄存器(如 dx)暂存外层 cx;②寄存器不够用时暂存到内存单元;③通用方案——用栈:内层循环前
push cx,外层loop s0前pop cx。“需要暂存数据时用栈”由此而来。 - 结构化数据定位:把 4 行 × 16 列的数据看做二维数组,用
[bx+si]定位(bx 定位行、si 定位列),用[bx+3+si]定位行内偏移 3 起的字段——CPU 提供多种寻址方式正是为了从结构化的角度看待数据。
实验6 实践课程中的程序
- 综合练习本章寻址方式:完成问题 7.9(将每个单词的前 4 个字母改为大写,
[bx+3+si]寻址)等课程程序。
8. 数据处理的两个基本问题
约定符号:
reg表示寄存器(ax、bx、cx、dx、ah/al/bh/bl/ch/cl/dh/dl、sp、bp、si、di);sreg表示段寄存器(ds、ss、cs、es)。
8.1 bx、si、di和bp
- 8086 中可用于
[...]的寄存器:bx、bp、si、di;bx/bp 是”基址”,si/di 是”变址”。 - bp 默认与 SS 搭配,其余默认与 DS 搭配。
8.2 机器指令处理的数据在什么地方
机器指令处理的数据可在三个地方:①指令内部(立即数)②寄存器 ③内存(段地址 SA:偏移地址 EA)。
8.3 汇编语言中数据位置的表达
| 位置 | 表达 | 例子 |
|---|---|---|
| 立即数 idata | 直接写在指令中 | mov ax, 1 |
| 寄存器 | 寄存器名 | mov ax, bx |
| 段地址+偏移 | SA:EA | mov ax, [0] |
- 立即数只能作为源操作数,不能作为目的。
8.4 寻址方式
| 寻址方式 | 偏移地址 EA | 常用例子 | 典型用途 |
|---|---|---|---|
| 直接寻址 | idata | [idata] | 直接定位一个单元 |
| 间接寻址 | BX/BP/SI/DI | [bx] | 用变量间接定位 |
| 相对寻址 | BX/BP/SI/DI + idata | [bx+idata] | 结构体 [bx].idata;数组 idata[si] |
| 基址变址 | BX/BP + SI/DI | [bx+si] | 二维数组 [bx][si] |
| 相对基址变址 | BX/BP + SI/DI + idata | [bx+si+idata] | 结构体中的数组项 [bx].idata[si] |
8.5 指令要处理的数据有多长
8086 可以处理 byte(8 位)和 word(16 位)两种尺寸,由以下方式指明:
- 寄存器名指明:
mov ax, [0]是字操作,mov al, [0]是字节操作。 - 操作符 word ptr / byte ptr 指明:
mov word ptr [bx], 0 ; 向 [bx]、[bx+1] 组成的字单元送 0mov byte ptr [bx], 0 ; 只向 [bx] 字节单元送 0- 其他方式:如 push/pop 默认只进行字操作。
8.6 寻址方式的综合应用
- 经典例(DEC 公司记录,1982→1988):记录存于 seg:60
处——公司名 +0(3 字节)、总裁名 +3(9 字节)、排名 +0C(字)、收入 +0E(字)、产品 +10(3 字节)。修改排名/收入/产品:
bx定位整条记录(mov bx,60h),idata定位字段(mov word ptr [bx+0ch],38、add word ptr [bx+0eh],70),si定位数组项中的字符(mov byte ptr [bx+10h+si],'V')——与 C 语言dec.pm=38; dec.sr+=70; dec.cp[i]='V'一一对应。 - 总结:bx 定位结构体,idata 定位结构体中的数据项,si 定位数组项中的元素。
8.7 div指令
- 除数:8 位或 16 位,在寄存器或内存中(不能是立即数)。
- 被除数默认放在 AX(8 位除法)或 DX 和 AX(16 位除法,DX 存高 16 位)。
| 除数宽度 | 被除数 | 商 | 余数 |
|---|---|---|---|
| 8 位 | AX | AL | AH |
| 16 位 | DX(高):AX (低) | AX | DX |
; 8 位除法:1001 / 100mov ax, 1001mov bl, 100div bl ; AL = 商(10 = 0AH), AH = 余(1)
; 16 位除法:100001 / 100mov dx, 1mov ax, 86A1H ; DX:AX = 186A1H = 100001mov bx, 100div bx ; AX = 商(03E8H = 1000), DX = 余(1)⚠️ 除法溢出:若商超出寄存器容量(8 位除法商 > 255,16 位除法商 > 65535),CPU 产生除法溢出,触发 0 号中断(详见第 12 章)。编程时要保证商放得下。
8.8 伪指令dd
db定义字节、dw定义字、dd定义双字(dword,4 字节):
dd 12345678H ; 占 4 字节8.9 dup
- dup 用来重复定义数据:
db 3 dup (0) ; 等价 db 0,0,0db 3 dup (0,1,2) ; 等价 db 0,1,2,0,1,2,0,1,2db 200 dup (?) ; 预留 200 个字节的空间(不确定值)实验7 寻址方式在结构化数据访问中的应用
- 任务:把 Power idea 公司 1975~1995 共 21 年的数据(data 段:21 个年份字符串
db、21 个总收入dd、21 个雇员数dw)写入 table 段(db 21 dup ('year summ ne ?? '),每行 16 字节:年份 4 + 空格 + 收入 4 + 空格 + 雇员 2 + 空格 + 人均收入 2 + 空格),并计算人均收入(取整)。 - 思路:data 段看作 3 个数组,table 段看作结构体数组——
bx定位每条记录(每行 +10H)、si定位数组元素,用[bx].idata、[bx].idata[si]访问;人均收入 = 收入(dd) ÷ 雇员(dw),收入最大 5937000 > 65535,要用 dx:ax 联合存放被除数再div。
9. 转移指令的原理
9.1 操作符offset
offset 标号取得标号的偏移地址(由编译器处理):
start: mov ax, offset start ; ax = start 处的偏移地址9.2 jmp指令
- jmp 是无条件转移指令:可以只修改 IP,也可以同时修改 CS 和 IP。
9.3 依据位移进行转移的jmp指令
jmp short 标号 ; 段内短转移:IP = IP + 8 位位移;范围 −128 ~ 127jmp near ptr 标号 ; 段内近转移:IP = IP + 16 位位移;范围 −32768 ~ 32767- 机器码中不含转移目的地址,而含相对当前 IP 的位移:位移 = 标号处偏移 − jmp 指令后第一条指令的偏移,便于程序在内存中浮动装配。
9.4 转移的目的地址在指令中的jmp指令
- 段间转移(远转移,far):机器码中直接含目的段地址和偏移地址,格式为 EA + 偏移地址(低字)+ 段地址(高字),共 5 字节。
jmp far ptr 标号 ; CS = 标号所在段地址,IP = 标号偏移地址9.5 转移地址在寄存器中的jmp指令
jmp ax ; IP = ax(不改变 CS)9.6 转移地址在内存中的jmp指令
jmp word ptr [bx] ; 段内:从内存字单元取偏移送 IPjmp dword ptr [bx] ; 段间:低字 → IP,高字 → CS9.7 jcxz指令
- jcxz = jump if cx is zero:cx 为 0 时转移,否则向下执行(段内短转移,位移 −128~127)。
jcxz 标号 ; 等价于:if ((cx)==0) jmp short 标号9.8 loop指令
loop 标号:cx 减 1,cx≠0 则转移;也是段内短转移,机器码中存 8 位位移。
9.9 根据位移进行转移的意义
- 转移指令中记录的是”相对位移”而非绝对地址,程序被装载到内存任意位置都能正确执行。
9.10 编译器对转移位移超界的检测
- 编译器会对转移位移超界进行检测,超界直接编译错误(如 jmp short 位移超出 −128~127)。
- ⚠️ 形如
jmp 2000:0100的”段地址:偏移地址 ”写法只能在 Debug 中使用,masm 编译器不认识,源程序中使用会报错。
实验8 分析一个奇怪的程序
- 跟踪一个”奇怪”程序,深入理解位移转移的机器码含义。
实验9 根据材料编程
- 任务:在屏幕中间分别显示绿色、绿底红色、白底蓝色的字符串 ‘welcome to masm!’。
- 80×25 彩色字符模式显示缓冲区:内存 B8000H
BFFFFH 共 32KB;分 8 页、每页 4KB,通常显示第 0 页(B8000HB8F9FH,4000 字节 = 25 行 × 160 字节)。每行 80 字符、每字符占 2 字节:偶地址存 ASCII 码、奇地址存属性;第 n 行第 m 列的字符偏移 = n×160 + m×2。 - 属性字节格式:
7 6 5 4 3 2 1 0= 闪烁 BL、背景 RGB、高亮、前景 RGB。例:黑底绿字 02H、红底绿字 42H、白底蓝字 71H、红底高亮闪烁绿字 CAH。闪烁效果必须在全屏 DOS 方式下才能看到。
10. CALL和RET指令
10.1 ret和retf
| 指令 | 等价操作 | 用途 |
|---|---|---|
ret | pop IP | 段内近返回 |
retf | pop IP;pop CS | 段间远返回 |
10.2 call指令
- CPU 执行 call 时先把返回地址(当前 IP 或 CS:IP )压栈,再转移。
- call 不能实现短转移(没有 8 位位移的格式);除此之外,实现转移的方法和 jmp 相同。
10.3 依据位移进行转移的call指令
call 标号(近):push IP;jmp near ptr 标号(16 位位移)。
10.4 转移的目的地址在指令中的call指令
call far ptr 标号(远):push CS;push IP;jmp far ptr 标号。- 段间 call 先压 CS 再压 IP,正好对应 retf 先弹 IP 再弹 CS。
10.5 转移地址在寄存器中的call指令
call reg:push IP;jmp reg(IP = 16 位寄存器)。
10.6 转移地址在内存中的call指令
call word ptr 内存:push IP;jmp word ptr 内存单元。call dword ptr 内存:push CS;push IP;jmp dword ptr 内存单元。
10.7 call和ret的配合使用
; 主程序 mov ax, 0 call s ; 调用子程序 s:push 返回地址后跳转 mov bx, ax ; 返回后继续 mov ax, 4c00H int 21H
s: add ax, 1 ; 子程序 ret ; 返回:pop IP 回到 call 的下一条指令10.8 mul指令
- mul 的操作数只能是寄存器或内存单元(不能是立即数)。
| 乘数位数 | 被乘数 | 乘数 | 结果 |
|---|---|---|---|
| 8 位 | AL | r8/m8 | AX |
| 16 位 | AX | r16/m16 | DX(高 16 位):AX (低 16 位) |
mov al, 64mov bl, 64mul bl ; AX = 64×64 = 4096 = 1000H
mov ax, 100mov bx, 100mul bx ; DX:AX = 100×100 = 10000 = 2710H(DX=0, AX=2710H)10.9 模块化程序设计
- 子程序(过程)= 具有一定功能的程序段,用 call 调用、ret 返回,避免重复代码。
10.10 参数和结果传递的问题
- 调用子程序前需要把参数传给子程序,子程序返回后需要把结果交给主程序——这就是参数和结果传递问题。
10.11 批量数据的传递
- 参数多(批量数据)时用**内存单元(数据表)**传递:把参数放到内存区,用 ds:si / ds:bx 传地址。
- 三种传递方式汇总:
| 方式 | 适用 |
|---|---|
| 寄存器传递 | 参数少 |
| 内存单元(数据表)传递 | 参数多(批量数据) |
| 栈传递 | 通用、规范(见附注4) |
寄存器传参示例:子程序 cube 计算 ax 的三次方,结果 dx:ax :
cube: mov bx, ax mul bx ; dx:ax = N × N mul bx ; dx:ax = N² × N = N³ ret10.12 寄存器冲突的问题
- 子程序中使用的寄存器可能与主程序正在使用的冲突(如主程序用 bx 做指针,cube 却改了 bx)。
- 标准解决框架:子程序开头把用到的寄存器全部压栈,返回前出栈恢复(注意出栈顺序相反):
sub: push ax ; 保存现场 push bx push cx ; ... 子程序功能,随意使用 ax/bx/cx ... pop cx ; 恢复现场(逆序) pop bx pop ax ret- 经典例 capital:将以 0 结尾的字符串转大写——
mov cl,[si]/mov ch,0/jcxz ok(检测到 0 结束)/and byte ptr [si],11011111B/inc si/jmp short change。主程序用 cx 控制”处理 4 个字符串”的循环,capital 内部也用 cx,冲突由此产生;改写为开头push cx、push si,结尾pop si、pop cx即可。
实验10 编写子程序
编写 3 个后面课程要反复用到的经典子程序:
| 子程序 | 功能 | 参数 | 要点 |
|---|---|---|---|
show_str | 在指定位置、以指定颜色显示 0 结尾的字符串 | dh=行(0 | 行列 → 显存偏移 dh×160+dl×2;字符写偶地址、颜色写奇地址;子程序内部屏蔽显存结构,向外提供通用接口 |
divdw | 不会溢出的除法(dword ÷ word) | ax=低 16 位、dx=高 16 位、cx=除数 | 公式 $X/N = \text{int}(H/N)×65536 + [\text{rem}(H/N)×65536+L]/N$(证明见附注5);返回 dx:ax =商、cx=余数 |
dtoc | word 型数据转十进制字符串(0 结尾) | ax=数据、ds:si →缓冲区 | 反复除以 10 记下余数,余数 +30H 转为字符;用 jcxz 检测商为 0 结束(程序员无法预知循环次数) |
课程设计1
- 任务:将实验 7 中 Power idea 公司的数据按”年份 收入 雇员数 人均收入”四列的表格显示在屏幕上。
- 综合运用:实验 7 的寻址方式整理数据 +
show_str显示 + dword 版dtoc(收入最大 5937000 > 65535,除 10 时须用divdw防止除法溢出)。
11. 标志寄存器
标志寄存器(FLAGS)按位起作用,每一位有专门含义。8086 的 16 位中使用了 9 位(另:AF 辅助进位标志在第 4 位,用于 BCD 运算,教材不展开;TF/IF 在第 8/9 位,见第 12、15 章)。
11.1 ZF标志
- ZF(第 6 位)零标志:运算结果为 0 时 ZF=1,否则 ZF=0。
mov ax, 1sub ax, 1 ; 结果 0 → ZF=111.2 PF标志
- PF(第 2 位)奇偶标志:结果二进制中 1 的个数为偶数时 PF=1。
mov ax, 1sub ax, 1 ; 结果 0,0 个 1(偶数)→ PF=111.3 SF标志
- SF(第 7 位)符号标志:结果为负(有符号运算)时 SF=1,即 SF = 结果的最高位。
- SF 只对有符号数运算有意义:把结果当有符号数时,SF 指示正负。
11.4 CF标志
- CF(第 0 位)进位标志:无符号运算时最高位向外有进位(加)/向最高位有借位(减)→ CF=1。
mov al, 98Hadd al, 98H ; 98H+98H = 130H,8 位放不下 → CF=1,AL=30H11.5 OF标志
- OF(第 11 位)溢出标志:有符号运算结果超出机器能表示的范围 → OF=1。
- 8 位有符号范围 −128
127;16 位 −3276832767。
CF 与 OF 的区别:
| CF 进位标志 | OF 溢出标志 | |
|---|---|---|
| 视角 | 无符号数运算 | 有符号数运算 |
| 为 1 | 最高位向外有进位(加)/向最高位有借位(减) | 结果超出机器能表示的范围 |
| 典型 | FFH+01H=100H → CF=1,OF=0(有符号看是 −1+1=0,没溢出) | 97H+88H=11FH → CF=1 且 OF=1(有符号看是 −105−120=−225,超出 −128~127) |
- CF 和 OF 互不相关,各自记录不同视角下的结果。
11.6 adc指令
adc ax, bx:ax = ax + bx + CF(加上进位位)。- 用途:对超出寄存器宽度的大数分高低位依次运算,低位产生的进位由 CF 带入高位。
; 16 位寄存器实现 32 位加法:1EF000H + 201000H = 3F0000Hmov ax, 001EHmov bx, 0F000Hadd bx, 1000H ; 低 16 位相加:F000H+1000H = 0000H,CF=1adc ax, 0020H ; 高 16 位相加再加 CF:1EH+20H+1 = 3FH ; 结果 AX(高)=003FH, BX(低)=0000H- 任意大数相加:最低位用 add,其后每个 16 位都用 adc 链接进位。经典例 add128(两个 128 位整数相加,ds:si
/ ds:di
指向两数的 8 个字单元,结果存回第一个数的空间):循环
mov ax,[si]/adc ax,[di]/mov [si],ax;循环开始前先sub ax,ax清 CF;循环内用inc递增指针(inc 不影响 CF,若换成add si,2会破坏进位链)。
11.7 sbb指令
sbb ax, bx:ax = ax − bx − CF(减去借位位),用于大数减法的高位借位传播。
11.8 cmp指令
cmp ax, bx做 ax − bx 运算但不保存结果,只影响标志寄存器,供后续条件转移指令判断。
无符号数比较结果判断:
| cmp a,b 后 | 关系 | 标志 |
|---|---|---|
| ZF=1 | a = b | — |
| CF=1 | a < b | 产生借位 |
| CF=0 且 ZF=0 | a > b | — |
有符号数比较:看 SF 与 OF 是否相同(a>b ⇔ SF=OF 且 ZF=0;a<b ⇔ SF≠OF)。
11.9 检测比较结果的条件转移指令
条件转移指令都是段内短转移(位移 −128~127),通常紧跟 cmp 使用。
| 指令 | 含义 | 判断条件 | 等价指令 |
|---|---|---|---|
je | 相等则转移 | ZF=1 | jz |
jne | 不等则转移 | ZF=0 | jnz |
jb | 低于则转移(无符号) | CF=1 | jnae/jc |
jnb | 不低于则转移 | CF=0 | jae/jnc |
ja | 高于则转移(无符号) | CF=0 且 ZF=0 | jnbe |
jna | 不高于则转移 | CF=1 或 ZF=1 | jbe |
jl | 小于则转移(有符号) | SF≠OF | jnge |
jge | 大于等于则转移 | SF=OF | jnl |
jg | 大于则转移(有符号) | SF=OF 且 ZF=0 | jnle |
jle | 小于等于则转移 | SF≠OF 或 ZF=1 | jng |
记忆:equal 相等;below/above 无符号高低;less/greater 有符号大小;n = not。另有
js/jns(按 SF)、jp/jnp(按 PF)。
cmp ax, bxje equal ; ax==bx 才跳; ...equal:11.10 DF标志和串传送指令
- DF 决定串操作后变址寄存器的增减方向:
cld:DF=0,递增;std:DF=1,递减。
| 指令 | 操作 |
|---|---|
movsb | 以字节传送:es:[di] = ds:[si];si、di 各 ±1 |
movsw | 以字传送:es:[di] = ds:[si];si、di 各 ±2 |
rep movsb | 根据 cx 重复执行 movsb(cx 次) |
mov ax, data mov ds, ax mov ax, dest mov es, ax ; 目的段用 ES mov si, 0 mov di, 0 mov cx, 8 cld ; 递增方向 rep movsb ; 连续传送 8 个字节- 逆向传送:如把 F000H 段最后 16 个字符复制到 data 段——
si=0FFFFH、di=15,std置 DF=1 后rep movsb,si/di 每次递减。
11.11 pushf和popf
pushf:将标志寄存器压栈;popf:从栈中弹出送入标志寄存器。- 配合实现标志寄存器的暂存与设置。
11.12 标志寄存器在Debug中的表示
| 标志 | =1 | =0 |
|---|---|---|
| OF | OV | NV |
| DF | DN | UP |
| IF | EI | DI |
| SF | NG | PL |
| ZF | ZR | NZ |
| PF | PE | PO |
| CF | CY | NC |
实验11 编写子程序
- 综合练习子程序与标志位判断。
12. 内中断
12.1 内中断的产生
CPU 内部发生以下情况时产生内中断:
| 类型码 | 来源 |
|---|---|
| 0 | 除法错误(div 溢出、除以 0) |
| 1 | 单步执行(TF=1) |
| 4 | 溢出(into 指令检测 OF=1) |
| n | 执行 int n 指令 |
12.2 中断处理程序
- 中断处理程序(中断例程):CPU 收到中断信息后转去执行的处理程序。
12.3 中断向量表
- 中断向量表:建立”中断类型码 → 中断处理程序入口地址”的映射。
- 8086 的中断向量表放在内存 0000:0000 ~ 0000:03FF ,共 1024 字节;
- 每个表项 4 字节:低字存偏移 IP,高字存段地址 CS;
- 类型码为 N 的中断,其入口地址存放在 0:N ×4(IP)和 0:N ×4+2(CS)。
例:类型码 9 的中断(键盘),向量位于 0:9 ×4 = 0:24H 起的 4 个字节。
12.4 中断过程
CPU 硬件自动完成(中断过程):
- 取得中断类型码 N;
- pushf(标志寄存器压栈);
- TF = 0、IF = 0(中断处理过程中不响应单步中断、可屏蔽外中断);
- push CS、push IP;
(IP) = (N×4),(CS) = (N×4+2)—— 转去执行中断例程。
12.5 中断处理程序和iret指令
中断例程末尾用 iret 返回,等价于:pop IP → pop CS → popf,与中断过程的压栈正好对应。
中断例程标准结构:
doit: push ax ; 保护现场 ; ... 处理 ... pop ax ; 恢复现场 iret ; 中断返回12.6 除法错误中断的处理
- 除法溢出时 CPU 产生 0 号中断;默认的 0 号中断处理程序显示 “Divide overflow” 后返回 DOS。
12.7 编程处理0号中断
- 完整套路:①编写中断例程
do0→ ②安装:把 do0 复制到不会被覆盖的内存(0:200H 起)→ ③设置中断向量表:把 0 号表项改为指向 0:200H 。
12.8 安装
- 用
rep movsb把例程机器码从代码段复制到安全内存(0:200H )。 - 传送长度用编译器算:在例程末尾放一个标号
do0end: nop,写mov cx, offset do0end-offset do0(编译器可以处理常量表达式)——这样例程内容变化也不必重新数字节数。
12.9 do0
; do0 例程:显示 "overflow!" 后结束程序do0: jmp short do0start db "overflow!" ; 字符串放在 0:202H 处(jmp short 占 2 字节)do0start: mov ax, cs mov ds, ax mov si, 202H ; 指向字符串(除法溢出发生时 CS 必为 0,安装后偏移不变) mov ax, 0b800H ; 显存段 mov es, ax mov di, 12*160+36*2 ; 屏幕中间 mov cx, 9 ; 9 个字符s: mov al, [si] mov es:[di], al ; 偶地址写字符(不写属性即默认黑底白字) inc si add di, 2 loop s mov ax, 4c00H ; 例程功能是"报错后结束程序",故用 4c00H 返回 int 21Hdo0end: nop注意:①do0 结尾用
mov ax,4c00H; int 21H结束程序而不是 iret——因为除法溢出说明程序已无法继续,处理完就应退出。若例程处理完后还要返回原程序继续运行,才用 iret。②字符串必须定义在 do0 内部(用jmp short do0start跳过它),随例程一起安装到 0:200 ——若放在安装程序的 data 段,安装程序返回后内存被系统释放,例程执行时字符串很可能已被覆盖。
12.10 设置中断向量
; 设置 0 号中断向量:0:0 = 200H,0:2 = 0mov ax, 0mov es, axmov word ptr es:[0], 200Hmov word ptr es:[2], 012.11 单步中断
- TF=1 时,CPU 每执行一条指令就触发类型 1 的单步中断,Debug 正是利用它实现单步跟踪。
- 中断过程中自动把 TF 置 0,避免中断例程自身被单步;进入例程前 pushf 保存了原 TF。
12.12 响应中断的特殊情况
- 有些情况下(如设置 SS 的指令之后紧接着设置 SP),CPU 不会在两条指令之间响应中断,以保证 SS:SP 成对正确设置。
实验12 编写0号中断的处理程序
- 完整实现:编写 do0 → 安装 → 设置向量 → 用除法溢出触发验证。
13. int指令
13.1 int指令
int n触发类型码为 n 的中断,执行过程等价于:取 n → pushf → TF=0、IF=0 → push CS、push IP → 读 n×4 表项跳转。- int 指令使程序员可以主动调用系统提供的中断例程;例程末尾用 iret 返回。
13.2 编写供应用程序调用的中断例程
编写、安装中断例程的过程与 0 号中断相同:编写例程 → 安装到安全内存(0:200H )→ 改写对应中断向量表项。
示例:编写并安装 7ch 中断例程,功能是计算 word 数据的平方(参数 ax,结果 dx:ax ):
; 例程本体lp: mul ax ; dx:ax = ax × ax iret
; 安装:复制到 0:200H(略),设置 7ch 向量(7CH×4 = 1F0H) mov ax, 0 mov es, ax mov word ptr es:[1F0H], 200H mov word ptr es:[1F2H], 0
; 调用 mov ax, 3456 int 7ch ; 返回后 dx:ax = 3456 的平方13.3 对int、iret和栈的深入理解
int n与call的异同:都要压返回地址后跳转,但 int 压的是标志寄存器 + CS + IP 共 3 个字(call 压 1~2 个字),且 int 会先清 TF、IF。iret与retf的区别:iret 多弹一次标志寄存器(pop IP → pop CS → popf)。- 栈平衡:中断例程入口/出口的压栈出栈必须一一对应,才能保证 iret 弹出正确的内容。
- 经典问题:用 7ch 中断例程实现 loop 指令的功能。参数:cx=循环次数、bx=转移位移(
mov bx,offset s-offset se)。例程核心:push bp/mov bp,sp/dec cx/jcxz lpret/add [bp+2],bx/lpret: pop bp/iret——int 压栈后栈中从顶向下依次是 IP(se 的偏移)、CS、FLAGS,[bp+2]即 se 的偏移地址,加上 bx 中的位移就变成标号 s 的偏移,iret 返回即”跳”到 s。直接修改栈中的返回地址,是理解 int/iret/栈三者关系的最好练习。
13.4 BIOS和DOS所提供的中断例程
- BIOS(基本输入输出系统):存放在系统板 ROM 中,主要包含 4 部分内容:①硬件系统的检测和初始化程序;②外部中断和内部中断的中断例程;③用于对硬件设备进行 I/O 操作的中断例程;④其他和硬件系统相关的中断例程。
- DOS 的中断例程是操作系统向程序员提供的编程资源;和硬件设备相关的 DOS 中断例程中,一般都调用了 BIOS 的中断例程。
- 应用程序用
int指令直接调用它们完成硬件相关操作,不必关心硬件细节。
13.5 BIOS和DOS中断例程的安装
- 开机过程中:CPU 加电后从 FFFF0H 执行 BIOS 初始化程序 → BIOS 安装自己的中断例程(如 int 9、int 10H、int 13H)→ BIOS 引导 DOS 启动 → DOS 安装自己的中断例程(如 int 21H)。
13.6 BIOS中断例程应用
int 10H(屏幕/显示相关),通过 ah 指定功能号:
; 在屏幕第 5 行第 12 列显示一个黑底绿色的字符 '!'mov ah, 2 ; 2 号功能:置光标位置mov bh, 0 ; 第 0 页mov dh, 5 ; 行mov dl, 12 ; 列int 10H
mov ah, 9 ; 9 号功能:在光标位置显示字符mov al, '!'mov bl, 2 ; 颜色属性(绿色)mov bh, 0mov cx, 1 ; 显示次数int 10H13.7 DOS中断例程应用
int 21H(程序功能):
| ah | 功能 |
|---|---|
| 4CH | 程序返回(mov ax,4c00H + int 21H) |
| 02H | 显示一个字符(dl=字符) |
| 09H | 显示以 ’$’ 结尾的字符串(ds:dx 指向串首) |
| 01H | 读取一个键盘输入并回显,等待到有键(结果在 al) |
| 07H | 读取一个键盘输入不回显(结果在 al) |
| 0AH | 读取一个字符串到缓冲区 |
0AH 号功能的缓冲区格式(ds:dx 指向缓冲区):
- 第 1 字节:缓冲区可容纳的最大字符数(事先填好);
- 第 2 字节:实际读入的字符数(DOS 返回时填写,不含回车);
- 第 3 字节起:读入的字符串(以回车结束输入)。
; int 21h 9 号功能显示字符串assume cs:codedata segment str db 'Hello, Assembly!', '$' ; 必须以 $ 结束data endscode segmentstart: mov ax, data mov ds, ax mov dx, offset str mov ah, 9 int 21h mov ax, 4c00h int 21hcode endsend start实验13 编写、应用中断例程
- 自编 int 7ch 中断例程并调用。
课程设计2
- 综合应用:用中断例程实现多功能的综合程序。
14. 端口
14.1 端口的读写
- 各种接口芯片中的寄存器称为端口,CPU 将它们统一编址、当作端口来访问。至此 CPU 可以直接读写 3 个地方的数据:CPU 内部的寄存器、内存、端口。
- 访问端口只有两条指令:in(读)/ out(写);**数据只能用 al(8 位端口)或 ax(16 位端口)**存放。
in al, 60H ; 从 60H 端口读一个字节到 alout 20H, al ; 把 al 写入 20H 端口- 端口地址 0~FFH 可直接写在指令中;超过 FFH(范围 0~FFFFH)必须先放入 dx:
mov dx, 3F8Hin al, dxout dx, al14.2 CMOS RAM芯片
- CMOS RAM 包含一个实时钟和 128 字节 RAM,靠电池供电,关机后时钟继续走。
- CPU 通过两个端口访问:
- 70H(地址端口):写入要访问的 CMOS 单元地址;
- 71H(数据端口):读写该单元内容。
mov al, 0out 70H, al ; 选中 0 号单元(秒)in al, 71H ; 读出秒14.3 shl和shr指令
- shl(逻辑左移):各位左移 X 位,最低位补 0,最后移出的一位进入 CF;移位位数只能是 1 或 cl。
- shr(逻辑右移):各位右移,最高位补 0,最后移出位进 CF。
mov al, 01001000Bshl al, 1 ; 左移 1 位 = ×2 → 10010000B,CF=0mov cl, 4shr al, cl ; 右移 4 位 → 00001001B(常用于取 BCD 高 4 位的十位)- 左移一位相当于 ×2,右移一位相当于 ÷2(对无符号数)。
14.4 CMOS RAM中存储的时间信息
- CMOS 中时间信息以 BCD 码存放(一个字节两位十进制,高 4 位十位、低 4 位个位)。
| 单元 | 内容 | 单元 | 内容 |
|---|---|---|---|
| 0 | 秒 | 7 | 日 |
| 2 | 分 | 8 | 月 |
| 4 | 时 | 9 | 年 |
- BCD 值转 ASCII 显示:十位 = 高 4 位(右移 4 位)+30H,个位 = 低 4 位(
and 0FH)+30H。
实验14 访问CMOS RAM
- 读出 CMOS 中的时间并显示。
15. 外中断
15.1 接口芯片和端口
- CPU 通过接口芯片提供的端口与外设联系;接口芯片向外设/CPU 传递信息并产生中断请求。
15.2 外中断信息
外设输入/输出到达时,相关芯片向 CPU 发出外中断。分两类:
| 类型 | 特点 |
|---|---|
| 可屏蔽中断 | CPU 是否响应取决于 IF:IF=1 响应、IF=0 忽略;sti 置 IF=1,cli 置 IF=0 |
| 不可屏蔽中断 | 必须响应,类型码固定为 2(如紧急硬件故障) |
可屏蔽外中断的中断过程与内中断一致(进入中断过程时 IF 会被置 0)。
15.3 PC机键盘的处理过程
- 键盘按下/松开产生扫描码,送入主板上接口芯片的 60H 端口(通码为按下产生的扫描码,断码 = 通码 + 80H,松开时产生);
- 接口芯片向 CPU 发出类型 9 的可屏蔽中断;
- CPU 若 IF=1,执行 int 9 中断例程:
- 从 60H 端口读入扫描码;字符键的扫描码与对应 ASCII 码写入键盘缓冲区(16 个字单元、可存 15 个输入,高位字节=扫描码、低位字节=ASCII 码);控制键/切换键(Ctrl、Shift、CapsLock 等)转变为状态字节,写入 0040:17 单元(8 个二进制位依次记录右 Shift、左 Shift、Ctrl、Alt、ScrollLock、NumLock、CapsLock、Insert 状态);
- 对键盘系统进行应答等控制,处理完成后向 20H 端口发送中断结束指令 EOI(值 20H):
mov al, 20Hout 20H, al ; 向外设中断控制器发送 EOI15.4 编写int 9中断例程
套路(以”显示扫描码”为例):
- 保存原 int 9 入口(向量表 0:24H 起 4 字节)到安全内存(如 0:200H );
- 编写新例程:先
call dword ptr调用原例程(它负责把键处理进缓冲区并发送 EOI),再加自定义功能; - 把新例程安装到安全内存(如 0:204H );
- 把 0:24H 表项改为新例程入口。
15.5 安装新的int 9中断例程
; 新 int 9 例程(已安装到 0:204H,故 cs=0;原入口保存在 0:200H)newint9: push ax push bx push es in al, 60h ; 读扫描码 pushf ; 模拟 int 的中断现场(iret 要弹标志) call dword ptr cs:[200H] ; 调用原 int 9 例程(它发 EOI) ; ---- 自定义功能:在屏幕中间显示扫描码 ---- mov ax, 0b800H mov es, ax mov bx, 160*12+40*2 mov byte ptr es:[bx], 'a' ; ---- pop es pop bx pop ax iret自写外中断例程若不调用原 int 9 例程(自含处理、不经过原例程结束中断),则结束前必须自己向 20H 端口发 EOI,再 iret 返回;若例程通过
call dword ptr调用原例程,EOI 由原例程发送,无需重复发送。
- 改向量表项的竞态:设置新 int 9 入口的两条指令(改偏移、改段地址)之间若恰逢键盘中断,CPU 会跳到”半新半旧”的错误入口——应先
cli屏蔽可屏蔽中断,两条设置指令执行完再sti恢复。 - 教材 15.5 完整示例:安装新 int 9 到 0:204H
(原入口存 0:200H
),按 F1(通码 3BH) 将全屏 2000 个属性字节逐个加 1 改变屏幕颜色,其他键
call dword ptr cs:[200H]照常处理。
实验15 安装新的int 9中断例程
- 完整安装新的 int 9 例程并验证(如:按 A 键、松开后满屏显示 ‘A’——判断断码 = 通码 + 80H)。
指令系统总结(教材第 15 章末)
| 类别 | 代表指令 | 说明 |
|---|---|---|
| 数据传送 | mov、push、pop、pushf、popf、xchg | 寄存器/内存/段寄存器间单个数据传送,不影响标志位 |
| 算术运算 | add、sub、adc、sbb、inc、dec、cmp、mul、div | 影响标志寄存器的 sf、zf、of、cf、pf(/af) |
| 逻辑 | and、or、not、xor、test、shl、shr | 除 not 外都影响标志位 |
| 转移 | 无条件 jmp;条件 jcxz、je、jb、ja…;循环 loop;过程 call/ret/retf;中断 int/iret | 修改 IP,或同时修改 CS、IP |
| 处理机控制 | cld、std、cli、sti、nop 等 | 对标志寄存器或其他处理器状态进行设置 |
| 串处理 | movsb、movsw(配合 rep 等前缀) | 对内存中的批量数据进行处理 |
16. 直接定址表
16.1 描述了单元长度的标号
代码段中的标号有两种:
- 地址标号:仅表示段地址:偏移地址
(如
s:),无长度属性; - 数据标号:同时表示地址和单元长度:
a db 1,2,3,4,5,6 ; a 既代表地址(偏移 0),又代表字节单元b dw 0 ; b 代表一个字单元的地址(偏移 6)mov al, a[0] ; = mov al, cs:[0],编译器知道按字节取mov ax, b ; = mov ax, cs:[6],按字取16.2 在其他段中使用数据标号
- 用
assume把段与段寄存器关联后,编译器在访问该段中的数据标号时会自动加上相应的段前缀;若 assume 与实际不符,程序会出错,仍须自己在代码中设置段寄存器。 - 段前缀也可手动显式给出(如
ds:b)。 - 标号可以作为数据来定义:
c dw a,b等价于c dw offset a,offset b(在 c 处存放标号 a、b 的偏移地址);c dd a,b则存放每个标号的偏移地址 + 段地址。seg 标号操作符取标号的段地址(offset取偏移地址)。
16.3 直接定址表
直接定址表:以数据(如编号、字符码)为索引,通过查表直接得到目标数据/地址,避免一长串 cmp 条件分支。
数值查表:如用十六进制数码 0~F 作下标,查一张字符表直接得到对应字符:
table db '0123456789ABCDEF'; al 中为 0~F 的数值mov bl, almov bh, 0mov al, table[bx] ; 直接查出对应字符的 ASCII- 查表的 3 个目的:①算法清晰简洁(避免”≤9 加 30H、否则加 37H”的分支判断);②用空间换时间加快运算——如 sin(x) 只取 0°~180° 的 7 个特殊角度,把结果字符串存表,用
角度/30直接查出,免去麦克劳林公式的多次乘除;③程序易于扩充。
16.4 程序入口地址的直接定址表
- 把一组子程序的入口偏移依次存入表中,按编号
call word ptr table[bx]直接分派,是”多功能中断例程”的标准写法。
实验16 编写包含多个功能子程序的中断例程
- 用入口地址表实现多功能中断例程。
17. 使用BIOS进行键盘输入和磁盘读写
17.1 int 9中断例程对键盘输入的处理
int 9 例程把按键转换后放入键盘缓冲区(环形队列,最多存 15 个按键),供后续程序读取;Shift/CapsLock 等状态键改变状态字节。
17.2 使用int 16h中断例程读取键盘缓冲区
BIOS 提供 int 16h 键盘中断:
| ah | 功能 |
|---|---|
| 0 | 读取一个键盘输入(从缓冲区取出一个键并移除;缓冲区空则等待)。结果:ah=扫描码,al=ASCII 码 |
| 1 | 查询缓冲区(不取出):有键 ZF=0,无键 ZF=1 |
| 2 | 读键盘状态字节到 al(Shift/Ctrl/CapsLock 等状态位) |
mov ah, 0int 16H ; 结果:ah = 扫描码,al = ASCII 码17.3 字符串的输入
实现字符串输入需要处理:字符入栈/缓存、显示回显、退格(删除栈顶并擦除屏幕字符)、回车结束。核心循环(结构参照教材 17.4,charstack 负责栈缓冲与显示):
getstr: push axgetstrs: mov ah, 0 int 16h cmp al, 20h ; ASCII < 20H 的控制键 jb nochar mov ah, 0 call charstack ; 字符入栈 mov ah, 2 call charstack ; 显示 jmp getstrsnochar: cmp ah, 0eh ; 退格键的扫描码 je backspace cmp ah, 1ch ; 回车键的扫描码 je enter jmp getstrsbackspace: mov ah, 1 call charstack ; 出栈 mov ah, 2 call charstack ; 显示 jmp getstrsenter: mov al, 0 mov ah, 0 call charstack ; 0 入栈 mov ah, 2 call charstack ; 显示 pop ax ret17.4 应用int 13h中断例程对磁盘进行读写
BIOS int 13h 访问软盘/硬盘,参数约定:
| 寄存器 | 内容 |
|---|---|
| ah | 功能号:2 读扇区、3 写扇区 |
| al | 读写的扇区数 |
| ch | 磁道号(柱面号) |
| cl | 扇区号(从 1 开始) |
| dh | 磁头号(面号) |
| dl | 驱动器号(软盘 A 为 0,硬盘从 80H 起) |
| es:bx | 数据缓冲区首地址 |
; 从 0 号软驱 0 面 0 道 1 扇区读 1 个扇区到 es:bxmov ax, datamov es, axmov bx, 0mov ah, 2mov al, 1mov ch, 0mov cl, 1mov dh, 0mov dl, 0int 13H- 操作成功:CF=0,AH=0,AL=实际读写扇区数;失败:CF=1,AH 为错误码。
- 磁盘访问三要素:磁道(柱面)、磁头(面)、扇区,扇区是磁盘读写的最小单位;面号、磁道号从 0 开始,扇区号从 1 开始。
- 3.5 英寸软盘:2 面 × 80 磁道 × 18 扇区 × 512B = 1.44MB。直接写 0 面 0 道 1 扇区(引导扇区)会使软盘在现有操作系统下不可用(需重新格式化);实验务必用空闲软盘、核对驱动器号。
实验17 编写包含多个功能子程序的中断例程
- 综合键盘输入与磁盘读写的多功能中断例程。
综合研究
教材结尾的 5 个研究试验围绕”C 语言与汇编的接口”展开(在 C 环境下直接使用汇编能力)。
研究试验1 搭建一个精简的C语言开发环境
- 用汇编实现最小运行支持,搭建一个精简的 C 语言开发环境。
研究试验2 使用寄存器
- C 语言中直接使用 8086 寄存器(寄存器变量/内联汇编)。
研究试验3 使用内存空间
- 在 C 语言中直接读写指定的内存地址空间。
研究试验4 不用main函数编程
- 不写 main 函数、直接指定程序入口的编程方式。
研究试验5 函数如何接收不定数量的参数
- 结合栈传参机制,理解 C 函数如何接收不定数量的参数(与附注4 呼应)。
附注
附注1 Intel系列微处理器的3种工作模式
| 模式 | 说明 |
|---|---|
| 实模式 | 8086 工作方式,段地址×16+偏移形成 20 位物理地址,直接访问物理内存(本课程全部内容) |
| 保护模式 | 80286 起引入,段寄存器存放段选择子,经段描述符转换地址,支持特权级与分页 |
| 虚拟 8086 模式 | 80386 起引入,保护模式下模拟实模式,使多个 8086 程序得以并发运行 |
附注2 补码
- 负数 $-X$ 的补码 = $2^N - X$(N 为位数);求补运算:取反加 1。
- 8 位补码范围 −128
127;16 位 −3276832767。CPU 内部的有符号减法统一用补码加法实现。
附注3 汇编编译器(masm.exe)对jmp的相关处理
jmp short 标号/jmp near ptr 标号:masm 计算位移 = 标号处偏移 − jmp 指令后第一条指令的偏移,写入机器码;jmp far ptr 标号:masm 直接把标号的段地址和偏移地址写入机器码;- 位移超出 short 范围(−128~127)却写了
jmp short,编译报错;超出 near 范围同理; jmp 标号不带类型说明时,masm 依据位移范围自动选择 short/near。
附注4 用栈传递参数
高级语言(C)调用函数时常用栈传参:调用者把参数从右向左依次压栈后 call;子程序用 bp 访问参数([bp+4] 为第一个参数,[bp+2] 是返回 IP,[bp+0] 是保存的旧 bp),返回后调用者平衡栈(或用 ret n 由被调用方平衡)。
复习速查
以下内容是本笔记的补充整理,不属于教材目录,供考试前快速过一遍。
寄存器总表
| 类别 | 寄存器 | 作用 |
|---|---|---|
| 通用 | AX/BX/CX/DX(各可拆 H/L) | 数据;BX 基址、CX 计数、DX 配合乘除/端口 |
| 变址 | SI、DI | 源/目的变址,串操作、寻址 |
| 指针 | SP、BP | SP 栈顶;BP 基址指针(默认 SS) |
| 段寄存器 | CS、DS、SS、ES | 代码/数据/栈/附加段 |
| 控制 | IP、FLAGS | 指令指针、标志寄存器 |
指令对标志位的影响
| 指令 | 影响标志位? |
|---|---|
| add/sub/adc/sbb/cmp | 影响 ZF/PF/SF/CF/OF/AF |
| inc/dec | 影响 ZF/PF/SF/OF/AF,不影响 CF(考试陷阱) |
| mul/div | 标志位无确定含义,编程时不要依据 |
| mov、push、pop、lea、xchg、not | 不影响任何标志位 |
| and/or/xor | 影响 ZF/PF/SF;CF=OF=0 |
| shl/shr | 最后移出位进 CF;SF/ZF/PF 按结果设置 |
| cld/std | 只改 DF;sti/cli 只改 IF |
考试重点速查
| 知识点 | 频率 | 题型 |
|---|---|---|
| 物理地址 = 段地址×16+偏移 | ⭐⭐⭐⭐⭐ | 计算 |
| 寻址方式判断([bx+si+idata] 等) | ⭐⭐⭐⭐⭐ | 选择/读程序 |
| push/pop 与 SS:SP 变化 | ⭐⭐⭐⭐⭐ | 画图/填空 |
| loop + [bx] 循环读程序写结果 | ⭐⭐⭐⭐⭐ | 大题 |
| jmp 转移原理(位移 vs 绝对地址) | ⭐⭐⭐⭐ | 选择/分析 |
| call/ret 子程序执行流程 | ⭐⭐⭐⭐⭐ | 读程序 |
| 标志位 ZF/CF/OF/SF 判断 | ⭐⭐⭐⭐⭐ | 填空 |
| cmp + 条件转移(je/jb/ja/jl/jg) | ⭐⭐⭐⭐⭐ | 读程序 |
| div/mul 的隐含寄存器约定 | ⭐⭐⭐⭐ | 计算 |
| 中断向量表(N×4、低 IP 高 CS) | ⭐⭐⭐⭐⭐ | 填空/编程 |
| 中断过程与 iret | ⭐⭐⭐⭐ | 简答 |
| int 10H / int 21H / int 16H / int 13H | ⭐⭐⭐⭐ | 编程 |
| 端口 in/out、CMOS 70H/71H | ⭐⭐⭐ | 编程 |
| shl/shr、BCD 码转换 | ⭐⭐⭐ | 编程 |
| 直接定址表 | ⭐⭐⭐ | 编程 |
易混点最后过一遍
- 指令和数据在内存中无差别,区别在 CPU 当前如何使用。
- 段是逻辑概念,段起始地址必为 16 的倍数,段长上限 64KB。
- 小端:字的低字节在低地址;栈从高地址向低地址增长,push 先 SP−2。
- mov 不能给段寄存器立即数,要经通用寄存器中转;两个内存单元不能直接传送。
- 源程序中
[0]是立即数,要写ds:[0];Debug 中[0]才是内存。 - assume 不负责填段寄存器,DS/SS 要自己 mov,CS:IP
由
end 入口自动设置。 - CF 看无符号进位,OF 看有符号溢出,两者独立;inc/dec 不影响 CF。
- 条件转移:无符号用 b/a(below/above),有符号用 l/g(less/greater)。
- 中断向量表每项 4 字节:低字 IP、高字 CS,N 号中断在 0:N ×4。
- 中断过程硬件自动:pushf → TF=0、IF=0 → push CS、push IP → 跳转;iret 正好反向。
- div 商超出范围 → 除法溢出,触发 0 号中断;mul/div 的操作数不能是立即数。
- 8086 移位位数只能是 1 或 cl;断码 = 通码 + 80H。
- EOI 由(原)int 9 例程发送;自写例程不调用原例程时才需要自己发。
- int 16H ah=0 读键后缓冲区中该键被移除;int 21H 01H 等待并回显、07H 不回显。
💡 全书主线:寄存器 → 内存分段寻址 → 栈 → 程序结构 → 灵活寻址 → 转移与子程序 → 标志位 → 中断体系(内中断 → int → 端口 → 外中断)。抓住”CPU 如何用 CS:IP 取指、用 DS/bx 寻址、用 SS:SP 管理栈、用中断向量表找例程”这四条线,所有章节都能串起来。
