中断的意思是指,CPU不再接着(刚执行完的指令)向下执行,而是转去处理这个特殊信息。中断的信息有可能来自cpu内部,也有可能是外部。我们先来讨论内中断的情况。
1. 内中断的产生
n 当CPU 的内部有什么事情发生的时候,将产生需要马上处理的中断信息呢?
n 对于8086CPU,当内部有下面情况发生的时候,将产生中断信息:
n 1、除法错误,比如:执行div指令产生的除法溢出;
n 2、单步执行;
n 3、执行int0指令;
n 4、执行int 指令。
n 上述的4种中断源,在8086CPU中的中断类型码如下:
n (1)除法错误:0
n (2)单步执行:1
n (3)执行 int0 指令:
n (4)执行 int 指令 ,该指令的格式为 int n,指令中的n为字节型立即数,是提供给CPU的中断类型码。
2.中断处理程序
n CPU的设计者必须在中断信息和其处理程序的入口地址之间建立某种联系,使得CPU根据中断信息可以找到要执行的处理程序。
n 我们知道,中断信息中包含有标识中断源的类型码。根据CPU的设计,中断类型码的作用就是用来定位中断处理程序。
n 比如CPU 根据中断类型码 4,就可以找到4号中断的处理程序。
n 可随之而来的问题是,若要定位中断处理程序,需要知道它的段地址和偏移地址,而如何根据 8位的中断类型码得到中断处理程序的段地址和偏移地址呢?
3. 中断向量表
n CPU用 8 位的中断类型码通过中断向量表找到相应的中断处理程序的入口地址。
n 那么什么是中断向量表呢?
中断向量表就是中断向量的列表
n 中断向量表在内存中保存,其中存放着 256个中断源所对应的中断处理程序的入口,如下图所示:
如:0000:0000 12,23,34,23,a2,b3,c4,a2,a3,a4,a5,v3,b5,b6,b7,b8
四号中断源对应的入口地址为:b5,b6,b7,b8(因为一个中断源占用两个字的内存空间),其中
段地址为b7b8,偏移地址为b6b5。
4. 中断过程
n 从上面的讲解中,我们知道,可以用中断类型码,在中断向量表中找到中断处理程序的入口。
n 找到这个入口地址的最终目的是用它设置CS和IP,使CPU执行中断处理程序。
n 用中断类型码找到中断向量,并用它设置CS和IP,这个工作是由CPU的硬件自动完成的。
n CPU 硬件完成这个工作的过程被称为中断过程。
n 在使用 call 指令调用子程序时有同样的问题,子程序执行后还要返回到原来的执行点继续执行,
所以,call 指令先保存当前 CS 的 IP值,然后再设置CS和IP。
n 8086CPU的中断过程:
n (1)(从中断信息中)取得中断类型码;
n (2)标志寄存器的值入栈( 因为在中断过程中要改变标志寄存器的值,所以先将其保存在栈中。);
n (3)设置标志寄存器的第8位TF 和第9位IF的值为0;(这一步的目的后面将介绍)
n (4)CS的内容入栈;
n (5)IP的内容入栈;
n (6)从内存地址为中断类型码*4 和中断类型码 *4+2 的两个字单元中读取中断处理程序的入口地址设置IP和CS。
5. 中断处理程序
n 由于CPU随时都可能检测到中断信息,也就是说,CPU 随时都可能执行中断处理程序,所以中断处理程序必须一直存储在内存某段空间之中。
n 而中断处理程序的入口地址,即中断向量,必须存储在对应的中断向量表表项中。
n 中断处理程序的编写方法和子程序的比较相似,下面是常规的步骤:
n (1)保存用到的寄存器。
n (2)处理中断。
n (3)恢复用到的寄存器。
n (4)用 iret 指令返回。
n iret指令的功能用汇编语法描述为:
pop IP
pop CS
popf
n iret通常和硬件自动完成的中断过程配合使用。
n 可以看到,在中断过程中,寄存器入栈的顺序是标志寄存器、CS、IP ,而iret的出栈顺序是 IP、CS、标志寄存器,刚好和其对应,实现了用执行中断处理程序前的CPU现场恢复标志寄存器和CS、IP的工作。
6. 除法错误中断的处理
编程:当发生除法溢出时,在屏幕中间显示“overflow!”,返回DOS。
n 分析(1)当发生除法溢出的时候,产生0号中断信息,从而引发中断过程。
此时,CPU将进行以下工作:
n ① 取得中断类型码0;
n ② 标志寄存器入栈,TF、IF设置为0;
n ③ CS、IP入栈;
n ④ (IP) = (0*4),(CS) = (0*4+2)
n 分析(2)可见 ,当中断 0 发生时,CPU将转去执行中断处理程序。
只要按如下步骤编写中断处理程序,当中断0发生时,即可显示“overflow!”。
n ① 相关处理。
n ② 向显示缓冲区送字符串“overflow!”。
n ③ 返回DOS
n 我们将这段程序称为do0。
n 分析(3)现在的问题是:do0 应放在内存中。
因为除法溢出随时可能发生,CPU随时都可能将 CS:IP指向 do0的入口,执行程序。
n 问题变得简单而直接,我们只需找到一块别的程序不会用到的内存区,将do0传送到其中即可。
n 前面讲到,内存0000:0000~0000:03FF,大小为1KB的空间是系统存放中断处理程序入口地址的中断向量表
n 8086 支持 256 个中断,但是,实际上,系统中要处理的中断事件远没有达到256 个 。
所以在中断向量表中,有许多单元是空的。
n 中断向量表是PC系统中最重要的内存区,只用来存放中断处理程序的入口地址,DOS 系统和其他应用程序都不会随便使用这段空间。
n 分析(4 - 1)
我们将中断处理程序do0放到 0000:0200 后,若要使得除法溢出发生的时候,CPU转去执行do0,则必须将do0的入口地址,即0000:0200登记在中断向量表的对应表项中。
n 分析(4 - 2)
因为除法溢出对应的中断类型码为0,它的中断处理程序的入口地址应该从0×4地址单元开始存放,段地址存放在 0×4+2 字单元中,偏移地址存放在0×4字单元中
n 分析(4 - 3)
也就是说要将do0的段地址0存放在 0000:0002 字单元中 ,将偏移地址200H存放在0000:0000字单元中
n 总结上面的分析,我们要做以下几件事情:
n (1)编写可以显示“overflow!”的中断处理程序:do0;
n (2)将do0送入内存0000:0200处;
n (3)将do0的入口地址0000:0200存储在中断向量表0号表项中。
n 程序框架
assume cs:code
code segment
start: do0安装程序
设置中断向量表
mov ax,
int 21h
do0: 显示字符串“overflow!”
mov ax,
int 21h
code ends
end start
n 首先执行do0安装程序,将 do0 的代码拷贝到内存 0:200处,然后设置中断向量表,将do0的入口地址,即偏移地址200H和段地址0,保存在0号表项中。
n 这两部分工作完成后,程序就返回了
n 我们来看一下do0是如何变成0号中断的中断处理程序的:
(1)程序12.1 在执行时,被加载到内存中,此时do0的代码在程序12.1 所在的内存空间中,它只是存放在程序12.1的代码段中的一段要被传送到其他单元中的数据,我们不能说它是0号中断的中断处理程序;
(2)程序12.1中安装do0 的代码执行完后,do0的代码被从程序12.1的代码段中拷贝到0:200处。此时,我们也不能说它是0号中断的中断处理程序,它只不过是存放在0:200处的一些数据;
(3)程序12.1中设置中断向量表的代码执行完后,在0号表项中填入了do0的入口地址0:200,此时0:200 处的信息,即do0 的代码,就变成了0号中断的中断处理程序。
因为当除法溢出(即0号中断)发生时,CPU将执行0:200处的代码。
7. 安装
n 我们来看一下,用rep movsb指令的时候需要确定的信息:
n (1)传送的原始位置,段地址:code,偏移地址:offset do0;
n (2)传送的目的位置:0:200;
n (3)传送的长度:do0部分代码的长度;
n (4)传送的方向:正向。
assume cs:code
code segment
start: 设置es:di指向目的地址
设置ds:si指向源地址
设置cx为传输长度
设置传输方向为正
rep movsb
设置中断向量表
mov ax,
int 21h
do0: 显示字符串“overflow!”
mov ax,
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,200h ;设置es:di指向目的地址
mov cx,do0部分代码的长度;设置cx为传输长度
cld ;设置传输方向为正
rep movsb
设置中断向量表
mov ax,
int 21h
do0: 显示字符串“overflow!”
mov ax,
int 21h
code ends
end start
n 问题是,我们如何知道do0代码的长度?
n 最简单的方法是,计算一下do0 所有指令码的字节数。
但是这样做太麻烦了,因为只要do0的内容发生了改变,我们都要重新计算它的长度。
n 我们可以利用编译器来计算do0的长度。
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,200h ;设置es:di指向目的地址
mov cx,offset do0end-offset do0;设置cx为传输长度
cld ;设置传输方向为正
rep movsb
设置中断向量表
mov ax,
int 21h
do0: 显示字符串“overflow!”
mov ax,
int 21h
do0end:nop
code ends
end start
下面我们编写do0程序。
n do0程序的主要任务是显示字符串,程序如下:
do0:设置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,
int 21h
do0end:nop
n 程序写好了,可要显示的字符串放在哪里呢?
n 如果按照上面的方法,就大错特错了,因为字符串是在开头定义的,当程序执行完后,会释放内存。此时极有可能把字符串擦除。所以要想法将字符串定义放入db0这段空闲的内存区域中。
n 错误分析
n 注意,“overflow!”在程序的data段中。程序12.2执行完成后返回,它所占用的内存空间被系统释放,而在其中存放的“overflow!”也将很可能被别的信息覆盖;
n 而do0程序被放到了0:200处,随时都会因发生了除法溢出而被CPU 执行,很难保证 do0 程序从原来程序12.2所处的空间中取得的是要显示的字符串“overflow!”。
n 因为在do0程序开始处的“overflow!”不是可以执行的代码,所以在“overflow!”之前加上一条jmp 指令,转移到正式的do0 程序。
当除法溢出发生时,CPU 执行0:200 处的jmp 指令,跳过后面的字符串,转到正式的do0 程序执行。
n 再来看偏移地址,0:200处的指令为jmp short do0start ,这条指令占两个字节,所以“overflow!”的偏移地址为202h 。
8. 设置中断向量
下面,我们将do0的入口地址0:200,写入中断向量表的 0 号表项中,使do0成为0 号中断的中断处理程序。
n 0号表项的地址为0:0,其中0:0字单元存放偏移地址,0:2字单元存放段地址。
n 程序如下:
mov ax,0
mov es,ax
mov word ptr es:[0*4],200h
mov word ptr es:[0*4+2],0
9. 单步中断
n 基本上,CPU在执行完一条指令之后,如果检测到标志寄存器的TF位为1,则产生单步中断,引发中断过程。单步中断的中断类型码为1,则它所引发的中断过程如下:
n (1)取得中断类型码1;
n (2)标志寄存器入栈,TF、IF设置为0;
n (3) CS、IP入栈;
n (4)(IP)=(1*4),(CS)=(1*4+2)。
n 如上所述,如果TF=1,则执行一条指令后,CPU就要转去执行1号中断处理程序。
n CPU为什么要提供这样的功能呢?
n 好了,我们来简要地考虑一下Debug是如何利用CPU所提供的单步中断的功能的。
n 首先,Debug提供了单步中断的中断处理程序,功能为显示所有寄存器中的内容后等待输入命令
然后,在使用 T 命令执行指令时,Debug 将TF设置为 1,使得CPU在工作于单步中断方式下,则在CPU执行完这条指令后就引发单步中断,执行单步中断的中断处理程序,所有寄存器中的内容被显示在屏幕上,并且等待输入命令。
n 我们再来看一下中断过程
n (1)取得中断类型码N;
n (2)标志寄存器入栈,TF=0、IF=0;
n (3)CS、IP入栈;
n (4)(IP) = (N*4),(CS) = (N*4+2)
n 最后,CPU提供单步中断功能的原因就是,为单步跟踪的执行过程,提供了实现机制。
n 一般情况下,CPU在执行完当前指令后,如果检测到中断信息,就响应中断,引发中断过程。
n 可是,在有些情况下,CPU 在执行完当前指令后,即便是发生中断,也不会响应。
n 对于这些情况,我们不一一列举,只是用一种情况来进行说明。
n 在执行完向 ss寄存器传送数据的指令后,即便是发生中断,CPU 也不会响应。
n 这样做的主要原因是,ss:sp联合指向栈顶,而对它们的设置应该连续完成。
我们应该利用这个特性,将设置ss和sp的指令连续存放,使得设置sp的指令紧接着设置ss的指令执行,而在此之间,CPU不会引发中断过程。如下表示正确:
mov ax,1000h
mov ss,ax
mov sp,0