Windows NT为每个硬件中断和少数软件事件赋予了一个优先级,即中断请求级(interrupt request level - IRQL)。IRQL为单CPU上的活动提供了同步方法,它基于下面规则:

一旦某CPU执行在高于PASSIVE_LEVEL的IRQL上时,该CPU上的活动仅能被拥有更高IRQL的活动抢先。

图4-1显示了x86平台上的IRQL值范围。(通常,这个IRQL数值要取决于你所面对的平台) 用户模式程序执行在PASSIVE_LEVEL上,可以被任何执行在高于该IRQL上的活动抢先。许多设备驱动程序例程也执行在 PASSIVE_LEVEL上。第二章中讨论的DriverEntryAddDevice 例程就属于这类,大部分IRP派遣例程也属于这类。

某些公共驱动程序例程执行在DISPATCH_LEVEL上,而DISPATCH_LEVEL级要比PASSIVE_LEVEL级高。这些公共例程包括StartIo 例程,DPC(推迟过程调用)例程,和其它一些例程。这些例程的共同特点是,它们都需要访问设备对象和设备扩展中的某些域,它们都不受派遣例程的干扰或互相干扰。当任何一个这样的例程运行时,上面陈述的规则可以保证它们不被任何驱动程序的派遣例程抢先,因为派遣例程本身执行在更低级的IRQL上。另外,它们也不会被同类例程抢先,因为那些例程运行的IRQL与它们自己的相同。只有拥有更高IRQL的活动才能抢先它们。

注意


派遣例程(Dispatch routine)和DISPATCH_LEVEL级名称类似。之所以称做派遣例程是因为I/O管理器向这些函数派遣I/O请求。而存在派遣级 (DISPATCH_LEVEL)这个名称是因为内核线程派遣器运行在这个IRQL上,它决定下一次该执行哪个线程。(现在,线程调度程序通常运行在 SYNCH_LEVEL级上)

图4-1. 中断请求级

在DISPATCH_LEVEL级和PROFILE_LEVEL级之间是各种硬件中断级。通常,每个有中断能力的设备都有一个IRQL,它定义了该设备的中断优先级别。WDM驱动程序只有在收到一个副功能码为IRP_MN_START_DEVICE的IRP_MJ_PNP请求后,才能确定其设备的 IRQL。设备的配置信息作为参数传递给该请求,而设备的IRQL就包含在这个配置信息中。我们通常把设备的中断级称为设备IRQL,或DIRQL。

其它IRQL级的含义有时需要依靠具体的CPU结构。这些IRQL通常仅被Windows NT内核内部使用,因此它们的含义与设备驱动程序的编写不是特别密切相关。例如,我将要在本章后面详细讨论的APC_LEVEL,当系统在该级上为某线程调度APC(异步过程调用)例程时不会被同一CPU上的其它线程所干扰。在HIGH_LEVEL级上系统可以执行一些特殊操作,如系统休眠前的内存快照、处理bug check、处理假中断,等等。

IRQL的变化

为了演示IRQL的重要性,参见图4-2,该图显示了发生在单CPU上的一系列事件。在时间序列的开始处,CPU执行在PASSIVE_LEVEL 级上。在t1时刻,一个中断到达,它的服务例程执行在DIRQL1上,该级是在DISPATCH_LEVEL和PROFILE_LEVEL之间的某个 DIRQL。在t2时刻,另一个中断到达,它的服务例程执行在DIRQL2上,比DIRQL1低一级。我们讨论过抢先规则,所以CPU将继续服务于第一个中断。当第一个中断服务例程在t3时刻完成时,该中断服务程序可能会请求一个DPC。而DPC例程是执行在DISPATCH_LEVEL上。所以当前存在的未执行的最高优先级的活动就是第二个中断的服务例程,所以系统接着执行第二个中断的服务例程。这个例程在t4时刻结束,假设这之后再没有其它中断发生,CPU将降到DISPATCH_LEVEL级上执行第一个中断的DPC例程。当DPC例程在t5时刻完成后,IRQL又落回到原来的 PASSIVE_LEVEL级。

图4-2. 变化中的中断优先级

基本同步规则

