IRQL的理解和认识

介绍:

   中断请求(IRQ,Interrupt Request)一般有两种,一种是外部中断,也就是硬件产生的中断(例如:键盘中断、打印机中断、定时器中断):另一种是由软件指令 int n 产生的中断(例如:INT 3 断点中断、INT 1 单步中断)。后来 Windows 将中断进行的扩展,提出一个中断请求级(IQRL)的概念。其中规定了 32个中断请求级别,分别是 0-2 级别为软件中断,3-31 为硬件中断。

 

Windows IRQL的宏定义

#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)

对于驱动编程,我们通常只会接触到 0 到 2 的 IRQL 以及 DIRQL。

PASSIVE_LEVEL 0: 

     IRQL最低级别,没有被屏蔽的中断(无法屏蔽其他中断),在这个级别上,线程执行用户模式,可以访问分页内存。用户模式的代码是运行在最低级别的PASSIVE_LEVEL中,驱动程序的DriverEntry函数,派遣函数、AddDevice函数一般运行在PASSIVE_LEVEL中(驱动程序的StartIO和DPC函数运行在DISPATCH_LEVEL中),它们在必要的时候可以申请进入
DISPATCH_LEVEL级别(为了保证当前例程不会被CPU切换到其他地方),使用内核函数KeGetCurrentIrql()可以知道系统的当前IRQL。

APC_LEVEL 1 :

    在这个级别上,只有处于APC级别的中断被屏蔽,可以访问分页内存。当有APC发生时,处理器提升到APC级别,这样,就屏蔽掉其他的APC,为了和APC执行一些同步,驱动程序可以手动提升到这个级别。APC级别仅仅比PASSIVE_LEVEL高,这也是在一个线程中插入一个APC可以打断该线程(如果该线程运行在PASSIVE_LEVEL上)运行的原因。 

 DISPATCH_LEVEL 2:

     DISPATCH_LEVEL是一个重要的区分点,它代表了线程调度器正在运行。一个处理器运行运行在IRQL上,代表他可能正在做两件事之一:正在进行线程调度;正在处理硬件中断的后半部(不那么重要的部分),这个被称为DPC(Deferred Procedure Call:延迟调用)。这个级别,DPC(延迟过程)和更低的中断被屏蔽,不能访问分页内存,所有的被访问的内存不能分页。因为只能处理非分页内存,所有在这个级别上,能够访问的Api大大减少。

     Windows负责线程调度的组件运行在DISPATCH_LEVEL级别,当前线程运行完时间片时,操
作系统自动从PASSIVE_LEVEL提升到DISPATCH_LEVEL级别,从而可以使得线程调度组件可以
调度其他线程。当线程切换完成后,操作系统又从DISPATCH_LEVEL级别恢复到
PASSIVE_LEVEL级别。

线程优先级和 IRQL

    线程优先级是指线程是否有更多的机会运行在CPU上,线程优先级高的线程有更多的机会被内核调用。ReadFile内部创建IRP_MJ_READ,然后这个IRP被传递到驱动程序的派遣函数中,这时派遣函数运行于ReadFile所在的线程中,或者说ReadFile和派遣函数位于同一个线程的上下文中。
    线程运行在PASSIVE_LEVEL级别,这个时候OS随时可能将当前线程切换到别的线程中去。但是将IRQL提升到DISPATCH_LEVEL级别时,这时则不会出现线程切换。这是一种很常用的处理机制,但是这种方法只能使用于单CPU的系统,对于多Cpu的系统,需要采用别的同步处理机制。 

IRQL  与分页内存

     在使用内存分页时,可能会导致页故障。因为分页内存随时可能从物理内存交换到磁盘文件中。读取不在物理内存中的分页时,会引发一个页故障,从而执行这个异常的处理函数。异常处理函数会重新将磁盘文件的内容交换到物理内存中。页故障允许在PASSIVE_LEVEL级别的程序中,但如果在DISPATCH_LEVEL或者更高级别IRQL的程序中会带来系统崩溃。
     对于等于或者高于DISPATCH_LEVEL级别的程序不能使用分页内存,必须使用非分页内存。驱动程序的StartIO全程、DPC例程、中断服务例程都运行在DISPATCH_LEVEL或者更高的IRQL,因为这些例程不能使用分页内存,否则会导致系统崩溃。 

当 IRQL< DISPATCH_LEVEL v1 内存申请成功

 

当 IRQL 提升到 DISPATCH_LEVEL 申请分页内存,导致系统蓝屏

错误码:IRQL_NOT_LESS_OR_EQUAL

蓝屏原因:缺页中断机制是运行在 DISPATCH_LEVEL 级别下的,和当前代码处于一个级别,当代码访问到一个内存页在换页文件的时候,缺页机制无法打断当前代码的运行,从而无法进行页交换,导致访问到了一个错误的内存地址,进而蓝屏。

 

IRQL  的提升和降低

       有些时候驱动程序中需要提升IRQL级别,在运行一段时间后,再将降回原来的IRQL级别,这样做的目的一般是基于同步处理的需要。(当提升IRQL后,不允许低于当前IRQL的活动相互抢先,从而防止了潜在的冲突,这只适用于单Cpu的系统。当是多Cpu系统时,需要采用其他的同步机制,例如事件,自旋锁等等)首先驱动程序需要知道当前状态IRQL级别,可以通过KeGetCurrentIrql内核函数获取当前的IRQL级别。
       然后驱动程序使用内核函数KeRaiseIrql将IRQL提高。KeRaiseIrql需要两个参数,第一个数是提升后的IRQL级别,第二个参数是保存提升前的IRQL级别。最后,驱动程序某个时刻需要将IRQL恢复到以前的IRQL级别,驱动程序可以调用
KeLowerIrql内核函数。

      下面代码演示了驱动中如何提升和降低IRQL的代码 

 

VOID RaiseIRQL_Test()
{
KIRQL oldirql;
// 确保当前IRQL等于或小于DISPATCH_LEVEL
ASSERT(KeGetCurrentIrql() <= DIPATCH_LEVEL); 
// 提升IRQL到DISPATCH_LEVEL,并将先前的IRQL保存起来
KeRaiseIrql(DISPATCH_LEVEL, &oldirql);
//... 
// 恢复到先前的IRQL
KeLowerIrql(oldirql);
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值