风月无古今 2020-06-28
任何一个通用的CPU,比如8086,都具备一种能力,可以在执行完当前正在执行的指令之后,检测到从CPU外部发送过来的或内部产生的一种特殊信息,并且可以立即对所接收到的信息进行处理。这种特殊的信息,我们可以称其为:中断信息。中断的意思是指,CPU不再接着(刚执行完的指令)向下执行,而是转去处理这个特殊信息。
中断信息,是为了便于理解而采用的一种逻辑上的说法。它是对几个具有先后顺序的硬件操作所产生的事件的统一描述。“中断信息”是要求CPU马上进行某种处理,并向所要进行的该种处理提供了必备的参数的通知信息。
中断信息可以来自CPU的内部和外部,来自CPU内部的中断信息称为内中断
当CPU内部有下面的情况发生的时候,将产生相应的中断信息
(1) 除法错误,比如,执行div指令产生的除法溢出;
(2) 单步执行;
(3) 执行into指令;
(4) 执行int指令。
8086CPU用称为中断类型码的数据来标识中断信息的来源。中断类型码为一个字节型数据,可以表示256 种中断信息的来源。
产生中断信息的事件,即中断信息的来源,简称为中断源。
在8086CPU中的中断类型码如下:
(1)除法错误:0
(2)单步执行:1
(3)执行into指令:4
(4)执行int指令,该指令的格式为int n, 指令中的n为字节型立即数,是提供给CPU的中断类型码。
CPU收到中断信息后,需要对中断信息进行处理。而如何对中断信息进行处理,可以由编程决定。一般来说,需要对不同的中断信息编写不同的处理程序。
CPU在收到中断信息后, 应该转去执行该中断信息的处理程序。若要8086CPU执行某处的程序,就要将CS: IP指向它的入口(即程序第一条指令的地址)。
CPU的设计者必须在中断信息和其处理程序的入口地址之间建立某种联系,使得CPU根据中断信息可以找到要执行的处理程序。
中断信息中包含有标识中断源的类型码。根据CPU的设计,中断类型码的作用就是用来定位中断处理程序。比如CPU根据中断类型码4, 就可以找到4号中断的处理程序。可随之而来的问题是,若要定位中断处理程序,需要知道它的段地址和偏移地址。
CPU用8位的中断类型码通过中断向量表找到相应的中断处理程序的入口地址。
中断向量,就是中断处理程序的入口地址。展开来讲,中断向量表,就是中断处理程序入口地址的列表。
中断向量表在内存中保存,其中存放着256个中断源所对应的中断处理程序的入口。
中断向量表在内存中存放,对于8086PC机,中断向量表指定放在内存地址0处。从内存0000:0000到0000:03FF的1024个单元中存放着中断向量表。
用中断类型码,在中断向量表中找到中断处理程序的入口。找到这个入口地址的最终目的是用它设置CS和IP,使CPU执行中断处理程序。用中断类型码找到中断向量,并用它设置CS和IP,这个工作是由CPU的硬件自动完成的。CPU硬件完成这个工作的过程被称为中断过程。
CPU收到中断信息后,要对中断信息进行处理,首先将引发中断过程。硬件在完成中断过程后,CS:IP将指向中断处理程序的入口,CPU开始执行中断处理程序。
8086CPU在收到中断信息后,中断过程如下:
(1)(从中断信息中)取得中断类型码;
(2)标志寄存器的值入栈(因为在中断过程中要改变标志寄存器的值,所以先将其保存在栈中);
(3)设置标志寄存器的第8位TF和第9位IF的值为0;
(4)CS的内容入栈;
(5)IP的内容入栈;
(6)从内存地址为中断类型码*4和中断类型码*4+2的两个字单元中读取中断处理程序的入口地址设置IP和CS。
用汇编表示如下过程
CPU随时都可能执行中断处理程序,所以中断处理程序必须一直存储在内存某段空间之中。而中断处理程序的入口地址,即中断向量,必须存储在对应的中断向量表表项中。
中断处理程序的编写方法和子程序的比较相似,步骤如下:
(1) 保存用到的寄存器;
(2) 处理中断;(中断过程)
(3) 恢复用到的寄存器;
(4) 用iret指令返回。
iret(interrupt return)中断返回。
指令的功能用汇编语法描述为:
pop IP #恢复IP pop CS #恢复CS popf #恢复标志寄存器
iret通常和硬件自动完成的中断过程配合使用。可以看到,在中断过程中,寄存器入栈的顺序是标志寄存器、CS、IP,而iret的出栈顺序是IP、CS、标志寄存器,刚好和其相对应,实现了用执行中断处理程序前的CPU现场恢复标志寄存器和CS、IP的工作。iret指令执行后,CPU回到执行中断处理程序前的执行点继续执行程序。
0号中断,即除法错误中断的处理
当CPU执行div等除法指令的时候,如果发生了除法溢出错误,将产生中断类型码为0的中断信息,CPU将检测到这个信息,然后引发中断过程,转去执行0号中断所对应的中断处理程序。
mov ax,1000h #被除数存放在ax中 mov bh,1 div bh #al = ax/bh的商; ah = ax/bh的余数
当CPU执行div bh时,发生了除法溢出错误,产生0号中断信息,从而引发中断过程,CPU执行0号中断处理程序。系统中的0号中断处理程序的功能:显示提示信息“divide overflow"后,返回到操作系统中。
注意:这里如果使用dosbox,是不会显示divide overflow的。
改变一下0号中断处理程序的功能,即重新编写一个0号中断处理程序,它的功能是在屏幕中间显示“overflow! ",然后返回到操作系统。
编程:当发生除法溢出时,在屏幕中间显示“overflow!",返回DOS。
分析:
内存0000:0000~0000:03FF,大小为1KB的空间是系统存放中断处理程序入口地址的中断向量表。8086支持256个中断,但是,实际上,系统中要处理的中断事件远没有达到256个。所以在中断向量表中,有许多单元是空的。
中断向量表是PC系统中最重要的内存区,只用来存放中断处理程序的入口地址,DOS系统和其他应用程序都不会随便使用这段空间。可以利用中断向量表中的空闲单元来存放自定义的程序。一般情况下,从0000:0200至0000:02FF的256个字节的空间所对应的中断向量表项都是空的,操作系统和其他应用程序都不占用。
assume cs:code code segment start:do0安装程序 设置中断向量表 mov ax,4c00h int 21h do0:显示字符串"overflow!" mov ax,4c00h int 21h code ends end start
首先执行do0安装程序,将doO的代码复制到内存0:200处,然后设置中断向量表,将do0的入口地址,即偏移地址200H和段地址0,保存在0号表项中。这两部分工作完成后,程序就返回了。程序的目的就是在内存0:200处安装do0的代码,将0号中断处理程序的入口地址设置为0:200。do0的代码虽然在程序中,却不在程序执行的时候执行。它是在除法溢出发生的时候才得以执行的中断处理程序。
可以使用movsb指令,将do0的代码送入0:200处。程序如下:
assume cs:code code segment start:设置es:di指向目的地址 设置ds:si指向源地址 设置cx为传输长度 设置传出方向为正 rep movsb 设置中断向量表 mov ax,4c00h int 21h do0:显示字符串"overflow!" mov ax,4c00h int 21h code ends end start
rep movsb指令的时候要确定的信息
(1) 传送的原始位置,段地址:code,偏移地址:offset do0;
(2) 传送的目的位置:0:200:
(3) 传送的长度: do0部分代码的长度;
(4) 传送的方向: 正向。
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部分代码的长度 mov cx,offset do0end-offset do0 #设置cx为传输长度 cld #设置传出方向为正 rep movsb 设置中断向量表 mov ax,4c00h int 21h do0:显示字符串"overflow!" mov ax,4c00h int 21h do0end:nop code ends end start
"-"是编译器识别的运算符号,编译器可以用它来进行两个常数的减法。
指令:mov ax,8-4, 被编译器处理为指令: mov ax,4。
指令:mov ax,(5+3)*5/10,被编译器处理为指令:mov ax,4
用offset do0end-offset do0 , 得到do0代码的长度。
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,do0部分代码的长度 mov cx,offset do0end-offset do0 #设置cx为传输长度 cld #设置传出方向为正 rep movsb 设置中断向量表 mov ax,4c00h int 21h do0:jmp short do0start db "overflow!" do0start:mov ax,cs mov ds,ax mov si,202 #设置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
将“overflow!" 放到do0程序中,程序执行时,将标号do0到标号do0end之间的内容送到0000:0200处。不能用data segment是因为如果程序执行完会释放空间,所以要写在do0中。
do0程序执行过程中必须要找到“overflow!","overflow!"和do0的代码处于同一个段中,而除法溢出发生时,CS中必然存放do0的段地址,也就是“overflow!"的段地址;再看偏移地址,0:200处的指令为jmp short do0start,这条指令占两个字节,所以“overflow!"的偏移地址为202h。
将do0的入口地址0:200, 写入中断向量表的0号表项中,使do0成为0号中断的中断处理程序。
0号表项的地址为0:0, 其中0:0字单元存放偏移地址,0:2 字单元存放段地址。
mov ax,0 mov es,ax mov word ptr es:[0*4],200h #0:0字单元存放偏移地址 mov word ptr es:[0*4+2],0 #0:2字单元存放段地址
assume cs:code code segment start:mov ax,cs mov ds,ax mov si,offset do0 mov ax,0 mov es,ax mov di,200h mov cx,offset do0end-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:jmp short do0start db "overflow!" do0start:mov ax,cs mov ds,ax mov si,202 mov ax,0b800h mov es,ax mov di,12*160+36*2 mov cx,9 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
执行结果
基本上,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号中断处理程序。
使用t命令执行指令时, Debug将TF 设置为1,使得CPU工作,于单步中断方式下,则在CPU执行完这条指令后就引发单步中断,执行单步中断的中断处理程序,所有寄存器中的内容被显示在屏幕上,并且等待输入命令。
(1) 取得中断类型码N;
(2) 标志寄存器入栈, TF=0 、IF=0;
(3) cs 、IP 入栈;
(4) (IP)=(N*4), (CS)=(N*4+2) 。
最后, CPU 提供单步中断功能的原因就是,为单步跟踪程序的执行过程,提供了实现机制。
一般情况下,CPU在执行完当前指令后,如果检测到中断信息,就响应中断,引发中断过程。可是,在有些情况下,CPU在执行完当前指令后,即便是发生中断,也不会响应。
在执行完向ss寄存器传送数据的指令后,即便是发生中断,CPU也不会响应。这样做的主要原因是,ss:sp联合指向栈顶,而对它们的设置应该连续完成。如果在执行完设置ss的指令后,CPU响应中断,引发中断过程,要在栈中压入标志寄存器、CS和IP的值。而ss改变,sp并未改变,ss:sp指向的不是正确的栈顶,将引起错误。所以CPU在执行完设置ss的指令后,不响应中断。这给连续设置ss和sp指向正确的栈顶提供了一个时机。即,我们应该利用这个特性,将设置ss和sp的指令连续存放,使得设置sp的指令紧接着设置ss的指令执行,而在此之间,CPU不会引发中断过程。
mov ax,1000h mov ss,ax mov sp,10h
在mov ss,ax指令执行后,CPU根本就不响应任何中断,其中也包括单步中断,所以Debug设置好的用来显示寄存器状态和等待输入命令的中断处理程序根本没有得到执行,所以我们看不到预期的结果。CPU接着向下执行后面的指令mov sp,l0h, 然后响应单步中断,我们才看到正常的结果。