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正在执行这个ExceptionISR的时候,又接到多个键盘Interrupt。这个时候硬件(内部总线)已经发生了错误,CPU应该优先解决这个错误,如果这时候CPU转而去处理键盘Interrupt的话,可能会引发灾难性的后果。

 

针对这种情况,一个方法还起来好像能够解决这个问题,那就是,当一个Interrupt/ExceptionISR正在被执行的时候,不响应任何其它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位还受下列情况的影响:

  • PUSHF指令将所有的flags存入Stack中,在Stack中flags可以被检测和修改。然后POPF指令会将这些修改过的flags放入EFLAGS寄存器。
  • 任务切换,以及POPF和IRET指令会引起内存中的值装入EFLAGS寄存器,因此,它们可以被用来修改IF位的设置。
  • 当一个Interrupt通过一个Interrupt Gate处理时,为了屏蔽掉所有的Maskable Hardware Interrupts,IF flag会被自动清除。但如果一个中断通过一个Trap Gate处理时,IF flag不受影响。

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指令之后,会禁止:

  • 所有的Interrupts(包括著名的NMI);
  • debug exceptions;
  • single-step trap exceptions

知道下一条指令被执行完毕之后(不管下一条指令是什么)为止。在这个期间,其它的Exceptions仍然可以被生成。 

如果使用LSS指令来修改SS寄存器,这个问题就不存在。并且这种方法也是被Intel建议用来修改SS寄存器的方法。