这篇文章翻译自Intel 80386 Reference Programmer’s Manual的第9章。
第九章 异常和中断(Exceptions and Interrupts)
中断和异常是特殊类型的控制转移,他们执行起来有点像非编程(unprogramed)的CALL(调用)。他们改变正常的程序流去处理外部事件(external events)、报告错误( report errors)或异常条件(exceptional conditions)。中断和异常的不同之处在于中断用来处理处理器外部的异步(asynchronous)事件,而异常用来处理处理器在执行指令过程中检测到的条件。
外部中断和异常都有两个来源:
- 中断
- 可屏蔽(maskable)中断,通过
INTR
引脚发出信号。 - 不可屏蔽(nomaskable)中断,通过
NMI
(Non-Maskable Interrupt)引脚发出信号。
- 可屏蔽(maskable)中断,通过
- 异常
本章介绍了80386在保护模式下(protected mode)执行时,控制和响应中断的特性。
9.1 标识中断Identifying Interrupts
处理器把一个标识号(identifying number)和一个不同类型的中断或异常关联起来。
处理器通过预先分配的范围为0到31的标识符(identifiers)来识别NMI
和异常。并不是这些数字都在被使用,这个范围内未分配的标识符被英特尔保留,待未来可能的拓展。
可屏蔽中断的标识符由外部中断控制器(external interrupt controllers,例如英特尔8259A可编程中断控制器(Programmable Interrupt Controller))决定,并在处理器的中断确认序列(interrupt-acknowledge sequence)中和其通信。可以由软件指定8259A PIC分配的数字。在32到255这个范围内的数字都能用。表9-1显示了中断和异常标识符的分配情况。
Table 9-1. Interrupt and Exception ID Assignments表9-1. 中断和异常ID分配
Identifier(标识符) 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
根据报告异常的方式以及是否支持重新启动导致异常的指令,将异常分为故障、陷阱或中止。
故障(faults):
指的是在指令造成异常之前进行报告的异常。故障在指令开始执行之前或者执行之中被检测到。如果是在执行之中被检测到,在故障被报告的同时,机器会保存状态,以便重新执行指令。
陷阱(traps):
trap是在检测到异常的指令之后,立即在指令边界报告的异常。
中止(aborts):
中止是既不精确定位引起异常的指令,也不重新执行造成异常的程序的异常。中止用来报告严重的错误,例如硬件错误或者系统表中有不一致、非法的值。
9.2 开关中断Enabling and Disabling Interrupts
处理器只有在一条指令执行完,下一条开始前才处理中断和异常。当重复前缀用于重复一条字符串指令时,重复之间可能会发生中断和异常。因此,长字符串的操作并不会延迟中断响应。
我们可以通过一些条件和标志(flag)设置使得处理器在指令边界上禁止中断和异常。
9.2.1 NMI Masks Further NMIs
当一个NMI handler在执行时,处理器会忽略NMI引脚上的中断信号,直到下一个IRET 指令执行。
9.2.2 IF Masks INTR
IF (interrupt-enable flag)控制着是否接受INTR
引脚上的外部中断信号。当IF=0
,INTR
中断是被禁止的,反之是可用的。和其他标记位一样,收到RESET
信号时,处理器会清零IF
位。指令 CLI 和STI 改变IF
位。
CLI (Clear Interrupt-Enable Flag) 和 STI (Set Interrupt-Enable Flag)显式改变IF
(标志寄存器(flag register)中的9号位)。这些指令只能在 CPL <= IOPL时执行,否则会发生保护异常(protected exception)。
下面的操作会对IF
产生隐式影响:
- 指令 PUSHF把把所有的标志,包括
IF
存储到可以检查它们的栈里。 - 任务切换并且指令 POPF 和IRET 加载标志寄存器;因此它们可用来修改
IF
。 - 中断通过中断门(interrupt gates)自动复位IF,并禁用中断。(本章稍后会解释中断门)
9.2.3 RF Masks Debug Faults
EFLAGS
中的RF
位控制调试故障(debug faults)的识别,这允许对给定指令最多抛出一次调试错误,无论该指令被重新启动多少次。(关于debugging的更多信息参阅第12章)
9.2.4 MOV or POP to SS Masks Some Interrupts and Exceptions
需要改变栈段(stack segment)的软件经常使用一对指令,例如:
MOV SS, AX
MOV ESP StackTop
如果一个中断或异常在SS
已经改变但是ESP
还未改变时被执行,栈指针的两部分SS:ESP
在中断处理程序(interrupt handler)或异常处理程序(exception handler)中的持续是不一致的(the two parts of the stack pointer SS:ESP are inconsistent for the duration of the interrupt handler or exception handler)。
为了防止这种情况发生,80386在执行一条对于SS
的 MOV 或POP指令之后,禁止NMI
,INTR
,调试异常,单步陷阱(single-step traps)。(To prevent this situation, the 80386, after both a MOV to SS and a POP to SS instruction, inhibits NMI, INTR, debug exceptions, and single-step traps at the instruction boundary following the instruction that changes SS.)一些异常仍可能发生,即页故障(page fault)和一般保护故障。使用 LSS 指令可防止问题的发生。
9.3 中断和异常的优先级Priority Among Simultaneous Interrupts and Exceptions
如果一个指令边界上挂起多条中断或异常,处理器一次只能处理一条。中断和异常的优先级见表9-2。处理器首先处理高优先级的中断或异常,控制转移到中断处理程序的第一条指令。低优先级的异常被丢弃;低优先级的中断被挂起。当中断返回时,丢弃的异常会被恢复。
Table 9-2. Priority Among Simultaneous Interrupts and Exceptions
Priority 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
中断描述表(IDT)将每个中断或异常描述符和为相关事件服务的指令描述符关联起来。类似于GDT和LDTs,IDT是一个8字节的描述符的数组,不同的是,IDT的第一个条目可能包含一个描述符。为了在IDT中形成索引,处理器把中断和异常描述符乘8。因为只有256个标识符(identifier),IDT需要包含不超过256个描述符(descriptor)。可以包含少于256个条目;条目只对实际使用的中断标识符是必需的。
IDT
可以驻留在物理内存中的任何位置,如图9-1,处理器通过IDT
寄存器(IDDTR
)定位其位置。
指令 LIDT 和SIDT操作寄存器IDTR
,这两条指令都有一个显式操作数:内存中的一个6字节地址,图9-2说明了其格式。
LIDT
(load IDT register)将内存操作数中的线性基址和limit
值加载到IDT
寄存器中。这条指令只有当CPL
为0时才能执行。它经常在创建IDT
时,由操作系统的初始化逻辑使用。操作系统也可能用它从一个IDT
切换到另一个。
SIDT
(store IDT register)把存在IDTR
中的base和limit值存到内存里。这条指令能在任何特权级下执行。
9.5 中断描述符表的描述符IDT Descriptors
IDT
可能包含三种描述符的任何一个:
- 任务门(Task getes)
- 中断门(Interrupt gates)
- 陷阱门(trap getes)
图9-3说明了格式。(IDT
中的任务门和第7章已经讨论过的任务门一样)
9.6 中断任务和中断程序Interrupt Tasks and Interrupt Procedures
就像一个CALL指令可以调用一个过程或者程序,一个中断或异常可以“调用”一个既是任务也是程序的中断处理程序。处理器使用中断或异常标识符索引IDT
中的描述符来响应中断或异常。当处理器索引中断或陷阱门时,它像CALL
指令调用门一样调用中断处理程序。当处理器找到一个任务门,它像CALL
指令调用任务门一样进行任务切换。
9.6.1 中断程序Interrupt Procedures
如图9-4所示,一个中断门或陷阱门间接指向一个程序,它将在当前任务中的上下文(context)中执行。门的选择符指向一个在GDT
或当前IDT
中的的可执行段描述符(executable-segmen descriptor)。门的偏移字段指向指向中断或异常处理程序的开始。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EFVkqjTD-1643808650978)(https://raw.githubusercontent.com/DavidZyy/PicBed/master/img/20220201213304.png)]
80386像调用一个程序那样调用中断或异常处理程序。不同之处在后文中解释。
9.6.1.1 中断过程的栈Stack of Interrupt Procedure
就像有CALL
指令引起的控制转移一样,一个中断或者异常处理程序的控制转移使用栈来存储信息,以便返回原来的程序。如图9-5,中断时,EFLAGS
寄存器先于中断指令的指针(译注:指OLD EIP
?)被放入栈中。特定类型的异常会产生一个异常码(error code),放入栈中。异常处理程序使用异常码来诊断异常。
9.6.1.2 中断程序返回Returning from an Interrupt Procedure
中断程序的返回和普通程序也不同。IRET
指令用来从中断程序返回,其和RET
指令很像,除了比后者的ESP
多了4个字节(因为标志位被保存在栈上),并且把保存的标志位放到EFLAGS
寄存器里。EFALGS
寄存器的IOPL
字段只有当CPL
为0时才改变。IF
标志位只有当CPL
<=IOPL
时才改变。
9.6.1.3 中断过程使用的标志位Flags Usage by Interrupt Procedure
在当前TF
的值被作为EFLAGS
的一部分保存在栈上之后,通过中断门和陷阱门的中断把TF
(陷阱标志位)重置。通过这个步骤,处理器防止使用但不的调试活动影响中断响应。(By this action the processor prevents debugging activity that uses single-stepping from affecting interrupt response. )随后的IRET
指令使用保存在栈上的EFLAGS
映像恢复TF
的值。
中断门和陷阱门不同的是对于IF
(开关中断标志)的影响。通过中断门的中断复位IF
,以便阻止其他中断干扰当前中断程序。指令使用保存在栈上的EFLAGS
映像恢复IF
的值。一个通过陷阱门的中断不会改变IF
。
9.6.1.4 中断过程保护Protection in Interrupt Procedures
管理中断过程的特权规则类似于中断过程调用;CPU不允许一个中断控制转移到一个段中的过程,如果那个段的特权级比当前特权级低(数值上更大)。破坏这条规则的尝试会导致一般保护异常。
因为中断发生的过程通常是不可预测的,这条特权规则有效地限制了中断和异常处理过程可以执行的特权级别。下面两条策略都可以用来确保特权规则不会破坏。
- 把处理程序放在符合标准的段中。这条策略适合一些特定的异常(例如除法错误)。这样的程序必须只使用栈中对它有用的程序。如果它需要数据段中的数据,数据段需要具有特权级3,从而使其不受保护。
- 把处理程序放在特权级0的段中
9.6.2 中断任务Interrupt Tasks
一个IDT
中的任务门间接指向一个任务,如图9-6。门的选择符指向一个GDT
中的TSS
描述符。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tyLVtP8G-1643808650981)(https://raw.githubusercontent.com/DavidZyy/PicBed/master/img/20220202152933.png)]
当中断或异常指向IDT
中的一个任务门时,任务切换发生了。用一个分离的任务处理中断有两个优点:
- 整个上下文被自动保存
- 中断处理程序可以通过给它一个分隔(通过它的LDT或通过它的页目录)的地址空间和其他任务隔离。
中断处理器切换任务的方式已经在第7章讨论过了。中断任务通过指令IRET
返回被中断任务。
如果任务切换是被一个具有错误码的异常造成的,处理器自动把错误码放在与中断任务中要执行的第一个指令的特权级别对应的栈里。
当中断任务被一个80386操作系统使用时,实际上有两个调度器:软件调度器(操作系统的一部分)和硬件调度器(处理器中断机制的一部分)。软件调度器的设计应该考虑到硬件调度器可以在中断被启用时调度中断任务。
9.7 Error Code
对于与特定段相关的异常,处理器将错误码push到异常处理程序(无论是过程还是任务)的栈上。错误码具有图9-7的格式。错误码的格式类似于选择符的格式;但是,错误码不包含RPL字段,而是包含其他两个1位项:
- 如果程序的外部事件造成异常,处理器设置
EXT
位。 - 如果错误码的索引部分指向一个
IDT
中的门描述符,处理器设置I
位(IDT
位)。
如果I
位没有被设置,TI
位表示错误码相关于GDT(值为0)还是LDT(值为1)。剩下的14位是所涉及的段选择符的前14位。在某些情况下,栈上的错误码是null,也就是说,低阶字中的所有位都是零。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vr3zsVcn-1643808650982)(https://raw.githubusercontent.com/DavidZyy/PicBed/master/img/20220202183104.png)]
9.8 Exception Conditions
待译。
9.8.14 中断14——页错误 Interrupt 14 – Page Fault
当分页打开(PG
=1),而且处理器在将线性地址转化为物理地址的过程中检测到下面两个条件之一时,会发生这个异常:
- 地址转换所需的页目录项或页表项的
present
位为零。 - 当前过程没有访问指定页的足够权限。
处理器向页面错误处理程序提供两项信息,帮助诊断异常并从异常中恢复:
- 一个栈上的错误码。一个页错误的错误码的格式和其它异常的不同(见图9-8)。错误码向异常处理程序提供三项信息:
- 异常是由不存在的页引起的还是由访问权限破坏引起的。
- 在异常发生时,处理器是在用户级还是管理员级执行。
- 造成异常的内存访问是读还是写。
- CR2 (control register two,2号控制寄存器)。处理器将造成异常的访问的线性地址存在其中(见图9-9)。异常处理程序可以使用这个地址定为相应的页目录项和页表项。如果在执行页面错误处理程序期间可能发生另一个页面错误,则处理程序应该将CR2压入堆栈。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5d867CIi-1643808650983)(https://raw.githubusercontent.com/DavidZyy/PicBed/master/img/20220202203222.png)]
9.8.14.1 任务切换期间的页错误Page Fault During Task Switch
处理器在任务切换期间可能访问四个段中的任何一个:
- 把原任务的状态写到那个任务的
TSS
中。 - 读取
GDT
来定位新任务的TSS
描述符。 - 读取新任务的TSS,从TSS中检查段描述符的类型。
- 为了验证存储在新TSS中的段寄存器,可能读取新任务的LDT。
访问这些段中的任意一个可能引起页错误。在后面两种情况,异常发生在新任务的上下文。指令指针指向新任务的下一条指令,而非造成任务切换的指令。如果操作系统在设计上允许页错误在任务切换期间发生,页错误处理程序应该通过一个任务门调用。
9.8.14.2 栈指针不一致的页面错误Page Fault with Inconsistent Stack Pointer
特别注意确保一个页面错误不会导致处理器使用一个无效的堆栈指针(SS:ESP)。为8086系列的早期处理器写的软件经常使用一对指令来改变一个新栈,例如:
MOV SS, AX
MOV SP, StackTop
对于80386,因为第二条指令访问内存,所以在SS被更改之后,但在SP收到相应的更改之前,可能会出现页面错误。在这种情况下,栈指针的两部分SS:SP(对于32位是SS:ESP)是不一致的。
如果对于页错误的处理使得栈切换到一个定义良好的栈,处理器不会使用不一致的栈指针(即,处理程序是一个任务或一个更高优先级的程序)。然而,如果错误处理程序被一个陷阱或者中断门调用并且页错误发生在和错误处理程序相同的优先级下,处理器将尝试使用被当前(无效的)栈指针指示的栈。
在实现分页和在错误任务中处理页错误(使用陷阱或中断门)的系统中,执行在和页错误处理程序相同优先级的软件应该初始化一个新栈,通过使用新的LSS
指令而不是上面显示的指令对。当页错误处理程序执行在优先级优先级0(通常情况),问题的范围局限于特权级0的代码,通常是操作系统的内核。
9.9 Exception Summary
表9-6总结了386识别的异常。
Table 9-6. Exception Summary
Description Interrupt Return Address Exception Function That Can Generate
Number Points to Type the Exception
Faulting
Instruction
Divide error 0 YES FAULT DIV, IDIV
Debug exceptions 1
Some debug exceptions are traps and some are faults. The exception
handler can determine which has occurred by examining DR6. (Refer to Chapter 12.)
Some debug exceptions are traps and some are faults. The exception
handler can determine which has occurred by examining DR6. (Refer to Chapter 12.) Any instruction
Breakpoint 3 NO TRAP One-byte INT 3
Overflow 4 NO TRAP INTO
Bounds check 5 YES FAULT BOUND
Invalid opcode 6 YES FAULT Any illegal instruction
Coprocessor not available 7 YES FAULT ESC, WAIT
Double fault 8 YES ABORT Any instruction that can
generate an exception
Coprocessor Segment
Overrun 9 NO ABORT Any operand of an ESC
instruction that wraps around
the end of a segment.
Invalid TSS 10 YES FAULT
An invalid-TSS fault is not restartable if it occurs during the
processing of an external interrupt. JMP, CALL, IRET, any interrupt
Segment not present 11 YES FAULT Any segment-register modifier
Stack exception 12 YES FAULT Any memory reference thru SS
General Protection 13 YES FAULT/ABORT
All GP faults are restartable. If the fault occurs while attempting to
vector to the handler for an external interrupt, the interrupted program is
restartable, but the interrupt may be lost. Any memory reference or code
fetch
Page fault 14 YES FAULT Any memory reference or code
fetch
Coprocessor error 16 YES FAULT
Coprocessor errors are reported as a fault on the first ESC or WAIT
instruction executed after the ESC instruction that caused the error. ESC, WAIT
Two-byte SW Interrupt 0-255 NO TRAP INT n
9.10 Error Code Summary
表9-7总结了每个异常可用的错误信息
Table 9-7. Error-Code Summary
Description Interrupt Error Code
Number
Divide error 0 No
Debug exceptions 1 No
Breakpoint 3 No
Overflow 4 No
Bounds check 5 No
Invalid opcode 6 No
Coprocessor not available 7 No
System error 8 Yes (always 0)
Coprocessor Segment Overrun 9 No
Invalid TSS 10 Yes
Segment not present 11 Yes
Stack exception 12 Yes
General protection fault 13 Yes
Page fault 14 Yes
Coprocessor error 16 No
Two-byte SW interrupt 0-255 No