内中断
任何一个通用的CPU,都具备一种能力,可以在执行完当前正在执行的指令之后,检测到从CPU外部发送过来的或内部产生的一种特殊信息,并且可以立即对所接收到的信息进行处理。这种特殊的信息,我们可以称其为:中断信息。中断的意思是指,CPU不再接着(刚执行完的指令)向下执行,而是转去处理这个特殊信息。
1. 内中断的产生
对于8086CPU,当CPU内部有下面的事情发生的时候,将产生相应的中断信息。
- 除法错误:比如,执行div指令产生的除法溢出;
- 单步执行;
- 执行into指令
- 执行int指令。
我们先不用关心这四种情况的具体含义,只要知道CPU内部有4种情况可以产生需要及时处理的中断信息即可。既然是不同的信息,就需要进行不同的处理。要进行不同的处理,CPU首先要知道,所接收到的中断信息的来源。所以中断信息中必须包含识别来源的编码。8086CPU用称为中断类型码的数据来标识终端信息的来源。中断类型码为一个字节型数据,可以表示256种中断信息的来源。
在8086CPU中的中断类型码如下:
- 除法错误:0
- 单步执行:1
- 执行into指令:4
- 执行int指令,该指令的格式为int n,指令中的n为字节型立即数,是提供给CPU的中断类型码。
2. 中断处理程序和中断向量表
CPU收到中断信息后,需要对中断信息进行处理。而如何对中断信息进行处理,可以由我们编程决定。我们编写的,用来处理中断信息的程序被称为中断处理程序。一般来说,需要对不同的中断信息编写不同的处理程序。
CPU在收到中断信息后,应该转去执行该中断信息的处理程序。我们知道,若要8086CPU执行某处的程序,就要将CS:IP指向它的入口。可见,首要的问题是,CPU在收到中断信息后,如何根据中断信息确定其程序的入口。
我们知道,中断信息中包含有标识中中断源的类型码。CPU用8位的中断类型码通过中断向量表找到相应的中断处理程序的入口地址。那么,什么是中断向量表呢?中断向量表就是中断向量的列表,而中断向量就是中断处理程序的入口地址。展开来讲:中断向量表,就是中断处理程序入口地址的列表。
也就是说:CPU只要知道了中断类型码,就可以将中断类型码作为中断向量表的表项号,定位相应的表项,从而得到中断处理程序的入口地址。
对于8086机,中断向量表指定放在内存地址0处。内存0000:0000到0000:03FF的1024个单元中存放着中断向量表。一个表项存放一个中断向量,也就是一个中断处理程序的入口地址。对于8086CPU,这个入口地址包含段地址和偏移地址,所以一个表项占两个字,高地址字存放段地址,低地址字存放偏移地址。
3. 中断过程
用中断类型码找到中断向量,并用它设置CS和IP,这个工作是由CPU的硬件自动完成的。CPU硬件完成这个工作的过程被称为中断过程。
CPU收到中断信息后,要对中断信息进行处理,首先将引发中断过程。硬件在完成中断过程后,CS:IP将指向中断处理程序的入口,CPU开始执行中断处理程序。
CPU在执行完中断处理程序后,应该返回原来的执行点继续执行下面的指令。所以在中断过程中,在设置CS:IP前,还要将原来的CS和IP的值保存起来。
下面是8086CPU在收到中断信息后,所引发的中断过程。
- (从中断信息中)取得中断类型码
- 标志寄存器的值入栈(因为在中断过程中要改变标志寄存器的值)
- 设置标志寄存器的第8位TF和第9位IF的值位0
- CS的内容入栈
- IP的内容入栈
- 从内存地址为 中断类型码x4 和中断类型码x4+2 的两个字单元中读取中断处理程序的入口地址设置IP和CS。
更简洁的描述过程如下:
- 取得中断类型码N
- pushf
- TF = 0, IF = 0
- push CS
- push IP
- (IP) = (Nx4), (CS) = (Nx4+2)
4. 中断处理程序和iret指令
由于CPU随时都能检测到中断信息,也就是说,CPU随时都可能执行中断处理程序,所以中断处理程序必须一直存储在内存某段空间中。而中断处理程序的入口地址,即中断向量,必须存储在对应的中断向量表表项中。
中断处理程序的编写方法和子程序相似,下面是常规步骤:
- 保存用到的寄存器
- 处理中断
- 恢复用到的寄存器
- 用iret指令返回
ire指令的功能用汇编语法描述为:
pop IP
pop CS
popf
iret通常和硬件自动完成的中断过程配合使用。
5. 除法错误中断的处理和编程处理0号中断
0号中断即除法错误中断的处理。当CPU执行div等除法指令的时候,如果发生了除法溢出错误,将产生中断类型码为0的中断信息。
现在,我们改变一下0号中断处理程序的功能,即重新编写一个0号中断处理程序,它的功能实在屏幕中间显示"overflow!",然后返回到操作系统。
我们将这段程序称为do0,现在的问题是:do0应该放在内存中。因为除法溢出随时可能发生,CPU随时都可能将CS:IP指向do0的入口,执行程序。由于过多地讨论申请内存会偏离问题地主线,在可能的情况下,我们不去理会操作系统,而直接面向硬件资源。我们只需找到一块别的程序不会用到的内存区,将do0传送到其中即可。前面讲到,内存0000:0000~0000:03FF,大小为1KB的空间是系统存放中断处理程序入口地址的中断向量表。8086支持256个中断,但是实际上,系统中要处理的中断事件远没有达到256个。所以在中断向量表中,有许多单元是空的。中断向量表是PC系统中最重要的内存其,只用来存放中断处理程序的入口地址,DOS系统和其它应用程序都不会随便使用这段空间。一般情况下:从0000:0200-0000:02FF的256个字节的空间所对应的中断向量表项都是空的。
结论:我们可以将do0传送到内存0000:0200处。
总结上面的分析,我们要做一下几件事情。
- 编写可以显示”overflow!"的中断处理程序:do0;
- 将do0送入内存0000:0200处;
- 将do0的入口地址0000:0020存储在中断向量表0号表项中。
程序的框架如下:
assume cs:code
code segment
start: do0安装程序
设置中断向量表
mov ax, 4c00H
int 21H
do0: 显示字符串"overflow!"
mov ax, 4c00H
int 21H
code ends
end start
完整程序如下:
assume cs:code
code segment
start: mov ax, cs
mov ds, ax
mov si, offset do0 ;设置ds:si指向源地址
mov ax, 0
mov es, ax
mov di, 0200H ;设置es:di指向目的地址
mov cx, offset do0end - offset do0 ;设置cx为传输长度
cld ;设置传输方向为正
rep movsb
;开始设置中断向量表
mov ax, 0
mov es, ax
mov word ptr es:[0*4], 0200H
mov word ptr es:[0*4+2], 0000H
mov ax, 4c00H
int 21H
do0: jmp short do0start
db "overflow!"
do0start: mov ax, cs
mov ds, ax
mov si, 0202H ;设置ds:si指向字符串
mov ax, 0B800H
mov es, ax
mov di, 12*160+36*2 ;设置es:di指向显存空间的中间位置
mov cx, 9 ;设置cx为字符串长度
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
6. 单步中断
基本上,CPU在执行一条指令后,如果检测到标志寄存器的TF位为1,则产生单步中断,引发中断过程。单步中断的中断类型码为1,它引发的中断过程如下:
- 取得中断类型码1;
- 标志寄存器入栈,TF、IF设置为0;
- CS、IP入栈;
- (IP) = (1x4), (CS) = (1x4+2)。
单步中断能力的提供为Debug模式的T命令提供了实现基础。当TF = 1时,CPU在执行完一条指令后将引发单步中断,转去执行中断处理程序。注意,中断处理程序也是由一条条指令组成的,如果在执行中断处理程序之前,TF = 1,则CPU在执行完中断处理程序的第一条指令后,又产生单步中断,如此下去陷入循环,CPU永远执行单步中断处理程序的第一条指令。所以在进入中断处理程序之前,设置TF=0。
7. 响应中断的特殊情况
一般情况下,CPU在执行完当前指令后,如果检测到中断信息,就响应中断,引发中断过程。可是,在有些情况下,CPU在执行完当前指令后,即便是发生中断,也不会响应。下面用一种情况来说明。
在执行完向ss寄存器传送数据的指令后,即便是发生中断,CPU也不会响应。这样做的主要原因是,ss:sp联合指向栈顶,而对它们的设置应该连续完成,避免指向不正确的栈顶,引发错误。