windows驱动开发-I/O请求(三)

之前的两篇文章已经将I/O请求的使用说清楚了,接下来试着探索一下I/O请求的其它方面。

I/O请求原理

如果对IRP结构有印象的话,会发现IRP结构中有一个DeviceObject成员以及FileObject成员,这里已经隐含了IRP是如何传递的。

在DriverEntry中,我们一开始会收到一个PDRIVER_OBJECT的参数,后续我们会在IoCreateDevice中创建设备对象DeviceObject,并使用IoAttachDeviceToDeviceStack建立设备堆栈,这个过程隐含了以下信息:

这个过程中DriverObject其实就对应FileObject,每个驱动文件是一个DLL,这个DLL在系统重仅有一份,也就只有一个DriverObject,但是DeviceObject可以有很多个,这里是类定义和类对象的关系;

设备堆栈从上往下是一定的,一条总线上可能有N个设备,但一个设备上只有一条总线;故我们调用IoCallDriver 传递的时候,只要传递DeviceObject即可,I/O管理器解析DeviceObject就能找到它的下级驱动对象;

值得指出的是,IRP总是从非分页内存池中分配的,故任何驱动都能直接访问它而不引起违规!同时,I.O管理器是依赖于IRP中的信息来管理IRP和驱动的交互的,它派发IRP和完成IRP的时候,并没有太多的依赖它自身的数据结构,故完全可以利用这个特征来实现远程I/O。


创建IRP

我们同样在驱动中创建IRP,相关函数如下:

IoAllocateIrp: 用于分配 IRP 和多个零初始化的 I/O 堆栈位置。Dispatch例程必须为新分配的 IRP 设置下一个较低驱动程序的 I/O 堆栈位置,通常是从原始 IRP 中自己的堆栈位置复制 (可能修改) 信息。 如果更高级别的驱动程序为新分配的 IRP 分配自己的 I/O 堆栈位置,则Dispatch例程可以在其中设置每个请求的上下文信息,供 IoCompletion 例程使用。

IoBuildAsynchronousFsdRequest:根据调用方指定的参数为调用方设置下一个较低驱动程序的 I/O 堆栈位置,高级别的驱动程序可以调用此例程,为 IRP_MJ_READ、 IRP_MJ_WRITE、 IRP_MJ_FLUSH_BUFFERS和IRP_MJ_SHUTDOWN请求分配 IRP 。

为此类 IRP 调用 IoCompletion 例程时,它可以检查 I/O 状态块,并在必要时 (或可能) 再次在 IRP 中设置下一个较低驱动程序的 I/O 堆栈位置,然后重试请求或重复使用它。 但是, IoCompletion 例程在 IRP 中本身没有本地上下文存储,因此驱动程序必须在驻留内存中的其他位置维护有关原始请求的上下文。

IoMakeAssociatedIrp:用于分配 IRP 和多个零初始化的 I/O 堆栈位置,并将 IRP 与 主 IRP 相关联,中间驱动程序无法调用 IoMakeAssociatedIrp 来创建较低驱动程序的 IRP。

调用 IoMakeAssociatedIrp 为较低驱动程序创建 IRP 的任何最高级别驱动程序,在发送其关联的 IRP 并为原始主 IRP 调用 IoMarkIrpPending 后,可以将控制权返回到 I/O 管理器。 当所有关联的 IRP 都由较低驱动程序完成时,最高级别的驱动程序可以依赖 I/O 管理器来完成主 IRP。

驱动程序很少为关联的 IRP 设置 IoCompletion 例程。 如果高级别驱动程序为其创建的关联 IRP 调用 IoSetCompletionRoutine ,则如果驱动程序从其 IoCompletion 例程返回STATUS_MORE_PROCESSING_REQUIRED,则 I/O 管理器不会完成主 IRP。 在这些情况下,驱动程序的 IoCompletion 例程必须使用 IoCompleteRequest 显式完成主 IRP。

如果驱动程序在新 IRP 中分配自己的 I/O 堆栈位置,则调度例程必须先调用 IoSetNextIrpStackLocation ,然后才能调用 IoGetCurrentIrpStackLocation ,以便在 IoCompletion 例程的自己的 I/O 堆栈位置中设置上下文。 

