一,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的终端请求,如果等于或高于这个中断请求级,将会产生这个断言
简单实现代码
1 #include "IRQL.h" 2 3 //bp IRQL!DriverEntry 4 void Sub_1(); 5 6 NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegisterPath) 7 { 8 NTSTATUS Status = STATUS_SUCCESS; 9 PDEVICE_OBJECT DeviceObject = NULL; 10 11 //KIRQL Irql; 12 KIRQL NewIrql; 13 KIRQL OldIrql; 14 15 int* v1 = NULL; 16 int* v2 = NULL; 17 18 DriverObject->DriverUnload = DriverUnload; 19 20 21 Sub_1(); 22 23 NewIrql = APC_LEVEL; 24 25 KeRaiseIrql(NewIrql, &OldIrql); 26 27 Sub_1(); 28 29 v1 = ExAllocatePool(NonPagedPool, sizeof(int)); 30 v2 = ExAllocatePool(PagedPool, sizeof(int));//DispatchLevel不能用分页内存 31 32 //APC_LEVEL下的申请内存可以是分页也可以是不分页, 33 34 /* 35 页故障允许出现在PASSIVE_LEVEL级别的程序中,但如果在DISPATCH_LEVEL或更高的 IRQL的程序中会带来系统崩溃。 36 */ 37 38 ExFreePool(v1); 39 ExFreePool(v2); 40 41 /* 42 KeGetCurrentIrql 43 KeRaiseIrql 44 KeLowerIrql 45 */ 46 KeLowerIrql(OldIrql); 47 Sub_1(); 48 49 50 51 52 /* 53 PASSIVE_LEVEL 54 APC_LEVEL 55 DISPATCH_LEVEL 56 57 58 59 // Interrupt Request Level definitions 60 // 61 62 #define PASSIVE_LEVEL 0 // Passive release level 63 #define LOW_LEVEL 0 // Lowest interrupt level 64 #define APC_LEVEL 1 // APC interrupt level 65 #define DISPATCH_LEVEL 2 // Dispatcher level 66 #define CMCI_LEVEL 5 // CMCI handler level 67 68 #define PROFILE_LEVEL 27 // timer used for profiling. 69 #define CLOCK1_LEVEL 28 // Interval clock 1 level - Not used on x86 70 #define CLOCK2_LEVEL 28 // Interval clock 2 level 71 #define IPI_LEVEL 29 // Interprocessor interrupt level 72 #define POWER_LEVEL 30 // Power failure level 73 #define HIGH_LEVEL 31 // Highest interrupt level 74 75 #define CLOCK_LEVEL (CLOCK2_LEVEL) 76 77 78 79 */ 80 81 82 83 return Status; 84 } 85 86 void Sub_1() 87 { 88 KIRQL Irql; 89 Irql = KeGetCurrentIrql();//passive_level 90 switch (Irql) 91 { 92 case PASSIVE_LEVEL: 93 { 94 DbgPrint("PASSIVE_LEVEL "); 95 break; 96 97 } 98 case APC_LEVEL: 99 { 100 DbgPrint("APC_LEVEL "); 101 102 break; 103 } 104 case DISPATCH_LEVEL: 105 { 106 DbgPrint("DISPATCH_LEVEL "); 107 108 break; 109 } 110 111 } 112 113 114 } 115 116 117 VOID DriverUnload(PDRIVER_OBJECT DriverObject) 118 { 119 DbgPrint("DriverUnload() "); 120 }
>=DISPATCH_LEVEL,程序只能使用非分页内存,这里用PAGED_CODE产生断言
1 #include "IRQL_ApcLevel_Dispatch_Level.h" 2 3 //bp IRQL_ApcLevel_Dispatch_Level!DriverEntry 4 5 6 7 KIRQL __NewIrql; 8 KIRQL __OldIrql; 9 10 11 void ShowIrql(); 12 void ApcLevel(); 13 void DispatchLevel(); 14 15 NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegisterPath) 16 { 17 NTSTATUS Status = STATUS_SUCCESS; 18 PDEVICE_OBJECT DeviceObject = NULL; 19 20 21 DriverObject->DriverUnload = DriverUnload; 22 23 24 ApcLevel(); 25 26 27 28 29 __NewIrql = DISPATCH_LEVEL; 30 31 KeRaiseIrql(__NewIrql, &__OldIrql); 32 DispatchLevel(); 33 34 35 /* 36 PASSIVE_LEVEL 37 APC_LEVEL 38 DISPATCH_LEVEL 39 40 41 42 // Interrupt Request Level definitions 43 // 44 45 #define PASSIVE_LEVEL 0 // Passive release level 46 #define LOW_LEVEL 0 // Lowest interrupt level 47 #define APC_LEVEL 1 // APC interrupt level 48 #define DISPATCH_LEVEL 2 // Dispatcher level 49 #define CMCI_LEVEL 5 // CMCI handler level 50 51 #define PROFILE_LEVEL 27 // timer used for profiling. 52 #define CLOCK1_LEVEL 28 // Interval clock 1 level - Not used on x86 53 #define CLOCK2_LEVEL 28 // Interval clock 2 level 54 #define IPI_LEVEL 29 // Interprocessor interrupt level 55 #define POWER_LEVEL 30 // Power failure level 56 #define HIGH_LEVEL 31 // Highest interrupt level 57 58 #define CLOCK_LEVEL (CLOCK2_LEVEL) 59 60 61 62 */ 63 64 65 66 return Status; 67 } 68 69 70 void ApcLevel() 71 { 72 DbgPrint("In Apc "); 73 int* v1 = NULL; 74 int* v2 = NULL; 75 76 ShowIrql(); 77 78 __NewIrql = APC_LEVEL; 79 80 KeRaiseIrql(__NewIrql, &__OldIrql); 81 82 ShowIrql(); 83 84 v1 = ExAllocatePool(NonPagedPool, sizeof(int)); 85 v2 = ExAllocatePool(PagedPool, sizeof(int));//Dispatch不能用分页内存 86 87 //APC_LEVEL下的申请内存可以是分页也可以是不分页 88 89 90 ExFreePool(v1); 91 ExFreePool(v2); 92 93 94 KeLowerIrql(__OldIrql); 95 } 96 97 #pragma PAGEDCODE //使函数加载到分页内存中 98 //#pragma LOCKEDCODE //使函数加载到未分页内存中 99 void DispatchLevel() 100 { 101 PAGED_CODE(); 102 //#define PAGED_CODE() PAGED_ASSERT(KeGetCurrentIrql() <= APC_LEVEL); 103 104 //PAGED_CODE() 是DDk提供的宏,在check版本中生效。 105 //他会检查这个函数是否低于DISPATCH_LEVEL的终端请求, 106 //如果等于或高于这个中断请求级,将会产生这个断言。 107 108 109 110 111 DbgPrint("In Dispatch "); 112 int* v1 = NULL; 113 int* v2 = NULL; 114 115 ShowIrql(); 116 117 __NewIrql = DISPATCH_LEVEL; 118 119 KeRaiseIrql(__NewIrql, &__OldIrql); 120 121 122 123 124 ShowIrql(); 125 126 v1 = ExAllocatePool(NonPagedPool, sizeof(int)); 127 v2 = ExAllocatePool(PagedPool, sizeof(int));//Dispatch不能用分页内存 128 129 //APC_LEVEL下的申请内存可以是分页也可以是不分页 130 131 132 ExFreePool(v1); 133 ExFreePool(v2); 134 135 136 KeLowerIrql(__OldIrql); 137 } 138 139 void ShowIrql() 140 { 141 KIRQL Irql; 142 Irql = KeGetCurrentIrql();//passive_level 143 switch (Irql) 144 { 145 case PASSIVE_LEVEL: 146 { 147 DbgPrint("PASSIVE_LEVEL "); 148 break; 149 150 } 151 case APC_LEVEL: 152 { 153 DbgPrint("APC_LEVEL "); 154 155 break; 156 } 157 case DISPATCH_LEVEL: 158 { 159 DbgPrint("DISPATCH_LEVEL "); 160 161 break; 162 } 163 164 } 165 166 167 } 168 169 170 VOID DriverUnload(PDRIVER_OBJECT DriverObject) 171 { 172 DbgPrint("DriverUnload() "); 173 }