中断请求级

2023-10-26

[返回] [上一页] [下一页]

中断请求级


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驱动程序不应该使用该函数。

 
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

中断请求级 的相关文章

随机推荐

  • 窈窕如烟秋水流转——同人立绘征集大赛赵婵雪·金奖

    导语 本期介绍的作品是由来自江西科技师范大学软件动漫学院的裴欣怡设计的赵婵雪形象 荣获了本次大赛赵婵雪组别的金奖 2020年12月22日 由首都版权协会联合全国部分高等院校和链游玩家及部分企业共同举办的 2020同人立绘征集大赛 正式启动
  • 为什么很多程序员 到了30来岁 就面临失业,这是真实存在的?

    前言 最近老是能在某乎上看到这样的热点问题 35 岁很多人会失业 究竟是危言耸听 还是真实存在的 为什么会有这样的情况 现在社会上有一种流行的说法 那就是在35岁左右的年龄段 许多人可能会面临失业的风险 这种说法是否夸大其词 或者确实是真实
  • ES 教程

    ES快速入门 一篇就懂 如何用Elasticsearch实现Word PDF TXT文件的全文内容检索
  • el-table 实现单元格内编辑功能

    el table 实现单元格内编辑功能 功能 双击单元格出现编辑框 编辑框失去焦点后保存内容 原理 通过v if控制编辑框与显示值显示和隐藏 通过el table 组件 的cell dblclick事件 得到row column的数据 并且
  • 使用ETL工具Kettle实现,把一个数据库中的多张表的数据同步到另外一个数据库中

    需求 使用ETL工具Kettle实现 把一个数据库中的多张表的数据 不少于3张表 同步到另外一个数据库中 1 使用Kettle工具连接MySQL数据库 连接第一个数据库db03 出现圈3说明连接成功 依次点击 转换 gt 主对象树 gt D
  • csgo服务器找不到,csgo社区服务器进不去解决方法

    近期有玩家在玩csgo的时候遇到了一些小问题 他们在询问 csgo社区服务器进不去怎么办 今天小编就带来csgo社区服务器进不去解决方法 希望对大家能有所帮助 csgo社区服务器进不去解决方法 好几个人喊进不去服务器 提示什么会话错误什么的
  • 无盘服务器秒卡 锐起0359,锐起无盘系统问题汇集

    锐起无盘系统问题汇集 锐起无盘系统问题汇集 说难也不难 上手快 但是做好难 随着大家做锐起的 时间长了 各种各样的问题都出现了 下面我说最常见的问题 无限滚动 这个很常见 有些人勾选了锐起自带的网卡pnp 导致无限滚动 这类问题最多 还有一
  • JavaWeb-实体类对象嵌套实体类对象的查询

    1 1 实体类代码 Cart类 购物车类 public class Cart 自增的购物车记录id private int cid 用户id private int uid 产品id private int pid 产品数量 private
  • C# 中的委托和事件(详解) ....

    C 中的委托和事件 委托和事件在 NET Framework 中的应用非常广泛 然而 较好地理解委托和事件对很多接触 C 时间不长的人来说并不容易 它们就像是一道槛儿 过了这个槛的人 觉得真是太容易了 而没有过去的人每次见到委托和事件就觉得
  • 编译原理题-带答案

    一 判断题 1 一个 LL l 文法一定是无二义的 Y 2 正规文法产生的语言都可以用上下文无关文法来描述 N 3 一张转换图只包含有限个状态 其中有一个被认为是初态 最多只有一个终态 Y 4 目标代码生成时 应考虑如何充分利用计算机的寄存
  • 协同过滤(Collaborative Filtering):UserCF and Item CF

    具体的学习资料可以参考王喆老师的 深度学习推荐系统 已经梳理好了知识体系 我也将按照这个路线再次梳理一遍 同时做一些拓展和加深理解 一 前言 系统过滤曾是多年前推荐系统领域的应用最广泛的模型 也是基石一样的存在 重要 重要 这里推出两篇论文
  • php书籍

    1 Extending and Embedding PHP 讲述php的引擎zend 告知如何进行php的扩展 php是开源的 所以我们可以对php进行改进 实现自己的php 真好呀
  • 利用Matlab搭建U_net回归网络,以及绘制loss函数曲线图

    U net网络一般用于图像处理比较广泛 但是强大的U net同样也可以用于探索各类回归拟合问题 现在我们就开始用matlab去搭建一个U net拟合回归网络 第一步 数据集处理 在此任务中首先得拥有一套用于回归得数据集 首先将数据集导入工作
  • deployment介绍和使用

    什么是deployment deployment是对pods和ReplicaSet的定义 定义了pods和ReplicaSet的定义和实现方式等 如下为deployment的定义 apiVersion apps v1 kind Deploy
  • C/C++编程:右值引用

    右值引用不过是C 的一种新语法 重要的是基于右值引用引申处理的两种C 编程技巧 移动语义和完美转发 右值引用 C 98 03标准中就有引用 用 表示 但是此种引用方式有一个缺陷 即正常情况下只能操作C 中的左值 无法对右值添加引用 举个例子
  • 狙击涨停板-通达信,同花顺,金字塔,TB等指标公式量化开发安装及使用教程...

    原文链接 http tecdat cn p 7260 选股结果 如果您有任何疑问 请在下面发表评论 大数据部落 中国专业的第三方数据服务提供商 提供定制化的一站式数据挖掘和统计分析咨询服务 统计分析和数据挖掘咨询服务 y0 cn terad
  • Spring 快速入门的一个程序:HelloSpring

    Spring 快速入门的一个程序 HelloSpring 1 新建一个Maven项目 建好之后有以下文件 2 pom xml中注入依赖项 然后刷新Maven 会自动下载依赖包
  • C# 获取本机连接的所有 串口设备名称 与 串口号

    代码 class Program static void Main string args GetComList private static void GetComList try using ManagementObjectSearch
  • vscode添加自定义的用户代码片段

    在vscode中添加代码片段 选择 新建全局代码片段文件 然后输入文件名 随便输入 然后会生成文件 安装文件中的Example就可以添加代码片段 里面各个字段的含义 Print to console 代码片段的名称 不同代码片段需要不同 在
  • 中断请求级

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