遵循下面规则,你可以利用IRQL的同步效果:

所有对共享数据的访问都应该在同一(提升的)IRQL上进行。

换句话说,不论何时何地,如果你的代码访问的数据对象被其它代码共享,那么你应该使你的代码执行在高于PASSIVE_LEVEL的级上。一旦越过 PASSIVE_LEVEL级,操作系统将不允许同IRQL的活动相互抢先,从而防止了潜在的冲突。然而这个规则不足以保护多处理器机器上的数据,在多处理器机器中你还需要另外的防护措施——自旋锁(spin lock)。如果你仅关心单CPU上的操作,那么使用IRQL就可以解决所有同步问题。但事实上,所有WDM驱动程序都必须设计成能够运行在多处理器的系统上。

IRQL与线程优先级

线程优先级是与IRQL非常不同的概念。线程优先级控制着线程调度器的调度动作,决定何时抢先运行线程以及下一次运行什么线程。然而,当IRQL级高于或等于DISPATCH_LEVEL级时线程切换停止,无论当前活动的是什么线程都将保持活动状态直到IRQL降到DISPATCH_LEVEL级之下。而此时的“优先级”仅指IRQL本身,由它控制到底哪个活动该执行,而不是该切换到哪个线程的上下文。

IRQL和分页

执行在提升的IRQL级上的一个后果是,系统将不能处理页故障(系统在APC级处理页故障)。这意味着:

执行在高于或等于DISPATCH_LEVEL级上的代码绝对不能造成页故障。

这也意味着执行在高于或等于DISPATCH_LEVEL级上的代码必须存在于非分页内存中。此外,所有这些代码要访问的数据也必须存在于非分页内存中。最后,随着IRQL的提升,你能使用的内核模式支持例程将会越来越少。

DDK文档中明确指出支持例程的IRQL限定。例如,KeWaitForSingleObject 例程有两个限定:

  • 调用者必须运行在低于或等于DISPATCH_LEVEL级上。
  • 如果调用中指定了非0的超时,那么调用者必须严格地运行在低于DISPATCH_LEVEL的IRQL上。

上面这两行想要说明的是:如果KeWaitForSingleObject真的被阻塞了指定长的时间(你指定的非0超时),那么你必定运行在低于 DISPATCH_LEVEL的IRQL上,因为只有在这样的IRQL上线程阻塞才是允许的。如果你所做的一切就是为了检测事件是否进入信号态,则可以执行在DISPATCH_LEVEL级上。但你不能在ISR或其它运行在高于DISPATCH_LEVEL级上的例程中调用 KeWaitForSingleObject例程。

IRQL的隐含控制

在大部分时间里,系统都是在正确的IRQL上调用驱动程序中的例程。虽然我们还没有详细地讨论过这些例程,但我希望举一个例子来表达这句话的含义。你首先遇到的I/O请求就是I/O管理器调用你的某个派遣例程来处理一个IRP。这个调用发生在PASSIVE_LEVEL级上,因为你需要阻塞调用者线程,还需要调用其它支持例程。当然,你不能在更高的IRQL级上阻塞一个线程,而PASSIVE_LEVEL也是唯一能让你无限制地调用任何支持例程的 IRQL级。

如果你的派遣例程通过调用IoStartPacket 来排队IRP,那么你第一个遇到的请求将发生在I/O管理器调用你的StartIo例程时。这个调用发生在DISPATCH_LEVEL级,因为系统需要在没有其它例程(这些例程能在队列中插入或删除IRP)干扰的情况下访问I/O队列。回想一下前面提到的规则:所有对共享数据的访问都应该在同一(提升的)IRQL级上进行。因为每个能访问IRP队列的例程都执行在DISPATCH_LEVEL级上,所以任何例程在操作队列期间都不可能被打断(仅指在单CPU系统)。

之后,设备可能生成一个中断,而该中断的服务例程(ISR)将在DIRQL级上被调用。设备上的某些寄存器也许不能被安全地共享。但是,如果你仅在 DIRQL上访问那些寄存器,可以保证在单CPU计算机上没人能妨碍你的ISR执行。如果驱动程序的其它代码需要访问这些关键的硬件寄存器,你应该让这些代码仅执行在DIRQL级上。KeSynchronizeExecution 服务函数可以帮助你强制执行这个规则,我将在第七章的“与中断处理连接”段中讨论这个函数。

