以下内容摘抄自《80386 Programmer’s Manual》
《Chapter 9, Exceptions and Interrupts》
中断和异常是特殊类型的控制转移,它们的表现有时候像不受计算机程序支配的CALL。中断和异常改变了正常程序流,以处理外部事件或报告错误、异常情况。中断和异常的区别是,中断interrupts用于处理处理器外部的异步事件,异常exceptions用于处理处理器在执行指令时检测到的情况。
外部中断的两个来源:
1、可屏蔽中断Maskable interrupts,通过INTR pin来发送信号。
2、不可屏蔽中断Nonmaskable interrupts,通过NMI (Non-Maskable Interrupt) pin来发送信号。
异常的两个来源:
1、处理器检测。进一步分为故障faults、陷阱traps和中止aborts。
2、编程。指令INTO、INT 3、INT n、BOUND可以引发异常。这些指令通常被称为“软件中断”,但处理器把它们当作异常处理。
《9.1 Identifying Interrupts》
每一个不同类型的中断或异常都有一个处理器可识别的数。处理器给不可屏蔽中断和异常分配了0-31的标识符。80386目前没有使用所有的数字,在这个范围内未赋值的标识符保留了未来扩张的可能性。可屏蔽中断的标识符是由外部中断控制器决定的,如英特尔的8259A可编程中断控制器,并在处理器的中断应答序列interrupt-acknowledge sequence过程中与处理器通信。具体的分配如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21Identifier Description
0 Divide error
1 Debug exceptions
2 Nonmaskable interrupt
3 Breakpoint (one-byte INT 3 instruction)
4 Overflow (INTO instruction)
5 Bounds check (BOUND instruction)
6 Invalid opcode
7 Coprocessor not available
8 Double fault
9 (reserved)
10 Invalid TSS
11 Segment not present
12 Stack exception
13 General protection
14 Page fault
15 (reserved)
16 Coprecessor error
17-31 (reserved)
32-255 Available for external interrupts via INTR pin
根据报告的方式以及是否支持重新启动导致异常的指令,异常被分为故障、陷阱、终止。
故障:
故障是这样一种异常:要么在指令开始执行之前被检测到,要么在指令执行期间被检测到。如果在指令执行期间被检测到,机器恢复到能允许重启导致故障的指令的状态,然后报告故障。
陷阱:
陷阱是这样一种异常:在检测到异常的指令之后的指令边界立即被报告。
终止:
终止是这样一种异常:它既不允许获取引起异常的指令的精确位置,也不允许重启导致异常的程序。终止用于报告严重的错误,比如硬件错误和不一致、系统表的非法值。
《9.2 Enabling and Disabling Interrupts》
处理器只在一条指令结束及下一条指令开始之际处理异常和中断。在指令边界,处理器通过某些条件和标识设置禁止某些异常和中断。
《9.2.1 NMI Masks Further NMIs》
如果正在执行一个不可屏蔽中断的处理程序,处理器将忽略其他来自NMI pin的中断信号,直至执行IRET指令。
《9.2.2 IF Masks INTR》
允许中断标志位IF(interrupt-enable flag)控制着是否接受经由INTR pin的外部中断信号。当IF=0,禁止INTR中断;当IF=1,允许INTR中断。处理器接收到RESET信号后,将清除IF和其他标志位。
显式改变IF:CLI(Clear Interrupt-Enable Flag)和STI(Set Interrupt-Enable Flag)显式改变IF(bit 9 in the flag register)。只有CPL < = IOPL时才允许执行CLI和STI,否则将会发生保护异常。
隐式改变IF:1、指令PUSHF将会在栈上存储所有的标识,包括IF。2、任务切换和指令POPF、IRET将加载标志寄存器,会修改IF。3、中断门interrupt gates自动重置IF,禁止中断。
《9.2.3 RF Masks Debug Faults》
EFLAGS的RF位控制识别出调试错误,给定一条指令,无论它被重启多少次,RF使得最多只有一次调试错误出现。
《9.2.4 MOV or POP to SS Masks Some Interrupts and Exceptions》
软件经常需要使用成对的指令来改变堆栈段,比如MOV SS, AX、MOV ESP, StackTop。如果SS已经改变而ESP还未收到相应的改变的时候处理异常或中断,中断或异常处理程序执行期间栈指针SS:ESP是不一致的。为了防止这种情况的发生,80386在执行MOV SS和POP SS指令之后,在下一条指令的指令边界内禁止NMI、INTR、debug exceptions、single-step traps。但是页错误和保护错误仍可能发生,若使用80386 LSS指令,则不会出现这些问题。
《9.3 Priority Among Simultaneous Interrupts and Exceptions》
如果在指令边界有多个中断或异常在等待,处理器一次只会处理其中一个。该中断或异常的分类需具有最高优先级。处理器将控制转移到中断处理程序的第一个指令。低优先级的异常被丢弃,低优先级的中断保持等待。在中断处理程序返回控制权的时候,被丢弃的异常将被重新发现。
同时发生的异常和中断的优先级:1
2
3
4
5
6
7
8Priority Class of Interrupt or Exception
HIGHEST Faults except debug faults
Trap instructions INTO, INT n, INT 3
Debug traps for this instruction
Debug faults for next instruction
NMI interrupt
LOWEST INTR interrupt
《9.4 Interrupt Descriptor Table》
中断描述符表(Interrupt Descriptor Table,IDT)将每个异常或中断向量分别与它们的处理过程联系起来。与GDT和LDT表类似,IDT也是由8字节长描述符组成的一个数组。与GDT和LDT表不同的是,IDT的第一项可以包含一个描述符。为了构成IDT表中的一个索引值,处理器把异常或中断的向量号乘以8。因为最多只有256个中断或异常向量,所以IDT无需包含多于256个描述符。IDT中可以含有少于256个描述符,因为只有可能发生的异常或中断才需要描述符。不过IDT中所有空描述符项应该设置其存在位(标志)为0。
IDT表可以驻留在线性地址空间的任何地方,处理器使用IDTR寄存器来定位IDT表的位置。这个寄存器中含有IDT表32位的基地址和16位的长度(限长)值。IDT表基地址应该对齐在8字节边界上以提高处理器的访问效率。限长值是以字节为单位的IDT表的长度。
LIDT和SIDT指令分别用于加载和保存IDTR寄存器的内容,两者都有一个显式的操作数:内存中一个6字节区域的地址。LIDT指令用于把内存中的限长值和基地址操作数加载到IDTR寄存器中。该指令仅能由当前特权级CPL是0的代码执行,通常被用于创建IDT时的操作系统初始化代码中。SIDT指令用于把IDTR中的基地址和限长内容复制到内存中。该指令可在任何特权级上执行。如果中断或异常向量引用的描述符超过了IDT的界限,处理器会产生一个一般保护性异常。
中断描述符寄存器和中断寄存器表:
LIDT和SIDT的伪描述符格式:
《9.5 IDT Descriptors》
IDT可能包括3种描述符:Task gates、Interrupt gates、Trap gates。
参考于http://stackoverflow.com/questions/3425085/%20the-difference-between-call-gate-interrupt-gate-trap-gate,对各种门描述符进行分析。
通用寄存器EFLAGS保存的是CPU的执行状态和控制信息,其中只需要关注两个寄存器:IF和TF。
TF(Trap Flag):跟踪标志。置1则开启单步执行调试模式,置0则关闭。在单步执行模式下,处理器在每条指令后产生一个调试异常,这样在每条指令执行后都可以查看执行程序的状态。
IF(Interrupt enable):中断许可标志。控制处理器对可屏蔽硬件中断请求的响应。置1则开启可屏蔽硬件中断响应,置0则关闭。IF标志不影响异常和不可屏蔽终端NMI的产生。
gate用于实现从一段代码跳转到另一段代码(可能存在不同的代码段、不同的特权级)时的保护机制问题。一般而言,存在四种gate:
1、Task gate。只能存在GDT或IDT中并被int指令调用。因特尔工程师设计TSS(Task State Segment)主要是通过保存任务的寄存器状态来应用于硬件任务切换。触发硬件任务切换有两种方式,一种是使用TSS本身,一种是使用Task gate。可以通过call和jmp指令导致任务切换。Task gate设计的目的可能是用于在中断到来时触发硬件任务切换,因为跳转到TSS的选择器并不能触发硬件任务切换。现实情况下,由于使用不方便和性能不佳的原因,Task gate和硬件上下文切换功能基本不被使用。例如考虑到TSS只能存在GDT中,而GDT的长度不能大于8192,这就意味着从硬件的角度来说任务数不能超过8k。
2、Trap gate。只能存到IDT中并被int指令调用。Trap gate只是将控制权传递给陷阱门描述符指定的拥有更多特权的段的地址。Trap gate的用处在于:system call的实现;异常处理的实现;中断处理的实现(on machines with APIC)。
3、Interrupt gate。只能存到IDT中并被int指令调用。类似于Trap gate但会清空IF标志。
4、Call gate。可被存到GDT和LDT中并被call和jmp指令调用。类似于Trap gate,Call gate可以从用户模式的任务栈传递参数到内核模式的任务栈。传递的参数数量在Call gate的描述符中指定。Call的使用场景不多,因为:可以使用Trap gate代替;不轻便,如果另一个处理器没有类似的功能,则操作系统移植到另一个处理器的时候为了支持Call gate必须编写更多的代码;不灵活,栈之间可以传递的参数是有限的;性能不优。
Interrupt gate和Trap gate用来专门处理处理器异常或者中断,Task gate和Call gate一般用来处理用户的软件切换。
Interrupt gate和Trap gate的区别:一般情况下,Interrupt gate用于处理意外发生的错误,Trap gate用于处理软件中断和异常,比如页错误、调试中断、除0错误等。Interrupt gate会修改IF,即不再响应接下来的中断了。比如操作系统捕获了一个硬件中断正在处理,又来了另一个。如果用Trap gate,那么第一个处理就被打断了,这样会造成数据崩溃,所以必须屏蔽掉第二个,使一次硬件操作成为原子操作(NMI另说)。还有一个就是断点异常的处理必须暴露给用户程序进行调用,使用Trap gate处理。
Call gate和Task gate的区别:task是一个具体可运行的单位,可以运行、挂起、重启等,task的状态保存在TSS中。可以通过call或jmp指令具体调用一个task程序。Call gate和Task gate都可以用来切换到一个task,但是Task gate的寻址需要经过一个TSS找到code selector(具体见task gate descriptor),这比Call gate麻烦,但是带来的好处是:1、切换的时候原来task的上下文环境被自动保存到TSS。2、如果使用Task gate来处理中断例程,可以使程序和其他例程分开,使其具有独立的地址空间等。
以上四种切换机制的细节如下:
《9.6 Interrupt Tasks and Interrupt Procedures》
类似于CALL指令可以调用程序procedure或任务task,中断或异常可以调用一个中断处理程序handler,该handler可能是程序procedure或任务task。当响应中断或异常的时候,处理器使用中断或异常标志在IDT中索引描述符。如果处理器索引到一个中断门或陷阱门,它调用处理程序的方式类似于CALL指令调用一个调用门call gate。如果处理器索引到一个任务门,类似于CALL指令调用一个任务门task gate,它将导致一个任务切换。
《9.6.1 Interrupt Procedures》
中断门或陷阱门间接指向一个处理程序,该程序将在当前执行任务的上下文中被执行。中断门或陷阱门的选择器指向了GDT或当前LDT的一个可执行段描述符。中断门或陷阱门的偏移部分指向了中断或异常处理程序的起始位置。如下所示:
80386调用一个中断或异常处理程序的方式大致类似于CALL一个程序,以下部分将解释两者差异之处。
《9.6.1.1 Stack of Interrupt Procedure》
就像CALL指令导致控制转移一样,中断或异常处理程序的控制转移使用了栈存储了返回原先程序需要的信息。一个中断将在指针指向中断指令之前将EFLAGS进栈,如下图所示。某些异常会导致error code进栈,异常处理函数可以通过error code判断是什么异常。
《9.6.1.2 Returning from an Interrupt Procedure》
中断程序离开程序的方法也不用于普通程序,它将使用IRET指令离开。IRET类似于RET,除了IRET会将ESP额外增量四个字节(因为flags在栈上),并将保存的flags赋值到EFLAGS寄存器中。仅当CPL=0时,EFLAGS的IOPL部分将会被改变;仅当CPL <= IOPL时,EFLAGS的IF部分才会被改变。
《9.6.1.3 Flags Usage by Interrupt Procedure》
通过中断门或陷阱门的中断在当前TF(the trap flag)作为EFLAGS的一部分被保存到栈后,将清零TF。通过这个动作处理器可以防止使用单步调试活动影响中断响应。随后IRET指令恢复EFLAGS在栈上的值,也恢复了TF。
中断门和陷阱门的不同在于对IF(the interrupt-enable flag)的影响。经由中断门的中断将重置IF,防止其他中断(当IF=1,允许INTR中断)干扰当前的中断处理程序,随后IRET指令恢复EFLAGS在栈上的值。经由陷阱门的中断将不改变IF。
《9.6.1.4 Protection in Interrupt Procedures》
管理中断程序的特权规则类似于程序调用:CPU不允许一个中断转移控制到一个比当前权限更少特权的程序。试图违反该规则将会导致一般保护异常。由于中断的发生一般不可预测,特权规则可以有效地在可执行的中断或异常处理程序的特权级别上强加限制。以下策略确保特权规则不被违反:1、将处理程序安置在一个conforming segment,这种策略用于处理某些异常(如divide error),该程序只能使用栈上可被使用的数据,如果它需要数据段的数据,数据段必须有特权级别3,从而使其不受保护。2、将处理程序安置在特权级别为0的栈上。
《9.6.2 Interrupt Tasks》
IDT的任务门间接指向一个任务,任务门的选择器指向GDT的TSS描述符。如下图所示:
经由任务门的中断或陷阱的结果是出现一个任务切换。使用一个单独任务来处理中断有两个优点:1、整个上下文将被自动保存。2、通过LDT或页目录给予处理程序单独的地址空间,使其独立于其他任务。
中断任务通过IRET返回被中断的任务。
如果导致任务切换的异常有一个错误代码,处理器会自动把错误代码放入栈中,对应于中断任务执行的第一条指令的特权级别。
当80386操作系统使用中断任务时,实际上有两个调度器:软件调度器(操作系统的一部分)和硬件调度器(处理器的中断机制的一部分)。软件调度器的设计应该考虑一种情况:在启用中断时,硬件调度器随时可能派遣一个中断任务。
《9.7 Error Code》
当异常涉及到特定的段,处理器将异常处理程序的错误码放入栈中(无论是程序或任务)。错误码的格式如下:
类似于选择器的格式,但是它没有RPL,而是包含两个one-bit项:1、如果程序的外部事件导致异常,处理器设置EXT位;2、如果错误码的索引部分指向了IDT的门描述符,处理器设置I-bit (IDT-bit)。
如果没有设置I-bit,TI值为0表示错误码指向GDT,TI值为1表示指向LDT。剩下的14 bits(包括TI)是段选择符的高14位。在某些情况下,栈上的错误码为空,即低位的word为空。
《9.8 Exception Conditions》
下面详细描述每一个可能的异常情况。每个描述将异常按故障、陷阱、中止分类。这种分类为系统重启发生异常的程序提供了必要信息。
故障faults:当故障被报告时,导致故障的指令的CS和EIP值将被保存。
陷阱traps:当陷阱被报告时,导致陷阱的指令的下一条动态指令的CS和EIP值将被保存。如果是在指令改变程序流期间发现陷阱,CS和EIP的值会反映程序流的变更。例如,执行JMP指令时发现陷阱,CS和EIP的值指向JMP的目标。
终止aborts:终止是这样一种异常:它既不允许获取引起异常的指令的精确位置,也不允许重启导致异常的程序。终止用于报告严重的错误,比如硬件错误和不一致、系统表的非法值。
《9.8.1 Interrupt 0 – Divide Error》
divide-error属于故障fault,在执行DIV或IDIV指令时,如果除数为0则出现该故障。
《9.8.2 Interrupt 1 – Debug Exceptions》
处理器触发调试异常的情况有很多种,该异常是故障还是陷阱取决于具体的情况:
指令地址断点 故障fault
数据地址断点 陷阱trap
一般检测 故障fault
单步调试single-step 陷阱trap
任务切换断点 陷阱trap
处理器不会将该异常的错误码进栈,异常处理程序可以通过检查调试寄存器来确定是哪些条件引起异常。
《9.8.3 Interrupt 3 – Breakpoint》
INT 3指令导致该陷阱trap。INT 3指令只有一个字节长,这使得在一个可执行段将一个操作码替换为断点操作码变得很容易。操作系统或调试子系统可以为可执行段使用一个数据段别名,在任何方便捕获正常执行的地方放置INT 3,这样可以执行一些特殊处理,如显示寄存器变量。
CS:EIP保存的值指向了断点的后续字节。如果调试器用另外的有效操作符替代了断点,则应该在返回前将保存的EIP值减一。
《9.8.4 Interrupt 4 – Overflow》
当处理器遇到INTO指令并且设置了溢出标志OF时,引发该陷阱trap。由于有符号算术和无符号算术都使用相同的运算指令,处理器无法区分,因此不会自动引起溢出异常。因此,当在结果被解释为有符号数并且会越界的时候,处理器将设置OF位。当计算有符号操作数时,程序员和编译器要么直接测试OF位,要么使用INTO指令。
《9.8.5 Interrupt 5 – Bounds Check》
当处理器在执行BOUND指令并发现操作数超过指定的限制的时候,引发该故障fault。程序可以使用BOUND指令来检查定义在内存块中的有符号数组索引和有符号限长。
《9.8.6 Interrupt 6 – Invalid Opcode》
如果在可执行单元中发现了无效的操作码,引发该故障fault。只有当试图执行该非法操作码的时候才会引发故障。该故障的错误码没有入栈。可以在相同的任务里处理该异常。
对于给定的操作码,如果操作数的类型是无效的也会引发该异常。比如段内JMP引用一个寄存器操作数,或者LES指令涉及寄存器源操作数
《9.8.7 Interrupt 7 – Coprocessor Not Available》
以下两种情况会引发该异常:
处理器遇到ESC(逃避escape)指令,且设置了CR0(control register zero)的EM(模拟emulate)位。
处理器遇到WAIT指令或ESC指令,且设置了CR0的MP(监控协处理器monitor coprocessor)和TS(任务切换task switched)位。
《9.8.8 Interrupt 8 – Double Fault》
通常情况下,当处理器试图调用异常处理程序处理之前的例外时又检测到异常,可以连续处理这两个异常。如果处理器不能串行处理它们,将会发出一个double-fault异常。为了确定何时将两个故障标识为double-fault,80386将异常分为三类:benign exceptions、contributory exceptions、page faults。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21Table 9-3. Double-Fault Detection Classes
Class ID Description
1 Debug exceptions
2 NMI
3 Breakpoint
Benign 4 Overflow
Exceptions 5 Bounds check
6 Invalid opcode
7 Coprocessor not available
16 Coprocessor error
0 Divide error
9 Coprocessor Segment Overrun
Contributory 10 Invalid TSS
Exceptions 11 Segment not present
12 Stack exception
13 General protection
Page Faults 14 Page fault
以下说明哪些异常组合导致double-fault而哪些不会:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16Table 9-4. Double-Fault Definition
-----------------------------------------------------------------
| SECOND EXCEPTION
|
| Benign | Contributory | Page
| Exception | Exception | Fault
-----------------------------------------------------------------
Benign | OK | OK | OK
Exception | | |
------------------------------------------------------
FIRST Contributory | OK | DOUBLE | OK
EXCEPTION Exception | | |
------------------------------------------------------
Page | | |
Fault | OK | DOUBLE | DOUBLE
-----------------------------------------------------------------
处理器总会将double-fault的错误码入栈,但是错误码总为0。可能不会重新启动引发故障的指令。如果试图调用double-fault处理程序的过程中发生任何异常,将关闭处理器。
《9.8.9 Interrupt 9 – Coprocessor Segment Overrun》
在保护模式下,如果在传递协处理器操作数的中间部分到NPX的过程中80386检测到页面或段非法,将引发该异常。该异常是可以避免的。
《9.8.10 Interrupt 10 – Invalid TSS》
当任务切换期间新TSS是非法的将引发该异常。以下情况下TSS被认为是非法的:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18Table 9-5. Conditions That Invalidate the TSS
Error Code Condition
TSS id + EXT The limit in the TSS descriptor is less than 103
LTD id + EXT Invalid LDT selector or LDT not present
SS id + EXT Stack segment selector is outside table limit
SS id + EXT Stack segment is not a writable segment
SS id + EXT Stack segment DPL does not match new CPL
SS id + EXT Stack segment selector RPL < > CPL
CS id + EXT Code segment selector is outside table limit
CS id + EXT Code segment selector does not refer to code
segment
CS id + EXT DPL of non-conforming code segment < > new CPL
CS id + EXT DPL of conforming code segment > new CPL
DS/ES/FS/GS id + EXT DS, ES, FS, or GS segment selector is outside
table limits
DS/ES/FS/GS id + EXT DS, ES, FS, or GS is not readable segment
该故障fault的错误码将入栈,用于帮助识别故障的原因。EXT位表示该异常是否由不受程序控制的外部情况所引发,比如经由任务门的外部中断引起非法TSS切换。
该故障可能出现在原始任务的上下文或新任务的上下文。异常停留在原始任务的上下文中直到处理器完全验证新TSS的存在。一旦新TSS被验证存在,任务切换则是完整的:也就是说,TR位已经被更新,并且如果是由CALL或中断导致该切换,新TSS反向链接旧TSS。此后处理器发现的任何错误都将在新任务的上下文中进行处理。
为了确保有合适的TSS,Interrupt 10的处理程序必须是经由任务门调用的一个任务。
《9.8.11 Interrupt 11 – Segment Not Present》
当处理器发现描述符的存在位为0时引发该异常。处理器在以下任何一种情况下可以引发该故障fault:
尝试加载CS、DS、ES、FS或GS寄存器;加载SS寄存器但导致了栈错误。
尝试通过LLDT指令加载LDT寄存器;在任务切换期间加载LDT寄存器,但导致”invalid TSS”异常。
尝试使用被标记为不存在的门描述符。
该故障可以重启。如果异常处理程序设置段存在并返回,被中断的程序将恢复执行。
如果在任务切换过程中发生了not-present异常,可能未完成任务切换的所有步骤。在任务切换时,处理器首先载入所有段寄存器,然后检查他们的有效性。一旦发现某个not-present异常,剩下的寄存器将不会被检查,因此不能用它们来引用内存。not-present异常处理程序在试图恢复新任务之前需要检查所有段寄存器,否则后续可能引发一般保护错误,使得诊断更加困难。有三种方法可以处理这种情况:
1、使用新任务来处理not-present故障。任务切换为被中断程序时,会导致处理器从TSS中加载所有寄存器并检查。
2、PUSH和POP所有段寄存器。POP指令会导致处理器检查所有段寄存器的新内容。
3、检查每个段寄存器在TSS中的镜像,模拟测试处理器加载它们的情况。
该异常会将错误码入栈。如果外部事件引用了不存在段并引发中断,错误码的EXT位将被置位。如果错误码指向IDT项,I位将被置位,比如INT指令引用了一个不存在的门。
操作系统经常使用“segment not present”在段级别上实现虚拟内存。然而,门描述符not-present通常并不表示段不存在,因为门不一定对应于段。Not-present门可能只是操作系统为了某些特殊意义用于触发异常。
《9.8.12 Interrupt 12 – Stack Exception》
以下两种情况一般会触发栈故障fault:
引用SS寄存器的任何操作由于违反限制的结果。包括POP、PUSH、ENTER、LEAVE,以及其他隐式使用SS的内存引用操作,如MOV AX, [BP+6]。当栈空间太小不满足局部变量空间时,ENTER引发该故障。
描述符有效但被标记为不存在的情况下尝试加载SS寄存器,引发该故障。这种情况可能发生在任务切换、interlevel CALL、interlevel return、LSS指令,或者针对SS的MOV或POP指令。
当处理器检测到栈异常时,将错误码入栈。如果是由于not-present栈段,或由于interlevel CALL期间新栈溢出而引发异常,错误码将包含出问题的段选择器,处理器可以通过测试描述符的存在位来判断发生哪些异常;其他情况下错误码为0。
在所有情况下,导致该故障的指令都可被重启。保存到异常处理程序栈上的返回指针指向了需要被重启的指令。这个指令通常是导致该故障的指令,但如果是由于在任务切换时加载一个不存在的栈段描述符而引发了栈异常,该指令将是新任务的第一条指令。
如果在任务切换过程中发生了栈异常,段寄存器不能用于引用内存。在任务切换时,处理器在检查描述符之前首先载入所有段寄存器,然后检查段寄存器的有效性。一旦发现栈异常,剩下的寄存器将不会被检查,因此不能用CS, SS, DS, ES, FS, GS来引用内存。栈异常处理程序在试图恢复新任务之前需要检查所有段寄存器,否则后续可能引发一般保护错误,使得诊断更加困难。
《9.8.13 Interrupt 13 – General Protection Exception》
所有不会引发另一个异常的保护违反会引发一般保护异常。包括但不限于:
1、使用CS, DS, ES, FS, GS的时候超过限制。
2、引用一个描述符表的时候超过段长限制。
3、控制转移到一个不可执行的段。
4、写入只读数据段或代码段。
5、读取只执行段。
6、以一个只读描述符来加载SS。除非段寄存器是在任务切换期间来源于TSS,这种情况会引发TSS异常。
7、以系统段的描述符来加载SS, DS, ES, FS, GS。
8、以可执行但不可读的段的描述符来加载DS, ES, FS, GS。
9、以可执行段的描述符来加载SS。
10、当段寄存器包含一个空选择器时通过DS, ES, FS, GS访问内存。
11、切换到一个繁忙的任务。
12、违反特权规则。
13、加载CR0时 PG=1 且 PE=0。
14、经由中断门或陷阱门的中断或异常从V86模式到除0以外的特权级别。
15、超过15个字节的指令长度限制。仅当冗余前缀被放置在一条指令之前才会发生。
一般保护异常属于故障fault。在处理一般保护异常时,处理器将错误码保存到异常处理程序的栈上。如果因为加载描述符而导致异常,错误码将包含一个指向描述符的选择器;否则错误码为空。错误码的选择器可能来自于:1、选择器来源于指令的一个操作数。2、一个门的选择器是指令的操作数。3、选择器来源于任务切换时的TSS。
《9.8.14 Interrupt 14 – Page Fault》
允许分页的情况下,处理器在将线性地址转换为物理地址的过程中如果发现以下情况将会引发页错误:1、地址转换涉及到的页目录项或页表项的存在位为0;2、当前程序没有权限访问该物理页。
处理器为异常处理程序提供两种信息用于诊断和恢复:
1、保存到栈上的错误码,该错误码的格式不同于其他异常的错误码。如下所示:
错误码告知异常处理程序三件事:
1.1、异常是由于页面不存在还是违反访问权限所引起的。
1.2、发生异常的时间点上处理器是在用户级别还是supervisor级别。
1.3、引起异常的内存访问是写操作还是读操作。
2、CR2(control register two)。处理器将因访问内存而引起异常的线性地址保存到CR2中,异常处理程序可以根据该地址定位到页目录项和页表项。如果在页面错误处理程序执行期间出现了另一个页面错误,异常处理程序应该将CR2保存到栈上。如下所示:
《9.8.14.1 Page Fault During Task Switch》
任务切换期间,处理器可能访问四个部分:
1、在原先任务的TSS上保存原先任务的状态。
2、读取GDT,定位新任务的TSS描述符。
3、读取新任务的TSS,检查来源于TSS的段描述符的类型。
4、可能读取新任务的LDT,用于验证保存到新TSS上的段寄存器。
上述任何一种情况都可能导致页错误。后两种情况的页错误发生在新任务的上下文中。指令指针指的是新任务的下一个指令,而不是导致任务切换的指令。
如果操作系统允许在任务切换期间发生页错误,页错误处理程序应该经由任务门调用。
《9.8.14.2 Page Fault with Inconsistent Stack Pointer》
应该特别注意的是,页错误不应该导致处理器使用了无效的栈指针(SS:ESP)。8086系列早期处理器经常使用一对指令来切换到新栈,如:MOV SS, AX; MOV SP, StackTop。由于第二条指令会访问到内存,在SS改变而SP还没有改变的时候,可能会出现页错误。这种时候栈指针SS:SP(32位程序是SS:ESP)的两部分是不一致的。
如果页错误处理程序导致切换到定义良好的栈(即处理程序是一个任务或有更高特权的程序),处理器将不会使用不一致的栈指针。然而,如果页错误处理程序是经由中断门或陷阱门调用的,并且是在与页错误处理程序一样的特权级别下发生页错误,处理器仍然会尝试使用当前栈指针指向的栈(非法)。
系统实现分页后,在处理页错误时,应该使用LSS指令来初始化新的栈。如果页错误处理程序在特权级别0上执行(通常情况下),页错误问题的范围就被限制在特权级别为0的代码上,通常这是操作系统的内核代码。
《9.8.15 Interrupt 16 – Coprocessor Error》
如果在80386的ERROR# input pin上发现80287或80387信号时,80386引发该异常。80386只在开始执行ESC指令的时候,和遇到WAIT指令的时候(MSW的EM位为0),才会测试该pin。
《9.9 Exception Summary》
1 | Table 9-6 summarizes the exceptions recognized by the 386 |
《9.10 Error Code Summary》
1 | Table 9-7 summarizes the error information that is available with each |
系统预留中断类型: