上一篇文章中简单的提到了IDT是记录了终端号和中断函数之间的关系,实际上在保护模式下,IDT在中断中的地位举足轻重。
IDT是在保护模式中出现的,计算机启动后运行在实模式下,所以需要将CPU从实模式切换到保护模式。切换这一步大约位于BIOS加载完bootsector后,执行部分或者执行完bootsector代码时进行的,这么靠前是因为如果不切换就无法进入32位模式,寻址空间还是可怜的1MB。
切换的步骤一般为以下几步:
在实模式下,中断处理函数注册在中断向量表(IVT)中,切换到保护模式后,中断函数注册在IDT中,IDT是一个8字节的描述符数组,共包含256项,具体的IDT表项的结构前面的一篇里说过了。
设置好IDT后,将首地址装载到idtr寄存器中,剩下的就可以注册使用的中断函数了。
IDT可以位于内存的任意位置,CPU通过IDT寄存器(IDTR)的内容来寻址IDT的起始地址。指令LIDT和SIDT用来操作IDTR。两条指令都有一个显示的操作数:一个6字节表示的内存地址。指令的含义如下:
操作系统需要对计算机系统中的外设进行管理,而管理就需要和外设进行通信。CPU的处理速度比外设快很多,如果是通过CPU主动“询问”(即轮询(polling)机制)外设是否产生事件则会浪费许多时间。所以提供外设可以主动通知CPU和操作系统的机制非常必要,也就是中断机制。
80386共支持256种中断,其中异常(包括故障(Fault)和陷阱(Trap))由CPU自身产生,不使用中断控制器,也不能被屏蔽。硬件中断又分为可屏蔽中断(INTR)和非屏蔽中断(NMI),I/O设备产生的中断请求(IRQ)引起可屏蔽中断,而紧急的外设事件(如掉电故障)引起的中断事件引起非屏蔽中断。
非屏蔽中断和异常的编号是固定的,而屏蔽中断的编号可以通过对中断控制器的编程来调整。256个中断的分配如下:
PIC,即Programmable Interrupt Controller,可编程中断控制器。硬件中断由它实现。
80386通过两片中断控制器8259A来响应15个外中断源,每个8259A可管理8个中断源。第一级(称主片,master)的第二个中断请求输入端,与第二级8259A(称从片,salve)的中断输出端INT相连,如下图所示。IRQ号和中断号之间的映射关系可以通过中断控制器来调整。
保护模式下可以设定 PIC 产生的中断对应的 ISR 所在 IDT 中的 offset,通常设置为从 0x20 开始,到 0x2F 结束(0x0 到 0x1F 被异常占用)。
PIC 的端口号如下表:
PIC | IO | Port |
---|---|---|
Master | Command | 0x20 |
Master | Data | 0x21 |
Slave | Command | 0xA0 |
Slave | Data | 0xA1 |
在中断产生过程中,中断控制器8259A监视外设产生的中断请求(IRQ)信号,如果外设产生了一个中断请求信号,则8259A执行如下操作:
屏蔽外部I/O请求有两种方法。一种是从CPU的角度清零CPU的EFLAG的中断标志位(IF);另一种是从中断控制器的角度,即通过把中断控制器中的中断屏蔽寄存器(IMR)相应位置1,则表示禁用某条中断线。
由于 CPU 与 PIC 之间的竞争条件可能会产生 IRQ 7(Master 产生) 和 IRQ 15(Slave 产生) 的 Spurious IRQs。为了处理这种情况,我们要知道什么时候是无效的 IRQ,通过判断 IRR(Interrupt Request Register) 寄存器的值可以获知哪些 IRQ 发生了,这个寄存器的每个 bit 表示相应的 IRQ 是否发生。在 IRQ 7 和 IRQ 15 的 ISR 中先读取 IRR,然后判断对应的 bit 位是否被设置,如果没有设置,那么表示当前是一个 Spurious IRQ,不需要处理,也不需要写入 EOI,直接返回即可(如果是 Slave PIC 产生的,需要往 Master PIC 写入 EOI,由于 Master 不知道 Slave 产生的 IRQ 是不是 Spurious 的)。
在保护模式下,中断门描述符表(IDT)中的每个表项由8个字节组成,其中的每个表项叫做一个门描述符(Gate Descriptor), “门”的含义是指当中断发生时必须先访问这些“门”,能够“开门”(即将要进行的处理需通过特权检查,符合设定的权限等约束)后,然后才能进入相应的处理程序。而门描述符则描述了“门”的属性(如特权级、段内偏移量等)。在IDT中,可以包含如下3种类型的系统段描述符:
80386的中断门描述符、陷阱门描述符的格式:
中断服务例程包括具体负责处理中断(异常)的代码是操作系统的重要组成部分。需要注意区别的是,有两个过程由硬件完成:
硬件中断处理过程1(起始):
从CPU收到中断事件后,打断当前程序或任务的执行,根据某种机制跳转到中断服务例程去执行的过程。其具体流程如下:
硬件中断处理过程2(结束):
每个中断服务例程在有中断处理工作完成后需要通过iret(或iretd)指令恢复被打断的程序的执行。CPU执行IRET指令的具体过程如下:
下图显示了从中断向量到GDT中相应中断服务程序起始位置的定位方式:
中断处理得特权级转换是通过门描述符(gate descriptor)和相关指令来完成的。一个门描述符就是一个系统类型的段描述符,一共有4个子类型:调用门描述符(call-gate descriptor),中断门描述符(interrupt-gate descriptor),陷阱门描述符(trap-gate descriptor)和任务门描述符(task-gate descriptor)。与中断处理相关的是中断门描述符和陷阱门描述符。这些门描述符被存储在中断门描述符表(Interrupt Descriptor Table,简称IDT)当中。CPU把中断向量作为IDT表项的索引,用来指出当中断发生时使用哪一个门描述符来处理中断。中断门描述符和陷阱门描述符几乎是一样的。中断发生时实施特权检查的过程如下图所示:
门中的DPL和段选择符一起控制着访问,同时,段选择符结合偏移量(Offset)指出了中断处理例程的入口点。内核一般在门描述符中填入内核代码段的段选择子。产生中断后,CPU一定不会将运行控制从高特权级转向低特权级,特权级必须要么保持不变(当操作系统内核自己被中断的时候),或被提升(当用户态程序被中断的时候)。无论哪一种情况,作为结果的CPL必须等于目的代码段的DPL。如果CPL发生了改变(比如从用户态到内核态),一个栈切换操作(通过TSS完成)就会发生。如果中断是被用户态程序中的指令所触发的(比如软件执行INT n生产的中断),还会增加一个额外的检查:门的DPL必须具有与CPL相同或更低的特权。这就防止了用户代码随意触发中断。如果这些检查失败,就会产生一个一般保护异常(general-protection exception)。
附A:异常中断表
Name | Vector nr. | Type | Mnemonic | Error code? |
---|---|---|---|---|
Divide-by-zero Error | 0 (0x0) | Fault | DE | No |
Debug | 1 (0x1) | Fault/Trap | DB | No |
Non-maskable Interrupt | 2 (0x2) | Interrupt | - | No |
Breakpoint | 3 (0x3) | Trap | BP | No |
Overflow | 4 (0x4) | Trap | OF | No |
Bound Range Exceeded | 5 (0x5) | Fault | BR | No |
Invalid Opcode | 6 (0x6) | Fault | UD | No |
Device Not Available | 7 (0x7) | Fault | NM | No |
Double Fault | 8 (0x8) | Abort | DF | Yes (Zero) |
Coprocessor Segment Overrun | 9 (0x9) | Fault | - | No |
Invalid TSS | 10 (0xA) | Fault | TS | Yes |
Segment Not Present | 11 (0xB) | Fault | NP | Yes |
Stack-Segment Fault | 12 (0xC) | Fault | SS | Yes |
General Protection Fault | 13 (0xD) | Fault | GP | Yes |
Page Fault | 14 (0xE) | Fault | PF | Yes |
Reserved | 15 (0xF) | - | - | No |
x87 Floating-Point Exception | 16 (0x10) | Fault | MF | No |
Alignment Check | 17 (0x11) | Fault | AC | Yes |
Machine Check | 18 (0x12) | Abort | MC | No |
SIMD Floating-Point Exception | 19 (0x13) | Fault | XM/#XF | No |
Virtualization Exception | 20 (0x14) | Fault | VE | No |
Reserved | 21-29 (0x15-0x1D) | - | - | No |
Security Exception | 30 (0x1E) | - | SX | Yes |
Reserved | 31 (0x1F) | - | - | No |
Triple Fault | - | - | - | No |
FPU Error Interrupt | IRQ 13 | Interrupt | FERR | No |
附录B:PIC 产生的标准 IRQ 如下表:
IRQ | Description |
---|---|
0 | Programmable Interrupt Timer Interrupt |
1 | Keyboard Interrupt |
2 | Cascade (used internally by the two PICs. never raised) |
3 | COM2 (if enabled) |
4 | COM1 (if enabled) |
5 | LPT2 (if enabled) |
6 | Floppy Disk |
7 | LPT1 / Unreliable “spurious” interrupt (usually) |
8 | CMOS real-time clock (if enabled) |
9 | Free for peripherals / legacy SCSI / NIC |
10 | Free for peripherals / SCSI / NIC |
11 | Free for peripherals / SCSI / NIC |
12 | PS2 Mouse |
13 | FPU / Coprocessor / Inter-processor |
14 | Primary ATA Hard Disk |
15 | Secondary ATA Hard Disk |