Dispatch例程必须使用原始 IRP 调用 IoMarkIrpPending ,但不能调用任何驱动程序分配的 IRP,因为 IoCompletion 例程将释放它们。

如果Dispatch例程正在为某个传输分配 IRP(文件系统中很常见),并且基础设备驱动程序可能控制可移动媒体设备,则调度例程必须从原始 IRP 中 Tail.Overlay.Thread 的值在其新分配的 IRP 中设置线程上下文。

可移动媒体设备的基础驱动程序可能会为驱动程序分配的 IRP 调用 IoSetHardErrorOrVerifyDevice,它引用 Irp-Tail.Overlay.Thread> 上的指针。 如果驱动程序调用此支持例程,则文件系统驱动程序可以向相应的用户线程发送一个对话框,提示用户取消、重试或失败驱动程序无法满足的操作。 

将驱动程序分配的所有 IRP 发送到较低驱动程序后,调度例程必须返回STATUS_PENDING。

驱动程序的 IoCompletion 例程应在调用原始 IRP 的 IoCompleteRequest 之前,使用 IoFreeIrp 释放所有驱动程序分配的 IRP。 完成原始 IRP 后, IoCompletion 例程必须释放所有驱动程序分配的 IRP,然后才能返回控制权。

每个高级别的驱动程序都会为低驱动程序设置可重用的IRP,这样,无论给定的请求来自中间驱动程序还是来自任何其他源(例如文件系统或用户模式应用程序),对基础设备驱动程序都无关紧要。

最高级别的驱动程序可以调用 IoMakeAssociatedIrp 来分配 IRP 并为较低级别的驱动程序链设置它们。 只要驱动程序不调用 IoSetCompletionRoutine 与原始 IRP 或其分配的任何关联 IRP,I/O 管理器会自动完成原始 IRP。 但是,最高级别的驱动程序不得为请求缓冲 I/O 操作的任何 IRP 分配关联的 IRP。

中间级别驱动程序无法通过调用 IoMakeAssociatedIrp 为低级别驱动程序分配 IRP。 中间驱动程序接收的任何 IRP 可能已经是关联的 IRP,并且驱动程序无法将另一个 IRP 与此类 IRP 相关联。

相反,如果中间驱动程序为较低驱动程序创建 IRP,它应调用 IoAllocateIrp、 IoBuildDeviceIoControlRequest、 IoBuildSynchronousFsdRequest 或 IoBuildAsynchronousFsdRequest。 但是, IoBuildSynchronousFsdRequest 只能在以下情况下调用:

由驱动程序创建的线程为读取或写入请求生成 IRP,因为此类线程可以在调度程序对象(如传递给 IoBuildSynchronousFsdRequest 的驱动程序初始化事件)的线程上下文中等待

  • 在初始化期间或在卸载时在系统线程上下文中
  • 为固有同步操作(例如创建、刷新、关闭、关闭和设备控制请求)生成 IRP

但是,与 IoBuildSynchronousFsdRequest 相比,驱动程序更可能调用 IoBuildDeviceIoControlRequest 来分配设备控制 IRP。

I/O请求的生存期

和我们想象的不一样,每个IRP在我们收到直到调用IRP处理函数IoCancelIrp、IoCallDriver 、IoCompleteRequest之前都是有效的,但是不要这么做,这么做是有可能带来问题的。

按照内核编程的风格,会有几种情况非常特殊,会让我们不得不维护自己的IRP请求队列,但是维护队列并不代表我们一定随时随地访问它们,按照安全性的描述,我们尽可能在Dispatch例程以及IoComplete完成例程、CancelIrp例程这几个明确的例程中访问它们。

第一种情况是一个IRP被分为几个IRP去执行,这种情况下,驱动需要将IRP存入自己的队列中,然后在完成的时候调用IoCompleteRequest完成它,但是这种情况下,可能导致性能下降;

第二种情况是异步I/O,在异步I/O中,往往会设置PEEDING标志以及通知事件,这样上层可以将控制流转向,直到通知事件被触发。

