2.3 Priority & Control
[Home] [Top] [Previous] [Next]
2.3.1 Overview
Interrupts/Exceptions有很多种,当一个Interrupt/Exception发生时,系统会自动调用它的ISR,在ISR被执行期间,可能会发生新的Interrupts/Exceptions,这样,ISR的执行就会被新的Interrupts/Exceptions打断,而新的ISR会被执行,当新的ISR被执行结束之后,再继续执行旧的ISR。这就是Interrupts/Exceptions的嵌套问题。
这种嵌套可以有很多层,甚至可以无穷嵌套下去——如果中断发生的频率超过ISR执行速度的话——但这种情况一般不会发生,因为ISR一般都写的非常短小。但如果发生多层嵌套,有时候会存在问题。比如,当OS正在运行的时候,CPU检测到一个内部总线错误,引发了一个Machine-Check Exception,这个时候,CPU应该马上来处理这个Exception。但当CPU正在执行这个Exception的ISR的时候,又接到多个键盘Interrupt。这个时候硬件(内部总线)已经发生了错误,CPU应该优先解决这个错误,如果这时候CPU转而去处理键盘Interrupt的话,可能会引发灾难性的后果。
针对这种情况,一个方法还起来好像能够解决这个问题,那就是,当一个Interrupt/Exception的ISR正在被执行的时候,不响应任何其它Interrupts/Exceptions,也就是说Interrupts/Exceptions应该被一个一个按照FIFO(First In, First Out)的原则处理。
但不幸的是,这样仍然存在问题。我们将上一个例子反过来:当CPU正在处理一个Keyboard Interrupt的时候,CPU突然检测到一个内部总线错误,引发一个Machine-Check Exception,这个时候CPU应该马上去处理这个Exception,而不是继续处理Keyboard Interrupt。
从上面两个例子可以看出,那个Machine-Check Exception总是应该比Keyboard Interrupt优先处理。所以我们需要为不同的Interrupts/Exceptions定义不同的Priority。
2.3.2 Priority
当多个Interrupts/Exceptions同时或在很短的时间内相继发生的时候,Intel 80x86芯片可以以一种可以预知的顺序来先后处理它们。能够预知的原因就是每个Interrupt/Exception都有自己的Priority。对于这批Interrrupts/Exceptions,CPU优先执行高优先级的,对于相同优先级的,CPU以FIFO的顺序执行它们。
对于所有可能的Interrupts/Exceptions,Intel为它们定义了各自的Priority。这些Priority分类按照Interrupts/Exceptions的来源进行。下表总结了这些分类:
当一个Interrupt/Exception正在被处理时,如果又发生了其它Interrupts/Exceptions,CPU会去查看它们中间是否存在着比当前正在处理的Interrupt/Exception 优先级更高的,如果存在,则保留当前中断处理的Context,然后从这些Interrupts/Exceptions中挑选一个优先级最高的,并开始处理它。然后,剩下的那些未被处理的Exceptions将被丢弃,未被处理的Interrupts则继续处于Pending状态,等待被处理。那些被丢弃的Exceptions,都是由软件引起的(由硬件引起的Exceptions具有最高的Priority,永远不可能被丢弃),所以,等所有的Interrupts/Exceptions被处理结束时候,执行点会重新回到那些引起这些Exceptions的点,那时候,Exceptions会被重新生成并被处理。
下图表现了CPU的指令循环(Instruction Cycle):
2.3.3 Control - Disable & Enable Interrupts
依赖于状态寄存器EFLAGS的IF和RF位的设置,CPU可以禁止一些Interrupts的发生。
2.3.3.1 Masking Maskable Hardware Interrupts
EFLAGS寄存器的IF位的设置可以决定是否屏蔽那些来自于INTR pin的中断。
如果IF位被清除,CPU会禁止外部中断传递给INTR pin;如果IF位被设置,CPU则允许外部中断传递给INTR pin。INTR pin外接PIC-8259A。IF位的设置不影响NMI,也不影响处理器产生的Exceptions。当CPU被Reset之后,IF位被清除。
通过STI(Set Interrupt-Enable Flag)和CLI(Clear Interrupt Flag)指令可以设置和清除IF位。但在执行这些指令时,必须保证当前CPL小于或等于IOPL;如果CPL > IOPL,则会产生一个General Protection Exception。
我们可以使用C的Macro实现这两个操作:
#define sti() __asm__ __volatile__ ("sti": : :"memory")
#define cli() __asm__ __volatile__ ("cli": : :"memory")
也可以使用C++的inline函数:
inline void sti(void)
{
__asm__ __volatile__ ("sti": : :"memory");
}
inline void cli(void)
{
__asm__ __volatile__ ("cli": : :"memory")
}
IF位还受下列情况的影响:
2.3.3.2 Masking Instruction Breakpoints
EFLAGS寄存器的RF位被用来控制CPU对Instruction Breakpoints Condition的响应。如果被设置,则禁止Instruction Breakpoints产生Debug Exceptions;如果被清除,则Instruction Breakpoints将会产生Debug Exceptions。
2.3.3.3 Masking Exceptions & Interrupts when Switching Tasks
为了切换到一个不同的Stack Segment,通常会使用下面一对指令:
movw %ax, %ss
movl
$stack_top, %esp
如果在这个segment selector被装入SS寄存器之后,在ESP寄存器被装入之前,一个Interrupt/Exception恰好发生了,由于一个Stack是由SS和ESP两个寄存器决定的,那么在这个刚刚发生的Interrupt/Exception的ISR执行期间,这个Stack可能处于一种不合法的状态。
为了避免这种情况,CPU在执行了一个MOV to SS指令,或一个POP to SS指令之后,会禁止:
知道下一条指令被执行完毕之后(不管下一条指令是什么)为止。在这个期间,其它的Exceptions仍然可以被生成。
如果使用LSS指令来修改SS寄存器,这个问题就不存在。并且这种方法也是被Intel建议用来修改SS寄存器的方法。