一,IRQL的定義
Interrupt ReQuest Level
DDK對IRQL的定義是:中斷請求級(IRQL)標示中斷的優先順序。處理器在一個IRQL上執行執行緒程式碼,IRQL是幫助決定執行緒如何被中斷的。每個處理器都有自己的中斷IRQL。 在同一處理器上,執行緒只能被更高級別IRQL的執行緒能中斷。也就是說,較高IRQL的中斷能夠中斷其它的任務或具有較低IRQL的中斷,所有擁有與當前IRQL相同或更低的IRQL的中斷將被遮蔽,直到它在較高IRQL中斷服務程式結束後,得到再次執行的機會。
二,常見的IRQL
Windows大部分時間都執行在軟體中斷級別中,即0-2級別。當有裝置中斷來臨時,windows會將IRQL提升至硬體中斷級別(DIRQL, DEVICE INTERRUPT REQUEST LEVEL),並且執行相應的硬體中斷處理函式。當硬體中斷結束後,恢復到原來的IRQL。
我們經常遇見的有四種IRQL級別。“Passive”, “APC”, “Dispatch” and “DIRQL”。“DriverEntry”將會在PASSIVE_LEVEL被呼叫。
#define PASSIVE_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
PASSIVE_LEVEL
IRQL最低級別,沒有被遮蔽的中斷,在這個級別上,執行緒執行使用者模式,可以訪問分頁記憶體。
使用者模式的程式碼是執行在最低級別的PASSIVE_LEVEL中,驅動程式的DriverEntry函式,派遣函式,AddDevice函式一般執行在PASSIVE_LEVEL中(驅動程式的StartIO和DPC函式執行在DISPATCH_LEVEL中),它們在必要的時候可以申請進入DISPATCH_LEVEL級別,使用核心函式KeGetCurrentIrql()可以知道系統的當前IRQL。
APC_LEVEL
在這個級別上,只有APC級別的中斷被遮蔽,可以訪問分頁記憶體。當有APC發生時,處理器提升到APC級別,這樣,就遮蔽掉其它APC,為了和APC執行 一些同步,驅動程式可以手動提升到這個級別。APC級別僅僅比PASSIVE_LEVEL高,這也是在一個執行緒中插入一個APC可以打斷該執行緒(如果該執行緒執行在PASSIVE_LEVEL上)執行的原因。
DISPATCH_LEVEL
DISPATCH_LEVEL是一個重要的區分點,他代表了執行緒排程器正在執行。一個處理器執行在此IRQL上,代表他可能正在做兩件事之一:正在進行執行緒排程;正在處理一個硬體中斷的後半部(不那麼重要的部分),這被稱為DPC(Deferred Procedure Call:延遲呼叫)。
這個級別,DPC(延遲過程) 和更低的中斷被遮蔽,不能訪問分頁記憶體,所有的被訪問的記憶體不能分頁。因為只能處理分頁記憶體,所以在這個級別,能夠訪問的Api大大減少。
Windows負責執行緒排程的元件執行在DISPATCH_LEVEL級別,當前執行緒執行完時間片後,作業系統自動從PASSIVE_LEVEL提升至DISPATCH_LEVEL級別,從而可以使得執行緒排程元件可以排程其他的執行緒。當執行緒切換完成後,作業系統又從DISPATCH_LEVEL級別恢復到PASSIVE_LEVEL級別。
DIRQL (Device IRQL)
通常處於高層次的驅動程式不會使用這個IRQL等級,在這個等級上所有的中斷都會被忽略。這是IRQL的最高等級。通常使用這個來判斷裝置的優先順序。
一般的,更高階的驅動在這個級別上不處理IRQL,但是幾乎所有的中斷被遮蔽,這實際上是IRQL的一個範圍,這是一個決定某個驅動有更高的優先順序的方法。
三,IRQL與執行緒優先順序
執行緒優先順序的概念不同於IRQL,只有應用程式在PASSIVE_LEVEL執行的時候才有意義。程式設計師可以設定執行緒優先順序(可以使用API SetThreadPriority)。優先順序高代表可以有更多機會在CPU上執行。當執行緒排程核心元件運行於DISPATCH_LEVEL的時候,所有應用程式的執行緒都停止,等著被排程。
一個執行中的執行緒,它的優先順序可能有以下兩種型別,每種型別有16個層次:
1,可變的優先順序
可變的優先順序類的數值範圍是0到15,大多數的執行緒都屬於這種型別。屬於可變優先順序的執行緒總是可搶先的,也就是說,他們共同在相同的IRQL層和其它的執行緒一起被系統排程,並構成一個迴圈。
通常情況下,核心是這樣處理一個可變優先順序執行緒的,當執行緒是一個與使用者互動的執行緒時,它的優先順序最高,其它執行緒
的優先順序隨著執行時間片的增長而下降,直到到達最初程式設計者定義的基本優先順序層次。
2,實時優先順序
實時優先順序類別的範圍數值是16到31。這種型別的執行緒通常用於臨界區執行緒,只能由一個擁有較高優先順序的執行緒產生可搶先的執行緒。
要注意的是,無論執行緒的優先順序屬性是多少,都會被一個軟體或硬體中斷所搶先。執行緒的優先順序隻影響系統執行緒排程程式的決策。排程程式運行於DISPATCH_LEVEL級,它將基於執行緒的優先順序來決定一個執行緒何時該執行及這個執行緒將會獲得多少時間片,同時確定其它所有執行緒的狀態。
四,IRQL與記憶體分頁
在使用記憶體分頁時,可能會導致頁故障。因為分頁記憶體隨時可能從實體記憶體交換到磁碟檔案,讀取不在實體記憶體中的分頁記憶體時,會引發一個頁故障,從而執行這個異常的處理函式。異常處理函式會將磁碟檔案的內容重新交換到實體記憶體中。
頁故障允許出現在PASSIVE_LEVEL級別的程式中,但如果在DISPATCH_LEVEL或更高的IRQL的程式中會帶來系統崩潰。
因此,對於等於或高於DISPATCH_LEVEL級別的程式不能使用分頁記憶體。
當程式的中斷請求在DISPATCH_LEVEL以上,包括DISPATCH_LEVEL,程式只能使用非分頁記憶體,否則將導致藍屏宕機
#define PAGED_CODE() PAGED_ASSERT(KeGetCurrentIrql() <= APC_LEVEL);
PAGED_CODE() 是DDk提供的巨集,在check版本中生效。他會檢查這個函式是否低於DISPATCH_LEVEL的終端請求,如果等於或高於這個中斷請求級,將會產生這個斷言
簡單實現程式碼
#include "IRQL.h" //bp IRQL!DriverEntry
void Sub_1(); NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegisterPath)
{
NTSTATUS Status = STATUS_SUCCESS;
PDEVICE_OBJECT DeviceObject = NULL; //KIRQL Irql;
KIRQL NewIrql;
KIRQL OldIrql; int* v1 = NULL;
int* v2 = NULL; DriverObject->DriverUnload = DriverUnload; Sub_1(); NewIrql = APC_LEVEL; KeRaiseIrql(NewIrql, &OldIrql); Sub_1(); v1 = ExAllocatePool(NonPagedPool, sizeof(int));
v2 = ExAllocatePool(PagedPool, sizeof(int));//DispatchLevel不能用分頁記憶體 //APC_LEVEL下的申請記憶體可以是分頁也可以是不分頁, /*
頁故障允許出現在PASSIVE_LEVEL級別的程式中,但如果在DISPATCH_LEVEL或更高的 IRQL的程式中會帶來系統崩潰。
*/ ExFreePool(v1);
ExFreePool(v2); /*
KeGetCurrentIrql
KeRaiseIrql
KeLowerIrql
*/
KeLowerIrql(OldIrql);
Sub_1(); /*
PASSIVE_LEVEL
APC_LEVEL
DISPATCH_LEVEL // Interrupt Request Level definitions
// #define PASSIVE_LEVEL 0 // Passive release level
#define LOW_LEVEL 0 // Lowest interrupt level
#define APC_LEVEL 1 // APC interrupt level
#define DISPATCH_LEVEL 2 // Dispatcher level
#define CMCI_LEVEL 5 // CMCI handler level #define PROFILE_LEVEL 27 // timer used for profiling.
#define CLOCK1_LEVEL 28 // Interval clock 1 level - Not used on x86
#define CLOCK2_LEVEL 28 // Interval clock 2 level
#define IPI_LEVEL 29 // Interprocessor interrupt level
#define POWER_LEVEL 30 // Power failure level
#define HIGH_LEVEL 31 // Highest interrupt level #define CLOCK_LEVEL (CLOCK2_LEVEL) */ return Status;
} void Sub_1()
{
KIRQL Irql;
Irql = KeGetCurrentIrql();//passive_level
switch (Irql)
{
case PASSIVE_LEVEL:
{
DbgPrint("PASSIVE_LEVEL\r\n");
break; }
case APC_LEVEL:
{
DbgPrint("APC_LEVEL\r\n"); break;
}
case DISPATCH_LEVEL:
{
DbgPrint("DISPATCH_LEVEL\r\n"); break;
} } } VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
DbgPrint("DriverUnload()\r\n");
}
>=DISPATCH_LEVEL,程式只能使用非分頁記憶體,這裡用PAGED_CODE產生斷言
#include "IRQL_ApcLevel_Dispatch_Level.h" //bp IRQL_ApcLevel_Dispatch_Level!DriverEntry KIRQL __NewIrql;
KIRQL __OldIrql; void ShowIrql();
void ApcLevel();
void DispatchLevel(); NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegisterPath)
{
NTSTATUS Status = STATUS_SUCCESS;
PDEVICE_OBJECT DeviceObject = NULL; DriverObject->DriverUnload = DriverUnload; ApcLevel(); __NewIrql = DISPATCH_LEVEL; KeRaiseIrql(__NewIrql, &__OldIrql);
DispatchLevel(); /*
PASSIVE_LEVEL
APC_LEVEL
DISPATCH_LEVEL // Interrupt Request Level definitions
// #define PASSIVE_LEVEL 0 // Passive release level
#define LOW_LEVEL 0 // Lowest interrupt level
#define APC_LEVEL 1 // APC interrupt level
#define DISPATCH_LEVEL 2 // Dispatcher level
#define CMCI_LEVEL 5 // CMCI handler level #define PROFILE_LEVEL 27 // timer used for profiling.
#define CLOCK1_LEVEL 28 // Interval clock 1 level - Not used on x86
#define CLOCK2_LEVEL 28 // Interval clock 2 level
#define IPI_LEVEL 29 // Interprocessor interrupt level
#define POWER_LEVEL 30 // Power failure level
#define HIGH_LEVEL 31 // Highest interrupt level #define CLOCK_LEVEL (CLOCK2_LEVEL) */ return Status;
} void ApcLevel()
{
DbgPrint("In Apc\r\n");
int* v1 = NULL;
int* v2 = NULL; ShowIrql(); __NewIrql = APC_LEVEL; KeRaiseIrql(__NewIrql, &__OldIrql); ShowIrql(); v1 = ExAllocatePool(NonPagedPool, sizeof(int));
v2 = ExAllocatePool(PagedPool, sizeof(int));//Dispatch不能用分頁記憶體 //APC_LEVEL下的申請記憶體可以是分頁也可以是不分頁 ExFreePool(v1);
ExFreePool(v2); KeLowerIrql(__OldIrql);
} #pragma PAGEDCODE //使函式載入到分頁記憶體中
//#pragma LOCKEDCODE //使函式載入到未分頁記憶體中
void DispatchLevel()
{
PAGED_CODE();
//#define PAGED_CODE() PAGED_ASSERT(KeGetCurrentIrql() <= APC_LEVEL); //PAGED_CODE() 是DDk提供的巨集,在check版本中生效。
//他會檢查這個函式是否低於DISPATCH_LEVEL的終端請求,
//如果等於或高於這個中斷請求級,將會產生這個斷言。 DbgPrint("In Dispatch\r\n");
int* v1 = NULL;
int* v2 = NULL; ShowIrql(); __NewIrql = DISPATCH_LEVEL; KeRaiseIrql(__NewIrql, &__OldIrql); ShowIrql(); v1 = ExAllocatePool(NonPagedPool, sizeof(int));
v2 = ExAllocatePool(PagedPool, sizeof(int));//Dispatch不能用分頁記憶體 //APC_LEVEL下的申請記憶體可以是分頁也可以是不分頁 ExFreePool(v1);
ExFreePool(v2); KeLowerIrql(__OldIrql);
} void ShowIrql()
{
KIRQL Irql;
Irql = KeGetCurrentIrql();//passive_level
switch (Irql)
{
case PASSIVE_LEVEL:
{
DbgPrint("PASSIVE_LEVEL\r\n");
break; }
case APC_LEVEL:
{
DbgPrint("APC_LEVEL\r\n"); break;
}
case DISPATCH_LEVEL:
{
DbgPrint("DISPATCH_LEVEL\r\n"); break;
} } } VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
DbgPrint("DriverUnload()\r\n");
}