I/O 管理器提供异步 I/O 支持,以便 I/O 请求的发起方通常 (用户模式应用程序,但有时另一个驱动程序) 可以继续执行,而不是等待其 I/O 请求完成。 异步 I/O 支持可提高发出 I/O 请求的任何代码的总体系统吞吐量和性能。

使用异步 I/O 支持时,内核模式驱动程序不一定按照发送到 I/O 管理器的相同顺序处理 I/O 请求。 I/O 管理器或更高级别的驱动程序可以在收到 I/O 请求时重新排序。 驱动程序可以将大型数据传输请求拆分为较小的传输请求。 此外,驱动程序可以重叠 I/O 请求处理。

此外,内核模式驱动程序对单个 I/O 请求的处理不一定是序列化的。 也就是说,驱动程序在开始处理下一个传入 I/O 请求之前,不一定处理每个 IRP 以完成。

当驱动程序收到 IRP 时,它会通过尽可能多地执行特定于 IRP 的处理来做出响应。 如果驱动程序支持异步 IRP 处理,它可以根据需要将 IRP 发送到下一个驱动程序,并开始处理下一个 IRP,而无需等待第一个 IRP 完成。 驱动程序可以注册IoComplete例程,当另一个驱动程序处理完 IRP 时,I/O 管理器会调用该例程。 驱动程序在 IRP 的 I/O 状态块中提供状态值,其他驱动程序可以访问该值来确定 I/O 请求的状态。

驱动程序可以在设备对象的 设备扩展中维护有关其当前 I/O 操作的状态信息。

取消I/O请求

IRP 可能保持无限期排队 (以便用户可以取消以前提交的 I/O 请求的驱动程序) 必须具有一个或多个 Cancel 例程才能完成用户取消的 I/O 请求。 例如,键盘、鼠标、并行、串行和声音设备驱动程序 (或分层) 和文件系统驱动程序应具有 Cancel 例程。

适用于 Microsoft Windows XP 和更高版本的操作系统的驱动程序可以使用保证取消安全的 IRP 队列, 而不是实现自己的 Cancel 例程。

“取消 IRP”意味着在保持系统完整性的同时尽快完成 IRP。 

取消过程在系统或驱动程序调用 IoCancelIrp 时开始。 对于与尚未完全完成的线程关联的每个 IRP,都会调用此例程。 如果启动 I/O 请求的线程退出,系统将取消未处理的 IRP。 驱动程序只能取消已创建的 IRP, (请参阅 为 Lower-Level Drivers 创建 IRP。)

如果取消的 IRP 未在 5 分钟内完成,I/O 管理器将认为 IRP 超时。此类 IRP 与线程取消关联,并且会为当前拥有 IRP 的设备记录错误。 应确保驱动程序中可能需要很长时间才能完成的任何请求都是可取消的。

I/O 管理器调用驱动程序提供的 Cancel 例程,其中包含要取消的输入 IRP 和表示 I/O 请求的目标设备的 DeviceObject 指针。

IRP 可能是驱动程序的 DispatchReadWrite 例程在用户关闭当前 Win32 应用程序时已排队的 IRP。 IRP 也可能是更高级别驱动程序显式取消的 IRP,具体取决于基础设备的性质。

调用 Cancel 例程时,如果驱动程序具有 StartIo 例程,则输入 IRP 可能已经是目标设备对象中的 CurrentIrp,或者可能已在与目标设备对象关联的设备队列中。 如果驱动程序没有 StartIo 例程,则调用其 Cancel 例程时,IRP 可能位于驱动程序管理的 IRP 内部队列中。 在任何情况下,在 I/O 管理器为传入 IRP 调用 Cancel 例程之前,I/O 管理器会将此 IRP 中的 Cancel 成员设置为 TRUE ,并将 IRP 中的 CancelRoutine 成员设置为 NULL。具有关联 IRP 的主 IRP 的 Cancel 例程负责调用 IoCancelIrp 来取消这些关联的 IRP。

