首先,我们从硬件的角度来看CPU如何处理中断和异常。这里假定内核已被初始化,CPU已从实模式转到保护模式。
当CPU执行了当前指令之后,CS和EIP这对寄存器中所包含的内容就是下一条将要执行指令的逻辑地址。在对下一条指令执行前,CPU先要判断在执行当前指令的过程中是否发生了中断或异常。如果发生了一个中断或异常,那么CPU将做以下事情:
· 确定所发生中断或异常的向量i(在0~255之间)。
· 通过IDTR寄存器找到IDT表,读取IDT表第i项(或叫第i个门)。
· 分两步进行有效性检查:首先是“段”级检查,将CPU的当前特权级CPL(存放在CS寄存器的最低两位)与IDT中第i项段选择符中的DPL相比较,如果DPL(3)大于CPL(0),就产生一个“通用保护”异常(中断向量13),因为中断处理程序的特权级不能低于引起中断的程序的特权级。这种情况发生的可能性不大,因为中断处理程序一般运行在内核态,其特权级为0。然后是“门”级检查,把CPL与IDT中第i个门的DPL相比较,如果CPL大于DPL,也就是当前特权级(3)小于这个门的特权级(0),CPU就不能“穿过”这个门,于是产生一个“通用保护”异常,这是为了避免用户应用程序访问特殊的陷阱门或中断门。但是请注意,这种“门”级检查是针对一般的用户程序,而不包括外部I/O产生的中断或因CPU内部异常而产生的异常,也就是说,如果产生了中断或异常,就免去了“门”级检查。
· 检查是否发生了特权级的变化。当中断发生在用户态(特权级为3),而中断处理程序运行在内核态(特权级为0),特权级发生了变化,所以会引起堆栈的更换。也就是说,从用户堆栈切换到内核堆栈。而当中断发生在内核态时,即CPU在内核中运行时,则不会更换堆栈,如图3.5所示。
特权级发生了变化 特权级没有变化
图3.5中断处理程序堆栈示意图
从图可以看出,当从用户态堆栈切换到内核态堆栈时,先把用户态堆栈的值压入中断程序的内核态堆栈中,同时把 EFLAGS寄存器自动压栈,然后把被中断进程的返回地址压入堆栈。如果异常产生了一个硬件错误码,则将它也保存在堆栈中。如果特权级没有发生变化,则压入栈中的内容如图3.4中。你可能要问,现在SS:ESP和CS:EIP 这两对寄存器的值分别是什么?SS:ESP的值从当前进程的TSS中获得,也就是获得当前进程的内核栈指针,因为此时中断处理程序成为当前进程的一部分,代表当前进程在运行。CS:EIP的值就是IDT表中第i项门描述符的段选择符和偏移量的值,此时,CPU就跳转到了中断或异常处理程序。