第十二章 内中断
文章目录
章节内容
概述
本章前半部分主要介绍了什么是内中断,包括中断的产生,中断的过程,中断的处理,中断向量表等。
后半部分为实验11"编写0号中断的处理程序"的思路和主要步骤(这里将放到实验部分描述),以及单步中断和响应中断的特殊情况。
内中断
CPU执行完指令后可以检测到来自CPU外部或内部的一种特殊信息,并且立即对所接收到的信息进行处理,称之为中断。
来自CPU内部的中断信息称之为内中断
内中断的产生
有以下情况会产生内中断
- 除法错误,比如div指令产生的除法溢出;
- 单步执行;
- 执行into指令;
- 执行int指令。
中断信息会给CPU提供一个字节的中断类型码(共256种),以反映中断来源(称为中断源),上述四种类型中断源的中断类型码分别是0,1,4和n(int 指令格式为int n,n为中断类型码)。
中断处理程序
CPU接收到中断信息后,查询中断向量表,找到相应中断处理程序的地址,将CS:IP指向它,从而跳转到中断处理程序处。
中断向量表
中断向量表在内存中保存,存放了256个中断源所对应的中断处理程序的入口(地址)。从内存0000:0000到0000:03FF的1024个单元依次存放了从0号到255号中断类型码对应的中断程序入口。
存储N号中断源对应的中断处理程序入口的偏移地址的内存单元地址为4N,段地址的内存单元地址为4N+2。
中断过程
CPU收到中断信息后按以下过程处理:
- 从中断信息中取得中断类型码;
- 标志寄存器的值入栈;
- 设置标志寄存器的第8位TF和第9位IF的值为0;
- CS的内容入栈;
- IP的内容入栈;
- 从内存地址为N*4和N*4+2(设中断类型码为N)的两个字单元中读取中断处理程序的入口地址设置IP和CS。
相当于:
- 取得中断类型码N
- pushf
- TF=0,IF=0
- push CS
- push IP
- (IP)=(N*4),(CS)=(N*4+2)
这里设置TF和IF两位标志寄存器的值后面再讲原因。
中断处理程序和iret指令
对于CPU,中断随时可能发生,故中断处理程序应一直存储在内存某段空间中,中断向量应存在对应中断向量表中。
中断处理程序的常规步骤:
- 保存用到的寄存器
- 处理中断
- 恢复用到的寄存器
- iret指令返回
iret 相当于:
- pop IP
- pop CS
- popf
编程处理0号中断(除法错误中断)
我们在实验11中详细给出。
单步中断
CPU提供了这样一个功能:执行完一条指令后,检测TF位是否为1,若TF位为1,则产生单步中断,终端类型码为1,引发中断,跳转到1号中断处理程序。
提供这样的单步中断功能,为单步跟踪程序执行过程提供了实现机制。
例如Debug程序,每次使用t命令,CPU就执行一条指令。实际就是Debug在执行t命令时将TF位置1,CPU判断TF位为1,就停止当前的程序转到1号中断处理程序,这里Debug提供的1号中断处理程序将所有寄存器的内容显示在屏幕上,并且等待下一个命令输入。
注意只要CPU执行完一条指令后TF为1,就会触发单步中断。而单步中断的中断处理程序也是一个由一条条指令组成的程序,过程中若TF一直为1,则会不断触发单步中断,程序陷入无限循环嵌套。所以在中断过程中,有将TF和IF置0的过程(IF涉及到外中断)。
响应中断的特殊情况
部分情况下,CPU执行完指令后,发生中断也不会触发响应。
其中一种便是向ss寄存器传送数据的指令。由于ss:sp联合指向栈顶,二者设置应连续完成。我们知道中断过程中有将标志寄存器、CS、IP的值压入栈的过程,若ss改变后发生中断,则会指向错误的栈顶,入栈地址错误。所以在向ss寄存器传送数据后,不响应中断。同时我们也应该将ss和sp的值连续的设置。
实验11 编写0号中断的处理程序
编写0号中断的处理程序,使得在除法溢出发生时,在屏幕中间显示字符串"divide error!",然后返回到DOS。
0号中断是除法错误中断,比如执行div指令发生了除法溢出。
执行以下程序时,会发生除法溢出:
assume cs:code
code segment
start:
mov ax,1000h
mov bh,1
div bh
mov ax,4c00h
int 21h
code ends
end start
我们现在希望在发生除法溢出时,在屏幕中间显示字符串"divide error!",然后返回到DOS。
分析一下,首先按照中断处理过程,CPU获取中断类型码0,0号中断,将标志寄存器入栈置位再入栈CS:IP后,在0000:0002处找到0号中断处理程序的入口地址。
为了随时可执行0号中断处理程序(以下称do0),需将其放在一块固定的内存空间中。我们知道中断向量表有1K的空间,但实际上存放中断处理程序入口地址的空间远不到1K,后一大段内存单元(一般为0000:0200到0000:02FF的256个字节)为空。所以我们将do0程序存放在此处。
总的来说,分为3步:
- 编写do0程序,程序内容为在屏幕中间显示字符串"divide error!",然后返回到DOS。
- 将do0存放在0000:0200处。
- 将0000:0200这个地址存在中断向量表0号表项(0000:0002)处。
我们称将do0程序送入0000:0200处的过程称为安装。
编写完do0,安装然后设置中断向量后(编写do0和安装是在同一个程序中,顺序可以反过来,安装程序只是把do0代码复制到0000:0200处),再执行其他程序发生除法溢出后,就会看到我们想要的结果。
其他细节在代码注释中给出,代码:
assume cs:code
code segment
start:
;安装do0
;这里使用rep和movsb复制do0
;ds:si为源地址
mov ax,cs
mov ds,ax
mov si,offset do0
;es:di为目标地址
mov ax,0
mov es,ax
mov di,200h
;用编译器配合offset计算传送的长度
mov cx,offset do0_end-offset do0
;传送方向
cld
;传送
rep movsb
;设置中断向量表
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:
;程序从do0处进入,从do0_start处开始,所以跳转过去
jmp short do0_start
do0_data:
;将要显示的字符串也随程序一起记录在内存中,保证不会被覆盖
db "overflow!"
do0_start:
;设置ds:si指向字符串
mov ax,cs
mov ds,ax
mov si,202h;这里si要设置成最后在内存中字符串的位置202h
;设置es:si指向显存位置
mov ax,0b800h
mov es,ax
mov di,12*160+36*2;行号列号
;字符串长度
mov cx,9
;显示字符串
copy_start:
mov al,[si]
mov es:[di],al
inc si
add di,2
loop copy_start
;返回DOS
mov ax,4c00h
int 21h
;do0结束(方便计算传送长度)
do0_end:nop
code ends
end start
结果对比:
这样设置的中断向量表,重启之后会被刷新重新指向。