您的位置:首页 > 其它

Intel X86中的中断、陷阱和异常

2012-08-10 09:39 267 查看
  中断有两种,一种是CPU外部产生的,一种是CPU内部执行程序时产生的。

  外部的中断通常是由外部设备产生的,并且这类中断的产生是异步的,根本无法预测这类中断什么时候产生,这类中断也就是通常我们所说的“中断”。当然,软件也可以关闭对这类中断的响应。CPU内部产生的中断也分为两种,一种是软件主动产生的中断,通常称为陷阱,例如执行int 0x80指令,还有一种是CPU检测到异常,通常称为异常,例如除数为0.这样,一共有三种类似的机制:中断(Interrupt)、异常(Except)和陷阱(Trap)。

 Intel X86CPU支持256个中断向量,一直都是如此。但是早期的CPU的中断处理机制非常原始、非常简单。早起的CPU(只有实模式)中中断向量表存放在内存中从0开始到1KB(256x4)的位置,每个中断向量对应的项占4个字节,用来存储中断处理程序的地址。其中两个字节用来存储处理程序地址的段地址,两个字节用来存储段内偏移。由于这种设计,中断向量表中各项存储的信息非常少,根本无法满足现在的操作系统的需求,因为这个机制中没有提供用户空间和内核空间切换或者运行模式切换(系统和用户)的手段,而且也没有相应的寄存器来帮助实现这样的需求,后面可以看到之后的CPU中增加了特定功能的寄存器来支持这种需求。Unix最初是在PDP-11(PDP-11是美商迪吉多电脑(Digital
Equipment Corp.)于1970到1980年代,所销售的一系列16位迷你电脑)上实现的。PDP-11的CPU中有一个状态控制寄存器PSW,PSW中有一个位段来表示当前的运行级别和模式(系统和用户)。PDP-11的CPU的中断向量表中的项除了包含处理程序的入口地址外,还包含进入中断处理程序后的PSW,中断向量表中的PSW中的模式是系统模式。CPU平时运行在用户模式,当发生中断时,CPU会根据中断向量在中断向量表中查找对应的项,然后将该项中PSW的值装入状态控制寄存器,同时将中断处理程序的入口地址装入CS寄存器和IP寄存器,原来的状态控制寄存器中的值随中断返回的地址一起压入用户堆栈中。通过中断向量表,发生中断时,CPU运行模式从用户模式切换到内核模式,中断处理完成后将之前压入栈的PSW值重新装载到状态控制寄存器中,从而使CPU运行模式从系统模式重新回到用户模式。早期的X86CPU处理中断时缺少这样的机制。

  intel在实现保护模式时,将中断向量表的表项从单纯的处理程序入口地址改为类似PSW加地址的更为复杂的描述项,称为“门”。根据功能的不同,分为四类门:任务门(task gate)、中断门(interrupt gate)、陷阱门(trap gate)和调用门。其中除任务门外其他三种门的结构基本相同,不过调用门并不是与中断向量表相联系的。

 先看任务门,其大小为64bit(8个字节),其结构如下图所示:


其中TSS段选择码用来在全局描述符表GDT或局部描述符表LDT中定位一个特殊的“系统段”,称为任务状态段TSS(Task Status Segment)。TSS是用来保存一个任务的运行现场的数据结构,包括与具体进程有关的各个寄存器的内容(包括页目录表指针cr3),还包括三个堆栈指针(运行级别为0,1或2的堆栈指针,运行级别为3表示用户态,所以不是在此处保存)。发生中断时,如果中断向量指向中断向量表中的一个任务门并且通过了级别检查,CPU会将当前任务的运行状态保存在相应的TSS中,并将该任务门的TSS装入寄存器中,从而实现一次任务的切换(即模式发生切换)。

  中断门、陷阱门和调用门大小也是64bit,其结构如下图所示:



与任务门相比,不同之处在于:任务门不需要段内偏移,因为任务门不需要指向一个处理程序的入口,主要是用来实现任务切换的,而其他三种都要对应一个处理处理程序,所以要使用段选择码和地址位移。通过段选择码来从GDT或LDT中找到对应的段描述符,这个段描述符中包括要执行的代码段的基址和堆栈的地址等。通过代码段的基址和位移,就可以找到处理程序的入口地址。如果处理程序的运行级别和发生中断时的运行级别不同,还要修改栈寄存器的值。这里的段选择码和任务门中的TSS选择码都是位于GDT或LDT中,只是他们所指向的段描述的信息不同。而且在中断门、陷阱门和调用门中没有发生任务切换,所以不需要像任务门那样需要保存所有与进程相关的寄存器的值和额外的三个堆栈指针。

  下面总结一下中断发生的过程:

  当通过一条INT指令进入一个中断服务程序时,在指令中给出一个中断向量。CPU先根据该向量在中断向量表中找到一扇门(描述项),在这种情况下一般总是中断门。然后将这个门的DPL(描述符优先级)与CPU的CPL(当前运行优先级)相比,CPL必须小于或等于DPL,也就是优先级别不低于DPL,才能穿过这扇门。注意,这里的检查只是检查当前的程序是否有访问该中断门的权限,只有通过权限检查才能去获取段选择码等信息,从而找到相应的段描述符。不过,如果中断是由外部产生或是因CPU异常而产生的话,那就免去了这一层检验。穿过了中断门之后,还要进一步讲目标代码段描述符中的DPL与CPL比较,目标段的DPL必须小于或等于CPL。也就是说,通过中断门时只允许保持或提升CPU的运行级别;而不允许降低其运行级别。这两个环节中的任何一个失败都会产生一次全面保护异常。

  进入中断服务程序时,CPU要将当前EFLAGS寄存器的内容以及返回地址压入堆栈,返回地址是由段寄存器CS的内容和取指令指针EIP的内容共同组成的。如果中断是由异常引起的,则还要讲一个表示异常的出错代码也压入堆栈。进一步,如果中断服务程序的运行级别,也就是目标代码段的DPL,与中断发生时的CPL不同,那就要引起更换堆栈。CPU会根据寄存器TR的内容找到当前TSS结构,并根据目标代码的DPL,从这TSS结构中取出新的堆栈指针(SS加ESP),并装入其堆栈段寄存器SS和堆栈指针寄存器ESP,达到更换堆栈的目的。在这种情况下,CPU不但将EFLAGS、返回地址以及出错代码压入堆栈,还要先将原来的堆栈指针也压入新的堆栈。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: