bt

汇编语言(王爽·第4版)—— 完整复习笔记

· 50 min read · 汇编语言 , 8086 , 期末复习

面向考试 / 期末复习,基于王爽《汇编语言(第4版)》17 章全部核心知识点,CPU 模型为 8086(16 位、实模式)。


目录

  1. 基础知识
  2. 寄存器
  3. 寄存器与内存访问
  4. 第一个程序
  5. [BX] 和 loop 指令
  6. 包含多个段的程序
  7. 更灵活的定位内存地址的方法
  8. 数据处理的两个基本问题
  9. 转移指令的原理
  10. CALL 和 RET 指令
  11. 标志寄存器
  12. 内中断
  13. int 指令
  14. 端口
  15. 外中断
  16. 直接定址表
  17. BIOS 键盘输入与磁盘读写
  18. 附注与考试重点速查

1. 基础知识

1.1 机器语言与汇编语言

  • 机器语言:二进制机器指令的集合,CPU 能直接执行,但人难以读写。
  • 汇编语言:用助记符代替机器指令,经编译器翻译成机器语言。
  • 汇编语言的组成:①汇编指令(核心,有对应机器码)②伪指令(编译器执行,无机器码)③其他符号(如 +-)。

1.2 指令、数据与存储单元 🔥

  • 指令和数据在内存/磁盘中没有区别,都是二进制信息;CPU 在工作时把有的信息当指令、有的当数据,区别在于应用场合
  • 存储器被划分为若干存储单元,每个单元编号从 0 开始,称为物理地址
  • 8086 一个存储单元 = 1 字节(Byte)= 8 bit;1 字(Word)= 2 字节。

1.3 CPU 对存储器的读写

CPU 要读写数据,必须和外部芯片进行 3 类信息交互:

  1. 地址信息:存储单元的地址
  2. 控制信息:读还是写
  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(累加)AHAL
BX(基址)BHBL
CX(计数)CHCL
DX(数据)DHDL
  • 为保证兼容,上一代 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 = 18
mov ah, 78 ; ah = 78(不影响 AL)
add ax, 8 ; ax = ax + 8
mov ax, bx ; ax = bx
add ax, bx ; ax = ax + bx
  • 指令进行两个操作对象位数一致的运算;如 mov al, 98Hadd 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 指向的内容当作指令执行
    1. 从 CS×16+IP 单元读取指令,送入指令缓冲器;
    2. IP = IP + 所读指令长度(指向下一条指令);
    3. 执行指令。(取指与执行重叠进行)

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, 1000H
mov 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 执行过程

  1. SP = SP − 2(栈顶指针先向低地址移动)
  2. 将 ax 内容送入 SS:SP 指向的字单元

pop ax 执行过程

  1. 将 SS:SP 指向的字单元数据送入 ax
  2. SP = SP + 2
  • 空栈时,SP 指向栈底单元的下一个单元(栈空间最高地址的下一单元)。
  • 栈增长方向:从高地址向低地址增长
mov ax, 1000H
mov ss, ax
mov 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 21H
codesg 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 21HP 正常结束返回。

5. [BX] 和 loop 指令

5.1 [BX]

  • [bx] 表示内存单元,其偏移地址在 bx 中,段地址默认 DS。
mov bx, 0
mov ax, [bx] ; 等价于 mov ax, (DS:0)
inc bx ; bx = bx + 1

5.2 loop 指令 🔥🔥🔥

  • CPU 执行 loop 标号
    1. (cx) = (cx) − 1
    2. 判断 cx:不为 0 则转至标号处执行;为 0 则向下执行。
  • cx 中存放循环次数

循环程序框架

mov cx, 12 ; 循环次数
mov ax, 0
s: add ax, cx ; s 为标号
loop s ; cx-- ≠0 就跳到 s

5.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 s

5.5 段前缀与安全空间

  • 段前缀:显式给出段地址,如 ds:[bx]cs:[bx]ss:[bx]es:[bx]
  • 一段安全的空间:在一般 PC 机中,0:200H ~0:2FFH 的 256 字节空闲安全,Debug 实验可直接使用;不要随意向未知内存写数据。

6. 包含多个段的程序

6.1 在代码段中使用数据

  • 程序中定义的数据放在代码段最前面时,要让 CS:IP 跳过数据,用 end 标号 指定程序入口
assume cs:code
code segment
dw 0123H, 0456H, 0789H ; define word,定义 3 个字数据
; ...
start: ; 程序入口标号
mov bx, 0
mov ax, 0
mov cx, 3
s: add ax, cs:[bx]
add bx, 2
loop s
mov ax, 4c00H
int 21H
code ends
end 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:stack
data segment
dw 0123H, 0456H
data ends
stack segment
dw 0,0,0,0,0,0,0,0
stack ends
code segment
start:
mov ax, stack
mov ss, ax
mov sp, 16
mov ax, data
mov ds, ax ; 访问 data 段前,先把段地址送 DS
; ...业务指令...
mov ax, 4c00H
int 21H
code ends
end start

