x86启动中APIC与IOAPIC中断路由配置 - 第1节
1.1 中断向量:CPU 眼中的中断编号
在 x86 系统中,每个中断源都被分配一个唯一的 中断向量号(Vector Number),范围是 0~255。CPU 只通过这个 8 位编号来识别中断来源,而不关心中断是由硬件设备、外部控制器还是软件指令产生的。
| 向量范围 | 类型 | 典型用途 |
|---|---|---|
| 0~31 | 异常与 NMI | 除零错(#DE)、页故障(#PF)、不可屏蔽中断等 |
| 32~255 | 可屏蔽中断(INTR) | 外设中断、IPI、软件中断(INT n) |
| 0x80 | 系统调用(Linux) | 用户态进入内核的软中断入口 |
关键概念:向量号 0~31 被 Intel 保留给 CPU 内部异常和 NMI,固件和操作系统不能将这些向量分配给 IOAPIC 或外部设备。启动阶段必须确保 IDT 中这些入口有效,否则任何异常都会导致三重故障(Triple Fault)复位。
1.2 中断描述符表(IDT):中断处理的路由表
IDT 是 x86 保护模式与长模式下的一张 256 项 的描述符数组,每个描述符对应一个中断向量。CPU 收到中断向量后,直接以向量号为索引查找 IDT,获取处理程序的入口地址和属性。
IDT 描述符结构(保护模式 8 字节)
| 位域 | 宽度 | 说明 |
|---|---|---|
| Offset[15:0] | 16 bit | 处理程序地址低 16 位 |
| Selector | 16 bit | 代码段选择子(必须为可执行段) |
| IST(长模式) | 3 bit | 中断栈表索引,0 表示使用当前栈 |
| Type | 4 bit | 描述符类型:中断门(0xE)、陷阱门(0xF) |
| DPL | 2 bit | 描述符特权级,软件中断需 CPL ≤ DPL |
| Present | 1 bit | 1 表示有效,0 触发 #GP |
| Offset[31:16] | 16 bit | 处理程序地址高 16 位 |
启动阶段要点:BIOS/UEFI 在进入保护模式前会设置临时的 IDT(通常只覆盖前 32 个异常向量)。Bootloader 或内核必须在切换至长模式或启用 APIC 前重新加载完整的 IDT,否则外部中断(向量 32+)会因描述符缺失导致 CPU 抛出 #GP 异常。
1.3 中断响应流程:从向量到处理函数
当 CPU 收到一个中断向量 N 时,硬件自动执行以下步骤:
- 保存现场:将 EFLAGS、CS、EIP(或 RIP)压栈;如果涉及特权级切换,同时保存 SS 和 ESP。
- 清除 IF:自动清除 EFLAGS 中的 IF 位,屏蔽后续可屏蔽中断(中断门行为)。
- 查 IDT:以向量号
N为索引,从 IDTR 寄存器指向的 IDT 基址读取描述符。 - 权限检查:验证当前 CPL 是否 ≤ 描述符 DPL(软件中断)或描述符 DPL 是否 ≤ CPL(硬件中断)。
- 跳转处理:加载代码段选择子到 CS,偏移地址到 EIP/RIP,开始执行中断服务例程(ISR)。
常见陷阱:IDT 基址寄存器(IDTR)在系统复位后默认值为 0,此时任何中断都会导致 CPU 从地址 0 取指,通常引发 Triple Fault。固件必须在 第一条指令之后、开中断之前 完成 IDT 的初始化。
1.4 中断向量与 IDT 关系图
下图展示了中断源、向量号、IDT 表项与 ISR 之间的映射关系:
1.5 中断门 vs 陷阱门:IF 位行为差异
IDT 描述符的类型字段决定了中断处理期间是否自动清除 IF 位:
| 类型 | Type 值 | 自动清除 IF | 典型场景 |
|---|---|---|---|
| 中断门(Interrupt Gate) | 0xE | 是 | 硬件中断处理,防止嵌套 |
| 陷阱门(Trap Gate) | 0xF | 否 | 异常调试、系统调用(允许嵌套) |
固件设计建议:在 APIC 初始化阶段,所有外部中断(向量 32+)应使用中断门,避免 ISR 执行期间被同级或更高优先级中断打断导致栈溢出。异常处理建议使用陷阱门,以便在调试时保留中断使能状态。
1.6 IDT 在启动阶段的典型初始化代码
以下代码展示在保护模式下使用 lidt 指令加载 IDT 的基本流程:
; 汇编示例:设置 IDT 并加载 IDTR
section .data
align 8
idt_start:
; 为每个向量预留 8 字节描述符空间
times 256 dq 0 ; 256 个描述符,初始为 0
idt_descriptor:
dw (idt_end - idt_start - 1) ; IDT 限长
dd idt_start ; IDT 基址(32 位)
idt_end:
section .text
global setup_idt
setup_idt:
; 1. 填充 IDT 描述符(以向量 32 为例)
mov eax, timer_isr ; 处理函数地址
mov ebx, 0x0008 ; 代码段选择子(GDT 索引 1)
mov ecx, 0x8E00 ; Present=1, DPL=0, Type=0xE(中断门)
lea edi, [idt_start + 32*8] ; 定位向量 32 的描述符
mov [edi], ax ; Offset[15:0]
shr eax, 16
mov [edi+6], ax ; Offset[31:16]
mov [edi+2], bx ; Selector
mov [edi+4], cx ; 属性 + 保留位
; 2. 加载 IDTR
lidt [idt_descriptor]
; 3. 开中断
sti
ret
注意:上述代码仅为演示结构。实际固件中需要为每个使用的向量重复填充过程,并确保所有异常向量(0~31)至少指向一个错误处理函数,避免未处理异常导致系统崩溃。
1.7 本章知识结构总结
| 概念 | 核心要点 | 启动阶段注意事项 |
|---|---|---|
| 中断向量 | 0~255 编号,0~31 为异常保留 | 不要将外设中断分配到保留向量 |
| IDT | 256 项描述符表,CPU 硬件索引 | 必须在开中断前加载有效 IDTR |
| 中断门 vs 陷阱门 | 中断门自动关 IF,陷阱门不关 | 硬件中断用中断门,异常用陷阱门 |
| IDTR 加载 | 使用 lidt 指令,指定基址和限长 | 限长 = 256×8-1(保护模式) |