2.1 Sources & Classification
2.1.1 Overview
如果你是一个程序员,那么你工作的主要任务就是写代码,但除了写代码之外,你也可能去做一些别的事情,比如开会。假如有一天你正在聚精会神的写代码,你的老板过来通知你马上去参加一个事先没有任何通知的会议,那么你不得不放下手中的代码去开会——尽管开会也是你工作的一部分,但你当前正在进行的编码工作却被开会打断了,并且由于这是老板的要求,你不得不去做。等你开完会后,你重新回到你的座位,继续写你的代码。
这件事情还可以有一些变化——事先你的老板通知你,今天将有一个非常重要的会议,但具体时间未定。你为了不错过这次开会,你可以采取两种方法来处理:一是你不再写代码,而是不断的询问老板的秘书是否当前时间要开会,直到开始开会为止;另外一种方法是你可以告诉老板的秘书,当开始开会的时候通知你一声,而你在接到开会通知之前仍然去集中精力写你的代码。很明显,第二种方法更加科学——你不必在一件无法确定发生时间的事情没有发生之前进行盲目的等待,而是更加有效的利用你的时间来写你的代码,等事情真正发生之后,通过别人的通知来知道。
这就是中断的基本原理。在这个例子里,你是一个事务处理者,在没有别的事件发生的前提下,你的工作就是写代码,当有其它事件发生的时候,你通过某种通知机制(比如别人的提醒),来中断你写代码的任务,转而去处理这件事情,等别的事情处理结束后,你继续写你的代码。
中断是可以嵌套的。我们仍然以程序员为例,加入你正在写代码,突然你的邮件程序通知你有邮件到达,而且这份邮件是需要你尽快回复的,于是你停止写代码,开始回复这封邮件,但这个时候,老板通知你马上参加一个非常重要的会议,于是你又不得不停止回复邮件,去参加会议,等会议结束后,你马上回来接着回复邮件,等邮件回复完成后,你继续写你的代码。
中断也可以有优先级。在上一个例子中,当你的代码工作被一封重要邮件到达通知中断后,你开始回复这封邮件,但这个时候,又来了另外一封相对不太重要的邮件,于是你暂时不理会这个新到的邮件,继续回复之前的邮件,等回复完成后,你再来处理这封新到的,完成后,继续写你的代码。
某些中断是可以被屏蔽的。假如你是一个老板,某天你必须集中精力处理某件事情,于是你告诉你的秘书,你不想受任何打扰。那么当有人给你打电话的时候,你的秘书会帮你拒绝,而不是转接给你,类似其它的事情,你的秘书也统统帮你屏蔽掉了——而这些事情在平常你没有声明屏蔽的情况下,是可以通知给你,交给你来处理的。
但并非所有的中断都是可以屏蔽的。在上一个例子中,即使你告诉你的秘书,你不想受任何打扰,但是当一些非常紧急的事情发生的时候,比如,你所工作的办公大楼发生了火灾,那么你必然会收到火警通知,从而打断你手中的工作。
对于可屏蔽的中断,你可以有选择的屏蔽。比如,你可以告诉你的秘书,不想接某人的电话,当你的秘书接到此人的电话时,会帮你处理掉,而不通知给你。对于其它的事情,则正常通知给你。
另外,屏蔽中断的方法除了通过你的秘书之外,你也可以自身完全评比,比如把自己关在一个屋子里,门口挂着“请勿打扰”,然后拔掉所有的电话线和网线,让自己屏蔽掉你的秘书可以帮你屏蔽掉的所有中断。只不过,这种方法你无法有选择的屏蔽中断,比如,你只是不想接某人的电话,其它电话你仍然想正常的接,那么你拔掉电话线则让你拒绝了所有电话(或许可以使用带有来电显示的电话,但这只是个例子),而如果通过你的秘书,她则可以帮你进行识别和选择。
除了这些外部因素来中断你当前手中的事务之外,你在处理这些事务过程中遇到的问题也可以中断当前事务的处理。
你再回到程序员的角色来——你正在写代码,但这个时候你碰到了一个难题,让你的代码无法写下去,于是你不得不暂时停止写代码去请教别人,或者去查资料,等问题解决了之后,你重新回来继续写代码。
还有一种情况,当你一边写,一边测试你写的代码的时候,你发现了一个BUG,那么你也不得不停止继续写,而是去解决这个BUG,等BUG解决之后,再继续写下去。
所幸,这个两个例子中,问题毕竟还可能解决,从而让写代码这件事务得以继续处理。但有你无法保证你永远幸运——当你为某一个项目写代码的事务被一个难题中断时候,你停止写代码,转去解决这个难题,但不幸的是,你发现你之前所写的代码的立足点都是错误的,那么继续写下去只能是南辕北辙,你所能做的只能放弃这个事务,重新开启一个新的事务——重新设计,重新编码。
因为事务是被你在处理这些事务时遇到的意外问题打断,所以以上三种情况我们称之为异常。
事务除了被事务中遇到的问题打断之外,还可能被事务处理自身所需要的其它事务中断。比如,我们把“写”代码作为一个事物,那么在“写”的过程中,我们需要测试一下我们已经完成代码的正确性,于是我们暂时停止“写”,转而去“测试”一下,等“测试”OK后,再继续写下去。
最后,中断你手中事务的原因就来自于你自身,比如你困了,饿了,生病了等等,都会让你停止手中的工作,去休息,去就餐,去治疗,等这一切OK之后,你或许可以继续你的工作。这是你自身异常所造成的事务中断。
从上面的例子中,我们可以得知,对你手中事务的中断来自于:1、你自身的非事务原因,比如生病;2、你自身在处理事务过程中遇到的问题(三种异常);3、外界因素;4、你手中事务的自身需要(比如例子中的代码测试)。其中来自于1,2,4的中断你是无法屏蔽的,而来自于3的中断一部分是可以屏蔽的,一部分是不可屏蔽的。对于可屏蔽部分,你可以通过别人(比如,在这个例子中是你的秘书)来有选择的进行屏蔽(当然也可以选择全部评比),或者靠自己完全屏蔽。
如果将你比做CPU,将你处理的事务比作CPU当前正在执行的软件,将外界因素比做其它硬件,将你的秘书比作PIC(可编程中断控制器),那么中断将分类为:1、CPU内部硬件异常;2、软件异常;3、外部硬件中断;4、软件中断。其中3进一步被细分为可屏蔽中断和不可屏蔽中断。1,2,4则是完全不可屏蔽的。
在Intel 386系统上,PIC就是8259A,可屏蔽中断可以通过8259A进行有选择的控制,也可以由CPU自身完全屏蔽(通过cli指令)。软件通过INT n指令主动进行中断,CPU在执行软件时有可能被软件指令执行异常所中断,也可能被CPU自身的内部硬件异常所中断。如下图所示:
Interrupt的来源从总体上分为硬件(外部)中断和软件中断。其中硬件中断分为不可屏蔽中断NMI(Non-Maskable Interrupt)和可屏蔽硬件中断INTR。Exception的来源于CPU内部,比如软件指令执行异常(比如被0除),或者CPU芯片硬件异常。
NMI、软件中断和异常以及硬件异常都是不可屏蔽的,状态寄存器IF位的设置对它们没有影响。只有INTR是可以屏蔽的,是否屏蔽取决于状态寄存器IF的设置。
Excetpions被更进一步细分为faults,traps,和aborts。
2.1.2 Source of Interrupts
2.1.2.1 Hardware Interrupts
Hardware Interrupts又称为External Interrupts,分为NMI和INTR。
NMI产生于硬件本身的突发性事件,比如断电。NMI正如其名,是不可屏蔽的,也就是说不受EFLAGS-IF标志位的影响。NMI使用中断向量号2。
INTR是通过可编程中断控制器PIC(Programmable Interrupt Controller)发送给CPU的,在PIC外部,挂接的都是外部硬件设备,比如时钟,磁盘,键盘,RS232等。INTR是可以屏蔽的,我们可以通过对PIC编程来屏蔽某个或某些设备发出的IRQ。也可以通过CLI指令清除EFLAGS-IF标志位来屏蔽所有来自于PIC的中断。在IBM PC及其兼容机上,所使用的PIC一般是一个或两个Intel 8259A芯片。
2.1.2.2 Software Interrupt
Software Interrupt是软件通过执行INT n指令产生的,其中n是中断向量号。比如软件执行INT 36,则会迫使CPU执行向量号为36的ISR。
从0到255范围内的所有中断向量号都可以作为INT n指令的参数。
2.1.3 Source of Exception
2.1.3.1 Hardware Exception
Hardware Exception又称作Machine-Check Exception。P6系列的芯片实现了一套机器检测架构,来提供检测和报告芯片内部操作或总线传输等操作引起的硬件错误的机制,这些错误包括:
等等。
当一个引起Machine-Check Exception的错误被检测到以后,CPU会触发一个Machine-Check Exception(向量18),并返回一个Error Code。
2.1.3.2 Software Exception
Software Exception的来源有两个:
当CPU在执行一个程序(OS或Application)的时候,如果检测到一个程序错误,则会产生一个异常。Intel为每一个CPU可以检测到的异常都定义了一个向量号。
另外,软件可以通过执行指令INTO,INT 3,BOUND来产生一个异常。比如,INT 3指令可以引起一个断点异常。
软件可以通过INT n指令来模拟产生一个异常。如果INT n指令中的n是一个被Intel定义的异常向量,CPU可以产生一个对应那个向量的中断,这个中断将会调用相应的ISR。因为这实际上是一个中断,所以CPU不PUSH一个错误码到堆栈中去,虽然真正由硬件产生的针对那个向量的异常会那么做。对于这些产生错误码的异常来说,相应的ISR会从栈中POP一个错误码。但如果它们被软件通过使用INT n指令来模拟产生的话,由于错误码没有被产生并被压栈,所以放置在栈顶的EIP(用来替代未被生成的错误码)会被POP出来,并被丢弃掉。
2.1.4 Exception
Classifications
按照如下原则,
Exceptions可以被进一步细分为Faults,Traps,和Aborts。
2.1.4.1 Faults
一个Fault一般来说是可以被纠正的。一旦一个Fault被纠正,那个引起此Fault的程序可以继续执行,而不破坏程序的连续性。
当一个Fault被报告时,CPU会将机器状态恢复到引起此Fault的指令被执行之前的状态,此Fault的ISR的返回地址(被保存的CS和EIP寄存器的内容)被设置为指向引起此Fault的那条指令,而不是指向这条指令之后的那条指令。
2.1.4.2 Traps
当一个引起Trap的指令被执行之后,一个Trap异常会随即发生。在相应的ISR被执行之后,这个引起此Trap异常的程序可以继续执行,自身的连续性不会被破坏。
针对一个Trap的ISR的返回地址被设置为指向那条引起此Trap的指令之后的那条指令。
2.1.4.3 Aborts
一个Abort并不总是报告那条引起异常的指令的精确位置,也不允许那个引起Abort异常的程序继续执行。
Aborts被用来报告严重错误,比如硬件错误,以及系统表中(比如TLB)的数据不一致或者非法等错误。
当一个Abort发生时,引起此Abort的程序应该被终止运行。如果此程序是一个OS,则OS应该Halt;如果此程序是一个运行于OS之上的Application,则OS应该对此程序进行中止处理。