再往后,你应该安排一个DPC调用。DPC例程执行在DISPATCH_LEVEL级上,它们需要访问你的IRP队列,并取出队列中的下一个请求,然后把这个请求发送给StartIo例程。你可以调用IoStartNextPacket 服务函数从队列中提取下一个请求,但必须在DISPATCH_LEVEL级上调用。该函数在返回前将调用你的StartIo例程。注意,这里的IRQL吻合得相当巧妙:队列访问,调用IoStartNextPacket,和调用StartIo都需要发生在DISPATCH_LEVEL级上,并且系统也是在这个IRQL级上调用DPC 例程的。

尽管明确地控制IRQL也是可能的,但几乎没有理由这样做,因为你需要的IRQL和系统调用你时使用的IRQL总是相应的。所以不必不时地提高 IRQL,例程希望的IRQL和系统使用的IRQL几乎总是正确对应的。

IRQL的明确控制

如果必要,你还可以在当前处理器上临时提升IRQL,然后再降回到原来的IRQL,使用KeRaiseIrqlKeLowerIrql 函数。下面代码运行在PASSIVE_LEVEL级上:

KIRQL oldirql;        <--1
ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);    <--2
KeRaiseIrql(DISPATCH_LEVEL, &oldirql);     <--3
...
KeLowerIrql(oldirql);       <--4
  1. KIRQL定义了用于保存IRQL值的数据类型。我们需要一个变量来保存当前IRQL。
  2. 这个ASSERT断定了调用KeRaiseIrql的必要条件:新IRQL必须大于或等于当前IRQL。如果这个关系不成立,KeRaiseIrql将导致bug check。(即用死亡蓝屏报告一个致命错误)
  3. KeRaiseIrql把当前的IRQL提升到第一个参数指定的IRQL级上。它同时还把当前的IRQL值保存到第二个参数指定的变量中。在这个例子中,我们把IRQL提升到DISPATCH_LEVEL级,并把原来的IRQL级保存到oldirql 变量中。
  4. 执行完任何需要在提升的IRQL上执行的代码后,我们调用KeLowerIrql把IRQL降低到调用KeRaiseIrql时的级别。

DDK文档中提到,你必须用与你最近的KeRaiseIrql调用所返回的值调用KeLowerIrql。这在大的方面是对的,因为你提升了 IRQL就必须再降低它。然而,由于你调用的代码或者调用你的代码所做的各种假设会使后面的决定变得不正确。所以,文档中的这句话从严格意义上讲是不正确的。应用到KeLowerIrql函数的唯一的规则就是新IRQL必须低于或等于当前IRQL。

当系统调用你的驱动程序例程时,你降低了IRQL(系统调用你的例程时使用的IRQL,或你的例程希望执行的IRQL),这是一个错误,而且是严重错误,尽管你在例程返回前又提升了IRQL。这种打破同步的结果是,某些活动可以抢先你的例程,并能访问你的调用者认为不能被共享的数据对象。

有一个函数专用于把IRQL提升到DISPATCH_LEVEL级:

KIRQL oldirql = KeRaiseIrqlToDpcLevel();
...
KeLowerIrql(oldirql)

注意:该函数仅在NTDDK.H中声明,WDM.H中并没有声明该函数,因此WDM驱动程序不应该使用该函数。

