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.mfbz.cn/a/568843.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

绝地求生:16款战术手套,你最钟爱哪一款?

大家好,我是闲游盒! 喜迎PUBG七周年生日同时游戏里又迎来了一款新的战术手套,那么就让我们来回顾一下目前出游戏中的16款战术手套吧,看看你最中意的是哪一款? 1、MAZARIN1K 战术手套 2、SPAJKK 战术手套 3、SWAGGER 战…

机器学习(四)之无监督学习

前言: 前面写了监督学习的几种算法,下面就开始无监督啦! 如果文章有错误之处,小伙伴尽情在评论区指出来(嘿嘿),看到就会回复的。 1.聚类(Clustering) 1.1 概述&#xff…

8.4.3 使用3:配置单臂路由实现VLAN间路由

1、实验目的 通过本实验可以掌握: 路由器以太网接口上的子接口配置和调试方法。单臂路由实现 VLAN间路由的配置和调试方法。 2、实验拓扑 实验拓扑如下图所示。 3、实验步骤 (1)配置交换机S1 S1(config)#vlan 2 S1(config-vlan)#exit S…

华为海思校园招聘-芯片-数字 IC 方向 题目分享——第七套

华为海思校园招聘-芯片-数字 IC 方向 题目分享——第七套 (共9套,有答案和解析,答案非官方,未仔细校正,仅供参考) 部分题目分享,完整版获取(WX:didadidadidida313,加我备注&#x…

LayuiMini使用时候初始化模板修改(下载源码)

忘记加了 下载 地址 : layui-mini: layuimini,后台admin前端模板,基于 layui 编写的最简洁、易用的后台框架模板。只需提供一个接口就直接初始化整个框架,无需复杂操作。 LayuiMini使用时候初始化模板官网给的是: layu…

立即刷新导致请求的response没有来得及加载造成的this request has no response data available

1、前端递归调用后端接口 const startProgress () > {timer.value setInterval(() > {if (progress.value < 100) {time.value--;progress.value Math.ceil(100 / wait_time.value);} else {clearInterval(timer.value);progress.value 0;timer.value null;time.…

电磁兼容(EMC):静电放电(ESD)抗扰度试验深度解读(六)

目录 1. 静电测试干扰方式 2. 案例一 3. 案例二 4. 案例三 5. 案例四 6. 总结 静电放电测试的复杂性决定了这项测试对产品的主要影响方式也是多样的。标准里介绍了几种常见的影响方式&#xff1a; 1. 静电测试干扰方式 在静电放电试验中&#xff0c;测试了受试设备对于…

CDN、边缘计算与云计算:构建现代网络的核心技术

在数字化时代&#xff0c;数据的快速传输和处理是保持竞争力的关键。内容分发网络&#xff08;CDN&#xff09;、边缘计算和云计算共同构成了现代互联网基础架构的核心&#xff0c;使内容快速、安全地到达用户手中。本文将探讨这三种技术的功能、相互关系以及未来的发展趋势。 …

大语言模型微调过程中的 RLHF 和 RLAIF 有什么区别?

目前想要深入挖掘大型语言模型&#xff08;LLM&#xff09;的全部潜力需要模型与我们人类的目标和偏好保持一致。从而出现了两种方法&#xff1a;来自人类反馈的人力强化学习&#xff08;RLHF&#xff09;和来自人工智能反馈的人工智能驱动的强化学习&#xff08;RLAIF&#xf…

rosdep一键修复

External Player - 哔哩哔哩嵌入式外链播放器 rosdep失败原因 通常在执行rosdep init操作时就会报错&#xff0c;问题的核心在于rosdep会访问raw.githubusercontent.com这个网址下的资源&#xff0c;例如https://raw.githubusercontent.com/ros/rosdistro/master/rosdep/sour…

免费开源线上社交交友婚恋系统平台 可打包小程序 支持二开 源码交付!

婚姻是人类社会中最重要的关系之一&#xff0c;它对个人和家庭都有着深远的影响。然而&#xff0c;在现代社会的快节奏生活中&#xff0c;找到真爱变得越来越困难。在这个时候&#xff0c;婚恋产品应运而生&#xff0c;为人们提供了寻找真爱的新途径。 1.拓宽人际交流圈子 现代…

【Camera KMD ISP SubSystem笔记】CRM V4L2驱动模型

1. CRM为主设备 /dev/video0&#xff0c;先创建 v4l2_device 设备&#xff0c;再创建 video_device 设备&#xff0c;最后创建 media_device 设备/dev/media0 v4l2_device的mdev指向media_device&#xff0c;v4l2_device的entity链接到media_device的entities上&#xff08…

P1106 删数问题

本题为洛谷&#xff1a; #include<iostream> #include<string> using namespace std; int main(){string n;int k;cin>>n>>k;while(k--){for(int i0;i<n.length();i){if(n[i]>n[i1]){n.erase(i,1); break;} }for(int i0;i<n.length()-1&&…

SpringBoot学习之Kafka发送消费消息入门实例(三十五)

使用Kafka之前需要先启动fKafka,如何下载安装启动kafka请先参考本篇文章的前两篇: 《SpringBoot学习之Kafka下载安装和启动【Windows版本】(三十四)》 《SpringBoot学习之Kafka下载安装和启动【Mac版本】(三十三)》 一、POM依赖 1、加入kafka依赖 2、我的整个POM代码…

Jammy@Jetson Orin - Tensorflow Keras Get Started: 000 setup for tutorial

JammyJetson Orin - Tensorflow & Keras Get Started: 000 setup for tutorial 1. 源由2. 搭建环境2.1 安装IDE环境2.2 安装numpy2.3 安装keras2.4 安装JAX2.5 安装tensorflow2.6 安装PyTorch2.7 安装nbdiff 3. 测试DEMO3.1 numpy版本兼容问题3.2 karas API - model.compil…

Day 20 Linux的WEB服务——apache

WEB服务简介 目前主流的web服务器软件 Linux&#xff1a;apache &#xff0c; nginx Windows-server&#xff1a;IIS 服务器安装nginx或apache后&#xff0c;叫做web服务器&#xff08;又称WWW服务器&#xff09; web服务器软件属于C/S框架模型 web服务器是一种被动程序只…

单片机学习过程

继电器光耦隔离电压转换步进电机直流电机 arduino是最好用的一种&#xff0c;他提供了完整的设备库文件&#xff0c;任何外部设备只要查找相应的库&#xff0c;就可以很方便的使用 &#xff0c; 但是如果不去学习51 或stm32 或 嵌入式玩玩还可以&#xff0c;如果碰到没有实现的…

文字转粤语语音怎么转?文字转语音

文字转粤语语音怎么转&#xff1f;文字转粤语语音的应用&#xff0c;不仅展现了现代科技的魅力&#xff0c;也为我们提供了更加便捷的交流方式。它们将文字转化为粤语发音&#xff0c;让我们能够更直观地感受粤语的韵味和魅力。同时&#xff0c;这些软件还具备高度的可定制性&a…

【LeetCode】---118.杨辉三角

一、题目解析&#xff1a; 二、知识回顾&#xff1a; 1.二维数组&#xff1a; 2. C语言中的二维数组访问方式和vector二维数组的访问&#xff0c; 不同区别&#xff1a; &#xff08;1&#xff09;表面是一样的&#xff0c;但底层不同&#xff01; &#xff08;2&#xff09;静…

FSRCNN:加速超分辨率卷积神经网络,SRCNN的加速版

paper&#xff1a;https://arxiv.org/pdf/1608.00367 code: https://github.com/yjn870/FSRCNN-pytorch/tree/master 目录 1. 动机 2. 方法 3. 代码对比 4. 实验结果 1. 动机 作者此前提出的SRCNN证明了CNN在图像超分领域的有效性。然而&#xff0c;SRCNN计算效率较低&#…
最新文章