從IRQ到IRQL(APIC版)[zt]

從IRQ到IRQL(APIC版)

                                                    SoBeIt
    事實上,老久的PIC在很早以前就被淘汰了,取而代之的是APIC。由於APIC可以兼容PIC,所以在很多單處理器系統上我們看到的PIC實際是APIC的兼容PIC模式。APIC主要應用於多處理器操作系統,是爲了解決IRQ太少和處理器間中斷而產生的,當然,單處理器操作系統也可以使用APIC(不是模擬PIC)。APIC的HAL和PIC的HAL有很大的不同,很突出的一個特點就是APIC的HAL不用再象PIC的HAL那樣虛擬一箇中斷控制器,IRQL的概念已經可以通過中斷向量的形式被APIC支持。事實上,因爲被APIC所支持,所以在APIC HAL裏IRQL的實現比PIC HAL那樣虛擬一箇中斷控制器要簡單得多了。

    現在來簡單介紹一下APIC的結構(關於APIC詳細的描述請參考《IA-32 Inel Architecture Software Developer's Manual Volume 3 Chapter 8》)。整個APIC系統由本地APIC、IO APIC和APIC串行總線組成(在Pentium 4和Xeon以後,APIC總線放到了系統總線中)組成。每個處理器中集成了一個本地APIC,而IO APIC是系統芯片組中一部分,APIC總線負責連接IO APIC和各個本地APIC。本地APIC接收該處理器產生的本地中斷比如時鐘中斷,以及由該處理器產生的處理器間中斷,並從APIC串行總線接收來自IO APIC的消息;IO APIC負責接收所有外部的硬件中斷,並翻譯成消息選擇發給接收中斷的處理器,以及從本地APIC接收處理器間中斷消息。

    和PIC一樣,控制本地APIC和IO APIC的方法是通過讀寫該單元中的相關寄存器。不過和PIC不一樣的是,Intel把本地APIC和IO APIC的寄存器都映射到了物理地址空間,本地APIC默認映射到物理地址0xffe00000,IO APIC默認映射到物理地址0xfec00000。windows HAL再進一步把本地APIC映射到虛擬地址0xfffe0000,把IO APIC映射到虛擬地址0xffd06000,也就是說對該地址的讀寫實際就是對寄存器的讀寫,本地APIC裏幾個重要的寄存有EOI寄存器,任務優先級寄存器(TPR),處理器優先級寄存器(PPR),中斷命令寄存器(ICR,64位),中斷請求寄存器(IRR,256位,對應每個向量一位),中斷在服務寄存器(ISR,256位)等。IO APIC裏幾個重要的寄存器有版本寄存器,I/O寄存器選擇寄存器、I/O窗口寄存器(用要訪問的I/O APIC寄存器的索引設置地址I/O寄存器選擇寄存器,此時訪問I/O窗口寄存器就是訪問被選定的寄存器)還有很重要的是一個IO重定向表,每一個表項是一個64位寄存器,包括向量和目標模式、傳輸模式等相關位,每一個表項連接一條IRQ線,表項的數目隨處理器的版本而不一樣,在Pentium 4上爲24個表項。表項的數目保存在IO APIC版本寄存器的[16:23]位。APIC系統支持255箇中斷向量,但Intel保留了0-15向量,可用的向量是16-255。並引進一個概念叫做任務優先級=中斷向量/16,因爲保留了16個向量,所以可用的優先級是2-15。當用一個指定的優先級設置本地APIC中的任務優先級寄存器TPR後,所有優先級低於TPR中優先級的中斷都被屏蔽,是不是很象IRQL的機制?事實上,APIC HAL裏的IRQL機制也就是靠着這個任務優先級寄存器得以實現。同一個任務優先級包括了16箇中斷向量,可以進一步細粒度地區分中斷的優先級。

    在HAL裏雖然HalBeginSystemInterrupt仍然是IRQL機制的發動引擎,但是因爲有APIC的支持,它和其它共同實現IRQL的函數要比PIC HAL裏對應的函數功能簡單得多。HalBeginSystemInterrupt通過用IRQL做索引在HalpIRQLtoTPR數組中獲取該IRQL對應的任務優先級,用該優先級設置任務優先級寄存器TPR,並把TPR中原先的任務優先級/16做爲索引在HalpVectorToIRQL數組中獲取對應的原先的IRQL然後返回。若IRQL是從低於DISPATCH_LEVEL提升到高於DISPATCH_LEVEL,還需要設置KPCR+0x95(0xffdff095)爲DISPATCH_LEVEL(0x2),表示是從DISPATCH_LEVEL以下的級別提升IRQL。HalEndSystemInterrupt向本地APIC的EOI寄存發送0,表示中斷結束,可以接收新中斷。並還要判斷要降到的IRQL是否小於DISPATCH_LEVEL,若小於則進一步判斷KPCR+0x96(0xffdff096)是否置位,若置位則表示有DPC中斷在等待(在IRQL高於DISPATCH_LEVEL被引發,然後等待直到IRQL降到低於DISPATCH_LEVEL),則將KPCR+0x95和KPCR+0x96清0後調用KiDispatchInterrupt響應DPC軟中斷。否則做的工作就是和HalBeginSystemInterrupt一樣的過程:把要降到的IRQL轉換成任務優先級設置TRP,並把久的任務優先級轉成IRQL返回。KfRaiseIrql、KfLowerIrql之類的函數也是這麼一回事,把當前IRQL轉成任務優先級修改TPR,並把原先TPR的值轉成原先的IRQL並返回。而現在軟中斷的產生也有了APIC支持,APIC通過產生一個發向自己的處理器間中斷,就可以產生一個軟中斷,因爲可以指定該中斷的向量,所以軟中斷就可以區分優先級別,如APC_LEVEL、DISPATCH_LEVEL。產生軟中斷的函數一樣還是HalRequestSoftwareInterrupt,該函數會先判斷KPCR+0x95是否和要產生的軟中斷IRQL一樣,若是的話則置位KPCR+0x96並返回,表示現在IRQL大於DISPATCH_LEVEL所以不處理DPC中斷。否則以要產生的軟中斷的IRQL爲索引從HalpIRQLtoTPRHAL取出對應任務優先級,並或上0x4000,表示是發向自身的固定處理間中斷,並用該值設置中斷命令寄存器ICW的低32位,然後讀取中斷命令寄存器ICW的低32位是否爲0x1000,確定中斷消息已經發送後就返回,這時候軟中斷已經產生。值得注意的是APIC HAL裏沒有HalEndSoftwareInterrupt這個函數。HAL爲軟中斷的IRQL提供了一個固定的中斷向量:

#define ZERO_VECTOR             0x00    // IRQL 00
#define APC_VECTOR              0x3D    // IRQL 01
#define DPC_VECTOR              0x41    // IRQL 02
#define APIC_GENERIC_VECTOR     0xC1    // IRQL 27
#define APIC_CLOCK_VECTOR       0xD1    // IRQL 28
#define APIC_SYNCH_VECTOR       0xD1    // IRQL 28
#define APIC_IPI_VECTOR         0xE1    // IRQL 29
#define POWERFAIL_VECTOR        0xEF    // IRQL 30
#define APIC_PROFILE_VECTOR     0xFD    // IRQL 31


現在看一下一些重要的數據:

這是我寫的代碼輸出的IO APIC重定向表內容:

Redirect Table Index:    0x17
Redirect Table[ 0]:      ff
Redirect Table[ 1]:      b3
Redirect Table[ 2]:      ff
Redirect Table[ 3]:      51
Redirect Table[ 4]:      ff
Redirect Table[ 5]:      ff
Redirect Table[ 6]:      62
Redirect Table[ 7]:      ff
Redirect Table[ 8]:      d1
Redirect Table[ 9]:      b1
Redirect Table[ a]:      ff
Redirect Table[ b]:      ff
Redirect Table[ c]:      52
Redirect Table[ d]:      ff
Redirect Table[ e]:      ff
Redirect Table[ f]:      92
Redirect Table[10]:      ff
Redirect Table[11]:      a3
Redirect Table[12]:      83
Redirect Table[13]:      93
Redirect Table[14]:      ff
Redirect Table[15]:      ff
Redirect Table[16]:      ff
Redirect Table[17]:      ff

這是IDT表中被註冊的向量:

1f: 80064908 (hal!HalpApicSpuriousService)
37: 800640b8 (hal!PicSpuriousService37)
3d: 80065254 (hal!HalpApcInterrupt)
41: 800650c8 (hal!HalpDispatchInterrupt)
50: 80064190 (hal!HalpApicRebootService)
51: 817f59e4
(Vector:51,Irql:4,SyncIrql:4,Connected:TRUE,No:0,ShareVector:FALSE,Mode:Latched,ISR:serial!SerialCIsrSw(f3c607c7))
52: 817f5044
(Vector:52,Irql:4,SyncIrql:a,Connected:TRUE,No:0,ShareVector:FALSE,Mode:Latched,ISR:i8042prt!I8042MouseInterruptService(f3c57a2c))
83: 817d2d44
(Vector:83,Irql:7,SyncIrql:7,Connected:TRUE,No:0,ShareVector:TRUE,Mode:LevelSensitive,ISR:NDIS!ndisMIsr(bff1b794))
92: 81821384
(Vector:92,Irql:8,SyncIrql:8,Connected:TRUE,No:0,ShareVector:FALSE,Mode:Latched,ISR:atapi!ScsiPortInterrupt(bff892be))
93: 8185ed64
(Vector:93,Irql:8,SyncIrql:8,Connected:TRUE,No:0,ShareVector:TRUE,Mode:LevelSensitive,ISR:uhcd!UHCD_InterruptService(f3f0253e))
a3: 8186cdc4
(Vector:a3,Irql:9,SyncIrql:9,Connected:TRUE,No:0,ShareVector:TRUE,Mode:LevelSensitive,ISR:SCSIPORT!ScsiPortInterrupt(bff719f0))
b1: 818902e4
(Vector:b1,Irql:a,SyncIrql:a,Connected:TRUE,No:0,ShareVector:TRUE,Mode:LevelSensitive,ISR:ACPI!ACPIInterruptServiceRoutine(bffe14b4))
b3: 81881664
(Vector:b3,Irql:a,SyncIrql:a,Connected:TRUE,No:0,ShareVector:FALSE,Mode:Latched,ISR:i8042prt!I8042KeyboardInterruptService(f3c51918))
c1: 800642fc (hal!HalpBroadcastCallService)
d1: 80063964 (hal!HalpClockInterrupt)
e1: 80064858 (hal!HalpIpiHandler)
e3: 800645d4 (hal!HalpLocalApicErrorService)
fd: 80064d64 (hal!HalpProfileInterrupt)
fe: 80064eec (hal!HalpPerfInterrupt)

象a3、b1這類輸出內容很多的是被硬件註冊的中斷向量,而象d1、e3這種輸出內容少的是註冊爲了的HAL內部使用的中斷向量和本地APIC中斷向量

這是幾個重要的數組:

HalVectorToIrql(這個數組是以向量除於16做索引):
8006a304  00 ff ff 01 02 04 05 06-07 08 09 0a 1b 1c 1d 1e

HalpIRQLtoTPR:
8006a1e4  00 3d 41 41 51 61 71 81-91 a1 b1 b1 b1 b1 b1 b1
8006a1f4  b1 b1 b1 b1 b1 b1 b1 b1-b1 b1 b1 c1 d1 e1 ef ff

HalpINTItoVector:
8006ada0  00 b3 61 51 a2 b2 62 91-a1 b1 71 81 52 82 72 92
8006adb0  00 a3 83 93 00 00 00 00-00 00 00 00 00 00 00 00

HalVectorToINTI:
8006a204  ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff
8006a214  ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff
8006a224  ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff
8006a234  ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff
8006a244  ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff
8006a254  ff 03 0c ff ff ff ff ff-ff ff ff ff ff ff ff ff
8006a264  ff 02 06 ff ff ff ff ff-ff ff ff ff ff ff ff ff
8006a274  ff 0a 0e ff ff ff ff ff-ff ff ff ff ff ff ff ff
8006a284  ff 0b 0d 12 ff ff ff ff-ff ff ff ff ff ff ff ff
8006a294  ff 07 0f 13 ff ff ff ff-ff ff ff ff ff ff ff ff
8006a2a4  ff 08 04 11 ff ff ff ff-ff ff ff ff ff ff ff ff
8006a2b4  ff 09 05 01 ff ff ff ff-ff ff ff ff ff ff ff ff
8006a2c4  ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff
8006a2d4  ff 08 ff ff ff ff ff ff-ff ff ff ff ff ff ff ff
8006a2e4  ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff
8006a2f4  ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff  


vBucket:
8006ae30  02 02 02 03 03 03 03

    舉個例子來說明一下,在我虛擬機裏SCSI Controller的IRQ是17(注意,已經大於16了),到重定向表中查找第17項,得到中斷向量爲0xa3,再看IDT,0xa3對應處理例程是SCSIPORT!ScsiPortInterrupt。

    vBucket數組幹啥用的?它就是用來分配新的向量。分配算法很簡單,當要分配一個新的向量時,就在vBucket數組從右到左搜索最小的一個數i,該數對應在vBucket中索引爲Index,新向量爲(0x50+Index*16+i+1),新向量對應的IRQL爲(4+i+1),同時會把vBucket中這個i加1,i不等大於16。象給出的這個vBucket,下一次計算時i=2, index=2。不過這些用於硬件的向量在IO系統初始化時調用HalpGetSystemInterruptVector分配好了,然後通過IoConnectInterrupt把IDT中註冊的向量位置的例程註冊爲中斷處理程序。這裏並不是每個註冊的向量都會對應中斷處理程序,象上面給出的例子中,0xa1、0xa2、0xb1等向量就沒有對應。

    IRQL機制爲內核同步提供了很大的便利,既對驅動開發者隱藏了底層中斷機制,也方便了驅動開發者的內核同步。LINUX從2.5內核開始引進的軟中斷和任務隊列等機制,很大程度上也來自windows這套機制的借鑑。

    終於考完試,解放了,呵呵。這個東西其實還有很多可寫的,只是沒空再深入去分析了。在未來的64位系統裏,APIC這種基於中斷引腳的機制很快也要被SAPIC這種基於消息的更強大的機制所取代。
發佈了31 篇原創文章 · 獲贊 0 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章