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 (驱动开发中关于IRQL级别说明)相关推荐

  1. interrupt request level - IRQL

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

  2. 测试驱动开发与行为驱动开发中的测试先行方法

    Gil Zilberfeld将在 Agile Practitioners会议上举办小型研讨会,讨论测试先行(test first)方法,测试驱动开发(TDD)和行为驱动开发(BDD)的基础. \\ \ ...

  3. 驱动开发中常用的操作和小知识

    1.使用cat /proc/devices,查看内核中已经注册过的字符设备驱动和块设备驱动: 2.注册字符设备驱动完成后,添加设备类的操作,以让内核帮我们发信息!见高级篇3的代码 3.驱动开发 (1) ...

  4. Linux驱动开发中与设备树相关的6种debug方法

    整理出了6种驱动开发时与设备注册.设备树相关的调试方法,彼此间没有优先级之分,每种方法不一定是最优解,但可以作为一种debug查找问题的手段,快速定位问题原因.例如在芯片验证时,不同时钟频率下系统启动 ...

  5. 驱动开发中使用安全字符串函数

    一.前言 大量的系统安全问题是由于薄弱的缓冲处理以及由此产生的缓冲区溢出造成的,而薄弱的缓冲区处理常常与字符串操作相关.c/c++语言运行库提供的标准字符串操作函数(strcpy, strcat, s ...

  6. 驱动开发中platform设备驱动架构详解

    1.什么是platform总线 从Linux2.6开始Linux加入了一套驱动管理和注册机制-platform总线驱动模型.platform总线是一条虚拟总线(只有一条),这类总线没有对应的硬件结构. ...

  7. 驱动开发中的常用操作

    这篇文章会持续更新,由于在驱动中,有许多常用的操作代码几乎不变,而我自己有时候长时间不用经常忘记,所以希望在这把一些常用的操作记录下来,当自己遗忘的时候,有个参考 创建设备对象 创建设备对象使用函数I ...

  8. Linux驱动开发中的中间件:设备树

    Linux设备树 设备树的产生是为了解决内核源码的arch/arm目录下代码混乱和臃肿的问题(过去每个厂商出个板子就要提供外设硬件和平台硬件信息,这些信息以.c和.h文件的形式呈现).在使用设备树之后 ...

  9. 【引用】linux驱动开发中open 方法

    open 方法提供给驱动来做任何的初始化来准备后续的操作. 在大部分驱动中, open 应当进行下面的 工作: ●     检查设备特定的错误(例如设备没准备好, 或者类似的硬件错误 ●     如果 ...

最新文章

  1. IT市场10大技术伟人 Linux之父居首(转)
  2. datanode 不能连接master
  3. php mysql 分类_php+mysql实现无限分类实例详解
  4. Majority Element
  5. java小程序死机_求解,刚写的小程序,一运行我机器就死机
  6. 【Unity3D】Tags和Layers
  7. Take Your Seat Gym - 102222D
  8. 到底什么是 OAuth 2.0
  9. php 隐藏地址栏,工具栏,php – 我怎么能隐藏#!在浏览器地址栏上?
  10. WPF自定义控件 —— 布局
  11. MyBatis(3):SQL映射
  12. 关于util.Date,sql.Date,sql.Time,sql.Timestamp以及他们和Clendar类的区别和联系
  13. 使用html2canvas生成海报,阿里云oss图片或网络图片报跨域问题
  14. Boost.Asio 网络编程([译]Boost.Asio基本原理)
  15. 微信小程序开发:向数组中插入数据
  16. java毕业设计民航售票管理系统源码+lw文档+mybatis+系统+mysql数据库+调试
  17. 阮一峰老师-Auth 2.0 的一个简单解释
  18. bucket name does not follow Amazon S3 standards
  19. MySQL(加强)06 -- 触发器(Trigger)
  20. iOS - 二维码生成、扫描及页面跳转

热门文章

  1. 记一次服务器被挖矿木马攻击的经历
  2. win7文件传输服务器,大文件传输,教您大文件如何快速传输
  3. 最大数和最小数(排序)
  4. XP系统还不过时 教你完美征服3TB硬盘
  5. 信号与线性系统经典分析方法思维导图
  6. TensorFlow-gpuCould not load dynamic library ‘cudart64_102.dll‘; dlerror: cudart64_102.dll not found
  7. P2P网贷系统-核心功能-用户投标-业务讲解
  8. safari文件下载后缀加.exe
  9. Windows命令之netstat命令
  10. 如何白嫖你需要的知识?