汇编:中断及处理
中断(Interrupt)是 CPU 暂停当前执行流程、转而处理紧急 / 特殊事件(如硬件请求、软件异常、系统调用)的机制,是操作系统和硬件交互的核心。以下从中断的核心概念、中断处理流程、汇编实现示例三方面详细讲解。
一、中断的核心概念
1. 中断的分类
| 类型 | 触发方式 | 典型场景 |
|---|---|---|
| 硬件中断 | 外设(如键盘、硬盘)触发 | 键盘按键、鼠标点击、定时器溢出 |
| 软件中断 | 指令主动触发(如INT n) | 系统调用(如 DOS 的INT 21H)、程序异常 |
| 异常(陷阱) | 指令执行错误 | 除 0、内存越界、缺页 |
2. 中断向量表(IVT)
x86 架构中,中断处理的核心是中断向量表(IVT):
- 位于内存低地址(0x00000 ~ 0x003FF),共 256 个中断向量(0~255);
- 每个向量占 4 字节:
2字节偏移地址(IP) + 2字节段地址(CS),指向对应中断处理程序的入口; - 例如:中断号
0x21对应 IVT 中0x21*4=0x84地址开始的 4 字节,存储 DOS 系统调用处理程序的地址。
3. 中断处理的核心步骤
- 触发中断:硬件 / 软件向 CPU 发送中断请求(如执行
INT 0x21); - 保护现场:CPU 自动压栈
FLAGS、CS、IP(若有错误码则额外压栈); - 关中断:CPU 自动清除
FLAGS中的 IF 位(避免嵌套中断干扰); - 查找处理程序:根据中断号计算 IVT 地址,加载
CS:IP指向中断处理程序; - 执行处理程序:处理中断逻辑(需手动保护通用寄存器);
- 恢复现场:手动恢复寄存器,执行
IRET指令(自动弹出IP、CS、FLAGS); - 恢复执行:回到原程序中断处继续执行。
![]()
二、示例:系统中的0号中断
assume cs:code
code segment
start:
mov ax,8
mov bh,0
div bh
code ends
end start
用DOSBOX-X运行如下:

