APIC
APIC(“高级可编程中断控制器-Advanced Programmable Interrupt Controller”)是针对旧版PIC的最新英特尔标准。 它用于多处理器系统,是所有最新的英特尔(和兼容的)处理器的组成部分。 APIC用于复杂的中断重定向,以及在处理器之间发送中断。 使用旧的PIC规范无法实现这些功能。
检测
CPUID.01h:EDX [位9] 标志指定CPU是否具有内置的本地APIC。 通过解析MADT,可以找到系统上的所有APIC(本地APIC和IO APIC)。
本地APIC和IO-APIC
在基于APIC的系统中,每个CPU由 “核心(core)” 和 “本地APIC(local APIC)” 组成。 本地APIC负责处理特定于cpu的中断配置。 除此之外,它还包含“本地向量表(LVT)”,将“内部时钟”和其他“本地”中断源等事件转换为中断向量 (例如,LocalINT1管脚可以通过在LVT的相应条目中存储“2”来引发NMI异常)。
有关本地APIC的更多信息,请参见 Intel Intel系统编程指南,第3A卷第1部分 的第10章。
此外,还有一个I/O APIC(例如intel 82093AA),它是芯片组的一部分,提供多处理器中断管理,将静态和动态对称中断分布整合到所有处理器中。 在具有多个I/O子系统的系统中,每个子系统可以有自己的一组中断。
每个中断引脚都可以单独编程为边缘触发或电平触发。 每个中断都可以指定中断向量和中断控制信息。 间接寄存器访问方案优化了访问I/O APIC内部寄存器所需的存储空间。 为了在分配内存空间使用量时增加系统灵活性,I/O APIC的两个寄存器内存空间是可重定位的,但默认为0xFEC00000。
APIC的英特尔标准可在英特尔网站上的“多处理器规范”类别下找到,或者只需这个PDF文件。
处理器间中断(Inter-Processor Interrupts)
处理器间中断 (IPI) 由本地APIC生成,可以用作调度协调,多处理器自举等的基本信令。
本地APIC配置
本地APIC在启动时启用,可通过清除IA32_APIC_BASE型号特定寄存器(MSR)的位11来禁用 (请参见下面的示例,这仅适用于系列>5的CPU,因此奔腾没有这样的MSR)。 然后,CPU直接从8259兼容PIC接收中断。 但是,《英特尔软件开发人员手册》指出,一旦通过IA32_APIC_BASE禁用了本地APIC,在完全重置之前,您将无法再启用它。 I/O APIC也可以配置为在传统模式下运行,以便它模拟8259设备。
本地APIC寄存器在物理页FEE00xxx中进行内存映射(如英特尔P4 SPG的表8-1所示)。 此地址对于配置中存在的每个本地APIC都是相同的,这意味着您只能直接访问当前在其上执行代码的内核的本地APIC的寄存器。 请注意,有一个MSR指定实际的APIC基址 (仅在系列> 5的cpu上可用)。 MADT包含本地APIC基址,在64位系统上,它还可能包含一个字段,指定应该使用的64位基址覆盖。 您可以选择离开本地APIC基址,就在您找到它的地方,或者随心所欲地移动它。注:我认为你不能把它移到4GB以外的地方。
为了使本地APIC能够接收中断,必须配置 “虚假中断向量寄存器(Spurious Interrupt Vector Register)”。 该字段的正确值是您希望将虚假中断映射到最低8位内的IRQ号,第8位设置为1以实际启用APIC(有关更多详细信息,请参阅规范)。 您应该选择设置了最低4位且大于32的中断号(正如您可能猜到的那样);最简单的方法是使用0xFF。 这在一些较旧的处理器上很重要,因为此值的最低4位必须在这些处理器上设置为1。
正确禁用8259 PIC。 这几乎与设置APIC一样重要。 您可以在两个步骤中执行此操作: 屏蔽所有中断并重新映射irq。 屏蔽所有中断将禁用PIC中的中断。 重新映射可能是您在使用PIC时已经做过的事情:您希望中断请求从32开始,而不是从0开始,以避免与异常冲突。 然后,您应该避免将这些中断向量用于其他目的。 这是必要的,因为即使你屏蔽了PIC上的所有中断,它仍然可能发出虚假的中断,这将从你的内核被误解为异常。
以下是设置APIC的一些代码示例:
#define IA32_APIC_BASE_MSR 0x1B
#define IA32_APIC_BASE_MSR_BSP 0x100 // Processor is a BSP
#define IA32_APIC_BASE_MSR_ENABLE 0x800
/** 如果CPU支持APIC,则返回 “true” 值
* 如果本地APIC没有在MSRs中被禁用
* 请注意,这需要支持CPUID。
*/
bool check_apic() {
uint32_t eax, edx;
cpuid(1, &eax, &edx);
return edx & CPUID_FEAT_EDX_APIC;
}
/* 设置本地APIC寄存器的物理地址 */
void cpu_set_apic_base(uintptr_t apic) {
uint32_t edx = 0;
uint32_t eax = (apic & 0xfffff0000) | IA32_APIC_BASE_MSR_ENABLE;
#ifdef __PHYSICAL_MEMORY_EXTENSION__
edx = (apic >> 32) & 0x0f;
#endif
cpuSetMSR(IA32_APIC_BASE_MSR, eax, edx);
}
/**
* 获取APIC寄存器页的物理地址
* 确保将其映射到虚拟内存;)
*/
uintptr_t cpu_get_apic_base() {
uint32_t eax, edx;
cpuGetMSR(IA32_APIC_BASE_MSR, &eax, &edx);
#ifdef __PHYSICAL_MEMORY_EXTENSION__
return (eax & 0xfffff000) | ((edx & 0x0f) << 32);
#else
return (eax & 0xfffff000);
#endif
}
void enable_apic() {
/* 如果未启用本地APIC,则硬件启用它 */
cpu_set_apic_base(cpu_get_apic_base());
/* 设置伪中断向量寄存器位8以开始接收中断 */
write_reg(0xF0, ReadRegister(0xF0) | 0x100);
}
本地APIC和x86 SMM攻击
在 英特尔的82489DX分立芯片 中,APIC被引入到英特尔核心处理器架构框架中,与 系统管理模式同时期被引入到操作系统。 在最初的体系结构中,APIC无法映射到内存,直到后来的更改,它才成为可映射的。
由于系统管理模式的内存(SMRAM-System Management Mode's memory)具有受保护的内存范围(可能因系统而异),因此可以将APIC内存位置映射到SMRAM中。 这样做的结果是SMM内存被推到其保护范围之外,并暴露于特权较低的权限环。 使用此方法,攻击者可以使用系统管理模式利用其权限,该模式受到-2以上所有环的保护。
在新一代英特尔处理器(从2013年的Intel Atom开始)中,这一点已被考虑在内。 当APIC重新定位到内存时,对 System Management Range Registers-系统管理范围寄存器 执行一个未记录的检查。 该检查确保APIC不会与SMRAM重叠。但是这取决于SMRR的正确配置。 否则,此缓解(mitigation)做法将无法正常工作,攻击者仍可使用该攻击。
本地APIC寄存器
本地APIC寄存器被内存映射到可以在MP/MADT表中找到的地址。 如果使用分页,请确保将这些映射到虚拟内存。 每个寄存器的长度为32位,并且期望被写入和读取为32位整数。 尽管每个寄存器有4个字节,但它们都在16字节的边界上对齐。
本地APIC寄存器列表(TODO:添加所有寄存器的描述):
偏移 | 寄存器名 | 读写权限 |
000h - 010h | 保留 | |
020h | LAPIC ID Register | Read/Write |
030h | LAPIC Version Register | Read only |
040h - 070h | Reserved | |
080h | Task Priority Register (TPR) | Read/Write |
090h | Arbitration Priority Register (APR) | Read only |
0A0h | Processor Priority Register (PPR) | Read only |
0B0h | EOI register | Write only |
0C0h | Remote Read Register (RRD) | Read only |
0D0h | Logical Destination Register | Read/Write |
0E0h | Destination Format Register | Read/Write |
0F0h | Spurious Interrupt Vector Register | Read/Write |
100h - 170h | In-Service Register (ISR) | Read only |
180h - 1F0h | Trigger Mode Register (TMR) | Read only |
200h - 270h | Interrupt Request Register (IRR) | Read only |
280h | Error Status Register | Read only |
290h - 2E0h | Reserved | |
2F0h | LVT Corrected Machine Check Interrupt (CMCI) Register | Read/Write |
300h - 310h | Interrupt Command Register (ICR) | Read/Write |
320h | LVT Timer Register | Read/Write |
330h | LVT Thermal Sensor Register | Read/Write |
340h | LVT Performance Monitoring Counters Register | Read/Write |
350h | LVT LINT0 Register | Read/Write |
360h | LVT LINT1 Register | Read/Write |
370h | LVT Error Register | Read/Write |
380h | Initial Count Register (for Timer) | Read/Write |
390h | Current Count Register (for Timer) | Read only |
3A0h - 3D0h | Reserved | |
3E0h | Divide Configuration Register (for Timer) | Read/Write |
3F0h | Reserved |
EOI寄存器
使用值0写入偏移量为0xB0的寄存器,以发出中断结束的信号。 非零值可能导致一般保护故障。
Local Vector Table Registers
处理器和LAPIC可以自行生成一些特殊的中断。 虽然在I/O APIC中配置了外部中断,但必须使用LAPIC中的寄存器配置这些中断。 最有趣的寄存器是:0x320=lapic定时器,0x350=lint0,0x360=lint1。 有关详细信息,请参阅英特尔SDM第3卷。
寄存器格式:
Bits 0-7 | The vector number |
Bits 8-10 (reserved for timer) | 100b if NMI |
Bit 11 | Reserved |
Bit 12 | Set if interrupt pending. |
Bit 13 (reserved for timer) | Polarity, set is low triggered |
Bit 14 (reserved for timer) | Remote IRR |
Bit 15 (reserved for timer) | trigger mode, set is level triggered |
Bit 16 | Set to mask |
Bits 17-31 | Reserved |
Spurious Interrupt Vector Register
偏移量为0xF0。 低位字节包含虚假中断的编号。 如上所述,您可能应该将其设置为0xFF。 要启用APIC,请设置该寄存器的位8(或0x100)。 如果设置了第12位,则不会广播EOI消息。 目前保留所有其他位。
Interrupt Command Register
中断命令寄存器(interrupt command register)由两个32位寄存器组成;一个位于0x300,另一个位于0x310。 它用于向不同的处理器发送中断。 写入0x300时发出中断,但写入0x310时不发出中断。 因此,要发送中断命令,应先写入0x310,然后写入0x300。 在0x310处,在位24-27处有一个字段,它是目标处理器的本地APIC ID (对于物理目的地模式)。 下面是0x300的结构:
Bits 0-7 | SIPI的向量号或起始页码 |
Bits 8-10 | 目标模式。0是正常的,1是最低优先级,2是SMI,4是NMI,5可以是INIT或INIT级别的断言,6是SIPI。 |
Bit 11 | 目标模式。清除物理目标,或设置逻辑目标。如果该位已清除0,则0x310中的目标字段将正常处理。 |
Bit 12 | 投递状态。目标已接受中断时清除。通常应在发送中断后等待此位清0。 |
Bit 13 | 保留 |
Bit 14 | 清除0表示INIT级别de-assert,否则设置1。 |
Bit 15 | 设置1为INIT级别de-assert,否则清除0。 |
Bits 18-19 | 目标类型。 如果该值大于0,则忽略0x310中的目标字段。 1将始终向自身发送中断,2将向所有处理器发送中断,3将向除当前处理器以外的所有处理器发送中断。 最好避免使用模式1、2和3,并坚持使用0。 |
Bits 20-31 | 保留 |
IO APIC配置
IO APIC的大部分操作都使用两个寄存器 - IOAPICBASE+0处的地址寄存器和IOAPICBASE+0x10处的数据寄存器。 所有访问必须在4字节边界上进行。 地址寄存器使用底部8位进行寄存器选择。 下面是一些示例代码,说明了这一点:
uint32_t cpuReadIoApic(void *ioapicaddr, uint32_t reg)
{
uint32_t volatile *ioapic = (uint32_t volatile *)ioapicaddr;
ioapic[0] = (reg & 0xff);
return ioapic[4];
}
void cpuWriteIoApic(void *ioapicaddr, uint32_t reg, uint32_t value)
{
uint32_t volatile *ioapic = (uint32_t volatile *)ioapicaddr;
ioapic[0] = (reg & 0xff);
ioapic[4] = value;
}
请注意volatile关键字的用法。 这阻止了像 Visual C 这样的编译器重新排序或优化掉内存访问,这是一种利弊权衡。 volatile关键字放在“*”号之前。 这意味着指针指向的值是volatile,而不是指针本身。
IO APIC寄存器
使用上述方法,可以访问以下寄存器。
0x00 | 在第24-27位获取/设置IO APIC的id。所有其他位都被保留。 |
0x01 | 以0-7位获取版本。获取以位16-23为单位的重定向条目的最大数量。保留所有其他位。只读。 |
0x02 | 在第24-27位中获取仲裁优先级。所有其他位都已保留。只读。 |
0x10 to 0x3F | 包含重定向条目列表。它们可以被读取和写入。每个条目使用两个地址,例如0x12和0x13。 |
这是重定向条目的样子。
Bits 0-7 | 中断向量。允许的值从0x10到0xFE。 |
Bits 8-10 | 投递模式类型。0=正常,1=低优先级,2=系统管理中断,4=不可屏蔽中断,5=INIT,7=外部。其他的都是预订的。 |
Bit 11 | 目标模式。影响目标字段的读取方式,0是物理模式,1是逻辑模式。如果此条目的目标模式是物理模式,则位56-59包含APIC ID。 |
Bit 12 | 设置是否发送此中断,但APIC正忙。只读。 |
Bit 13 | 中断极性。0=高为激活状态,1=低为激活状态。 |
Bit 14 | 用于级别触发的中断,仅用于显示本地APIC是否已收到中断 (= 1) 或已发送EOI (= 0)。只读。 |
Bit 15 | 触发模式。0=边沿敏感,1=级别敏感。 |
Bit 16 | 中断屏蔽。如果设置,则停止中断到达处理器。 |
Bits 17-55 | 保留。 |
Bits 56-63 | 目标字段。如果目标模式位是清除的,则较低的4位包含发送中断的位APIC ID。如果设置了位,则上面的4位还包含一组处理器。(见下文) |
有关更多信息,请参阅I/O APIC数据表的第3章。
重定向表允许您选择将哪些外部中断发送到哪些处理器以及使用哪些中断向量。 在选择处理器时,您应该考虑:在处理器之间分配工作负载,避免处理器处于低功耗状态,以及避免处理器受到限制。 选择中断向量时,应记住中断0x00至0x1F是为内部处理器异常保留的,重新映射PIC到的中断可能会收到虚假中断,0xFF可能是放置APIC虚假中断的位置,并且中断向量的高4位表示其优先级。
逻辑目标模式(Logical Destination Mode)
逻辑目标模式使用LDR(逻辑目标寄存器,每个APIC唯一)中包含的8位逻辑APIC ID。 所有APIC将其本地ID与随中断发送的目的代码进行比较。 这允许通过使用相同的逻辑APIC ID对一组处理器进行编程来针对它们。
LDR的格式如下
Bits 0-23 | 保留。 | |
Bits 24-31 | Flat model | 目标处理器的位图 (bit标识单处理器; 支持最多8个本地APIC单元) |
Cluster model | Bits 24-27 | 本地APIC地址(标识组中的特定处理器) |
Bits 28-31 | 集群地址(标识一组处理器) |
DFR (目标格式寄存器) 指定平面或群集模型,其结构如下
Bits 0-27 | 保留。 |
Bits 28-31 | 模型(1111b=平面模型,0000b=集群模型) |
'不要'使用群集模式寻址,尤其是 “分层群集模式”。 AFAIK它适用于大型NUMA系统,其中每个NUMA域都有一个“节点控制器”,将中断转发给该NUMA域内的CPU(每个NUMA域都有一个单独的APIC总线)。 除非你的芯片组有这些“节点控制器”(或英特尔所称的“集群管理器”),否则它不会工作,而且没有现代计算机有它们(AFAIK只有几个鲜为人知的奔腾III/P6 NUMA系统曾经有过)。 您想为普通SMP和大多数NUMA系统 (包括AMD系统) 使用 “平面模型”。(Brendan)
更多信息请访问“奔腾处理器系统架构。第15章:APIC”
另见
文章
论坛主题
外部链接
- Original I/O APIC specification/datasheet
- Updated I/O APIC specification/datasheet
- Volume 3A:System Programming Guide, Part 1,manuals has a chapter on the APIC
- Volume 3A:System Programming Guide, Chapter 10.4 for further reading about the LAPIC
- Advanced Programmable Interrupt Controller by Mike Rieker
- "The Importance of Implementing APIC-Based Interrupt Subsystems on Uniprocessor PCs". Microsoft. 7 January 2003
- Pentium Processor System Architecture