3. Local APIC 架构:寄存器布局、版本寄存器与 LVT 条目
Local APIC(本地高级可编程中断控制器)是每个 CPU 核心内部的中断控制单元,负责接收来自 I/O APIC、IPI(处理器间中断)以及本地源(如 LINT0/LINT1、性能监控计数器等)的中断信号,并将其递交给 CPU 核心。理解 Local APIC 的内部寄存器布局、版本寄存器以及 LVT(Local Vector Table)条目,是配置中断路由的基础。
3.1 Local APIC 内部寄存器布局
Local APIC 的寄存器映射到物理地址空间中的一个 4KB 页面,基地址默认为 0xFEE00000(可通过 MSR 0x1B 修改)。所有寄存器均为 32 位或 64 位宽,访问时必须使用 16 字节对齐的地址。下表列出了关键寄存器的偏移量与功能:
| 偏移量 | 寄存器名称 | 功能描述 |
|---|---|---|
| 0x000 | APIC ID 寄存器 | 标识当前 Local APIC(即 CPU 核心)的唯一 ID |
| 0x020 | APIC 版本寄存器 | 指示 Local APIC 版本、最大 LVT 条目数 |
| 0x030 | 任务优先级寄存器 (TPR) | 设置当前 CPU 接受中断的优先级阈值 |
| 0x080 | 仲裁优先级寄存器 (APR) | 反映当前正在处理的中断优先级 |
| 0x0D0 | 处理器优先级寄存器 (PPR) | 结合 TPR 和 APR 计算出的实际优先级 |
| 0x0E0 | EOI 寄存器 | 中断处理结束时写入,通知 Local APIC 中断完成 |
| 0x0F0 | 远程读取寄存器 | 用于其他 CPU 读取本 Local APIC 的寄存器 |
| 0x100–0x170 | LVT 条目 (0–7) | 配置本地中断源(定时器、LINT0、LINT1、错误等) |
| 0x180–0x1E0 | LVT 条目 (8–15) | 扩展 LVT 条目(如性能计数器、温度传感器等) |
| 0x200–0x2E0 | ICR 寄存器(中断命令寄存器) | 发送 IPI(处理器间中断) |
| 0x300–0x3E0 | 定时器寄存器(初始计数、当前计数、分频配置) | 配置 Local APIC 定时器 |
关键提示: 在 x86 启动早期(实模式或保护模式初期),必须确保 Local APIC 的基地址映射正确且未被禁用(MSR 0x1B 的 bit 11 为 0 表示启用)。如果 Local APIC 被禁用,所有中断将无法到达 CPU 核心。
下图展示了 Local APIC 内部寄存器在 4KB 页面中的布局结构:
3.2 版本寄存器
版本寄存器位于偏移量 0x020,是一个 32 位只读寄存器,提供 Local APIC 的硬件版本信息和最大 LVT 条目数量。其位字段定义如下:
| 位域 | 宽度 | 名称 | 描述 |
|---|---|---|---|
| 31:24 | 8 位 | 保留 | 应忽略 |
| 23:16 | 8 位 | Max LVT Entry | 最大 LVT 条目索引(0–15),指示 LVT 表中可用的条目数量 |
| 15:8 | 8 位 | 保留 | 应忽略 |
| 7:0 | 8 位 | 版本 ID | Local APIC 版本号: 0x11 = 集成 APIC(P6 及之后) 0x14 = x2APIC 模式 |
版本 ID 解读: 在启动固件中,读取版本寄存器是判断 Local APIC 是否支持扩展 LVT 条目(如性能计数器、温度传感器)的关键步骤。如果版本 ID 为 0x11,则最大 LVT 条目通常为 5 或 6;如果为 0x14,则最大 LVT 条目可达 15。
读取版本寄存器的示例代码(C 语言风格,MMIO 访问):
// 假设 Local APIC 基地址已映射到虚拟地址 apic_base
volatile uint32_t *apic_version_reg = (volatile uint32_t *)(apic_base + 0x020);
uint32_t version_value = *apic_version_reg;
uint8_t version_id = version_value & 0xFF;
uint8_t max_lvt_entry = (version_value >> 16) & 0xFF;
printf("Local APIC Version ID: 0x%02X\n", version_id);
printf("Max LVT Entry Index: %d\n", max_lvt_entry);
3.3 LVT 条目
LVT(Local Vector Table)是 Local APIC 中一组用于配置本地中断源的寄存器。每个 LVT 条目是一个 32 位寄存器,控制一个特定的中断源(如 APIC 定时器、LINT0、LINT1、错误中断等)。LVT 条目的通用格式如下:
| 位域 | 宽度 | 名称 | 描述 |
|---|---|---|---|
| 31:18 | 14 位 | 保留 | 应写 0 |
| 17 | 1 位 | Delivery Status | 0 = 空闲,1 = 正在发送(只读) |
| 16 | 1 位 | Interrupt Input Pin Polarity | 0 = 高电平有效,1 = 低电平有效(仅 LINT0/LINT1) |
| 15 | 1 位 | Remote IRR | 0 = 未接收,1 = 已接收等待 EOI(只读,仅 LINT0/LINT1) |
| 14 | 1 位 | Trigger Mode | 0 = 边沿触发,1 = 电平触发(仅 LINT0/LINT1) |
| 13 | 1 位 | Mask | 1 = 屏蔽该中断源 |
| 12 | 1 位 | Timer Mode | 0 = 一次性,1 = 周期性(仅定时器 LVT) |
| 11:8 | 4 位 | Delivery Mode | 0000 = Fixed 0010 = SMI 0100 = NMI 0111 = ExtINT |
| 7:0 | 8 位 | Vector | 中断向量号(0–255),必须 ≥ 16(0x10) |
注意: 对于 LINT0 和 LINT1 引脚,如果系统使用 I/O APIC 作为外部中断源,通常将 LINT0 配置为 ExtINT 模式(Delivery Mode = 0111),将 LINT1 配置为 NMI 模式(Delivery Mode = 0100)。错误 LVT 条目用于捕获 Local APIC 内部错误(如校验错误),建议在启动时启用并分配一个有效向量。
下表列出了标准 LVT 条目的偏移量和对应的中断源:
| LVT 索引 | 偏移量 | 中断源 | 典型用途 |
|---|---|---|---|
| 0 | 0x100 | APIC 定时器 | 本地定时中断 |
| 1 | 0x110 | LINT0 | 外部中断(通常连接 8259A PIC) |
| 2 | 0x120 | LINT1 | NMI 中断 |
| 3 | 0x130 | 错误中断 | Local APIC 内部错误报告 |
| 4 | 0x140 | 性能监控计数器 | 性能监控中断(可选) |
| 5 | 0x150 | 温度传感器 | 温度阈值中断(可选) |
| 6–15 | 0x160–0x1E0 | 扩展 LVT | 处理器特定用途 |
配置 LVT 条目的示例代码(以 APIC 定时器为例):
// 配置 APIC 定时器 LVT 条目(偏移 0x100)
volatile uint32_t *lvt_timer = (volatile uint32_t *)(apic_base + 0x100);
uint32_t lvt_value = 0;
// 设置向量号 0x30(48),Fixed 模式,非屏蔽,一次性
lvt_value |= (0x30 & 0xFF); // Vector = 0x30
lvt_value |= (0x0 & 0x700); // Delivery Mode = Fixed (0000)
lvt_value &= ~(1 << 13); // Mask = 0 (启用)
lvt_value &= ~(1 << 12); // Timer Mode = 0 (一次性)
*lvt_timer = lvt_value;
// 设置定时器初始计数(偏移 0x380)
volatile uint32_t *timer_init_count = (volatile uint32_t *)(apic_base + 0x380);
*timer_init_count = 1000000; // 初始计数值
// 设置分频配置(偏移 0x3E0),分频系数 16
volatile uint32_t *timer_divide = (volatile uint32_t *)(apic_base + 0x3E0);
*timer_divide = 0x3; // 分频 16 (0x3 = 16)
下图展示了 LVT 条目与中断源之间的映射关系:
3.4 LVT 配置注意事项
- 向量号范围: 所有 LVT 条目中的 Vector 字段必须 ≥ 16(0x10),因为 0–15 被 Intel 保留用于异常和 NMI。使用非法向量号会导致中断被忽略或系统异常。
- 屏蔽与启用: 在初始化阶段,建议先屏蔽所有 LVT 条目(设置 Mask=1),逐个配置完成后,再清除 Mask 位启用。这可以防止未初始化的中断源意外触发。
- Delivery Mode 选择: 对于 LINT0,如果系统使用 I/O APIC 作为主中断控制器,应配置为 ExtINT 模式(0x7),以兼容 8259A PIC 的级联。对于 LINT1,通常配置为 NMI 模式(0x4),用于接收硬件 NMI 信号。
- 定时器模式: APIC 定时器支持一次性(One-shot)和周期性(Periodic)模式。在启动阶段,通常使用一次性模式进行延迟测量,进入操作系统后再切换为周期性模式用于时间片调度。
固件实践: 在 UEFI 或 BIOS 启动固件中,Local APIC 的初始化通常在进入保护模式或长模式后立即执行。建议按以下顺序操作:
1. 读取版本寄存器,确认 Local APIC 存在且版本正确。
2. 屏蔽所有 LVT 条目。
3. 配置 LINT0 为 ExtINT,LINT1 为 NMI,错误 LVT 分配有效向量。
4. 设置 TPR 为 0(允许所有优先级的中断)。
5. 写入 EOI 寄存器清除任何残留中断状态。
1. 读取版本寄存器,确认 Local APIC 存在且版本正确。
2. 屏蔽所有 LVT 条目。
3. 配置 LINT0 为 ExtINT,LINT1 为 NMI,错误 LVT 分配有效向量。
4. 设置 TPR 为 0(允许所有优先级的中断)。
5. 写入 EOI 寄存器清除任何残留中断状态。