一、先明确:触发的是哪类中断?
div bh是8 位除法指令:
- 被除数默认在
AX(16 位),除数是BH(8 位),运算逻辑为AX ÷ BH,结果商存AL、余数存AH。 - 你代码中
BH=0,除数为 0 → 触发除法溢出中断(Divide Error),对应 x86 的中断类型号 0(Intel 官方定义,中断类型 0 是除法错误 / 溢出)。
二、中断向量表(IVT):中断类型号→中断服务程序入口的映射
x86 实模式下,内存最低地址0000:0000~0000:03FF(共 1KB)是中断向量表(IVT),每个中断类型号对应一个 “4 字节入口地址”(2 字节 IP + 2 字节 CS),规则:
中断类型号
n→ 向量表地址 =0000: n×4
入口地址格式:
[0000:n×4] = IP,[0000:n×4+2] = CS
对于中断类型 0:
向量表地址 = 0×4 = 0000:0000 → 需读取0000:0000(IP)和0000:0002(CS),这两个值决定了中断服务程序的入口。
三、为什么最终 CS=F000H、IP=CA60H?
DOSBox-X 模拟的是IBM PC/XT/AT 兼容的实模式环境,而 PC 兼容机的 BIOS(基本输入输出系统)将核心中断服务程序存放在F000:0000~FFFF:FFFF(64KB BIOS ROM 区)。具体流程:
步骤 1:BIOS 初始化中断向量表
开机时,BIOS 会初始化 IVT,将中断类型 0 的入口地址 设置为 BIOS 中 “除法错误处理程序” 的地址:
- 向
0000:0000写入CA60H(IP); - 向
0000:0002写入F000H(CS)。
这是 PC 兼容机的标准 BIOS 布局(不同 BIOS 版本可能略有差异,但F000H段是 BIOS ROM 的核心段,CA60H是除法错误处理程序在该段的偏移)。
步骤 2:CPU 响应中断的硬件行为
当div bh触发除法错误时,CPU 自动执行以下操作(实模式):
- 压栈 FLAGS、CS、IP(保护现场);
- 从 IVT 的
0000:0000读取 IP(CA60H),从0000:0002读取 CS(F000H); - 将 CS 设为
F000H,IP 设为CA60H,跳转到该地址执行中断服务程序。
因此你在 DOSBox-X 中看到的CS=F000H、IP=CA60H,正是 CPU 从 IVT 中读取的、BIOS 预设的 “除法错误中断服务程序” 入口地址。
三、编制中断处理程序
问题:自行编写0号中断处理程序
assume cs:code
code segment
start:
; 源地址:cs:offset do0(当前代码段的do0标签处)
mov ax,cs
mov ds,ax ; ds = cs(源段地址)
mov si,offset do0 ; si = do0的偏移地址(源偏移)
; 目标地址:0:200H
mov ax,0
mov es,ax ; es = 0(目标段地址)
mov di,200H ; di = 200H(目标偏移)
; 复制长度:do0end - do0(中断处理程序的字节数)
mov cx,offset do0end-offset do0
; 将方向标志DF清零,使字符串操作(如movsb)按地址递增方向执行
; 即:每次操作后SI和DI自动增加(+1,取决于操作数大小,这里是字节操作所以+1)
cld ;置DF=0,SI和DI自动+1,并将DS:[SI]复制到ES:[DI]
rep movsb
;将自定义的CS:0000,IP=0200分别写入0号中断地址0000:0000和0000:0002处
mov ax,0
mov es,ax
mov word ptr es:[0*4],200H
mov word ptr es:[0*4+2],0
mov ax,4c00h
int 21h
do0:jmp short do0start
db 'overflow'
do0start:
;这三行代码的任务就是定位到字符串‘overflow’的首地址。
mov ax,cs
mov ds,ax
mov si,202H
;这一段就是将‘overflow’在屏幕中间显示出来
mov ax,0B800H
mov es,ax
mov di,12*160+36*2
mov cx,9
s: mov al,[si]
mov es:[di],al
inc si
add di,2
loop s
mov ax,4c00h
int 21H
do0end:nop
code ends
end start
关键点说明:
1.cld指令:在每次调用 rep movsb前都应确保方向标志正确。CLD表示正向(从低地址到高地址),STD表示反向。
2.rep movsb执行过程:
- 检查 CX 是否为 0,为 0 则结束
-
执行
movsb:将DS:[SI]的一个字节复制到ES:[DI] -
SI 和 DI 自动 +1(因为 DF=0)
-
CX 减 1
- 重复直到 CX=0
3.offset偏移地址的说明
在 8086 汇编中,offset 标号 计算的是该标号相对于其所在段的段起始地址的偏移地址(段内偏移),具体到这段代码中:
核心结论
offset do0 的偏移地址是相对于 code 段的段起始地址(code 段的基地址)计算的,与 CS 寄存器的值无关(编译 / 汇编阶段确定,而非运行时)。
详细解释
3.1. offset 是汇编期伪操作,而非运行时指令
offset 由汇编器(如 MASM/TASM)在编译阶段计算,而非 CPU 运行时计算:
- 汇编器会为每个段分配一个 “虚拟的段起始地址”(默认从 0 开始),然后计算标号相对于该起始地址的偏移。
- 例如:
start是code段的第一个标号,offset start = 0;- 从
start到do0之间的指令会占用字节,汇编器会累加这些字节数,得到do0相对于code段起始的偏移。
3.2. 结合代码的实际场景
这段代码中,do0 位于 code 段内,因此:
mov si,offset do0 ; si = do0在code段内的偏移(汇编期确定)
而后续通过 mov ax,cs; mov ds,ax 将 DS 指向 CS(即 code 段的实际段地址),此时 DS:SI 就精准指向了 do0 在内存中的实际地址。
3.3. 关键区分:段内偏移 vs 物理地址
offset do0:仅表示段内偏移(16 位),与段地址无关;- 物理地址 = 段地址 × 16 + 段内偏移(如
CS×16 + offset do0是do0的实际物理地址)。
4.修改 0 号中断向量表
mov ax,0
mov es,ax ; es = 0(中断向量表所在段)
mov word ptr es:[0*4],200H ; 0:0 写入偏移地址200H
mov word ptr es:[0*4+2],0 ; 0:2 写入段地址0
中断向量表中,0 号中断的自定义入口地址存放在 0:0(偏移)和 0:2(段地址),这里将其指向自定义的中断处理程序0:200H,如下图所示:

5.为什么是 202H?
自定义中断处理程序do0被复制到0:200H,而do0的第一条指令是:
do0:jmp short do0start ; jmp short 是2字节指令(偏移200H~201H)
db 'overflow' ; 字符串从偏移202H开始
因此SI=202H恰好指向字符串overflow的第一个字符(o),后续可通过DS:SI逐个读取字符。
这段代码的任务就是定位到‘overflow’字符串的首地址,以便后续使用每个字符。
6.为什么是 36 列(36×2)?
要让overflow(9 个字符)在 80 列的屏幕中水平居中,计算逻辑如下:
- 屏幕总列数:80 列;
- 字符串长度:9 个字符;
- 居中列起始位置 = (80 - 9) ÷ 2 = 35.5 → 取整为 36 列(视觉上更居中)。
对应显存偏移:
- 第 36 列的偏移 = 36 列 × 2 字节 / 字符 =
72(即36*2,十六进制为48H)。 -
di = 12*160 + 36*2四、中断程序的运行
将上面编写的中断程序使用masm命令编译、联接后生成exe文件,直接运行exe文件。
然后,进入debug模式,用a命令编写三行测试代码:
mov ax,8
mov bh,0
div bh
再使用g命令直接无中断运行,‘overflow’就会出现在屏幕中间了。



浙公网安备 33010602011771号