第12章 内中断

引言:

中断的意思是指,cpu不再接着(刚执行完的指令)向下执行,而是转去处理这个特殊信息。

注意,我们这里所说的中断信息,是为了便于理解而采用的某种逻辑上的说法。它是对几个具有先后顺序的硬件操作所产生的事件的统一描述。

当cpu的内部有什么事情发生的时候,将产生需要马上处理的中断信息呢?

对于8086cpu,当内部有下面情况发生的时候,将产生中断信息。

1、除法错误,比如:执行div指令产生的除法溢出

2、单步执行

3、执行int0指令

4、执行int指令

上述的4中中断源,在8086cpu中的中断类型码如下:

1)出发错误:0

2)单步执行:1

3)执行int0指令

4)执行int指令,该指令的格式为int  n,指令中的n为字节型立即数,是提供给cpu的中断类型码

cpu的设计者必须在中断信息和其处理程序的入口地址之间建立某种练习,使得cpu根据中断信息可以找到要执行的处理程序。

我们知道,中断信息中包含有标识中断源的类型码。根据cpu的设计,中断类型码的作用就是用来定位中断处理程序。

比如cpu根据中断类型码4,就可以找到4号中断的处理程序。

可随之而来的问题是,若要定位中断处理程序,需要知道它的段地址和偏移地址,而如何根据8位的中断类型码得到中断处理程序的段地址和偏移地址呢?

12.3  中断向量表

cpu用8位的中断类型码通过中断向量表找到相应的中断处理程序的入口地址。

那么什么是中断向量表呢?

中断向量表就是中断向量的列表

中断向量表在内存中保存,其中存放着256个中断源所对应的中断处理程序的入口,如下图所示:

 中断向量表在内存中存放,对于8086PC机,中断向量表指定放在内存地址0处

从内存0000:0000到0000:03FF的1024个单元中存放着中断向量表。

12.4  中断过程

从上面的讲解中,我们知道,可以用中断类型码,从中断向量表中找到中断处理程序的入口。

找到这个入口地址的最终目的是用它设置cs和ip,使cpu执行中断处理程序。

用中断类型码找到中断向量,并用它设置cs和ip,这个工作是由cpu的硬件自动完成的。

cpuing硬件完成这个工作的过程被称为中断过程。

在使用call指令调用子程序时有同样的问题,子程序执行后还要返回到原来的执行点继续执行,

所以call指令先保存当前的cs和ip值,然后再设置cs和ip。

8086cpu的中断过程:

1)(从中断信息中)取得中断类型码

2)标志寄存器的值入栈(因为再中断过程中要改变标志寄存器的值,所以先将其保存在栈中)

3)设置标志寄存器的第8位TF和第9位IF的值为0(这一步的目的以后介绍)

4)cs的内容入栈

5)ip的内容入栈

6)从内存地址为中断类型码*4和中断类型码*4+2的两耳光字单元中读取中断处理程序的入口地址设置ip和cs

cpu在收到中断信息之后,如果处理该中断信息,就完成一个由硬件自动执行的中断过程。

(程序员无法改变这个过程中所要做的工作)

中断过程的主要任务就是用中断类型码在中断向量表中找到中断处理程序的入口地址,设置cs和ip。

因为中断处理程序执行完成后,cpu还要回过头来继续执行被中断的程序,所以要在设置cs、ip之前,先将他们的值保存起来。

可以看到cpu将他们保存在栈中。

我们注意到,在中断过程中还要做的一个工作就是设置标志寄存器的TF、IF位。

对于这样做的目的,我们将在后面的内容和下一章中进行讨论。

因为在执行完中断处理程序后,需要恢复在进行中断处理程序之前的cpu现场(某一时刻,cpu中各个寄存器的值)。所以应该在修改标记寄存器之前,将它的值入栈

我们更简洁的描述中断过程,如下:

1)取得中断类型码N

2)pushf

3)TF=0,IF=0

4)push  CS

5)push  IP

6  IP=N*4,cs=N*4+2

在最后一步完成后,cpu开始执行由程序员编写的中断处理程序。

12.5  中断处理程序和iret指令

由于cpu随时都可能检测到中断信息,也就是说,cpu随时都可能执行中断处理程序,所以中断处理程序必须一直存储在内存某段空间之中。

而中断处理程序的入口地址,即中断向量,必须存储在对应的中断向量表项中。

中断处理程序的编写方法和子程序的比较相似,下面是常规的步骤:

1)保存用到的寄存器

2)处理中断

3)恢复用到的寄存器。

4)用iret指令返回。

iret指令的功能用汇编语法描述为:

pop  ip

pop   cs

popf

iret通常和硬件自动完成的中断过程配合使用

可以看到,在中断过程中,寄存器入栈的顺序是标志寄存器、cs、ip,而iret的出栈顺序是ip、cs、标志寄存器,刚好和其对应,实现了用执行中断处理程序前的cpu现场恢复标志寄存器和cs、ip的工作。

iret指令执行后,cpu回到执行中断处理程序前的执行点继续执行程序。

下面的内容中,我们通过对0号中断,即除法错误的中断处理,来体会一下前面所讲的内容。

当cpu执行div等除法指令的时候,如果发生了除法溢出等错误,将产生中断类型码为0的中断信息,cpu将检测到这个信息,然后引发中断过程,转去执行0号中断所对应的中断处理程序。

现在我们考虑改变一下0号中断处理程序的功能,即重新编写一个0号中断处理程序,他的功能是在屏幕中间显示overflow!后,然后返回到操作系统。

当cpu执行div  bh时,发生了除法溢出错误,产生0号中断信息,从而引发中断过程,cpu执行我们编写的0号中断处理程序。

在屏幕中间显示提示信息overflow!后返回到操作系统。

 分析:

1)当发生除法溢出的时候,产生0号中断信息,从而引发中断过程。

此时,cpu将进行以下工作:

1、取得中断类型码0

2、标志寄存器入栈,TF、IF设置为0

3、cs、ip入栈

4、ip=0*4,cs=0*4+2

2)可见,当中断0发生时,cpu将转去执行中断处理程序。

只要按如下步骤编写中断处理程序,当中断0发生时,即可显示:overflow

1、相关处理

2、向显示缓冲区送字符串overflow

3、返回dos

我们将这段程序称为do

3)现在的问题是:do应放在内存中。

因为除法溢出随时可能发生,cpu随时都可能将cs:ip指向do的入口,执行程序。

我们需要找到一块别的程序不会用到的内存区,将do传送到其中即可。

前面讲到,内存0000:0000~0000:03ff,大小为1Kb的内存空间是系统存放中断处理程序入口地址的中断向量表。

8086支持256个中断,但是,实际上,系统中要处理的中断事件远没有达到256个。

所以在中断向量表中,有许多单元是空的。

中断向量表是pc系统中最重要的内存区,只用来存放中断处理程序的入口地址,dos系统和其他应用程序都不会随便使用这段空间。

我们可以利用中断向量表中的空闲单元来存放我们的程序。

一般情况下:

从0000:0200至0000:02ff的256个字节的空间所对应的中断向量表项都是空的,操作系统和其他应用程序都不占用。

根据以前的编程经验,我们可以估计出,d0的长度不可能超过256个字节。

结论:我们可以将do传送到内存0000:0200处

我们将中断处理程序do放到0000:0200后,若要使得除法溢出发生的时候,cpu转去执行do,则必须将do的入口地址,即0000:0200登记在中断向量表的对应表项中。

因为除法溢出对应的中断类型码为0,他的中断处理程序的入口地址应该从0 x4 地址单元开始存放,段地址存放在0 x 4+2字单元中,偏移地址存放在0 x 4字单元中。

我们可以看到,上面的程序分为两部分:

1)安装do,设置中断向量的程序

2)do

程序执行时,do的代码是不执行的,他只是作为do安装程序所要传送的数据。

程序执行时,首先执行do安装程序,将do的代码拷贝到内存0:200处,然后设置中断向量表,将do的入口地址,即偏移地址200H和段地址0,保存在0号表项中。

这两部分工作完成后,程序就返回了

程序的目的就是在内存0:200处安装do的代码,将0号中断处理程序的入口地址设置为0:200

do的代码虽然在程序中,却不在程序执行的时候执行,它是在除法溢出发生的时候才得以执行的中断处理程序。

我们来看一下do是如何变成0号中断的中断处理程序的:

1)程序在执行时,被加载到内存中,此时do的代码在程序中所在的内存空间中,他只是存放在程序中的代码段中的一段要被传送到其他单元中的数据,我们不能说它是0号中断的中断处理程序。

2)程序中安装do的代码执行完后,do的代码被从程序中的代码段中拷贝到0:200处。此时,我们也不能说它是0号中断的中断处理程序,他不过是存放在0:200处的一些数据;

3)程序中设置中断向量表的代码执行完后,在0号表项中填入了do的入口地址0:200,此时0:200处的信息,即do的代码,就变成了0号中断的中断处理程序。

 因为当除法溢出时(即0号中断)发生时,cpu将执行0:200处的代码

回忆一下:

我们如何让一个内存单元成为栈顶?

将它的地址放入ss、sp中

我们如何让一个内存单元中的信息被cpu当作指令来执行?

将它的地址放入cs、ip中。

那么,我们如何让一段程序成为N号中断的中断处理程序?

将它的入口地址放入中断向量表的N号表项中。

完整程序如下:

;程序12.2

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,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:
          mov ax,cs
          mov ds,ax
          mov si,202h            ;设置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

 12.11  单步中断

基本上,cpu在执行完一条指令之后,如果检测到标志寄存器的tf位为1,则产生单步中断,引发中断过程。单步中断的中断类型码为1,则它所引发的中断过程如下:

1)取得中断类型码1

2)标志寄存器入栈,TF、IF设置为0.

3)cs、ip入栈

4)ip=1*4,cs=1*4+2.

如上所述,如果tf=1,则执行一条指令后,cpu就要转去执行1号中断处理程序。

注意:中断处理程序也是由一条条指令组成的,如果在执行中断处理程序之前,TF=1,则cpu在执行完中断处理程序的第一条指令后,又要产生单步中断,则又要转去执行单步中断的中断处理程序,在执行完中断处理程序的第一条指令后,又要产生单步中断,则又要转去执行单步中断的中断处理程序。

然后陷入循环。

cpu当然不能让这种事情发生,解决的办法就是,在进入中断处理程序之前,设置TF=0.

从而避免cpu在执行中断处理程序的时候发生单步中断。

这就是为什么在中断过程中有TF=0这个步骤。

一般情况下,cpu在执行完当前指令后,如果检测到中断信息,就响应中断,引发中断过程。

可是,在有些情况下,cpu在执行完当前指令后,即便是发生中断,也不会响应。

比如说:

在执行完向ss寄存器传送数据的指令后,即便是发生中断,cpu也不会响应。

这样做的原因是,ss:sp联合指向栈顶,而对他们的设置应该连续完成。

如果在执行完设置ss的指令后,cpu响应中断,引发中断过程,要在栈中压入标志寄存器、cs和ip的值。

而ss改变,sp并未改变,ss:sp指向的不是正确的栈顶,将引起错误。

所以cpu在执行完设置ss的指令后,不响应中断。

所以我们应该把ss和sp的指令连续存放。

原文地址:https://www.cnblogs.com/fate-/p/12968653.html