interrupt request level - IRQL相关推荐

  1. interrupt request level - IRQL (驱动开发中关于IRQL级别说明)

    Windows NT为每个硬件中断和少数软件事件赋予了一个优先级,即中断请求级(interrupt request level - IRQL).IRQL为单CPU上的活动提供了同步方法,它基于下面规则 ...

  2. Windbg dump分析 学习总结

    Windbg核心调试之dump分析 http://www.pediy.com/kssd/pediy08/pediy8-428.htm 标 题: Windbg核心调试之dump分析 作 者:Lvg 时 ...

  3. Windbg内核调试之四: Dump文件分析

    Dump 文件分析很大程度上就是分析蓝屏产生的原因.这种系统级的错误算是Windows提示错误中比较严重的一种(更严重的还有启动黑屏等硬件或软件兼容性错误等等).说它是比较严重,是因为毕竟Window ...

  4. 解决 QQ2006 键盘加密造成的系统当机故障

    该死的 QQ2006 键盘加密功能给我带来了很多的不便,为了保证我的系统和虚拟机能正常运行,我不得不一直使用 QQ2006 Beta3 ,并且在每次启动前必须把自动下载的升级程序删除才能继续正常使用 ...

  5. Windows用WinDbg分析蓝屏dump文件查找原因(转)

    WinDbg官方下载: http://msdl.microsoft.com/download/symbols/debuggers/dbg_x86_6.11.1.404.msi http://msdl. ...

  6. 蓝屏总结(一) ——基本分析方法

    目录 一.蓝屏造成原因 二.通常的排查步骤 三.Debug步骤 四.使用Driver Verifier进行排查 五.Debug示例 1.分析示例 1 2.分析示例 2 一.蓝屏造成原因 关于停止错误( ...

  7. 微软域服务器关闭445,服务器在已经屏蔽 445,135,137,138,139;69端口后依然由于bugcheck重启...

    开始windows服务器收到永恒之蓝攻击event显示由于bugcheck导致重启,在服务器屏蔽 445,135,137,138,139;69端口后依然由于bugcheck重启,怀疑是驱动问题,请帮忙 ...

  8. 第十二篇:从生稣出熟稣,从熟稣出醍醐-再读内核驱动设计目标

    "照葫芦画瓢"与"改例子代码为已所用"是软件工程师的一条捷径. 无论是Windows WDK/DDK还是Linux, 都提供了数不胜数的实例, 以供驱动编程学习 ...

  9. ZZ:windbg 常用命令

    ZZ from:http://www.cppblog.com/Walker/archive/2012/06/28/146523.html 不要再假装自己写的程序没bug了,不可能的,debug工具你早 ...

最新文章

  1. django中的缓存 单页面缓存,局部缓存,全站缓存 跨域问题的解决
  2. 分布式系统原理 之5 日志技术
  3. Nginx_查看并发连接数
  4. JavaScript——执行环境、变量对象、作用域链
  5. 前端怎么存token_学长:说说你理解的 Token
  6. 如何高效率获取作物类型分布数据?
  7. python凹多边形分割_Unity 凹多边形三角剖分
  8. 使用pthread后,界面假死现象问题
  9. 拼多多电商外部工具(浏览器插件)
  10. NSDTF-DEM格式高程数据转通用的tiff格式高程数据
  11. python布尔型变量错误的赋值_Python中布尔变量的值为( )
  12. EOS智能合约开发系列(13): 多签合约代码分析(二)
  13. macos13 Ventura虚拟机安装无网络问题
  14. C++ CMake入门和进阶(二):CMake语法
  15. java一键换壁纸_Java 版下载必应每日壁纸并自动设置 Windows 系统桌面(改编自 C# 版)...
  16. 千里马 android framework之MotionEvent.ACTION_CANCEL怎么产生-讨厌的android触摸面试题
  17. 《神经科学:探索脑》学习笔记(第19章 脑的节律)
  18. VMware为Linux生成新的Mac地址(克隆虚拟机时可以用)
  19. 二维水动力求解,特征线法
  20. kubesphpe单机集群删除以及重建

热门文章

  1. 《Modern Python Cookbook》(Python经典实例)笔记 1.10 使用键盘上没有的Unicode字符
  2. poi导出Excel之模板导出
  3. Linux 权限管理_sudo授权 学习总结(三)
  4. ec2 安装mysql_亚马逊云EC2安装Mysql5.6.41
  5. [原创]插卡路由器对接openwrt对接国内节点 手机免流通过USB口共享流量给路由器教程
  6. 模拟登陆CSDN——就是这么简单
  7. 笔记本网卡总断连,如何使得网卡不自动休眠?
  8. 尚学堂python培训的前景
  9. 将MATLAB的quadprog函数转化为C++代码在Visual Studio上可运行
  10. JavaScript 高级程序设计第二章