chapter 5. interrupt and exception 中断和异常
讨论保护模式下中断和异常, 参考Intel - System Programming Guide Book.
5.1 概述
中断和异常,即中断CPU当前运行的例程,执行中断和异常的处理程序,中断和异常处理完后,CPU返回执行先前的例程。
中断有外部硬件中断或软件中断INT n.
异常通常是CPU执行指令时发生异常情况,比如遇到除于0,内存页面失效...
5.1.1 中断源
处理器接收两种中断: 外部(硬件)中断和中断指令
1)外部中断
CPU通过CPU PIN接收外部(硬件)中断,在早期486和Pentium系列,CPU内部没有APIC,CPU通过CPU PIN:INTR和NMI来接收外部中断,
INTR连接到外部中断控制器8259A,由8259A控制器侦测外部中断,触发INTR,并把对应的中断向量号传递给CPU,CPU响应中断。
现代CPU都带有local APIC控制器,CPU接收外部中断通过CPU中断PIN:LINT[1:0],LINT[1:0]连接到Local APIC,
.当local APIC disable时,这两个pin被直接连接到CPU 的INTR(LINT0),NMI(LINT1),这时CPU接收中断方式仍从8259A控制器接收(兼容)
-------------------
| ---------- |
|| CPU | |
|| NMI INTR | |
| ---------- |
| | | |
| ------------ |
|| local APIC | |
|| LINT1 LINT0 | |
| ------------- |
| | | |
|-------------------
INTR连接到中断控制器8259A,通过编程8259A,当INTR pin有中断发生时,从8259A接收到中断向量号,CPU执行对应的中断服务例程。
NMI中断发生时,CPU执行中断向量2的服务例程。
.当local APIC enable时,可以通过编程APIC vector table处理LINT[1:0]的中断事件
另外外部中断还可以连接到IO APIC控制器,由IO APIC控制器向local APIC传递中断请求,IO APIC侦测中断并发送中断向量号给local APIC。
在多处理器系统中,当前处理器还可以接收其他处理器发送的中断请求,比如IPIs.
可屏蔽外部中断
通过INTR或local APIC处理的中断都是可屏蔽中断。CPU的寄存器EFLAGS中IF位可屏蔽中断。指令STI/CLI。
local APIC disable时,INTR pin可处理0-255个中断向量。
local APIC enable时,local APIC可处理16-255个中断向量。注,local APIC把0-15中断向量号当作非法的中断向量号。
3)中断指令
INT n
注指令INT n触发中断,IF标志位对其无效
5.1.2 异常
CPU处理三种异常:
.程序错误异常 - CPU为每个程序异常分配中断向量,如faults, traps, aborts
- faults故障: 故障类异常可以修正,允许程序回到发生故障指令前的地址重新运行。
注:有些特殊情况故障类异常也不可修正和重启程序,比如当在堆栈段底运行POPAD等指令时,将导致一个故障类
异常,这时异常处理程序回到POPAD指令执行前的指令队列执行,但是因为POPAD故障导致CPU寄存器被修改。
- traps陷阱: 通常用做调试,陷阱退出后回到正常指令后继续运行。
- aborts异常: 不可修正,报告硬件致命错误等
.异常指令 - INTO,INT3, BOUND
.machine-check异常 - CPU侦测内部chip及bus传输的异常,参看#MC机制,interrupt 18.
插入说明5.4 关于异常后程序或任务的重启
对故障类异常,当异常发生时CPU保存指向引起故障指令的地址,当处理完异常处理程序后重新执行这个指令。常见的故障类异常如PF#内存
页面故障,当程序要访问一个页面时该页面没有映射到内存就产生页面失效故障类异常,通常在异常处理程序中加载页面内存,然后回到故障
指令重新执行。
对于陷阱类异常,因为通常是用来跟踪调试,CPU在处理异常时保存引起异常指令的下一条指令,处理完异常后继续执行。
5.2 中断和异常的向量号
先来回顾一下X86实模式下的中断向量。
我们知道,在实模式下,可以通过对中断控制器编程,来配置各硬件中断对应的中断向量号,这样在硬件产生中断时,中断控制器用来传递
对应的中断向量号给CPU,CPU接到中断向量号后,跳到中断向量号对应的中断服务程序执行。
在X86系统中通常集成2个8259A中断控制器,外接15个中断.
master 8259A:
IRQ0 - timer/counter 0(8254定时器中断)
IRQ1 - keyboard (键盘中断)
IRQ2 - 级联,连接slave 8259A的INTR pin.
IRQ3 - COMA(串口1)
IRQ4 - COMB(串口2)
IRQ5 - LPT(并口)
IRQ6 - Floppy
IRQ7 - LPT
slave 8259A:
IRQ8 - RTC.
IRQ9 - generic
IRQ10 - generic
IRQ11 - generic
IRQ12 - PS/2 mouse
IRQ13 - CPU FERR#
IRQ14 - primary IDE channel(主IDE通道)
IRQ15 - secondary IDE channel(从IDE通道)
8259A中断控制器编程端口
master 8259A: 0x20
slave 8259A: 0xA0
在legacy BIOS中,通常编程master 8259A硬件中断IRQ0-7对应的中断向量号为vector 0x8-0x0f,
slave 8259A硬件中断IRQ8-15对应中断向量号vector 0x70-0x77
这部分详细可参考Intel SouthBridge Specifiction(8259 interrupt controller 部分)及BIOS初始化init_8259_FAR。
在保护模式下,中断向量号0-31被分配给异常和NMI,32-255分配给外部中断或用户定义的中断。
Vector Describe
0 #DE,divide error,除法错误 DIV/IDIV除0
1 #DB,Debug,调试 INT1指令
2 NMI
3 #BP,breakpoint INT3指令
4 #OF,Overflow INTO指令
5 #BR,BOUND Range Exceeded
6 #UD, Undefined,Invalid Opcode
7 #NM, No Math-coprocessor
8 #DF, Double Fault
9 Coprocessor Segment Overrun
10 #TS, Invalid TSS
11 #NP, Segment Not Present
12 #SS, Stack Segment Fault
13 #GP, General Protect
14 #PF, Page Fault
15 reserved
16 #MF, X87 FPU Floating-point Error
17 #AC, Alignment Check
18 #MC, Machine Check
19 #XF, SIMD Floating-point Exception
20-31 reserved
;我们也注意到,实模式和保护模式的中断向量0x08-0x0f不一样,所以在进入保护模式启动中断前必须重新初始化实模式下的硬件中断向量。
5.5 NMI非屏蔽中断
NMI中断不能通过EFLAGS的IF位屏蔽。
CPU非屏蔽中断有两种情况:
. 外部硬件触发NMI
. 在多处理器系统中其他CPU传递来的NMI事件
当CPU接收到NMI中断后,将执行中断向量2的服务程序,硬件(CPU)将禁止其他NMI中断,直到处理完当前NMI中断并执行IRET返回后,才能接
受其他NMI中断,并且建议通过中断门的方式跳转到中断向量2的服务程序,利用软件禁止可屏蔽中断。
事实上,我们也可以把一个可屏蔽的外部中断INTR通过编程,对应到中断向量2,但是硬件不把它当一个真正的NMI中断来响应。
5.6 中断的允许和禁止
1. 可屏蔽硬件中断
CPU EFLAGS寄存器的IF位可以用来屏蔽CPU INTR或者local APIC响应外部硬件中断.
1)IF位可以通过指令STI/CLI设置,注意在保护模式下只有当CPL<=IOPL时,才能执行STI/CLI指令,否则将产生#GP保护异常。
IF =1 响应中断,指令STI设置IF=1
IF =0 屏蔽终端,指令CLI设置IF=0
2)以下情况IF位也会被修改
.PUSHF/POPF指令,IRET指令
.任务跳转
.当利用中断门跳转到中断服务程序时,IF位被自动清0,自动屏蔽其他中断
注: IF位不能禁止硬件NMI中断和多处理器之间传递的NMI中断消息,及异常。
hardware reset时,IF位为0,禁止中断。
2. 屏蔽指令断点异常(instruction breakpoint)
CPU EFLAGS寄存器的RF位,当RF=1时,屏蔽#DB异常。
3. 当堆栈切换时屏蔽中断和异常
通常设置SS:ESP来切换堆栈,我们建议利用指令LSS来装入SS选择子,但是如果利用以下方式
mov SS, ax ;或者 POP SS
mov ESP, StackTop
这时,当SS被重装入但ESP还没有装入时,如果有中断或异常发生,这将产生一些问题。
为了避免这种情况,当用mov或pop指令设置SS后,CPU会自动禁止中断,debug异常和single-trap单步调试异常,直到下一个指令被执行。
5.7 中断和异常的优先级
当有多个中断和异常请求响应时,CPU根据中断的优先级从高到低来逐步处理相应的中断。
优先级
---------------------------------------------------------------------------------------------------------------------------
1(highest) hardware reset; machine check异常
2 Trap on Task Switch(T flag in TSS is set)
3 外部硬件中断请求(FLUSH, STOPCLK, SMI, INIT)
4 Traps on the previous instruction(Breakpoints; Debug Trap异常)
5 外部中断请求(NMI; 可屏蔽中断)
6 Faults from Fetching Next Instruction(code breakpoint fault; code segment limit violation; code page fault)
7 Faults from decoding Next Instruction(instruction length>15bytes; illegal opcode; coprocessor not available)
8(lowest) Faults on executing an instruction
(overflow; Bound error; invalid TSS; segment not present; stack fault; general protection;
data page fault; alignment check; x87 FPU floating-point exception; SIMD float-point exception )
5.8 保护模式中断描述符表IDT(interrupt describe table)
在实模式下,CPU根据中断的向量号执行中断向量表中的服务程序,中断向量表包含256个中断服务历程的地址(段+偏移地址),实模式的中断
向量表在内存0:0 - 0:3FFh的1K字节。
在保护模式下,建立中断描述符表,在进入保护模式前由指令LIDT加载,中断描述符表中包含指向中断服务例程的门描述符。
CPU接收到中断后,以中断向量号为索引,执行中断描述符表中门描述符指向的服务程序。
每个门描述符长8个字节,中断描述符表可包含256个中断服务的门描述符,所以中断描述符表最大长度为256*8byte=2k bytes.
当要访问的中断向量超过已定义的IDT描述符表界限时,将产生一个#GP异常。
例:
LIDT指令在进入保护模式前通过加载IDT伪描述符到寄存器IDTR,来确定系统使用的IDT。
;-------------------------------------------------------------------------------
;门描述符结构
Gate STRUC
OffsetL DW 0 ;32位偏移的低16位
Selector DW 0 ;选择子
DCount DB 0 ;双字计数(用于堆栈参数传递)
GType DB 0 ;门类型
OffsetH DW 0 ;32位偏移的高16位
Gate ENDS
;IDT伪描述符结构
PDesc STRUC
Limit DW 0 ;16位界限
Base DD 0 ;32位基地址
PDesc ENDS
;中断描述符表!
IDTSEG SEGMENT PARA USE16
IDT LABEL BYTE ;中断描述符表
Gate <,,,,> ;指向0号中断/异常处理程序的门描述符
Gate <,,,,> ;指向1号中断/异常处理程序的门描述符
.... ;...
IDTLen = $-IDT
IDTSEG ENDS
;实模式数据段
RMODEDATA SEGMENT PARA USE16
VGDTR PDesc <,> ;GDT伪描述符
VIDTR PDesc ;IDT伪描述符
RMODEDATA ENDS
;代码运行
start:
;实模式下初始化VIDTR,GDTR(这里不列出)
mov bx,16
mov ax,IDTSeg
mul bx
mov WORD PTR VIDTR.Base,ax
mov WORD PTR VIDTR.Base+2,dx
;准备进入保护模式
LGDT FWORD PTR VGDTR ;加载GDTR
cli ;关中断
LIDT FWORD PTR VIDTR ;加载IDTR
mov eax,cr0
or al,1
mov cr0,eax
;JUMP PM_CODE ;跳到保护模式段
;-------------------------------------------------------------------------------
中断描述符表中包含的门描述符可以是保护模式下的三种门描述符:中断门描述符,任务门描述符,陷阱门描述符。
这三种门描述符参考保护模式下编程资料。
5.10 中断和异常的处理
CPU通过CALL指令调用中断或异常处理程序。
1)通过中断门或陷阱门的处理程序
通过中断门或陷阱门调用中断/异常处理程序时,因为不涉及切换任务,处理程序运行在当前任务的上下文环境,门描述符的段选择子即包含
指向当前GDT或LDT中描述的段,门描述符中的偏移指向处理程序的代码入口。
CPU首先保存EFLAGS,CS:EIP寄存器到堆栈,如果异常处理程序有返回退出(错误)代码,也将退出代码紧接着压在堆栈中。
CPU清TF,VM, RF, NT标志.
中断门和陷阱门有一个不同之处,调用中断门时,CPU保存EFLAGS后自动清IF位禁止其它中断,处理完后恢复EFLAGS,而陷阱门不清IF位.
要注意中断/异常服务程序的特权级,如果涉及不同特权级的变换,注意不同特权级下的堆栈使用以及特权保护,参考保护模式编程。
中断/异常处理程序必须以指令IRET或IRETD返回.
2)通过任务门的处理程序
涉及任务切换. 利用任务门处理中断/异常有几个优点:
a. 中断任务当前上下文内容被自动保存
b. 启用新的任务状态段TSS, 可以使用新的0级堆栈,这样可以有效避免当前堆栈遭到破坏导致系统破坏
c. 使用任务门的处理程序可以使用独立的LDT,可以方便单独分离出来
缺点是因为任务切换涉及特权级变换,要保存任务上下文,这样任务执行相对较慢.
注: IA32架构不支持任务的重入,所以在任务执行时,必须禁止该中断再被响应执行,否则产生#GP异常.
5.11 异常的退出(错误)代码error code
当一个异常产生的条件和一个特殊段有关时,处理器把一个错误代码压入异常处理程序的堆栈中.
错误代码的格式如下:
31 15 3 2 1 0
----------------------------------------------
reserved index TI IDT EXT
----------------------------------------------
有点像一个段选择子,但是低位不是TI和RPL,而代表三个标志
bit 0 EXT External event, 当被设置,表明有外部硬件中断导致异常
bit 1 IDT Description location, 当为1,表明error code的索引index指向的是IDT表的门描述符
当为0,表明error code的索引index指向的是GDT或LDT表的描述符(根据TI bit2).
bit 2 TI GDT/LDT, 当bit 1 IDT为0时有效, TI=1 LDT, TI=0 GDT.
注在执行IRET返回时,error code并不并pop,所以必须有处理程序在执行IRET返回前POP.
在一般由外部INTR或LINT1:0, INT n引起的异常中,CPU并不使用error code.
关于异常使用error code,可参考各异常的详细资料,如中断向量号为10的异常:Invalid TSS(#TS)
5.12 各中断和异常的详细参考
interrupt 0 - Divide Error Exception (#DE)
description: 略
exception error code: 略
saved instruction pointer: 略
program state change: 略
详细参考intel - system programming guide(24547204)
heavensel
2008-6-11