1.异常和中断向量
每个需要被处理器进行特殊处理的异常和中断条件都被赋予了一个标号,称为向量,向量号的索引范围是0~255,其中0~31保留,32~255用于用户定义的中断。
保护模式下的异常和中断如下:
向量号 |
助记符 |
说明 |
类型 |
错误号 |
产生源 |
0 |
#DE |
除出错 |
故障 |
无 |
DIV或IDIV指令 |
1 |
#DB |
调试 |
故障/陷阱 |
无 |
任何代码或数据引用,或是INT 1指令 |
2 |
-- |
NMI中断 |
中断 |
无 |
非屏蔽外部中断 |
3 |
#BP |
断点 |
陷阱 |
无 |
INT 3指令 |
4 |
#OF |
溢出 |
陷阱 |
无 |
INTO指令 |
5 |
#BR |
边界范围超出 |
故障 |
无 |
BOUND指令 |
6 |
#UD |
无效操作码(未定义操作码) |
故障 |
无 |
UD2指令或保留的操作码。(Pentium Pro中加入的新指令) |
7 |
#NM |
设备不存在(无数学协处理器) |
故障 |
无 |
浮点或WAIT/FWAIT指令 |
8 |
#DF |
双重错误 |
异常终止 |
有(0) |
任何可产生异常、NMI或INTR的指令 |
9 |
-- |
协处理器段超越(保留) |
故障 |
无 |
浮点指令(386以后的CPU不产生该异常) |
10 |
#TS |
无效的任务状态段TSS |
故障 |
有 |
任务交换或访问TSS |
11 |
#NP |
段不存在 |
故障 |
有 |
加载段寄存器或访问系统段 |
12 |
#SS |
堆栈段错误 |
故障 |
有 |
堆栈操作和SS寄存器加载 |
13 |
#GP |
一般保护错误 |
故障 |
有 |
任何内存引用和其他保护检查 |
14 |
#PF |
页面错误 |
故障 |
有 |
任何内存引用 |
15 |
-- |
(Intel保留,请勿使用) |
|
无 |
|
16 |
#MF |
x87 FPU浮点错误(数学错误) |
故障 |
无 |
x87 FPU浮点或WAIT/FWAIT指令 |
17 |
#AC |
对起检查 |
故障 |
有(0) |
对内存中任何数据的引用 |
18 |
#MC |
机器检查 |
异常终止 |
无 |
错误码(若有)和产生源与CPU类型有关(奔腾处理器引进) |
19 |
#XF |
SIMD浮点异常 |
故障 |
无 |
SSE和SSE2浮点指令(PIII处理器引进) |
20-31 |
-- |
(Intel保留,请勿使用) |
|
|
|
32-255 |
-- |
用户定义(非保留)中断 |
中断 |
|
外部中断或者INT n指令 |
2.中断和异常源
2.1中断源
处理器从两种地方接受中断:
外部(硬件产生)的中断软件产生的中断
外部中断通过处理器芯片上的两个引脚(INTR和NMI)接收。注意,NMI接收到的是不可屏蔽中断,它使用固定的中断号2.其他中断都可以使用标志寄存器的IF标志位来屏蔽。
2.2异常源
处理器接收的异常源也有两个
@@@处理器检测到的错误异常
@@@软件产生的异常
异常可以详细分为故障、中止和陷阱。INT 0、INT 3(断点异常)、BOUND可以从软件中产生异常。INT n指令可以用于软件模拟异常,但是有一个限制。
3.异常分类
根据异常被报告的方式和异常的指令是否能够被重新执行,异常可以细分成故障(Fault)、陷阱(Trap)和中止(Abort)。
Fault:可以被纠正的异常,一旦被纠正,程序就可以正确运行。Trap:是一个引起陷阱的指令被执行后立刻会报告的异常。
Abort是一种不会总是导致异常的指令的精确位置的异常。
4.程序或者任务的重新执行
为了让程序可以在异常或者中断处理完成之后能够重新回复执行,除了中止之外的异常都能报告精确的指令位置,并且所有的中断保证是在指令边界上发生。
对于故障类异常,处理器产生异常时保存的返回指针指向出错指令。例如页面故障指令(想想怎么执行)。为了保证重新执行对于当前执行程序具有透明性,处理器会保存必要的寄存器和堆栈指针信息,使得自己能够返回到指令出错之前的状态。
对于陷阱类异常,处理器产生异常时保存的返回指针指向引起陷阱操作的后一条指令。例如,JMP指令执行的时候产生了异常,那么异常恢复后,回到JMP所指的目标位置,而不是JMP的后一条指令。
中止类异常不支持可靠地重新执行程序或者任务。
中断会严格地执行被中断程序的重新执行而不会丢失任何连贯性。
5.开启和禁止中断
EFLAGS中的IF可以用于禁止可以屏蔽的中断,可以用STI和CLI来设置和清楚这个指令。只有当程序的CPL<=IOPL时候,才可以执行这两条指令,否则将引发一般保护性异常。IF标志会受到下面操作的影响。
1)push和pop指令:push用于将eflags压栈,然后可以被修改,然后pop
2)任务切换、POPF、IRET指令都可以加载EFLAGS,从而可以修改它。
3)通过一个中断门来处理一个中断IF标志会被自动清零,从而禁止可屏蔽中断。如果通过一个陷阱门来处理一个中断,那么IF标志不会被复位。
6.异常和中断处理的优先级
处理器会首先处理最高级别的中断和异常,低优先级的异常将会被丢弃,低优先级的中断将会被保持等待。当中断处理程序回到任务的时候,被丢弃的异常会重新发生。
异常或者中断的优先级如下:
向量号 |
助记符 |
说明 |
类型 |
错误号 |
产生源 |
0 |
#DE |
除出错 |
故障 |
无 |
DIV或IDIV指令 |
1 |
#DB |
调试 |
故障/陷阱 |
无 |
任何代码或数据引用,或是INT 1指令 |
2 |
-- |
NMI中断 |
中断 |
无 |
非屏蔽外部中断 |
3 |
#BP |
断点 |
陷阱 |
无 |
INT 3指令 |
4 |
#OF |
溢出 |
陷阱 |
无 |
INTO指令 |
5 |
#BR |
边界范围超出 |
故障 |
无 |
BOUND指令 |
6 |
#UD |
无效操作码(未定义操作码) |
故障 |
无 |
UD2指令或保留的操作码。(Pentium Pro中加入的新指令) |
7 |
#NM |
设备不存在(无数学协处理器) |
故障 |
无 |
浮点或WAIT/FWAIT指令 |
8 |
#DF |
双重错误 |
异常终止 |
有(0) |
任何可产生异常、NMI或INTR的指令 |
9 |
-- |
协处理器段超越(保留) |
故障 |
无 |
浮点指令(386以后的CPU不产生该异常) |
10 |
#TS |
无效的任务状态段TSS |
故障 |
有 |
任务交换或访问TSS |
11 |
#NP |
段不存在 |
故障 |
有 |
加载段寄存器或访问系统段 |
12 |
#SS |
堆栈段错误 |
故障 |
有 |
堆栈操作和SS寄存器加载 |
13 |
#GP |
一般保护错误 |
故障 |
有 |
任何内存引用和其他保护检查 |
14 |
#PF |
页面错误 |
故障 |
有 |
任何内存引用 |
15 |
-- |
(Intel保留,请勿使用) |
|
无 |
|
16 |
#MF |
x87 FPU浮点错误(数学错误) |
故障 |
无 |
x87 FPU浮点或WAIT/FWAIT指令 |
17 |
#AC |
对起检查 |
故障 |
有(0) |
对内存中任何数据的引用 |
18 |
#MC |
机器检查 |
异常终止 |
无 |
错误码(若有)和产生源与CPU类型有关(奔腾处理器引进) |
19 |
#XF |
SIMD浮点异常 |
故障 |
无 |
SSE和SSE2浮点指令(PIII处理器引进) |
20-31 |
-- |
(Intel保留,请勿使用) |
|
|
|
32-255 |
-- |
用户定义(非保留)中断 |
中断 |
|
外部中断或者INT n指令 |
7.中断描述符表
IDT表可以处在线性地址空间的任何地方,使用IDTR来定位。IDTR是一个32b的寄存器,含有32位基地址和16b限长。IDT表基地址应该8b对齐,从而加快访问速度。
如果超过IDT界限,将产生一个一般保护性异常。
8.IDT描述符
中断门和陷阱门都有一个长指针(段选择符和偏移值),用于将程序转移到中断或者异常处理过程,她们的区别在于IF标志位。IDT中任务门和GDT/LDT中任务门的描述格式相同,任务门的描述符中含有一个TSS段的选择符,该任务用于处理异常和中断。
9.异常和中断处理
处理方法类似于call指令调用程序过程和任务的方法。中断或者异常处理过程如下图所示。
1)如果处理过程发生在高特权级,那么将发生堆栈切换操作,堆栈切换的过程如下
A、处理器从当前执行任务的TSS段得到中断或者异常处理过程使用的堆栈的段选择符和栈指针。然后将被中断任务的栈选择符和栈指针压入新栈内部,如下图所示。
C、如果异常产生一个错误号,那么错误号也将被压入新栈。
2)如果处理过程和中断任务在同一个特权级上
那么将没有上述A步骤
为了从中断处理任务中返回,处理过程必须使用IRET指令,因为和RET指令相比,IRET会将EFLAGS恢复到相关寄存器中。不过,只有当CPL=0的时候,才恢复EFLAGS的IOPL字段,并且只有当CPL<=IOPL时候,IF的标志才会改变。如果发生了堆栈切换,IRET将会恢复到原来的堆栈。
9.1异常或者中断处理过程的保护
异常或者中断处理过程的特权级保护机制和通过调用门调用普通过程类似(也就是说,中断或者异常处理过程不会发生优先级提升?)。CPU不会将控制转移到比CPL更低的代码段的中断处理过程,但是也有不同:
@@@中断或者异常向量没有RPL,所以也不会有相应检查。@@@只有当中断是由INT n、INT 3、INT 0产生的时候,处理器才会检查中断或者陷阱门中的DPL,而且要求CPL<=DPL.这样可以防止,应用程序通过软件中断调用重要的异常处理过程,例如页错误处理过程。对于硬件和处理器能够检测到的异常,处理器会忽略中断或者陷阱门中的DPL,例如时钟中断。
为了达到这个目标,我们通常采用以下方法之一:
@@@异常或者中断处理程序放在一个一致性代码段之中——该技术可以用于只需访问堆栈数据的处理过程(如除出错异常)。如果处理程序需要访问数据段中的数据,那么特权级3必须能够访问这个数据段,这样依赖,就没有保护可言了。因为~~~~~~~~
@@@处理过程可以放在具有特权级0的非一致性代码之中,这种处理过程总是可以执行的,与被中断任务或者程序的当前特权级无关。
9.2异常或者中断处理过程中的标志使用方式
中断门和陷阱门的唯一区别就是二者在处理器操作EFLAGS上IF标志位的方法上——想想是什么?9.3执行中断处理过程的任务
通过IDT表中任务门来访问终端或者异常的处理过程时,就会导致任务切换。从而可以在一个专用任务中执行中断或者异常处理过程。IDT表中的任务门,引用GDT表中的TSS描述符,中断任务的切换和普通任务的切换相同,但是0.12内核中没有使用这种方法。10.中断处理任务
当通过IDT表中任务门来访问终端或者异常的处理过程时,就会导致任务切换。使用单独的任务来处理异常和中断具有如下好处
@@@被中断任务的完整上下文会被自动保存@@@处理中断或者异常的时候,新的TSS允许处理过程使用新特权级别0的堆栈。在当前特权级0的堆栈已经损坏时,如果发生了一个异常和中断,那么在为中断处理过程提供一个新特权级0的堆栈条件下,通过任务们访问中断处理过程能够有效防止系统崩溃。
@@@通过使用单独的LDT给中断或异常处理任务独立的地址空间。
使用独立任务处理异常或者中断的不足——大量机器状态需要保存,中断相应慢,延迟高。
中断处理任务切机理如下图所示:
11.错误码
EXT:外部事件标志
IDT:如果置位,表明错误码所以部分指向IDT中的一个门描述符;反之,指向段描述符
Tl:当IDT=0的时候:Tl=1,表明指向LDT中的描述符;Tl=0,表明指向GDT中的描述符
索引字段:指明了段或者门描述符的索引值。某些情况下,错误码全部是0,表明错误不是某个特定的段造成,或者是填入了一个空的段描述符。
页故障异常的错误码格式与上面的不同,如下图所示。只有最低三位有用,它们的名称与页表项的后三位相同(U/S、W/R、P)。
另外,CPU还会把引起页面故障异常的线性地址放在CR2中,页错误异常处理程序就可以使用这个地址来定位相关的页目录项和页表项。
注意:错误不会被IRET指令自动弹出堆栈,因此中断处理程序在返回之前,必须清除堆栈上的错误码。另外,虽然处理产生的某些异常会产生错误码并会自动保存到处理过程的堆栈中,但是外部硬件中断或程序执行INT n指令产生的异常并不会把错误码压入堆栈中。