與此相關,Windows為CPU的運行狀態定義了許多“IRQ層級”,即IRQL。在任一時間中,CPU總是運行於其中的某一個層級,這個層級就表明了什麼事情可以做、什麼事情不可以做。下面是這些層級的定義:
#define PASSIVE_LEVEL 0
#define LOW_LEVEL 0
#define APC_LEVEL 1
#define DISPATCH_LEVEL 2
#define PROFILE_LEVEL 27
#define CLOCK1_LEVEL 28
#define CLOCK2_LEVEL 28
#define IPI_LEVEL 29
#define POWER_LEVEL 30
#define HIGH_LEVEL 31
其基本的意圖是,如果CPU從而一個線程已經處於某個層級,其操作就不能受同級或更低層級的操作所幹擾。
這裡的PASSIVE_LEVEL是層級最低
的,但是卻對應著系統結構中較高的層次。當CPU運行於使用者空間,或者雖然進入了核心但還只是運行於管理層的時候,其運行層級就是
PASSIVE_LEVEL。比其略高的是APC_LEVEL,那是在(核心中)為APC函數(見本書“進程與線程”一章)的執行進行準備時的運行級
別,APC請求相當於對使用者空間程式的(軟體)中斷。注意IRQL在x86系統結構中並沒有硬體的支援(CPU中並沒有這麼一個寄存器)而只是一個變數。
與CPU只能通過特殊的指令或中斷/異常才能進入系統態不同,IRQL是CPU可以自由設定的,每當CPU進入更底層、更核心的層次時就提高IRQL,反
之則降低IRQL。不過,表明IRQL的變數在核心中,運行於使用者空間時是無法改變IRQL的。
再高一級是DISPATCH_LEVEL,這大致相當於CPU運行於Windows核心中的核心層,即“核心”層。線程的切換隻能發生於CPU行將從DISPATCH_LEVEL層級下降的時候。
IRQL層級3及以上用於硬體中斷。顯然,設計者的意圖是採用中斷優先順序,即優先順序較高的中斷源可以中斷優先順序較低的中斷服務。但是x86的系統結構並不支援中斷優先順序,所以這實際上是來自VMS的遺迹,因為VAX和PDP的系統結構都是支援中斷優先順序的。
回到頁面換出的問題上,只要CPU的IRQL級
別不高於APC_LEVEL的層次,其代碼都是允許倒換的,但是從DISPATCH_LEVEL開始就不允許了。顯然,如果在這一點上搞錯了,後果是很嚴
重的。所以在管理層的代碼中幾乎每個函數的開頭都要放上一個宏操作PAGED_CODE(),說明代碼作者的意圖是讓這個函數所佔的頁面可以被倒換出去。
這個宏操作的定義如下:
#ifdef DBG
#define PAGED_CODE() { "
if (KeGetCurrentIrql() > APC_LEVEL) { "
KdPrint( ("NTDDK: Pageable code called at IRQL > APC_LEVEL (%d)"n",
KeGetCurrentIrql() )); "
ASSERT(FALSE); "
} "
}
#else
#define PAGED_CODE()
#endif
在Debug模式下,這個宏操作檢查CPU當前的運行層級,如果發現高於APC_LEVEL就說明這個函數有可能在DISPATCH_LEVEL或更高的層級上受到調用,因而是不應該被倒換出去的,所以就發出警告。至於在正式啟動並執行版本中,則這個宏操作定義為空白。
當然,光是在程式中引用宏操作
PAGED_CODE()不會使一個函數所在的頁面可倒換,真正使其可倒換的是編譯指示“#pragma
alloc_text()”。例如NtQueryObject()中的第一行就是PAGED_CODE(),與此相應,這個函數所在的源檔案中就有這麼一
行:
#pragma alloc_text(PAGE, NtQueryObject)
正是這一行編譯指示讓編譯工具將為此函數產生的可執行代碼放在可被倒換的區間。