关键assume 只是给编译器的”假设”,CPU 并不会因此自动把段地址送入 DS/SS,段地址必须由指令自己设置;只有 CS:IP 由加载程序根据 end 入口标号 自动设置。


7. 更灵活的定位内存地址的方法

7.1 and 和 or 指令 🔥

  • and:按位与(两 1 才 1),常用于把指定位清零
  • or:按位或(有 1 就 1),常用于把指定位置 1
mov al, 01100011B
and al, 11011111B ; 第 5 位清零,其余不变 → 01000011B
or 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
  • 源程序中字符常量用引号: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, 0
mov ax, [si]
mov di, 0
mov 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 位)两种尺寸,由以下方式指明:

  1. 寄存器名指明mov ax, [0] 是字操作,mov al, [0] 是字节操作。
  2. 操作符 word ptr / byte ptr 指明
mov word ptr [bx], 0 ; 向 [bx]、[bx+1] 组成的字单元送 0
mov byte ptr [bx], 0 ; 只向 [bx] 字节单元送 0
  1. 其他方式:如 push/pop 默认只进行字操作。

8.5 div 指令(除法) 🔥🔥🔥

  • 除数:8 位或 16 位,在寄存器或内存中。
  • 被除数默认放在 AX(8 位除法)或 DX 和 AX(16 位除法,DX 存高 16 位)
除数宽度被除数余数
8 位AXALAH
16 位DX(高):AX (低)AXDX
; 计算 100001 / 100(16 位除法)
mov dx, 1
mov ax, 86A1H ; DX:AX = 186A1H = 100001
mov bx, 100
div 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,0
db 3 dup (0,1,2) ; 等价 db 0,1,2,0,1,2,0,1,2
db 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] ; 段内:从内存字单元取偏移送 IP
jmp dword ptr [bx] ; 段间:低字 → IP,高字 → CS

9.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 🔥

指令等价操作用途
retpop IP段内近返回
retfpop IPpop 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 位ALr8/m8AX
16 位AXr16/m16DX(高 16 位):AX (低 16 位)
mov al, 64
mov bl, 64
mul bl ; AX = 64×64 = 100H

10.5 模块化程序设计与参数传递 🔥

  • 子程序(过程)= 具有一定功能的程序段,用 call 调用、ret 返回,避免重复代码。
  • 参数和结果传递的三种方式
方式适用
寄存器传递参数少
内存单元(数据表)传递参数多(批量数据)
栈传递通用、规范(见附注)

10.6 寄存器冲突问题 🔥🔥🔥

  • 子程序中使用的寄存器可能与主程序正在使用的冲突。标准解决框架:子程序开头把用到的寄存器全部压栈,返回前出栈恢复(注意出栈顺序相反):
sub: push ax ; 保存现场
push bx
push cx
; ... 子程序功能,随意使用 ax/bx/cx ...
pop cx ; 恢复现场(逆序)
pop bx
pop ax
ret

11. 标志寄存器

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, 1
sub 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 位有符号范围 −128127;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:ax
add ax, bx ; 低 16 位相加,产生 CF
adc dx, cx ; 高 16 位相加时再加 CF

11.5 cmp 指令 🔥🔥🔥

  • cmp ax, bxax − bx 运算但不保存结果,只影响标志寄存器,供后续条件转移指令判断。

无符号数比较结果判断

cmp a,b 后关系标志
ZF=1a = b
CF=1a < b产生借位
CF=0 且 ZF=0a > b

有符号数比较:看 SF 与 OF 是否相同(a>b ⇔ SF=OF 且 ZF=0;a<b ⇔ SF≠OF)。

11.6 条件转移指令 🔥🔥🔥🔥🔥

条件转移指令都是段内短转移(位移 −128~127),通常紧跟 cmp 使用。

指令含义判断条件等价指令
je相等则转移ZF=1jz
jne不等则转移ZF=0jnz
jb低于则转移(无符号)CF=1jnae/jc
jnb不低于则转移CF=0jae/jnc
ja高于则转移(无符号)CF=0 且 ZF=0jnbe
jna不高于则转移CF=1 或 ZF=1jbe
jl小于则转移(有符号)SF≠OFjnge
jge大于等于则转移SF=OFjnl
jg大于则转移(有符号)SF=OF 且 ZF=0jnle
jle小于等于则转移SF≠OF 或 ZF=1jng

记忆:equal 相等;below/above 无符号高低;less/greater 有符号大小;n = not。

cmp ax, bx
je 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
OFOVNV
DFDNUP
IFEIDI
SFNGPL
ZFZRNZ
PFPEPO
CFCYNC

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 硬件自动完成(中断过程):

  1. 取得中断类型码 N
  2. pushf(标志寄存器压栈)
  3. TF = 0、IF = 0(中断处理过程中不响应单步中断、可屏蔽外中断)
  4. push CSpush IP
  5. (IP) = (N×4)(CS) = (N×4+2) —— 转去执行中断例程

12.4 iret 指令 🔥

中断例程末尾用 iret 返回,等价于:pop IPpop CSpopf,与中断过程的压栈正好对应。

中断例程标准结构

doit: push ax ; 保护现场
; ... 处理 ...
pop ax ; 恢复现场
iret ; 中断返回

12.5 编程处理 0 号中断(除法错误)

完整套路:①编写中断例程 do0 → ②安装:把 do0 复制到不会被覆盖的内存(0:200H 起)→ ③设置中断向量表:把 0 号表项改为 0:200H

; 设置 0 号中断向量:0:0 = 200H,0:2 = 0
mov ax, 0
mov es, ax
mov word ptr es:[0], 200H
mov word ptr es:[2], 0

12.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, 0
mov cx, 1 ; 显示次数
int 10H

DOS 的 int 21H(程序功能)

ah功能
4CH程序返回(mov ax,4c00H + int 21H
02H显示一个字符(dl=字符)
09H显示以 ’$’ 结尾的字符串(ds:dx 指向串首)
01H读取一个键盘输入(结果在 al)
0AH读取一个字符串到缓冲区
; int 21h 9 号功能显示字符串
assume cs:code
data segment
str db 'Hello, Assembly!', '$' ; 必须以 $ 结束
data ends
code segment
start:
mov ax, data
mov ds, ax
mov dx, offset str
mov ah, 9
int 21h
mov ax, 4c00h
int 21h
code ends
end start

14. 端口

14.1 端口的读写 🔥🔥

  • 各种外设/接口卡的寄存器称为端口,CPU 通过端口地址统一编址访问(与内存地址空间独立)。
  • 访问端口只有两条指令:in(读)/ out(写)
in al, 60H ; 从 60H 端口读一个字节到 al
out 20H, al ; 把 al 写入 20H 端口
  • 端口地址 0~FFH 可直接写在指令中;超过 FFH(范围 0~FFFFH)必须先放入 dx
mov dx, 3F8H
in al, dx
out dx, al

14.2 CMOS RAM 芯片 🔥

  • CMOS RAM 包含一个实时钟和 128 字节 RAM,靠电池供电,关机后时钟继续走。
  • CPU 通过两个端口访问:
    • 70H(地址端口):写入要访问的 CMOS 单元地址;
    • 71H(数据端口):读写该单元内容。
mov al, 0
out 70H, al ; 选中 0 号单元(秒)
in al, 71H ; 读出秒
  • CMOS 中时间信息以 BCD 码存放(一个字节两位十进制,高 4 位十位、低 4 位个位)。
单元内容单元内容
07
28
49

14.3 shl 和 shr 指令 🔥

  • shl(逻辑左移):各位左移 X 位,最低位补 0,最后移出的一位进入 CF;移动位数 >1 时位数放在 cl
  • shr(逻辑右移):各位右移,最高位补 0,最后移出位进 CF。
mov al, 01001000B
shl al, 1 ; 左移 1 位 = ×2 → 10010000B,CF=0
mov cl, 4
shr 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 机键盘的处理过程 🔥🔥🔥

  1. 键盘按下/松开产生扫描码,送入主板上接口芯片的 60H 端口(通码按下、断码松开)。
  2. 接口芯片向 CPU 发出类型 9 的可屏蔽中断
  3. CPU 若 IF=1,执行 int 9 中断例程:
    • 从 60H 端口读入扫描码;
    • 将扫描码转换为 ASCII 码/状态信息,存入键盘缓冲区
  4. 处理完成后向 20H 端口发送中断结束指令 EOI(值 20H)
mov al, 20H
out 20H, al ; 向外设中断控制器发送 EOI

15.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, al
mov bh, 0
mov 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, 0
int 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:bx
mov ax, data
mov es, ax
mov bx, 0
mov ah, 2
mov al, 1
mov ch, 0
mov cl, 1
mov dh, 0
mov dl, 0
int 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 位补码范围 −128127;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、BPSP 栈顶;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 易混点最后过一遍

  1. 指令和数据在内存中无差别,区别在 CPU 当前如何使用。
  2. 段是逻辑概念,段起始地址必为 16 的倍数,段长上限 64KB。
  3. 小端:字的低字节在低地址;栈从高地址向低地址增长,push 先 SP−2。
  4. mov 不能给段寄存器立即数,要经通用寄存器中转;两个内存单元不能直接传送。
  5. 源程序中 [0] 是立即数,要写 ds:[0];Debug 中 [0] 才是内存。
  6. assume 不负责填段寄存器,DS/SS 要自己 mov,CS:IP 由 end 入口 自动设置。
  7. CF 看无符号进位,OF 看有符号溢出,两者独立。
  8. 条件转移:无符号用 b/a(below/above),有符号用 l/g(less/greater)。
  9. 中断向量表每项 4 字节:低字 IP、高字 CS,N 号中断在 0:N ×4。
  10. 中断过程硬件自动:pushf → TF=0、IF=0 → push CS、push IP → 跳转;iret 正好反向。

💡 全书主线:寄存器 → 内存分段寻址 → 栈 → 程序结构 → 灵活寻址 → 转移与子程序 → 标志位 → 中断体系(内中断 → int → 端口 → 外中断)。抓住”CPU 如何用 CS:IP 取指、用 DS/bx 寻址、用 SS:SP 管理栈、用中断向量表找例程”这四条线,所有章节都能串起来。