汇编语言(王爽·第4版)—— 完整复习笔记
面向考试 / 期末复习,基于王爽《汇编语言(第4版)》17 章全部核心知识点,CPU 模型为 8086(16 位、实模式)。
目录
- 基础知识
- 寄存器
- 寄存器与内存访问
- 第一个程序
- [BX] 和 loop 指令
- 包含多个段的程序
- 更灵活的定位内存地址的方法
- 数据处理的两个基本问题
- 转移指令的原理
- CALL 和 RET 指令
- 标志寄存器
- 内中断
- int 指令
- 端口
- 外中断
- 直接定址表
- BIOS 键盘输入与磁盘读写
- 附注与考试重点速查
1. 基础知识
1.1 机器语言与汇编语言
- 机器语言:二进制机器指令的集合,CPU 能直接执行,但人难以读写。
- 汇编语言:用助记符代替机器指令,经编译器翻译成机器语言。
- 汇编语言的组成:①汇编指令(核心,有对应机器码)②伪指令(编译器执行,无机器码)③其他符号(如
+、-)。
1.2 指令、数据与存储单元 🔥
- 指令和数据在内存/磁盘中没有区别,都是二进制信息;CPU 在工作时把有的信息当指令、有的当数据,区别在于应用场合。
- 存储器被划分为若干存储单元,每个单元编号从 0 开始,称为物理地址。
- 8086 一个存储单元 = 1 字节(Byte)= 8 bit;1 字(Word)= 2 字节。
1.3 CPU 对存储器的读写
CPU 要读写数据,必须和外部芯片进行 3 类信息交互:
- 地址信息:存储单元的地址
- 控制信息:读还是写
- 数据信息:读/写的内容
对应引出三类总线。
1.4 三大总线 🔥🔥🔥
| 总线 | 方向 | 决定的能力 |
|---|---|---|
| 地址总线 | CPU → 内存/芯片 | 寻址能力:$N$ 根 → $2^N$ 个单元 |
| 数据总线 | 双向 | 一次数据传送量(宽度 = 一次传送 bit 数) |
| 控制总线 | CPU → 外部芯片(不同控制线方向不同) | CPU 对外部器件的控制能力 |
- 8086 有 20 根地址总线 → 寻址 $2^{20}$ = 1 MB 内存空间。
- 8086 数据总线 16 根 → 一次传送 2 字节;8088 只有 8 根。
- 控制总线是一些控制线的集合,“控制总线宽度”指有多少根控制信号线。
计算例:地址总线宽度为 20,内存地址空间大小 = $2^{20}\text{B}=1048576\text{B}=1\text{MB}$。
1.5 内存地址空间
- 主板上通过总线相连的器件:CPU、存储器、外围芯片(接口卡)、扩展插槽。
- 接口卡:CPU 不能直接控制外设(显示器、键盘、音箱),通过向接口卡上的寄存器/端口发命令间接控制。
- 存储器芯片分类:
| 分类 | 特点 | 举例 |
|---|---|---|
| 随机存储器 RAM | 可读可写,断电丢失 | 主存、显存 |
| 只读存储器 ROM | 只能读,断电不丢 | BIOS ROM |
- 内存地址空间(地址空间):CPU 将各类存储器都看作一个由若干存储单元组成的逻辑存储器,每个物理存储器在地址空间中占有一段地址区间。
- 8086 PC 机地址空间分配(从低到高):主存储器地址空间 → 显存地址空间 → 各类 ROM 地址空间。对某地址读写,实际上就是在对它所归属的物理存储器读写。
2. 寄存器
2.1 通用寄存器 🔥
8086 CPU 有 14 个寄存器。通用寄存器 4 个,通常用于存放一般性数据:
| 16 位寄存器 | 高 8 位 | 低 8 位 |
|---|---|---|
| AX(累加) | AH | AL |
| BX(基址) | BH | BL |
| CX(计数) | CH | CL |
| DX(数据) | DH | DL |
- 为保证兼容,上一代 CPU 中的 8 位寄存器都可独立使用。
- 字节记为 byte(8 bit),字记为 word(16 bit),一个字由高字节、低字节组成。
2.2 字在寄存器中的存储
- 一个字存在一个 16 位寄存器中:高字节放高位、低字节放低位。
- 例:AX = 01001110 00100000 = 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)。 - 汇编中十六进制以字母开头时前面补 0(如
0C0H)。
2.4 物理地址与 8086 的分段机制 🔥🔥🔥🔥🔥
8086 是 16 位结构 CPU:寄存器、运算器一次处理 16 位;但地址总线 20 位。于是采用”两个 16 位地址合成 20 位物理地址”:
$$\textbf{物理地址} = \textbf{段地址} \times 16 + \textbf{偏移地址}$$
- ×16 在二进制中即左移 4 位(十六进制左移 1 位,后面补 0)。
- 例:段地址 123CH、偏移地址 0008H → 物理地址 = 123C0H + 0008H = 123C8H。
本质含义:CPU 用”基础地址(段地址×16)+ 相对于基础地址的偏移地址”给出内存单元的物理地址。
2.5 段的概念 🔥
- “段”并非内存真的被划分,而是 CPU 分段寻址方式带来的逻辑概念;可以根据需要安排若干”段”。
- 一个段的起始地址(段地址×16)一定是 16 的倍数。
- 偏移地址 16 位 → 一个段最长 64 KB($2^{16}$)。
- 同一物理地址可由不同的”段地址:偏移地址 ”组合得到(如 21F60H = 2000H:1F60H = 21F6H:0000H )。
2.6 段寄存器与 CS、IP 🔥🔥🔥
4 个段寄存器:CS、DS、SS、ES。
- CS(代码段寄存器)+ IP(指令指针寄存器):指示 CPU 当前要读取指令的地址。
- 任意时刻,CPU 将 CS:IP
指向的内容当作指令执行:
- 从 CS×16+IP 单元读取指令,送入指令缓冲器;
- IP = IP + 所读指令长度(指向下一条指令);
- 执行指令。(取指与执行重叠进行)
2.7 修改 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.8 代码段
- 将一段内存当作代码段,只需把它的段地址放在 CS、把第一条指令偏移放在 IP,CPU 就会依次执行其中指令。
- 代码段是人为安排的;CPU 只认 CS:IP 。
2.9 Debug 常用命令 🔥
| 命令 | 功能 |
|---|---|
R | 查看/修改寄存器内容(R ax 改 AX) |
D | 查看内存内容(D 段地址:偏移) |
E | 改写内存(E 1000:0 41 42 43) |
U | 将内存中的机器指令翻译成汇编指令(反汇编) |
A | 以汇编指令格式向内存写入机器指令 |
T | 执行一条指令(单步跟踪) |
G | 执行到指定位置(G 0016) |
P | 执行到循环结束/中断返回(类似 T 但不停进 int) |
Q | 退出 Debug |
3. 寄存器与内存访问
3.1 内存中字的存储(小端) 🔥🔥
- 小端方式(低对低、高对高):字的低字节存放在低地址单元,高字节存放在高地址单元。
- 例:在 0、1 单元存放 2030H,则
(0)=30H、(1)=20H;0、1 单元合起来构成一个字单元 0,值为 2030H。 - 字单元:由两个地址连续的内存单元组成,起始单元为低地址单元。
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 写入该内存单元字的传送一次完成(16 根数据线同时工作):
mov ax, [0]一次读 0、1 两个单元。
3.3 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。
3.4 数据段
把一段内存当作数据段:将段地址放入 DS,用 [偏移] 访问其中单元即可。数据段也是人为安排。
3.5 栈机制 🔥🔥🔥🔥🔥
- 栈:一种后进先出(LIFO)的结构,8086 提供相关指令以内存的一段空间作为栈。
- SS:SP 指示栈顶元素: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 ; 出栈3.6 push、pop 指令
可操作对象:寄存器、段寄存器、内存单元。
push ax ; 寄存器push ds ; 段寄存器push [0] ; 内存字单元pop [2] ; 出栈到内存3.7 栈顶超界问题 🔥
- push 前 SP 只减 2、pop 后 SP 只加 2,8086 不保证栈操作不会越界,不会因超界而报错。
- 编程者必须自己操心栈的范围,安排足够大的栈空间。
- 栈段:把一段内存当作栈段,只需 SS:SP 指向它。
4. 第一个程序
4.1 源程序到执行的过程
编写(.asm)→ 编译 masm(.obj)→ 连接 link(.exe)→ 加载运行
- 编译:把汇编指令翻译成机器码,生成目标文件。
- 连接:把目标文件组装成可执行文件;当程序很大时可分成多个源文件编译后再连接,也可调用库文件。
4.2 源程序的基本框架 🔥
assume cs:codesg ; 将代码段 codesg 与段寄存器 CS 关联(假设性说明)
codesg segment ; 定义一个段开始 mov ax, 0123H mov bx, 0456H add ax, bx
mov ax, 4c00H ; 程序返回:调用 DOS 中断,4CH 号功能 int 21Hcodesg ends ; 段结束
end ; 汇编程序结束标记| 伪指令 | 含义 |
|---|---|
xxx segment / xxx ends | 定义一个段 |
end | 标记整个程序结束(编译器遇 end 结束编译) |
assume | ”假设”某段与某段寄存器相关联 |
- 标号:codesg 作为段名,最终被编译处理为一个段地址。
- 程序返回固定套路:
mov ax,4c00H+int 21H。
4.3 可执行文件的装载与运行
- command.com(DOS 的 shell 程序)将 .exe 装入内存:找到一段容量足够的空闲内存 → 建立 PSP(程序段前缀,256 字节) → 在 PSP 之后装入程序。
- 程序加载后:DS 中存放 PSP 段地址,CS:IP 指向程序入口,SS:SP 指向栈顶。
- 跟踪:Debug 中用
T逐条执行,遇到int 21H用P正常结束返回。
5. [BX] 和 loop 指令
5.1 [BX]
[bx]表示内存单元,其偏移地址在 bx 中,段地址默认 DS。
mov bx, 0mov ax, [bx] ; 等价于 mov ax, (DS:0)inc bx ; bx = bx + 15.2 loop 指令 🔥🔥🔥
- CPU 执行
loop 标号:- (cx) = (cx) − 1
- 判断 cx:不为 0 则转至标号处执行;为 0 则向下执行。
- cx 中存放循环次数。
循环程序框架:
mov cx, 12 ; 循环次数 mov ax, 0s: add ax, cx ; s 为标号 loop s ; cx-- ≠0 就跳到 s5.3 Debug 和 masm 对指令的不同处理 🔥
| 写法 | Debug(A 命令) | masm 源程序 |
|---|---|---|
mov ax, [0] | 访问内存单元 DS:0 | 被当作 mov ax, 0(立即数)! |
| 访问偏移为 idata 的内存 | [0] | 必须写成 ds:[0] 或先送入 bx 用 [bx] |
在源程序中用 idata 表示偏移地址,要显式写段前缀
ds:[0]。
5.4 loop 和 [bx] 联合应用
典型套路:累加一段连续内存(ffff:0 ~ffff:b 字节单元求和到 dx):
mov ax, 0ffffH mov ds, ax mov bx, 0 ; 偏移指针 mov dx, 0 ; 累加结果 mov cx, 12 ; 12 个单元s: mov al, [bx] mov ah, 0 add dx, ax inc bx loop s5.5 段前缀与安全空间
- 段前缀:显式给出段地址,如
ds:[bx]、cs:[bx]、ss:[bx]、es:[bx]。 - 一段安全的空间:在一般 PC 机中,0:200H ~0:2FFH 的 256 字节空闲安全,Debug 实验可直接使用;不要随意向未知内存写数据。
6. 包含多个段的程序
6.1 在代码段中使用数据
- 程序中定义的数据放在代码段最前面时,要让 CS:IP
跳过数据,用
end 标号指定程序入口:
assume cs:codecode segment dw 0123H, 0456H, 0789H ; define word,定义 3 个字数据 ; ...start: ; 程序入口标号 mov bx, 0 mov ax, 0 mov cx, 3s: add ax, cs:[bx] add bx, 2 loop s
mov ax, 4c00H int 21Hcode endsend start ; end 后标号指示程序从 start 开始执行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 入口标号自动设置。
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 数):
- 大写 → 小写:第 5 位置 1(
or xx, 00100000B) - 小写 → 大写:第 5 位清零(
and xx, 11011111B)
- 大写 → 小写:第 5 位置 1(
- 源程序中字符常量用引号:
db 'unIX'等价于db 75H,6EH,49H,58H。
7.3 [bx+idata]
mov ax, [bx+200] ; 偏移地址 = (bx)+200,等价写法:mov ax, 200[bx]mov ax, [bx].200用于数组处理:两个数组分别从 [bx] 和 [bx+idata] 开始,同一 bx 同步定位。
7.4 SI 和 DI 🔥
- SI(源变址)、DI(目的变址):功能与 bx 相近,但不能拆成两个 8 位寄存器。
mov si, 0mov ax, [si]mov di, 0mov ax, [di+10]7.5 全部寻址方式组合 🔥🔥🔥
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。可用段前缀改变。
8. 数据处理的两个基本问题
8.1 数据在什么地方
机器指令处理的数据可在三个地方:①指令内部(立即数)②寄存器 ③内存(段地址 SA:偏移地址 EA)。
8.2 数据位置的表达
| 位置 | 表达 | 例子 |
|---|---|---|
| 立即数 idata | 直接写在指令中 | mov ax, 1 |
| 寄存器 | 寄存器名 | mov ax, bx |
| 段地址+偏移 | SA:EA | mov ax, [0] |
- 立即数只能作为源操作数,不能作为目的。
8.3 寻址方式汇总 🔥🔥🔥
| 寻址方式 | 偏移地址 EA | 常用例子 |
|---|---|---|
| 直接寻址 | idata | [idata] |
| 间接寻址 | BX/BP/SI/DI | [bx] |
| 相对寻址 | BX/BP/SI/DI + idata | [bx+idata] |
| 基址变址 | BX/BP + SI/DI | [bx+si] |
| 相对基址变址 | BX/BP + SI/DI + idata | [bx+si+idata] |
8.4 指令要处理的数据有多长 🔥
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.5 div 指令(除法) 🔥🔥🔥
- 除数:8 位或 16 位,在寄存器或内存中。
- 被除数默认放在 AX(8 位除法)或 DX 和 AX(16 位除法,DX 存高 16 位)。
| 除数宽度 | 被除数 | 商 | 余数 |
|---|---|---|---|
| 8 位 | AX | AL | AH |
| 16 位 | DX(高):AX (低) | AX | DX |
; 计算 100001 / 100(16 位除法)mov dx, 1mov ax, 86A1H ; DX:AX = 186A1H = 100001mov bx, 100div bx ; AX=商(03E8H=1000), DX=余(1)8.6 伪指令 dd 和 dup 🔥
db定义字节、dw定义字、dd定义双字(dword,4 字节):
dd 12345678H ; 占 4 字节- 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 个字节的空间(不确定值)9. 转移指令的原理
9.1 操作符 offset
offset 标号取得标号的偏移地址(由编译器处理):
start: mov ax, offset start ; ax = start 处的偏移地址9.2 jmp 指令分类 🔥🔥🔥🔥
jmp 是无条件转移指令,可以只修改 IP,也可以同时修改 CS 和 IP。
① 段内短转移(依据位移,short):
jmp short 标号 ; IP = IP + 8 位位移;范围 −128 ~ 127② 段内近转移(near):
jmp near ptr 标号 ; IP = IP + 16 位位移;范围 −32768 ~ 32767- 机器码中不含转移目的地址,而含相对当前 IP 的位移(标号处地址 − 跳转后下一条指令地址),便于程序在内存中浮动装配。
③ 段间转移(远转移,far):机器码中直接含目的段地址和偏移地址。
jmp far ptr 标号 ; CS = 标号所在段地址,IP = 标号偏移地址④ 转移地址在寄存器中:
jmp ax ; IP = ax(不改变 CS)⑤ 转移地址在内存中:
jmp word ptr [bx] ; 段内:从内存字单元取偏移送 IPjmp dword ptr [bx] ; 段间:低字 → IP,高字 → CS9.3 jcxz 指令 🔥
- jcxz = jump if cx is zero:cx 为 0 时转移,否则向下执行(段内短转移,位移 −128~127)。
jcxz 标号 ; 等价于:if ((cx)==0) jmp short 标号9.4 loop 再认识
loop 标号:cx 减 1,cx≠0 则转移;也是段内短转移,机器码中存 8 位位移。- 编译器会对转移位移超界进行检测,超界直接编译错误。
依据位移转移的意义:转移指令中记录的是”相对位移”而非绝对地址,程序被装载到内存任意位置都能正确执行。
10. CALL 和 RET 指令
10.1 ret 和 retf 🔥
| 指令 | 等价操作 | 用途 |
|---|---|---|
ret | pop IP | 段内近返回 |
retf | pop IP;pop CS | 段间远返回 |
10.2 call 指令 🔥🔥🔥
CPU 执行 call 时先把返回地址(当前 IP 或 CS:IP )压栈,再转移。
| 形式 | 栈操作 + 转移 |
|---|---|
call 标号(近) | push IP;jmp near ptr 标号(16 位位移) |
call far ptr 标号(远) | push CS;push IP;jmp far ptr 标号 |
call reg(寄存器) | push IP;jmp reg(IP = 16 位寄存器) |
call word ptr 内存 | push IP;jmp word ptr 内存单元 |
call dword ptr 内存 | push CS;push IP;jmp dword ptr 内存单元 |
10.3 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.4 mul 指令(乘法) 🔥
| 乘数位数 | 被乘数 | 乘数 | 结果 |
|---|---|---|---|
| 8 位 | AL | r8/m8 | AX |
| 16 位 | AX | r16/m16 | DX(高 16 位):AX (低 16 位) |
mov al, 64mov bl, 64mul bl ; AX = 64×64 = 100H10.5 模块化程序设计与参数传递 🔥
- 子程序(过程)= 具有一定功能的程序段,用 call 调用、ret 返回,避免重复代码。
- 参数和结果传递的三种方式:
| 方式 | 适用 |
|---|---|
| 寄存器传递 | 参数少 |
| 内存单元(数据表)传递 | 参数多(批量数据) |
| 栈传递 | 通用、规范(见附注) |
10.6 寄存器冲突问题 🔥🔥🔥
- 子程序中使用的寄存器可能与主程序正在使用的冲突。标准解决框架:子程序开头把用到的寄存器全部压栈,返回前出栈恢复(注意出栈顺序相反):
sub: push ax ; 保存现场 push bx push cx ; ... 子程序功能,随意使用 ax/bx/cx ... pop cx ; 恢复现场(逆序) pop bx pop ax ret11. 标志寄存器
11.1 标志寄存器概述
标志寄存器(flag)按位起作用,每一位有专门含义。8086 中主要标志位:
| 标志 | 名称 | 为 1 的条件 |
|---|---|---|
| ZF(第6位) | 零标志 | 运算结果为 0 |
| PF(第2位) | 奇偶标志 | 结果二进制中 1 的个数为偶数 |
| SF(第7位) | 符号标志 | 结果为负(有符号运算,SF = 结果最高位) |
| CF(第0位) | 进位标志 | 无符号运算最高位产生进位/借位 |
| OF(第11位) | 溢出标志 | 有符号运算结果超出表示范围 |
| DF(第10位) | 方向标志 | 串传送方向(见 11.10) |
| TF(第8位) | 单步标志 | TF=1 时每条指令后触发单步中断 |
| IF(第9位) | 中断允许 | IF=1 时响应可屏蔽外中断 |
11.2 ZF、PF、SF
mov ax, 1sub ax, 1 ; 结果 0 → ZF=1;0 个 1(偶数)→ PF=1;最高位 0 → SF=0- SF 只对有符号数运算有意义:把结果当有符号数时,SF 指示正负。
11.3 CF 与 OF 的区别 🔥🔥🔥🔥
| CF 进位标志 | OF 溢出标志 | |
|---|---|---|
| 视角 | 无符号数运算 | 有符号数运算 |
| 为 1 | 最高位向外有进位(加)/向最高位有借位(减) | 结果超出机器能表示的范围 |
| 典型 | 97H+88H=11FH → CF=1 | 有符号加变号(正+正得负)→ OF=1 |
- 8 位有符号范围 −128
127;16 位 −3276832767。 - CF 和 OF 互不相关,各自记录不同视角下的结果。
11.4 adc 和 sbb(带进位/借位运算) 🔥
adc ax, bx:ax = ax + bx + CF(加上进位位)sbb ax, bx:ax = ax − bx − CF(减去借位位)- 用途:对超出寄存器宽度的大数分高低位依次运算,低位产生的进位由 CF 带入高位。
; 16 位寄存器实现 32 位加法:dx:ax + cx:bx → dx:axadd ax, bx ; 低 16 位相加,产生 CFadc dx, cx ; 高 16 位相加时再加 CF11.5 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.6 条件转移指令 🔥🔥🔥🔥🔥
条件转移指令都是段内短转移(位移 −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。
cmp ax, bxje equal ; ax==bx 才跳; ...equal:11.7 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 个字节11.8 pushf 和 popf
pushf:将标志寄存器压栈;popf:从栈中弹出送入标志寄存器。- 配合实现标志寄存器的暂存与设置。
11.9 标志寄存器在 Debug 中的表示
| 标志 | =1 | =0 |
|---|---|---|
| OF | OV | NV |
| DF | DN | UP |
| IF | EI | DI |
| SF | NG | PL |
| ZF | ZR | NZ |
| PF | PE | PO |
| CF | CY | NC |
12. 内中断
12.1 内中断的产生 🔥
CPU 内部发生以下情况时产生内中断:
| 类型码 | 来源 |
|---|---|
| 0 | 除法错误(div 溢出、除以 0) |
| 1 | 单步执行(TF=1) |
| 4 | 溢出(into 检测 OF=1) |
| n | 执行 int n 指令 |
12.2 中断处理程序与中断向量表 🔥🔥🔥
- 中断处理程序(中断例程):CPU 收到中断信息后转去执行的处理程序。
- 中断向量表:建立”中断类型码 → 中断处理程序入口地址”的映射。
- 8086 的中断向量表放在内存 0000:0000 ~ 0000:03FF ,共 1024 字节;
- 每个表项 4 字节:低字存偏移 IP,高字存段地址 CS;
- 类型码为 N 的中断,其入口地址存放在 0:N ×4(IP)和 0:N ×4+2(CS)。
12.3 中断过程 🔥🔥🔥🔥
CPU 硬件自动完成(中断过程):
- 取得中断类型码 N
- pushf(标志寄存器压栈)
- TF = 0、IF = 0(中断处理过程中不响应单步中断、可屏蔽外中断)
- push CS、push IP
(IP) = (N×4),(CS) = (N×4+2)—— 转去执行中断例程
12.4 iret 指令 🔥
中断例程末尾用 iret 返回,等价于:pop IP → pop CS → popf,与中断过程的压栈正好对应。
中断例程标准结构:
doit: push ax ; 保护现场 ; ... 处理 ... pop ax ; 恢复现场 iret ; 中断返回12.5 编程处理 0 号中断(除法错误)
完整套路:①编写中断例程 do0 → ②安装:把 do0 复制到不会被覆盖的内存(0:200H
起)→ ③设置中断向量表:把 0 号表项改为 0:200H。
; 设置 0 号中断向量:0:0 = 200H,0:2 = 0mov ax, 0mov es, axmov word ptr es:[0], 200Hmov word ptr es:[2], 012.6 单步中断
- TF=1 时,CPU 每执行一条指令就触发类型 1 的单步中断,Debug 正是利用它实现单步跟踪。
- 中断过程中自动把 TF 置 0,避免中断例程自身被单步;进入例程前 pushf 保存了原 TF。
- 响应中断的特殊情况:有些情况下(如设置 SS 的指令之后紧接着设置 SP),CPU 不会在两条指令之间响应中断,以保证 SS:SP 成对正确设置。
13. int 指令
13.1 int n 指令 🔥
int n触发类型码为 n 的中断,执行过程等价于:取 n → pushf → TF=0、IF=0 → push CS、push IP → 读 n×4 表项跳转。- int 指令使程序员可以主动调用系统提供的中断例程;例程末尾用 iret 返回。
13.2 编写供应用程序调用的中断例程
编写、安装中断例程的过程与 0 号中断相同:编写例程 → 安装到安全内存 → 改写对应中断向量表项。
13.3 BIOS 和 DOS 提供的中断例程 🔥🔥
- BIOS(基本输入输出系统):开机时由 ROM 中的程序固化安装一系列中断例程;DOS 在启动后又安装一批。
- 应用程序调用它们完成硬件相关操作,不必关心硬件细节。
13.4 常用中断例程 🔥🔥🔥
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 10HDOS 的 int 21H(程序功能):
| ah | 功能 |
|---|---|
| 4CH | 程序返回(mov ax,4c00H + int 21H) |
| 02H | 显示一个字符(dl=字符) |
| 09H | 显示以 ’$’ 结尾的字符串(ds:dx 指向串首) |
| 01H | 读取一个键盘输入(结果在 al) |
| 0AH | 读取一个字符串到缓冲区 |
; 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 start14. 端口
14.1 端口的读写 🔥🔥
- 各种外设/接口卡的寄存器称为端口,CPU 通过端口地址统一编址访问(与内存地址空间独立)。
- 访问端口只有两条指令:in(读)/ out(写)。
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 ; 读出秒- CMOS 中时间信息以 BCD 码存放(一个字节两位十进制,高 4 位十位、低 4 位个位)。
| 单元 | 内容 | 单元 | 内容 |
|---|---|---|---|
| 0 | 秒 | 7 | 日 |
| 2 | 分 | 8 | 月 |
| 4 | 时 | 9 | 年 |
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 位(常用于取 BCD 高 4 位的十位)- 左移一位相当于 ×2,右移一位相当于 ÷2。BCD 码转十进制:高 4 位右移 4 位得十位,低 4 位
and 0FH得个位。
15. 外中断
15.1 外中断信息
CPU 通过接口芯片提供的端口与外设联系;外设输入/输出到达时,相关芯片向 CPU 发出外中断。分两类:
| 类型 | 特点 |
|---|---|
| 可屏蔽中断 | CPU 是否响应取决于 IF:IF=1 响应、IF=0 忽略;sti 置 IF=1,cli 置 IF=0 |
| 不可屏蔽中断 | 必须响应,类型码固定为 2(如紧急硬件故障) |
可屏蔽外中断的中断过程与内中断一致(IF 会被置 0)。
15.2 PC 机键盘的处理过程 🔥🔥🔥
- 键盘按下/松开产生扫描码,送入主板上接口芯片的 60H 端口(通码按下、断码松开)。
- 接口芯片向 CPU 发出类型 9 的可屏蔽中断。
- CPU 若 IF=1,执行 int 9 中断例程:
- 从 60H 端口读入扫描码;
- 将扫描码转换为 ASCII 码/状态信息,存入键盘缓冲区;
- 处理完成后向 20H 端口发送中断结束指令 EOI(值 20H):
mov al, 20Hout 20H, al ; 向外设中断控制器发送 EOI15.3 编写并安装新的 int 9 中断例程 🔥
套路:①保存原 int 9 入口(向量表 0:24H
起 4 字节)→ ②编写新例程(先调用原例程 call dword ptr cs:[oldint9] 或模拟 int 过程,再加入自定义功能)→ ③安装到安全内存 → ④把 0:24H
表项改为新例程入口。
自写外中断例程若不调用原 int 9 例程(自含处理、不经过原例程结束中断),则结束前必须自己向 20H 端口发 EOI(
mov al,20H+out 20H,al),再 iret 返回;若例程通过call dword ptr cs:[oldint9]调用原例程,EOI 由原例程发送,无需重复发送。
16. 直接定址表
16.1 描述了单元长度的标号
代码段中的标号有两种:
- 地址标号:仅表示段地址:偏移地址
(如
s:),无长度属性; - 数据标号:同时表示地址和单元长度:
a db 1,2,3,4,5,6 ; a 既代表地址,又代表字节单元b dw 0 ; b 代表一个字单元的地址mov al, a[0] ; = mov al, cs:[0],编译器知道按字节取mov ax, b ; = mov ax, cs:[6],按字取16.2 在其他段中使用数据标号
需要用 assume 关联,并在访问时由编译器自动加上段前缀;也可用”段名.标号”形式显式指定。
16.3 直接定址表 🔥🔥🔥
直接定址表:以数据(如编号、字符码)为索引,通过查表直接得到目标数据/地址,避免一长串 cmp 条件分支。
数值查表:如用十六进制数码 0~F 作下标,查一张字符表直接得到对应字符:
table db '0123456789ABCDEF'; al 中为 0~F 的数值mov bl, almov bh, 0mov al, table[bx] ; 直接查出对应字符的 ASCII程序入口地址的直接定址表:把一组子程序的入口偏移依次存入表中,按编号 call word ptr table[bx] 直接分派,是”多功能中断例程”的标准写法。
17. BIOS 键盘输入与磁盘读写
17.1 int 9 对键盘输入的处理
int 9 例程把按键转换后放入键盘缓冲区(环形队列,最多存 15 个按键),供后续程序读取;Shift/CapsLock 等状态键改变状态字节。
17.2 int 16H 读取键盘缓冲区 🔥
BIOS 提供 int 16H 键盘中断,ah=0 号功能:读取一个键盘输入(从缓冲区取出一个键并从缓冲区移除,缓冲区空则等待):
mov ah, 0int 16H ; 结果:ah = 扫描码,al = ASCII 码17.3 字符串的输入
实现字符串输入需要处理:字符入栈/缓存、显示回显、退格(删除栈顶并擦除屏幕字符)、回车结束。核心循环:int 16h 读键 → 判断是字符/退格/回车 → 分别处理。
17.4 int 13H 磁盘读写 🔥
BIOS int 13H 访问软盘/硬盘,参数约定:
| 寄存器 | 内容 |
|---|---|
| ah | 功能号:2 读扇区、3 写扇区 |
| al | 读写的扇区数 |
| ch | 磁道号(柱面号) |
| cl | 扇区号(从 1 开始) |
| dh | 磁头号(面号) |
| dl | 驱动器号(软盘 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- 操作成功:AH=0,AL=实际读写扇区数;失败:AH 为错误码,CF=1。
- 磁盘访问三要素:磁道(柱面)、磁头(面)、扇区,扇区是磁盘读写的最小单位。
18. 附注与考试重点速查
18.1 Intel 微处理器的三种工作模式
| 模式 | 说明 |
|---|---|
| 实模式 | 8086 工作方式,段地址×16+偏移形成 20 位物理地址,直接访问物理内存(本课程全部内容) |
| 保护模式 | 80286 起引入,段寄存器存放段选择子,经段描述符转换地址,支持特权级与分页 |
| 虚拟 8086 模式 | 保护模式下模拟实模式,使多个 8086 程序得以并发运行 |
18.2 补码
- 负数 $-X$ 的补码 = $2^N - X$(N 为位数);求补运算:取反加 1。
- 8 位补码范围 −128
127;16 位 −3276832767。CPU 内部的有符号减法统一用补码加法实现。
18.3 用栈传递参数
高级语言(C)调用函数时常用栈传参:调用者把参数从右向左依次压栈后 call;子程序用 bp 访问参数([bp+4] 为第一个参数,[bp+2] 是返回 IP,[bp+0] 是保存的旧 bp),返回后调用者平衡栈(或用 ret n 由被调用方平衡)。
18.4 寄存器总表 🔥
| 类别 | 寄存器 | 作用 |
|---|---|---|
| 通用 | AX/BX/CX/DX(各可拆 H/L) | 数据;BX 基址、CX 计数、DX 配合乘除/端口 |
| 变址 | SI、DI | 源/目的变址,串操作、寻址 |
| 指针 | SP、BP | SP 栈顶;BP 基址指针(默认 SS) |
| 段寄存器 | CS、DS、SS、ES | 代码/数据/栈/附加段 |
| 控制 | IP、flag | 指令指针、标志寄存器 |
18.5 寻址方式与默认段速查 🔥
[...]中只允许 bx/bp/si/di;出现 bp 默认 SS,其余默认 DS;段前缀可改。- 立即数在 CPU 内部、寄存器在 CPU 内部、内存单元在 SA:EA 。
18.6 指令对标志位的影响
| 指令 | 影响标志位? |
|---|---|
| add/sub/adc/sbb/cmp/inc/dec | 影响 ZF/PF/SF/CF/OF(inc/dec 不影响 CF) |
| mov、push、pop、lea | 不影响任何标志位 |
| and/or/xor | 影响 ZF/PF/SF;CF=OF=0 |
| shl/shr | 最后移出位进 CF;同时按移位结果影响 SF/ZF/PF |
| cld/std | 只改 DF;sti/cli 只改 IF |
18.7 考试重点速查 🔥
| 知识点 | 频率 | 题型 |
|---|---|---|
| 物理地址 = 段地址×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 码转换 | ⭐⭐⭐ | 编程 |
| 直接定址表 | ⭐⭐⭐ | 编程 |
18.8 易混点最后过一遍
- 指令和数据在内存中无差别,区别在 CPU 当前如何使用。
- 段是逻辑概念,段起始地址必为 16 的倍数,段长上限 64KB。
- 小端:字的低字节在低地址;栈从高地址向低地址增长,push 先 SP−2。
- mov 不能给段寄存器立即数,要经通用寄存器中转;两个内存单元不能直接传送。
- 源程序中
[0]是立即数,要写ds:[0];Debug 中[0]才是内存。 - assume 不负责填段寄存器,DS/SS 要自己 mov,CS:IP
由
end 入口自动设置。 - CF 看无符号进位,OF 看有符号溢出,两者独立。
- 条件转移:无符号用 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 正好反向。
💡 全书主线:寄存器 → 内存分段寻址 → 栈 → 程序结构 → 灵活寻址 → 转移与子程序 → 标志位 → 中断体系(内中断 → int → 端口 → 外中断)。抓住”CPU 如何用 CS:IP 取指、用 DS/bx 寻址、用 SS:SP 管理栈、用中断向量表找例程”这四条线,所有章节都能串起来。