所有 Cancel 例程必须遵循以下准则:

  • 调用 IoReleaseCancelSpinLock 以释放系统的取消旋转锁;
  • 将 I/O 状态块的 Status 成员设置为 STATUS_CANCELLED,并将其 信息 成员设置为零。
  • 通过调用 IoCompleteRequest 完成指定的 IRP;
  • 由于 始终调用 Cancel 例程并保留系统取消旋转锁,因此此例程不得调用 IoAcquireCancelSpinLock ,除非它先调用 IoReleaseCancelSpinLock ;
  • 当系统返回控件时,Cancel 例程不能持有系统取消旋转锁。 也就是说,每个 Cancel 例程必须至少调用 IoReleaseCancelSpinLock 一次,然后才能返回控制权;
  • 如果调用 IoAcquireCancelSpinLock, 则 Cancel 例程必须尽快对 IoReleaseCancelSpinLock 进行倒数调用;
  • 切勿在按住旋转锁时使用 IRP 调用 IoCompleteRequest 。 尝试在按住旋转锁时完成 IRP 可能会导致死锁;
远程I/O请求

一般的,每个驱动导出的例程中,至少包含1个DeviceObject,就是它自身,在这种情况下,IRP的下一级驱动非常明确,调用IoAttachDeviceToDeviceStack建立设备栈的时候,明确了下一级的驱动对象和设备对象。

但是问题在于总会有这样的需求,就是向非设备栈下一级的设备对象发送I/O请求,这种情况一般不会是上层发过来的I/O请求,而是自身的创建的I/O请求。

在前面的已经说过,I/O管理器是依赖于IRP本身来挂你IRP的,故远程I/O其实只需要创建IRP的时候,将一对应的结构设置好,就可以实现控制了。

最关键的一步就是,我们需要找到调用IoCallDriver需要的参数,它的参数既可以是自身对应的设备对象,也可以是系统中其它的设备对象,而系统中的设备对象是可以打开的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/5082.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JAVA面试专题-Redis

你在最近的项目中哪些场景使用了Redis 缓存 缓存穿透 缓存穿透:查询一个不存在的数据,mysql查询不到数据也不好直接写入缓存,导致每次请求都查数据库。 解决方案一:缓存空数据,即使查询返回的数据为空,也把…

MySQL 迁移到 Oracle 需要注意的问题

MySQL /Oracle 常见问题 1. VARCHAR/VARCHAR2/NVARCHAR 差异: MySQL 的 VARCHAR 是以字符为单位计算的,Oracle 的 VARCHAR 是 以字节为单位计算的,所以对中文的存储 Oracle 是 MySQL 的 2 倍 (GBK)和 3 倍(UTF8) 2. NULL 差异 A. MySQL…

微信小程序开发核心:样式,组件,布局,矢量图标

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,…

【蓝桥杯2024真题】好数

试题C: 好数 时间限制: 1.0s 内存限制: 256.0MB 本题总分:10分 【问题描述】 一个整数如果按从低位到高位的顺序,奇数位(个位、百位、万位)上 的数字是奇数,偶数位(十位、千位、十万位)上的数…

MAC 本地搭建Dify环境

Dify 介绍 Dify 是一款开源的大语言模型(LLM) 应用开发平台。它融合了后端即服务(Backend as Service)和 LLMOps 的理念,使开发者可以快速搭建生产级的生成式 AI 应用。即使你是非技术人员,也能参与到 AI 应用的定义和数据运营过…

邦注科技模具监视器 模具CCD影像检测 电子眼代替人眼

在制造行业,很多公司在模具方面损失很大,由于不同模具的特殊性不规则形导致尽管采取很多模具保护措施却依然无法减少压模带来的损失。针对这一行业难点讯采科技自主研发的模具监视器利用先进的机器视觉技术代替人眼实时监控模具运行情况,智能…

Qt | QDialogButtonBox(按钮框)、QButtonGroup(按钮组)、QGroupBox(组框)

01、上节回顾 Qt | 标准、复选、单选、工具、命令按钮大全02、QDialogButtonBox 一、QDialogButtonBox 类(按钮框) 1、QDialogButtonBox 直接继承自 QWidget 类。很多程序都需要把按钮组织在一起,以呈现给用户作出一个选择,比如当关闭文件时,会弹出一个询问用户是否保存文…

国内首个图计算平台团体标准发布,创邻科技参与编撰

