第12章 内中断
任何一个CPU,都具有一种能力,可以在执行完当前正在执行的指令后,监测到CPU外部发送过来的或者内部产生的一种特殊信息,并且可可以立即对所接收到的信息进行处理。这种特殊的信息,我们可以称其为:中断信息。
12.1 内中断的产生
当CPU内部有下面的情况发生的时候,将产生相应的中断信息,
- 除法错误,比如,执行div指令产生的除法溢出;
- 单步执行;
- 执行into指令;
- 执行int指令。
所有中断信息中必须包含识别来源的编码。8086CPU可称位中断类型码的数据来标示中断信息的来源。
中断类型码时一个字节型数据,可以表示256种中断信息的来源。
上述的4种中断源,在8086CPU中的中断类型码如下:
- 除法错误:0
- 单步执行:1
- 执行into指令: 4
- 执行int指令,该指令的格式为int n,指令中的n为字节型立即数,是提供给CPU的中断类型码。
12.2 中断处理程序
当中断发生时,这是我们要执行相关中断处理程序来处理中断任务,这个我们可以对不同的中断信息编写不同的处理程序。
CPU在收到中断信息后,应该转去执行该中断信息的处理程序。8086CPU在执行某处的程序,就要将CS:IP指向它的入口(即程序第一条指令的地址)。可见首要问题是:CPU在收到中断信息后,如何根据中断信息确定其程序的入口。
CPU的设计者必须在中断信息和其处理程序的肉蔻之间建立某种联系,使得CPU根据中断信息可以跳转到中断程序的执行入口,可以找到要执行的处理程序。
我们知道,中断嘻嘻中包含标识中断源的类型码。
类型码经过一系列计算就可以得出相关地址,而在这内存地址中,可以得出该中断处理程序的地址。
12.3 中断向量表
CPU用8位的中断类型码通过中断向量表找到相应的中断程序的入口地址。
我们知道地址需要偏移地址和段地址,每种地址需要两个字节,共需要4位。那么知道这些,表示的相关信息就很简单了。
中断处理程序的偏移地址: 中断号*4;段地址: 中断号*4+2,这样可以依次进行下去。
排列的顺序也符合,高位字存放段地址,低位字存放偏移地址。
中断向量表在内存中存放,对于8086PC机,中断向量表指令放在内存地址0处。从内存0000:0000到0000:03FF的1024个单元中存放着中断向量表,所以共可以表示256个中断码。
12.4 中断过程
通过上面的讲解,我们知道,可以用中断类型码,在中断向量表中找到中断处理程序的入口。找到这个入口地址的最终目的是用它设置CS和IP,使CPU执行中断处理程序。
下面,在CPU收到中断信息后,所引发的中断过程,
- (从中断信息中)取得中断类型码;
- 标志寄存器的值入栈(因为在中断过程中改变标志寄存器的值,所以先将其保存在栈中);
- 设置标志寄存器的第8位TF和第9位IF值为0(这一步的目的后面会介绍);
- CS的内容入栈;
- IP的内容入栈;
- 从内存地址中 中断码*4,中断码*4+2的两个字单元中读取中断处理程序的入口地址设置IP和CS。
注意,其他寄存器的内容并没有自动保存,所以,当调用中断程序程序前,我们应该手动保存相关地址。
更简洁的执行过程如下:
- 获取中断类型码N;
- pushf
- TF=0,IF=0
- push CS
- push IP
- (IP)=(N*4),(CS)=(N*4+2)
12.5 中断处理程序和iret指令
由于CPU随时都可能检测到中断信息,也就是说,CPU随时都可能执行中断处理程序,所以中断处理程序必须一直存储在内存段某空间中。而中断处理程序的入口地址,即中断向量,必须存储在对应的中断向量表表项中。
中断处理程序的编写方法和子程序编写方法类似,常有下面的步骤:
- 保存用到的寄存器
- 处理中断
- 恢复用到的寄存器
- 用iret指令返回
注意,这是一种标准化结构,并非必须按这样执行,但一般都最好按照这样的顺序来执行。
iret指令的功能用汇编语法表述为:
pop IP
pop CS
popf
其实地址和标志寄存器出栈的任务让它来完成即可。
12.6 除法错误中断的处理
代码
mov ax,1000h
mov bh,1
div bh
这时就会存在除法溢出错误,执行div bh后,我们从图中可以看出系统中的0号中段处理的功能:显示提示信息“Divide overflow”后,返回到操作系统中。
12.7 编写处理0号的中断
我们现在考虑改变一下0号中断处理程序的功能,即重新编写一个0号中断处理程序,它的功能是在屏幕中间显示“overflow!”,然后返回到操作系统中。
我们现在分析:
到那个发生除法溢出的时候,产生0号信息中断,从而引发中断过程。
此时,CPU将进行下列的工作1.取得中断码0
2.标志寄存器入栈,TF,IF=0
3.CS,IP入栈
4.修改CS和IP地址可见,当中断0发生时,CPU将转去执行中断处理程序
1.相关处理;
2.项显示缓冲区送字符串“overglow!”;
3.返回DOS
我们现在将这段程序为do0.现在问题是,do0应该存放在内存中,因为除法溢出随时都可能发生。那么do0应该放在哪里呢?我们可以向操作系统申请一块内存,但我们学习汇编可并不想这样做。我们直接使用程序不会用到的一块内存区域即可。
前面讲到,内存0000:0000-0000:03FF,大小为1KB的空间是系统存放中断处理程序入口地址的中断向量表。支持256个中断,但实际上处理的远没有达到这么多,所以,我们可以使用这块区域来处理中断程序。
中断向量表是PC系统中最重要的内存区,只用来存放中断处理程序的入口地址,DOS系统和其他应用程序都不会随便使用这段地址。一般情况下,0200-02FF的256个字节的空间所对应的中断向量表示空的,操作系统不占用。
我们的结论是将程序放到这里,然后将这段程序的开头放入中断向量表中。
我们现在分析下来需要做以下这几件事情:
1. 编写可以显示“overflow!”的中断处理程序,do0;
2. 将do0送入0000:0200处;
3. 将do0的入口地址000:0200存储在零号中断表中。
程序的框架大体如下图:
start: do0安装程序
设置中断向量表
mov ax,4c00h
int 21h
do0: 显示字符串:“overflow!”
mov ax,4c00h
int 21hxz
可以看出程序大概分为两部分:
1. 安装do0,设置中断向量程序。
2. do0,实际要传送的数据。
do0 start部分代码的最后两条指令是依照我们的变成要求,用来返回DOS的。我们并不需要计算机立刻来执行中断程序程序,这只是安装到内存中国的内容。
现在,我们从CPU的角度来看,什么事中断处理程序:
- 程序12.1再执行时,被加载到内存中,它只是一段放在代码段中的一段要求被传送到其他单元中的数据。
- 程序中安装do0的代码执行完后,我们也不能说它是中断处理程序,只是存放在内存的一段数据。
- 程序中设置中断向量表的代码执行完成,在0号表项中填入了do0的入口地址。之后,当发生除法溢出错误后,CPU将执行do0的代码
下面我们来讨论一下具体的编写方法。
12.8 安装
我们可以使用movsb指令,将do0的代码我送入0:200处,程序如下:
assume cs:code
code segment
start: 设置es:di指向目的地址
设置ds:si指向源地址
设置cx为传输地址
设置传输方向为证
rep movsb
设置中断向量表
mov ax,4c00h
int 21h
do0: ·····
·····
code ends
end start
下面直接写出全部正确的程序,然后在一一说明:
assume cs:code
code segment
start: mov ax,cs
mov ds,ax
mov si,offset do0
mov es,ax
mov di,200h
cld
mov cx,offset do0end - offset do0
rep movsb
设置中断向量表
mov ax,4c00h
int 21h
do0: jmp short do0start ;注意:这步骤是必须要添加的,目的是把字符串也存储到内存中。
db "overflow!"
do0start: mov ax,cs
mov ds,ax
mov si,202h ;这里来指向这个字符串,这个指令占两个字节。
mov ax,0b800h
mov es,ax
mov di,12*160+36*2 ;设置es:di指向现存空间的中间位置
mov cx,9
s: mov al,[si]
mov es:[di],al
inc si
add di,2 ;这里di目的地址要加2,因为在现存中还有一个字节是存储显示的色彩的。
loop s
mov ax,4c00h
int 21h
12.10 设置中断向量
现在要将程序的入口改为do0的在内存区域中安装好的地址,相关操作很简单。
mov ax,0
mov es,ax ;不能向地址中直接添加数字,必须使用寄存器。
mov word ptr es:[0*4],200h
mov word prt es:[0*4+2],0
注意,向内存中写入地址,必须使用“字操作”,而且偏移地址和段地址,所以,必须经过两次字操作。
12.11 单步中断
基本上,CPU在执行完一条指令之后,如果检测到标志寄存器的TF位为1,则产生单步中断,引发中断过程。单步中断的中断类型码为1,它引发的中断过程如下:
- 取得中断类型码1;
- 标志寄存器入栈,IF,TF=0;
- CS,IP入栈;
- (IP)=(1*4),(CS)-(1*4+2)。
如上所综,TF=1,则执行一条指令后,CPU就要去执行1号中断处理程序。
其实,Debug就是利用这样的原理,在让程序执行一条指令后,就停止执行转而去显示寄存器的状态。
每次执行一条指令后,设置TF=1,这样CPU就停止执行汇编程序,转而去来寄存器的状态,其实Debug就是利用这样的特性来实现对程序的调试和控制的。
当然,TF=1时,转而去执行中断程序,其实,这里TF并没有改变。跳转过程本身就是一条指令,当跳转完成后,TF=1,则又要进行下一次跳转,这样,就会陷入无限循环过程。
为了避免上述问题的发生,在开始跳转前,会将TF设置为0,避免了上述问题的发生。
这样,单步中断,为单步跟踪程序的执行过程,实现了控制机制。
12.12 响应中断的特殊情况
一般情况下,CPU在执行完当前指令后,如果检测到中断信息,就响应中断,引发中断过程。但是,有些情况下,当CPU响应中断信息,也不会立刻执行中断请求,这个,就不一一列举,就单独说明一点。
在执行完ss寄存器传送数据的指令后,即便发生中断,CPU也不会立刻向量。这样的主要原因是 SS:SP指向栈顶,而对它的设置应该是连续的。如果在执行完ss的指令后,CPU响应中断,要在栈中压入标志寄存器,这本身就并非IP的正确指向,而引起整个栈区的混乱,所以,在执行完SS后的指令,CPU并不会立刻响应中断请求,直到设置下步设置好IP之后。这样,SS:IP正确才能响应中断请求来向栈中压入标志寄存器的值。
mov ax,1000h
mov ss,ax
mov sp,0
而不应该
mov ax,1000h
mov ss,ax
mov ax,0
mov sp,ax
注意,SS:SP的操作应该是连续的。
903



被折叠的 条评论
为什么被折叠?



