ReactOS源代码分析APIC机制

上图是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的索引查找系统中的中断处理向量。












相关文章

一、前言 在组件方面react和Vue一样的,核心思想玩的就是组件...
前言: 前段时间学习完react后,刚好就接到公司一个react项目...
前言: 最近收到组长通知我们项目组后面新开的项目准备统一技...
react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom...