上图是APIC的组成,其中PIIX3是一个和之前标准中断控制器兼容的中断控制器,而Host-to-PCI桥是系统中与外设相连的桥(可能是北桥)。APIC包含IOAPIC和Local APIC,Local APIC包含在处理器内部,Local APIC用于处理器之间的相互中断,而IOAPIC处于系统芯片组中,IOAPIC用于处理外部IO提交的中断。IOAPIC和Local APIC通过APIC总线进行数据和控制的传送。不过,在IOAPIC和Local APIC之间传递的不是简单地电平信息,所有的中断在IOAPIC中编码成中断消息之后传递给Local APIC。
IOAPIC包含一系列可以变成的寄存器,这些寄存器中的IOREGSEL和IOWIN被编码到处理器的内存地址空间中,同时可以利用这两个寄存器间接访问其他的APIC寄存器。在Windows系统中将Local APIC的地址映射到映射到物理地址0XFFE00000,IOAPIC默认的物理地址是0XFEC00000。当然,物理地址最终还是要调整为虚拟地址进行访问的。Local APIC映射到内存地址空间的虚拟地址为0XFFFE0000,而IOAPIC映射的虚拟地址是0XFFD06000.
为了访问IOAPIC的寄存器,首先写一个字节到IOREGSEL寄存器中。这8位作为IOAPIC中将被访问的寄存器的索引号,而IOWIN寄存器中的数据将被写入到指定的寄存器中,需要注意的是IOWIN被当做一个双字格式进行访问。下表是每个寄存器在IOAPIC中的偏移,而Local APIC中的寄存器相对IOAPIC要多,在英特尔的官方文档的第三册的第十章有介绍。
Local APIC中相应寄存器的偏移在APIC.h头文件中有定义。
APIC系统支持255个中断向量,但是Intel保留了0-15号向量,可用的向量是16-25,同时引进一个概念叫做任务优先级。windows系统中的IRQL等于任务优先级/16,所以可用的优先级是2-15.这个任务优先级存放在本地APIC的任务优先级寄存器TPR中。优先级低于TPR优先级的中断都会被屏蔽,而同一个任务优先级中的16个中断向量可以进一步细粒度地区分中断的优先级。
VOID FORCEINLINE ApicRaiseIrql(KIRQL Irql) { ApicWrite(APIC_TPR,IrqlToTpr(Irql)); }
这是提升系统的IRQL的操作,写操作的实现根据上面的介绍很简单.利用APIC_TPR作为偏移,APIC中的IOREGSEL作为基地址,然后将IrqToTpr作为数据存放到IOWIN中,就实现将相应的TPR写入到Local APIC的任务优先级寄存器中。
下面是IOAPIC中关于IO重定位表的定义:
typedef union _IOAPIC_REDIRECTION_REGISTER { ULONGLONG LongLong; struct { ULONG Long0; ULONG Long1; }; struct { ULONGLONG Vector:8; ULONGLONG DeliveryMode:3; ULONGLONG DestinationMode:1; ULONGLONG DeliveryStatus:1; ULONGLONG Polarity:1; ULONGLONG RemoteIRR:1; ULONGLONG TriggerMode:1; ULONGLONG Mask:1; ULONGLONG Reserved:39; ULONGLONG Destination:8; }; } IOAPIC_REDIRECTION_REGISTER;
通过上面的定义可以发现,整个IO重定位表一项包含8个字节。最低8位表示中断向量,正好是256项。然后三位表示中断的发送模式,主要定义目标对中断的相应操作。接下来一位用于指示最高8位怎样指定处理中断的目标,因为有可能需要指定处理中断的处理器。而发送状态位则指示整个中断发送之后的状态。polarity设置中断激发的电平。remoteIRR则用于设置水平激发的中断,当IOAPIC接受中断的时候,设置这个位为1,在中断处理之后设置这个位为0.TriggerMode则用于指示激发模式。Mask则用于设置这个中断是否被屏蔽。
BOOLEAN NTAPI HalEnableSystemInterrupt( IN ULONG Vector,IN KIRQL Irql,IN KINTERRUPT_MODE InterruptMode) { IOAPIC_REDIRECTION_REGISTER ReDirReg; PKPRCB Prcb = KeGetCurrentPrcb(); UCHAR Index; Index = HalpVectorToIndex[Vector]; if (Index == 0xff) { return FALSE; } ReDirReg = ApicReadioRedirectionEntry(Index); if (ReDirReg.Vector != Vector) { ReDirReg.Vector = Vector; ReDirReg.DeliveryMode = APIC_MT_LowestPriority; ReDirReg.DestinationMode = APIC_DM_Logical; ReDirReg.Destination = 0; } if (ReDirReg.DestinationMode == APIC_DM_Logical) { ReDirReg.Destination |= ApicLogicalId(Prcb->Number); } ReDirReg.TriggerMode = 1 - InterruptMode; ReDirReg.Mask = FALSE; ApicWriteIORedirectionEntry(Index,ReDirReg); return TRUE; }
上面是对APIC中的中断重定位表的设置,首先读取原来的IO重定位表,如果和现在的目标向量不对应则重新设置中断向量,以及发送模式和目标,最后将修改后的重定位表填充到APIC的表项当中。
KIRQL FASTCALL HalpVectorToIrql(UCHAR Vector) { return TprToIrql(Vector); } UCHAR FASTCALL HalpVectorToIrq(UCHAR Vector) { return HalpVectorToIndex[Vector]; }上面两个函数体现了中断向量和IRQL以及中断信号的管理。TprToIrql通过将Vector除以16得到索引号寻找数组当中的Irql,实质上这里的vector更像是一个任务优先级。而Vector也会有一个函数将其进行一定的映射,得到IDT表项的入口索引,在中断发生的时候作为IDT的索引查找系统中的中断处理向量。