2024年,由中国通信标准协会批准的团体标准《大数据 图计算平台技术要求与测试方法》(编号:T/CCSA 470—2023)(下称:标准)正式实施。该标准于1月4日在全国团体标准信息平台(https://w…

超越GPT-4,清华发布网页导航智能体AutoWebGLM

随着大语言模型(LLMs)的发展,Agent在网络导航等任务中展现出了前所未有的能力。想象一下,一个基于LLM的Agent能够在你享用早餐时为你总结在线新闻,这样的场景已经不再遥不可及。这种将LLMs融入日常任务的做法&#xff…

AI小白使用Macbook Pro安装llama3与langchain初体验

1. 背景 AI爆火了2年有余,但我仍是一个AI小白,最近零星在学,随手记录点内容供自己复习。 上次在Macbook Pro上安装了Stable Diffusion,体验了本地所心所欲地生成各种心仪的图片,完全没有任何限制的惬意。今天想使用M…

vue3封装一个获取字典值的方法,或者公共数据的hooks

我这个场景是vue3的uniapp,和vuex4,基于ruoyi框架的useDict方法得来的。 如果可以的话,大部分情况下都适用,比如h5... 如果是vue2的话,可以适当修改。 场景就是,如果有公共的字典,男女&#…

Kafka客户端工具:Offset Explorer 使用指南

Kafka作为一个分布式流处理平台,在大数据处理和实时数据流应用中扮演着至关重要的角色。管理Kafka的topics及其offsets对于维护系统稳定性和数据一致性至关重要。Offset Explorer是一个强大的桌面应用程序,它使得管理和监控Kafka集群变得简单直观。本文将…

Ftrans文件外发系统 构建安全可控文件外发流程

文件外发系统是企业数据安全管理中的关键组成部分,它主要用于处理企业内部文件向外部传输的流程,确保数据在合法、安全、可控的前提下进行外发。 文件外发系统的主要作用包括: 1、防止数据泄露:通过严格的审批流程和安全策略&…

【JavaWeb】Day61.SpringBootWeb案例——配置文件

配置文件 参数配置化 在我们之前编写的程序中进行文件上传时,需要调用AliOSSUtils工具类,将文件上传到阿里云OSS对象存储服务当中。而在调用工具类进行文件上传时,需要一些参数: - endpoint //阿里云OSS域名 - accessKey…

JAVA基础---Stream流

Stream流出现背景 背景 在Java8之前,通常用 fori、for each 或者 Iterator 迭代来重排序合并数据,或者通过重新定义 Collections.sorts的 Comparator 方法来实现,这两种方式对 大数量系统来说,效率不理想。 Java8 中添加了一个…

【酱浦菌-模拟仿真】python模拟仿真PN结伏安特性

PN结的伏安特性 PN结的伏安特性描述了PN结在外部电压作用下的电流-电压行为。这种特性通常包括正向偏置和反向偏置两种情况。 正向偏置 当外部电压的正极接到PN结的P型材料,负极接到N型材料时,称为正向偏置。在这种情况下,外加的正向电压会…

如何编写测试用例

总结 测试用例需求来源 文档 用户角度 编写测试用例步骤 分析需求 写测试点 对需求的拆分 辅助完成测试用例的编写 编写测试用例 编写测试用例原则 能看懂 能执行 测试结果状…

kubernetes中Pod调度-Taints污点和污点容忍

一、污点的概念 所谓的污点,是给k8s集群中的节点设置的,通过设置污点,来规划资源创建是所在的节点 污点的类型 解释说明PreferNoshedule 节点设置这个污点类型后; 表示,该节点接收调度,但是会降低调度的概…

如何退出远程桌面连接?

远程桌面连接是一种便捷的远程访问方式,可以让用户在任何地方远程访问并控制另一台计算机。但是,在使用远程桌面连接过程中,有时我们需要及时退出连接,以保护数据安全或释放计算资源。本文将介绍如何退出远程桌面连接。 使用Windo…

IC设计数据传输 如何能保障安全高效?

IC(集成电路)设计数据,对于IC设计企业来说,其重要性不言而喻。所以IC设计数据传输过程中,其安全性和效率,也需要有保障。 首先我们来看看IC设计数据为什么重要,其重要性体现在多个方面&#xff…