一.80386中断介绍
中断最早是为了协调并同步高速的CPU与相对低速的外部设备而提出的概念。所谓中断,是指当前程序/任务的执行过程中由于某种随时可能发生的外部请求,使得CPU中断正在执行的程序/任务,并跳转执行另一个例程(中断处理程序)或者中断服务任务中去(发生任务切换)。在服务处理完成后返回到之前被打断的程序断点处,继续执行。
随着计算机技术的发展,中断的概念被进一步延伸。除了最早概念中包括的外设引起的外部事件中断(硬中断)外,还提出了内部事件中断(软中断)的概念。
在80386中,中断一词主要指的是硬中断,而内部引起的软中断则被称为异常。
硬中断
硬中断分为两种类型:可屏蔽中断(Interrupt Require,INTR)和不可屏蔽中断(NonMaskable Interrupt,NMI)。
相同点:都是CPU在处理完当前指令时,检查到硬中断请求时才开始进行服务的;同时,会在服务完成后,继续回到断点执行被打断时的下一条指令。
不同点:对于可屏蔽中断,CPU根据标志寄存器EFLAGS中的IF位(中断使能位)来决定是否响应。IF为0,表示关中断,CPU不响应任何可屏蔽中断。IF为1,表示开中断,CPU会在每条指令执行完毕后检查是否存在可屏蔽中断的请求,如果存在,则进行中断服务处理。而不可屏蔽中断,顾名思义,无论标志寄存器中的IF位是多少,CPU都必须对此种中断做出响应处理。
不可屏蔽中断是通过CPU的NMI引脚产生的,中断向量固定为2。可屏蔽中断是通过CPU的INTR引脚产生的,中断向量由引发中断的硬件提供。
绝大多数的外设中断请求都是可屏蔽中断,少数特别严重的问题发生时,会产生不可屏蔽中断。
软中断(异常)
80386中的软中断主要分为三种类型:失效、自陷和终止,软中断也被称为异常。这三种异常的不同之处主要在于两个方面,一是发生时的报告方式不同,二是异常中断服务程序返回时的处理方式不同。
失效异常
失效异常在某一指令加载,启动后、执行前被检测到,并且在异常中断服务程序执行完并返回后,重新执行该指令。失效异常也被成为故障(Fault),故障属于通过某种确切手段能排除的异常。
在后面会介绍的页式虚拟存储的实现中,当处理器执行一条指令,访问了不在物理内存中的内存页时,便会引起失效中断异常(缺页异常),随后CPU会跳转执行操作系统提供的缺页中断处理程序,将所缺失的内存页置换入物理内存中。随后,CPU重新执行那条引起失效的指令,此时由于物理内存页中已经存在了正确的数据,因此能正常的进行访问。
自陷异常
自陷异常的处理比较像硬中断。和失效异常不同的是,自陷是在引起自陷的指令执行完毕之后才产生的,同时在异常中断服务程序执行完毕后顺序执行引起自陷异常的下一条指令执行,而不是重新执行引起自陷的指令。
断点debug调试便是使用自陷异常来实现的。在每个需要断点的地方嵌入对应的自陷异常指令(例如INT 3),CPU执行到对应自陷异常指令时,便会跳转到对应的异常处理程序中。这一异常处理程序通常是由操作系统提供的,操作系统可以找到注册了断点事件的调试处理程序,此时的调试器便可以展示被调试程序的当前内存等信息。断点放过去之后,自陷异常处理程序便会返回,被调试的程序也能接着断点处继续执行了。
终止异常
终止异常是一种很严重的异常,对引起的异常无法确定确切位置,因而也没有相应的处理办法,只能终止并重启系统。
硬件错误或者系统表中的错误数值等造成的异常便属于终止异常。当发生终止异常时,原来运行的程序已经无法继续执行下去,只能被迫终止。对于应用程序引起的终止异常,一般由操作系统将其终止;而操作系统内核本身引发的终止异常通常只能重启系统,重新构建GDT等系统表。
二.中断描述符表
不同类型的中断请求都会拥有一个独有的中断类型码。当中断发生时,根据不同的中断来源,或是由CPU提供一个中断类型码(例如内存缺页异常),或是由指令给出(例如int n中的参数n),或是从外部的中断控制器接收一个中断类型码。
在8086中,存在着一张中断向量表,建立了中断类型码与对应中断服务程序入口地址的关联关系。而在80386中,由于引入了特权级保护,不能直接将服务程序的入口地址与中断类型码关联,而是与一种被称为中断描述符的数据结构关联,中断描述符有着DPL等信息,访问时受到特权级的限制。为了便于统一的进行管理,所有的中断描述符数据需要按照对应的中断类型码顺序集中存放在一起形成一张表,这张表被称为中断描述符表(Interrupt Descriptor Table,IDT)。
80386支持的中断类型和8086一样,都是256种。而中断描述符指的是包括中断门,陷阱们和任务门在内的门描述符,门描述符的大小和段描述符一样都是64位,即8字节,因此中断描述符表的实际逻辑大小最大为2KB。
中断类型码对应的异常信息:
当中断发生时,CPU用中断向量乘以8(因为中断描述符是8字节的)去中断描述符表中找到对应的中断处理门描述符,进行相应的中断处理。
和追踪GDT的方式一样,80386也提供了一个专用的寄存器用于定位中断描述符表,被称为中断描述符表寄存器(Interrupt Descriptor Table Register,IDTR)。IDT和GDT一样,全局唯一,因此IDTR被设计为48位,其中从高位到低位存放的分别是IDT的32位线性基地址和16位的界限。由于IDTR的存在,80386允许在进入保护模式后重新定义中断描述符表,而不必像8086那样将中断向量表固定在低位内存空间。
门描述符(Gate Descriptors)
其实在之前的博客中已经或多或少提到了门描述符,如特权级保护中的实现系统调用而接触到的调用门,任务切换中提到的任务门。而在中断相关的内容中又涉及到了中断门和陷阱门。在这里对门描述符进行统一的介绍。
段描述符是对某一指定范围的内存段的描述(起始地址、段界限以及特权级),而门描述符则是针对程序控制转移而提出的,指向的都是代码段。
门描述符一共有四种,即调用门、任务门、中断门和陷阱门。
调用门
调用门可以使用call far或者jmp far指令进行控制转移。通过调用门进行的程序控制转移,可以改变当前特权级CPL。
因此可以令低当前特权级CPL的程序控制转移至高DPL的代码段执行,操作系统提供的系统调用便是通过调用门实现的。调用门提供了必要的特权级保护,比起直接的控制转移而提升当前特权级CPL,要安全很多。
如图所示,调用门中存放着被调用过程的16位代码段选择子和32位的段内偏移量。
使用call far指令使用调用门时,可能会导致当前特权级CPL的变化,需要进行不同特权级堆栈的切换。由CPU固件进行调用时参数的压栈处理,因此需要提供调用门所需要的参数个数,指导CPU进行堆栈参数的复制。调用门高32位中的低5位便是用于指定调用门所需参数个数的,5位代表着参数个数最多不能超过31(参数个数范围:0~31)。
调用门描述符中的P位是有效位,一般应该为1表示有效。当调用P位为0的调用门时,CPU会发生一个失效异常,操作系统提供的中断处理程序便可以依此对该调用门的使用次数加1,同时将其P位置为1。发生失效异常的CPU会在中断处理程序返回后继续尝试执行该调用,此时P位校验通过。而操作系统可以在调用门的例程中,将当前调用门的P位再次置为0,以重复上述过程,进行该调用门使用频率的统计。
P位和DPL字段是四种门描述符共有的属性,作用大致相同。
调用门描述符中的TYPE字段(高32位中的8-10位)固定为100,代表调用门。调用门只能存在于GDT、LDT中,不能存在于IDT中。
任务门
任务门主要用于进行任务的切换,因此其中的内容便是对应任务的TSS段选择子,结构比较简单。任务门的门描述符TYPE字段固定为101,代表任务门。
任务门既能在GDT、LDT中,也能存放于IDT中。因为除了正常的任务调度外,中断发生时也可以触发中断处理任务的调度,此时中断向量所指向中断描述符表中的描述符便是任务门描述符。
中断门和陷阱门
中断门和陷阱门两者的差别很小,一起进行介绍。
主要相同点:
1.中断门和陷阱门都用于中断处理,标识对应的中断处理程序入口。因此都包含了中断处理程序的16位代码段选择子和32位的段内偏移地址。
2.中断门和陷阱门只能存放于中断描述符表IDT中,而不能存放在GDT、LDT中。
主要不同点:
1.中断门的TYPE字段固定为110,而陷阱门的TYPE字段固定为111。
2.通过中断门进入中断服务程序时,CPU会自动的关中断(将EFLAGS的IF位置0),这意味着中断门实现的中断服务程序默认不支持中断嵌套(有需要可以在程序中主动的开中断)。而通过陷阱门进入中断服务程序时,CPU不会复位IF的值,而是保持不变,这意味着陷阱门默认是支持进行中断嵌套的。
中断错误代码
有些异常产生时,CPU会在中断处理程序或中断任务的栈中压入一个中断错误代码。通常,这意味着异常和特定的段选择子或中断向量有关。错误代码是32位,但高16位在80386中没有使用,作为了保留位。
EXT位:
EXT位标识异常是否是由外部硬中断产生的。EXT位为1时,表示当前异常是由外部硬中断引发的(NMI或者INTR)。
IDT位:
IDT位标识段选择子索引指向的是否是中断描述符表IDT。IDT位为1时,表示段选择子的索引部分是指向IDT的;IDT位为0时,表示段选择子的索引部分是指向GDT或是LDT的。
TI位:
当IDT位为0时,需要进一步区分段选择子的索引是指向GDT或是LDT。
当IDT=0,TI=0时,表示段选择子索引指向的是GDT;当IDT=0,TI=1时,表示段选择子索引指向的是LDT。
段选择子索引:
段选择子索引部分用于指向对应的描述符表中的某一个具体的段描述符。错误代码中的段选择子索引共13位,对应着访问段描述符的16位段选择子的高13位。
三.中断优先级与中断嵌套
为了能让CPU及时的响应处理各种类型的中断,根据引起中断事件的重要性和紧迫程度,需要将中断源分为不同的级别,称作中断优先级。
当低中断优先级的中断处理程序正在执行时,此时如果出现了更高中断优先级的中断请求,CPU需要能够中断当前低中断优先级的处理程序,转而去处理高中断优先级的中断请求,同时屏蔽掉低优先级的中断请求,形成中断嵌套。在80386中,允许多优先级层次的中断嵌套。
80386的中断优先级为上述不同类型的中断设置了不同的中断优先级。中断优先级从高到低的排列分别是:终止异常 > 失效异常 > 自陷异常 > NMI不可屏蔽中断 > INTR可屏蔽中断。
80386CPU虽然对不同大类的中断类型设置了不同的中断优先级,可现实中的需求往往更复杂。例如,用户通常希望自己使用键盘外设输入的数据后能够尽快的得到响应,而不是被大数据的磁盘外设IO给阻塞。但键盘和磁盘同为外部设备,都是使用INTR可屏蔽中断与CPU进行交互的,其中断优先级在CPU看来是相同的。因此,上述提到的80386中断优先级机制无法实现令正在进行大数据磁盘IO的系统及时的响应时间上相对后操作的用户键盘输入。
为此,80386提供了对于INTR可屏蔽中断处理的拓展。80386支持使用可编程中断控制器8259A芯片将CPU的一根INTR可屏蔽中断线扩展为8根以上的硬件中断线,以支持更细致的INTR中断优先级控制。
8259A芯片是可编程的,可以按照不同类型码的中断向量,为约定的不同外设的不同可屏蔽中断向量赋予不同的中断优先级,允许8259A芯片中的高优先级中断打断低优先级中断的服务。使用了8259A芯片作为可屏蔽中断的控制器后,CPU在处理可屏蔽中断时,会根据8259A芯片是否发出了INTR请求来决定可屏蔽中断的嵌套执行。
以上述键盘和磁盘的I/O优先级的需求问题举例:操作系统在加载启动时,会执行针对8259A芯片的编码程序,对操作系统支持的各种INTR外设中断进行统一编排,以决定不同的外设、中断向量对应的中断优先级。其中,便可以将键盘I/O在8259A芯片的中断优先级设置为高于磁盘I/O的中断优先级。这样一来,当磁盘I/O中断服务程序执行时,8259A芯片接收到了来自用户键盘的输入,根据当前编码的中断优先级,发现键盘I/O的优先级是高于此时正在处理的磁盘I/O中断的。此时8259A芯片会向CPU发出一个键盘I/O的中断请求,CPU收到来自8259A的中断这一中断请求后,便会暂时打断磁盘I/O的处理,转而嵌套中断执行用户键盘的I/O。用户的操作得到了及时的相应,体验得到了很大提升。而如果新出现的外设中断没有高于当前处理的INTR中断,8259A只会将这一中断暂时保存下来,而不通知CPU。
关于8259A芯片的工作原理以及编程方法,限于篇幅就不在这里展开了。
四.中断服务处理流程
前面介绍了中断处理的各个细节部分,下面总体的介绍一下80386的中断处理流程。
从整体上看,80386的中断处理全流程可以分为判断是否存在中断请求,如存在中断则进行中断服务处理,中断服务处理返回后恢复现场这三部分。
判断是否存在中断请求
在当前指令即将结束的前一个机器周期时,CPU会按照上述中断优先级先后顺序依次判断是否存在对应的中断请求。
1、先判断是否存在软中断异常,如果存在则进入中断服务处理,中断类型码由异常的类型决定;
2、如果不存在软中断异常,接着判断是否存在NMI不可屏蔽中断,如果存在则进入中断服务处理,中断类型码固定为2;
3、如果不存在NMI不可屏蔽中断,接着判断是否存在INTR可屏蔽中断请求,如果存在INTR且IF=1开中断,则进入中断服务处理,中断类型码由发起INTR的外设给出;
4、如果不存在INTR可屏蔽中断,最后判断当前是否开启了单步调试(标志寄存器EFLAGS的TF是否为1),如果TF=1,则进入中断服务处理,中断类型码固定为1;
5、经过上述判断后,CPU认为当前没有中断请求,将正常执行下一条指令。
进行中断服务处理
当检测到中断请求后,CPU将会有一连串的硬件操作,跳转中断服务。
1、CPU从中断请求信号中获取对应的中断类型码(中断向量)。
2、根据中断向量从中断描述符表中查找对应的门描述符,根据门描述符的类型进行中断服务的处理(任务门进行任务切换,中断门和陷阱门则进行中断服务例程的调用)。
3、保护现场,按照门描述符的规则处理。如果是中断门或陷阱门,先将当前的EFLAGS标志寄存器入栈,并设置IF、TF的值(陷阱门的IF不变,TF置0,而中断门需要IF、TF都置0),最后按照顺序压入当前的CS、再压入当前的EIP,以便中断服务例程返回后恢复现场。如果是任务门,则进行之前博客中所说的任务切换,保存当前任务的TSS快照,并加载任务门中的中断服务任务的TSS。
4、如果对应的中断描述符是中断门或陷阱门,跳转进入对应的中断服务例程;如果对应的中断描述符是任务门,进行中断服务任务的调度,挂起当前任务,令中断服务任务获得CPU控制权。
中断服务返回
无论是中断服务例程或是另一个中断服务任务,正常情况下处理完中断服务后都需要返回并恢复现场。
中断门或陷阱门中断服务例程的返回,按照之前入栈的相反顺序将EIP、CS先后出栈;再将EFLAGS标志寄存器出栈,恢复现场(IRET指令)。
任务门中断任务的返回,同样是通过IRET指令进行的。在任务切换中提到的,执行中断服务任务代码中的IRET指令时,如果EFLAGS标志寄存器的NT嵌套任务位为1时,则会触发任务切换。CPU通过当前TSS中的前一个任务TSS字段,将被打断的任务TSS恢复,达到中断服务任务返回,恢复现场的目的。
五.总结
中断机制是CPU工作原理中很重要的一环,也是现代操作系统的基础。80386的中断由于保护模式和多任务切换的原因,原理比8086要复杂不少。
但只有理解了80386的中断,才能在学习基于80386的操作系统时,更好理解的系统、硬件的工作机制,学习和阅读源码时也能更加顺利。更进一步的,许多应用层的I/O、同步/异步程序的底层实现原理,也多少与硬件的中断功能有关联,如果站在CPU硬件这一层面去看上层的应用技术,也许能有更深刻的理解。