内中断
任何一种通用的CPU都有一种能力,就是在执行完当前正在执行的任务后,可以检测到CPU内部或外部的某种特殊信息,并且立刻对所收到的信号进行处理。这种特殊信息我们称为中断信息。中断的意思是,在执行完当前任务后,不是继续去执行下一个任务,而是转而去处理那个特殊信息。
内中断的产生:
中断信息的来源称为中断源。不同的中断信息对应不同的中断源,所以在中断信息中要包含识别来源的编码,这种编码称为中断类型码,中断类型码为一个字节,所以可以表示256种中信息来源,也就是中断源。我们可以先看一下基本的4种中断源:
它们对应的中断类型码分别是 0,1,4,最后一个的指令格式为 int n ,n为字节型立即数是提供给CPU的中断类型码。
中断处理程序和中断向量表:
CPU在收到中断信息后就要转而去对中断信息进行处理,而如何处理这个中断信息呢?我们有一种程序专门用来处理中断信息,叫做中断处理程序。而对每一种不同的中断信息或者说中断源,我们都有对应的不同的中断处理程序。然而,我们知道,在CPU内要执行某个程序,就要将CS:IP指向程序的入口,那么我们的CPU是如何知道中断处理程序的入口的呢?其实秘密就在中断类型码中,因为不同的中断源对应不同的中断处理程序,中断类型码其实就是用来定位中断处理程序的。
总的来说就是:中断源产生中断信息,中断信息是用来定位处理这个中断源的中断处理程序的,然后CPU根据中断信息中的信息找到中断处理程序,执行程序。
但是这里还有一个问题,CPU是如何根据8位中断信息找到中断程序入口的呢?
答案就是中断向量表。中断向量表有点像路由器的转发表,中断向量表中包含了每一个中断类型码以及它们对应的程序入口,当CPU接收到中断信息后,就到中断向量表中根据中断类型码查找中断处理程序的入口,然而这里又有一个问题,就是CPU需要找到中断向量表的位置才行。中断向量表在内存中存放,是有固定位置的,8086CPU的中断向量表在内存地址0处,中断向量表的一个表项占两个字,高位字存放段地址,低位字存放偏移地址。中断向量表中存放地址是按照从小到大的,就是说:0号中断类型码对应的中断处理程序的地址入口就放在从地址0开始的头两个字,然后1号就放在后面两个字,2号3号继续,依此类推.....
中断过程:
在中断的处理中,用中断类型码找到中断向量,将CS:IP设置为中断处理程序入口,这个过程称为中断过程,这个过程是由CPU硬件自动处理的。这两个过程中间其实还有几步需要做,然后这里还有一个问题需要考虑,就是,CPU在执行完中断处理程序后需要返回执行原来被中断的程序,所以这里肯定就要先将CS,IP还有一些其他相关的东西压入栈,执行完中断处理程序后再从栈中弹出来恢复原来的CPU现场。所以这个中断过程我们可以分成这几步:
- 从中断信息中取得中断类型码。
- 将标志寄存器的值入栈。(因为后面要恢复CPU现场,所以要先将相关的东西都储存起来)
- 设置标志寄存器的第8位TF以及第9位IF为0。
- 先将CS压入栈,再把IP压入栈。
- 将中断类型码在中断向量表中对应的程序入口地址设置为CS:IP的值。
中断过程完成后CPU就开始执行中断处理程序了。
中断处理程序:
中断处理程序也储存在某段内存中。中断处理程序一般为以下几个步骤:
- 把用到的寄存器中的值压入栈(因为在中断过程中只将标志寄存器中的值压入了栈,而因为中断程序执行完后要恢复CPU现场,所以这里又要将中断程序要用到的寄存器中的值先保存起来,程序执行完后再弹出来)
- 处理中断
- 出栈用到的寄存器(处理中断执行完了,恢复寄存器的值)
- iret(iret的功能就是 pop ip pop cs popf )
iret的功能正好和前面中断过程把标志寄存器以及CS,IP压入栈的顺序是对应的,中断过程是先把标志寄存器压入栈,然后把CS压入栈,然后再把IP压入栈,而iret的出栈顺序正好能把各个寄存器恢复原样。
除法错误的中断处理:
CPU处理 div 指令的除法计算时,如果发生了溢出,就会产生中断类型码为0的中断信息。CPU收到这个信息就会转而执行中断程序。我们可以试一个例子,我们将1000除以1,因为被除数为16位,所以结果将会存储在AX中,而因为商是16位的,AL只有8位,所以会发生溢出。
我们可以看到在执行完 div 指令后,左下角显示的地址变了,对应的指令也变了,这说明现在 CS:IP已经指向我们的中断处理程序的入口,要开始执行中断处理程序了。
完成中断处理程序的前三个步骤后,执行指令 iret 。
编程处理0号中断:
如果我们要重写一个0号中断处理程序,让它发生溢出错误时在屏幕中间显示一个 overflow 。
所以我们需要编写一个0号中断处理程序,把它放到一段内存中,然后把这段内存的首地址放入中断向量表中0号项表中。所以我们需要再写一个程序把我们这个中断处理程序安装也就是复制到我们指定的一个内存中,并把中断向量表中的0号项表设置为这段内存的首地址,也就是程序入口。所以我们的中断处理程序应该放在那个另一个程序中作为一个子程序。在我们编写的中断处理程序还没有被安装到我们指定的内存中以及向量表还没有设置的时候,这个程序都还不是中断向量程序,只有当这两步都完成了,这个程序才能叫做中断向量程序。
下面将详细写出使我们编写的这个程序成为中断处理程序的步骤
安装:
我们要将我们编写的将要成为中断处理程序的程序安装到指定内存中,那么就要将这个程序的所有代码都复制进去,这里我们可以用到一个很合适的指令,那就是 rep movsb ,既然要用到这个指令,我们就要先设置一些数据,把DS:SI设置为我们编写的那个准程序的首地址,然后把ES:DI设置为我们指定的那段内存单元的入口,然后再设置标志寄存器中的DF标志位为0,这样方向就为正。然后我们再设置中断向量表,使0号指向的地址为我们指定的内存单元的首地址。对于我们写的那个准程序的地址,我们可以用 offset 来获取它的偏移地址,从标号中得到它的段地址。
但是这里还有一个问题,我们如何知道那个程序的长度。这里我们可以用一个运算符 “ - ”,就是个减号,在汇编语言中我们还可以用其他的算术运算符,+ - * / 加减乘除都可以,比如 mov ax,8-4 编译器会将它处理为指令 mov ax,4 mov ax,(4-1)*2/2会被编译器处理为 mov ax,3 而我们可以用这种方式获取程序的长度:
假如这是我们编写的程序,我们需要再在其下面加一个标号,就像上面这样,然后我们用指令 mov ax,offset do0end- offset do0
程序do0:
接下来我们就要编写我们的中断处理程序了。既然我们要在屏幕中间显示一段字符,那么我们就要将这段字符送入显存中。那么我们又要设置一些寄存器,一个循环,将我们的那些字符送入显存。然后我们还要考虑一个问题,那些字符我们放在哪?要注意的是我们不能放在那个安装中断处理程序的程序中,因为我们执行这个程序就是要将我们的中断处理程序安装到我们指定的内存,并且设置一下向量表的,当这个程序执行完后,CPU就会释放这段程序所占内存,到时候我们存放那些字符的地方存放的可能就是别的东西了。所以我们要将这些字符放入我们的程序 do0 中。
这段程序我们可以这样写:
因为我们的字符串那里不是可以执行的指令,所以要跳过那里。这就是我们的这个程序的全部内容,先设置了我们要显示的字符串,然后再设置 DS:SI指向我们的字符串,然后再设置ES:DI指向我们的显存地址,然后再编写一段指令使我们的字符逐个转移到显存地址中,要注意的是,我们的字符是一个字节一个字节的,而当转移到显存中时,一个字符是占一个字的,因为还有一个字是存放了字符的属性。所以SI每次加一 ,DI每次加2。
但这里还有一个问题,就是上面我们可以看到我们将字符串的偏移地址送入SI ,用的是202,这个是计算出来的,因为这时假设我们要将这个程序送入的内存为 0 :200,而在这段字符串前面还有一个指令 jmp 指令,这个指令占两个字节,所以我们得出202.
设置中断向量:
这一步比较简单,我们直接在程序中将我们中断处理程序的地址送入0号表项就可以了,要注意的就是偏移地址占低位,段地址占高位,并且都是字单元。
到这我们就完成了整个程序的编写:
单步中断:
单步中断也是一种中断,单步中断的类型码为 1 。CPU提供这个功能就是给我们追踪程序执行的每一步提供了机制,单步中断也是我们每次执行中断程序前在中断过程中要把TF设置为0的原因。原因如下:我们每次在debug中,使用T 指令时它除了能执行指令,还会显示此时给寄存器的值。这个显示各寄存器的值其实就是单步中断程序的执行结果,单步中断程序的的中断源就是TF=1,每次debug执行 t 命令时都会将TF设置为 1 ,所以CPU在执行完 T 命令后就会转而去执行单步中断命令。但是也要注意CPU是一条一条指令处理的,每执行完一条指令都会看一下要不要执行中断程序,而单步中断程序也是由一条条指令组成的,如果TF中的值一直为1,那么执行完这条指令后又会转而去执行单步中断程序,这样就会一直在执行单步中断程序的第一条指令。所以我们所有中断程序执行前有个中断过程,其中有一条就是把TF设置为0。
响应中断的特殊情况:
CPU在处理某些指令时如果发出中断信息,那么就会响应中断,然后引发中断过程。但是有些时候,即使发生中断,也不会有响应,比如我们之前接触的 :当改变寄存器SS中的值的指令执行时,它的下一条指令也将接着执行,我们在debug中用 t 指令无法看到这一条指令的执行过程,因为在改变SS的值后虽然发生了中断,但是这个中断并没有响应。
但是为什么要这样做呢?
因为如果我们在执行完对SS的设置的指令后响应中断,注意:这个时候我们只设置了SS就转而去执行处理中断程序了,而我们还没有设置SP的值,而我们的中断处理过程以及程序中,都要将一些东西比如标志寄存器,CS,IP什么的压入栈,而这个时候我我们的SS是改变了的,而SP还没有改变,所以这个时候我们的SS:SP指向的是一个错误的栈,可能会引发一些不好的后果。