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

devtools/2024/9/23 3:30:18/

前面我们已经介绍了I/O请求的数据结构,接下来就是I/O请求的处理了。

IRP的处理

驱动例程收到IRP请求的时候,一般会使用一个巨大的switch语句,来处理每一个IOCTL,虽然前面我们简单的讲述了三种不同的I/O缓冲区方式,但是我们可以再讨论一下这三种的区别:

由于 DeviceIoControl 和 IoBuildDeviceIoControlRequest 接受输入缓冲区和输出缓冲区作为参数,因此所有 IRP_MJ_DEVICE_CONTROL 和 IRP_MJ_INTERNAL_DEVICE_CONTROL 请求都提供输入缓冲区和输出缓冲区。 系统描述这些缓冲区的方式取决于数据传输类型:

METHOD_BUFFERED: IRP 提供指向 Irp->AssociatedIrp.SystemBuffer的指针。 此缓冲区表示在调用 DeviceIoControl 和 IoBuildDeviceIoControlRequest时指定的输入缓冲区和输出缓冲区。 驱动程序将数据从此缓冲区中传输出去,然后传入此缓冲区。

故这种情况下,输出/输入缓冲区可以是两个地址,系统会负责讲输入缓冲区拷贝到Irp->AssociatedIrp.SystemBuffer,我们可以从中获取输入数据;同时在调用IoCompleteRequest之后,系统再将Irp->AssociatedIrp.SystemBuffer拷贝到输出缓冲区;

对于输入数据,缓冲区大小由驱动程序IO_STACK_LOCATION结构中的 Parameters.DeviceIoControl.InputBufferLength 指定。 对于输出数据,缓冲区大小由驱动程序IO_STACK_LOCATION结构中的 Parameters.DeviceIoControl.OutputBufferLength 指定。

系统为单个输入/输出缓冲区分配的空间大小是两个长度值中的较大值。

METHOD_IN_DIRECT或METHOD_OUT_DIRECT:

IRP 提供指向 Irp->AssociatedIrp.SystemBuffer 处缓冲区的指针。 这表示在调用 DeviceIoControl 和 IoBuildDeviceIoControlRequest 时指定的第一个缓冲区。

IRP 还提供指向 Irp->MdlAddress 处的 MDL 的指针。 这表示在调用 DeviceIoControl 和 IoBuildDeviceIoControlRequest 时指定的第二个缓冲区。 此缓冲区可用作输入缓冲区或输出缓冲区,如下所示:

  • 如果处理 IRP 的驱动程序在调用缓冲区时在缓冲区中接收数据,则指定METHOD_IN_DIRECT。 此时MDL 描述输入缓冲区,并指定METHOD_IN_DIRECT可确保执行线程具有对缓冲区的读取访问权限。
  • 如果处理 IRP 的驱动程序将在完成 IRP 之前将数据写入缓冲区,则指定METHOD_OUT_DIRECT。 MDL 描述输出缓冲区,并指定METHOD_OUT_DIRECT可确保执行线程对缓冲区具有写入访问权限。

第一个缓冲区大小由驱动程序IO_STACK_LOCATION结构中的 Parameters.DeviceIoControl.InputBufferLength 指定;第二个缓冲区大小由Parameters.DeviceIoControl.OutputBufferLength 指定。

METHOD_NEITHER:I/O 管理器不提供任何系统缓冲区或 MDL。 IRP 提供指定给 DeviceIoControl 或 IoBuildDeviceIoControlRequest 的输入和输出缓冲区都是用户模式虚拟地址,而无需验证或映射它们。

输入缓冲区的地址由驱动程序的 IO_STACK_LOCATION 结构中的 Parameters.DeviceIoControl.Type3InputBuffer 提供,输出缓冲区的地址由 Irp->UserBuffer 指定。

缓冲区大小由驱动程序IO_STACK_LOCATION结构中的Parameters.DeviceIoControl.InputBufferLength 和 Parameters.DeviceIoControl.OutputBufferLength 提供。

同时还有几个结构成员有关:

IO_STACK_LOCATION结构的Parameters.DeviceIoControl.IoControlCode: IOCTL的值 ;

IRP结构的IoStatus.Status: I/O请求的完成状态;

IRP结构的IoStatus.Information: I/O请求写回缓冲区的字节数;

收到上层传递的IRP后,我们有两种选择: 完成它、传递到下层;

IoComplete例程

在 IRP 的基础上监视低级别驱动程序如何执行特定请求的高级别的驱动程序可以具有一个或多个 IoCompletion 例程,分配 IRP 以将请求发送到低级别的驱动程序的高级别驱动程序必须设置 IoCompletion 例程。

高级别或中间驱动程序的 DispatchRead 或 DispatchWrite 例程最有可能为 IRP 设置 IoCompletion 例程,因为较低级别的驱动程序必须异步处理传输请求;驱动程序堆栈中的最低级别驱动程序无法注册 IoCompletion 例程;驱动程序通常不会为与同步 I/O 操作关联的 IRP 注册 IoCompletion 例程。 

高级别的驱动程序调用 IoSetCompletionRoutine注册 IoCompletion 例程,提供 IoCompletion 例程的地址,并使用 IoCallDriver 传递IRP给较低级驱动程序的。

调用 IoSetCompletionRoutine 时,调度例程指定 I/O 管理器应调用指定的 IoCompletion 例程的情况。 如果较低级别的驱动程序成功完成 IRP (InvokeOnSuccess) 、以错误状态值 (InvokeOnError) 完成 IRP,或者取消 IRP (InvokeOnCancel) ,此时I/O管理器会依次调用 IoCompletion 例程。

IoCompletion 例程的原型如下:

IO_COMPLETION_ROUTINE IoCompletionRoutine;NTSTATUS IoCompletionRoutine([in]           PDEVICE_OBJECT DeviceObject,[in]           PIRP Irp,[in, optional] PVOID Context
)
{...}

驱动程序的 IoCompletion 例程的最常见用途如下:

  • 释放驱动程序使用 IoAllocateIrp 或 IoBuildAsynchronousFsdRequest 分配的 IRP;
  • 使用这些支持例程之一分配 IRP 的任何更高级别的驱动程序都必须为该 IRP 提供 IoCompletion 例程。 IoCompletion 例程必须调用 IoFreeIrp 来释放驱动程序分配的 IRP;
  • 重复使用传入的 IRP 以请求较低的驱动程序完成一些操作,例如部分传输,直到 IoCompletion 例程满足并完成原始请求;
  • 重试低级驱动程序已完成但出现错误的请求;
  • 与中间驱动程序相比,最高级别驱动程序(如文件系统)更有可能具有尝试重试请求的 IoCompletion 例程,但可能位于紧密耦合的端口驱动程序之上的类驱动程序除外。 但是,任何中间驱动程序都使用 IoCompletion 例程重试请求。

对于驱动程序分配的 IRP 和重用的 IRP,调度例程必须使用这样调用 IoSetCompletionRoutine :

void IoSetCompletionRoutine([in]           PIRP                   Irp,[in, optional] PIO_COMPLETION_ROUTINE CompletionRoutine,[in, optional] __drv_aliasesMem PVOID Context,[in]           BOOLEAN                InvokeOnSuccess,[in]           BOOLEAN                InvokeOnError,[in]           BOOLEAN                InvokeOnCancel
);InvokeOnSuccess = TRUE;
InvokeOnError = TRUE;
// 如果设备链中的任何较低驱动程序可能处理可取消的 IRP,则 InvokeOnCancel 设置为 TRUE;

使用 IoAllocateIrp 或 IoBuildAsynchronousFsdRequest为较低级别的驱动程序分配 IRP 的调度例程必须为每个驱动程序分配的 IRP 设置 IoCompletion 例程:

  • Dispatch例程必须设置有关原始 IRP 及其分配的 IRP的状态,以便 IoCompletion 例程使用。 IoCompletion 例程至少需要访问原始 IRP 以及分配了多少个额外 IRP;
  • Dispatch例程应调用 IoSetCompletionRoutine ,并为其分配的 IRP 的所有 InvokeOnXxx 参数设置为 TRUE ;

对一系列操作重用 IRP 或重试 I/O 操作的调度例程必须为将重复使用或重试的每个 IRP 调用 IoSetCompletionRoutine :

  • Dispatch例程必须保存原始 IRP 的状态信息,以供 IoCompletion 例程后续使用;
  • 例如, DispatchReadWrite 例程必须先保存 IoCompletion 例程的输入 IRP 的相关传输参数,然后才能为该 IRP 中下一个较低级别的驱动程序设置部分传输。 如果 DispatchReadWrite 例程修改 IoCompletion 例程需要确定何时满足原始请求的任何参数,则保存参数尤其重要;
  • 如果 IoCompletion 例程可以重试请求,则调度例程必须为其 IoCompletion 例程在完成原始 IRP 并出错之前应尝试的重试次数设置驱动程序确定的上限;
  • 如果要重用 IRP,调度例程应调用 IoSetCompletionRoutine ,并将所有 InvokeOnXxx 参数都设置为 TRUE;
  • 对于异步请求,任何中间驱动程序的调度例程必须为原始 IRP 调用 IoMarkIrpPending 。 然后将 IRP 发送到较低的驱动程序后,它必须返回STATUS_PENDING;
IRP完成

下面是某一个IRP的处理例子:

NTSTATUS FilterDeviceIoControl(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{PIO_STACK_LOCATION          IrpSp;NTSTATUS                    Status = STATUS_SUCCESS;PUCHAR                      InputBuffer;PUCHAR                      OutputBuffer;ULONG                       InputBufferLength, OutputBufferLength;ULONG                       InfoLength = 0;IrpSp = IoGetCurrentIrpStackLocation(Irp);Irp->IoStatus.Information = 0;InputBuffer = OutputBuffer = (PUCHAR)Irp->AssociatedIrp.SystemBuffer;InputBufferLength = IrpSp->Parameters.DeviceIoControl.InputBufferLength;OutputBufferLength = IrpSp->Parameters.DeviceIoControl.OutputBufferLength;switch (IrpSp->Parameters.DeviceIoControl.IoControlCode){case IOCTL_XXXXX: {// process irp;break;}}Irp->IoStatus.Status = Status;Irp->IoStatus.Information = InfoLength;IoCompleteRequest(Irp, IO_NO_INCREMENT);return Status;
}

当驱动程序处理完 IRP 后,通常会调用 IoCompleteRequest ,这会导致 I/O 管理器检查是否有更高级别的驱动程序为 IRP 设置了 IoCompletion 完成例程。 如果有,则依次调用每个 IoCompletion 例程,直到设备对象链中的每个分层驱动程序都完成了 IRP。

当所有驱动程序完成 IRP 后,I/O 管理器会将状态返回到操作的原始请求者。 请注意,设置驱动程序创建的 IRP 的更高级别的驱动程序必须提供 IoCompletion 完成例程才能释放其创建的 IRP。

IRP传递

较高级别的驱动程序可以将 I/O 请求传递到下一个较低级别的驱动程序,如下:
1. 如果驱动程序将输入 IRP 传递到下一个较低级别的驱动程序,则调度例程应调用 IoSkipCurrentIrpStackLocation 或 IoCopyCurrentIrpStackLocationToNext 来设置下一个较低级别的驱动程序的 I/O 堆栈位置。

2. 如果驱动程序调用 IoAllocateIrp 为较低级别的驱动程序分配一个或多个额外的 IRP,则调度例程必须按照 在 Intermediate-Level 驱动程序中处理 IRP 中所述的步骤初始化下一个较低驱动程序的 I/O 堆栈位置。

Dispatch例程可以针对某些请求修改下一个较低驱动程序的 I/O 堆栈位置中的一些参数。

如果调度例程将收到的 IRP 传递给下一个较低级别的驱动程序,则设置 IoCompletion 例程是可选的,但很有用,因为该例程可以执行诸如确定较低驱动程序完成请求的方式、重用 IRP 进行部分传输、更新驱动程序在跟踪 IRP 时保持的任何状态,以及重试返回并出现错误的请求。

如果调度例程已分配了新的 IRP,则需要设置 IoCompletion 例程,因为该例程必须在较低的驱动程序完成该例程后释放每个 IRP。

3. 使用 IoCallDriver 将IRP 调用传递给较低驱动程序。

4. 返回相应的 NTSTATUS 值,例如:

  • STATUS_PENDING:如果输入 IRP 是异步请求 (例如IRP_MJ_READ 或IRP_MJ_WRITE),驱动程序通常会返回 STATUS_PENDING。
  • 调用 IoCallDriver 的结果:如果输入 IRP 是同步请求(例如IRP_MJ_CREATE),驱动程序通常会返回对 IoCallDriver 的调用结果。


http://www.ppmy.cn/devtools/25267.html

相关文章

【算法基础实验】图论-构建加权无向图

构建加权无向图 理论基础 在图论中,加权无向图是一种每条边都分配了一个权重或成本的图形结构。这种类型的图在许多实际应用中都非常有用,如路由算法、网络流量设计、最小生成树和最短路径问题等。 加权无向图的基本特征 顶点和边: 顶点&…

streampetr原版网络nuscenes数据pkl文件中的各字段含义

streampetr原版网络nuscenes数据pkl文件中的各字段含义 每帧数据都包含下列的信息 "token": 该帧数据的标识,具有唯一性 "prev": 该帧数据上一帧数据的token,如果没有就为"" "next": 该帧数据下一帧数据的toke…

【学习AI-相关路程-工具使用-NVIDIA SDK MANAGER==NVIDIA-jetson刷机工具安装使用 】

【学习AI-相关路程-工具使用-NVIDIA SDK manager-NVIDIA-jetson刷机工具安装使用 】 1、前言2、环境配置3、知识点了解(1)jetson 系列硬件了解(2)以下大致罗列jetson系列1. Jetson Nano2. Jetson TX23. Jetson Xavier NX4. Jetson…

[图解]领域驱动设计伪创新-为什么互联网是重灾区-03

0 00:00:01,260 --> 00:00:05,036 所以呢,把并存当成因果 1 00:00:05,036 --> 00:00:06,488 这种套路 2 00:00:06,488 --> 00:00:07,360 我们就 3 00:00:07,770 --> 00:00:09,247 可以类似这样用 4 00:00:09,247 --> 00:00:09,670 你看 5 00:00…

leetcode51.N皇后(困难)-回溯法

思路 都知道n皇后问题是回溯算法解决的经典问题,但是用回溯解决多了组合、切割、子集、排列问题之后,遇到这种二维矩阵还会有点不知所措。 首先来看一下皇后们的约束条件: 不能同行不能同列不能同斜线 确定完约束条件,来看看究…

2024年CMS市场的份额趋势和使用统计

目前市面上有超过一半的网站都是使用CMS来搭建的,据不完全统计,现在大概有900多种CDM可供选择,以下是最常见的CMS的市场份额和使用率信息: 除了WordPress以外,Shopify和Wix也是比较流行的内容管理系统,尤其…

若依前后端部署系统--详细附图

一、后端部署 1、在ruoyi项目的Maven中的生命周期下双击package.bat打包Web工程,生成jar包文件。 提示打包成功 2、多模块版本会生成在ruoyi/ruoyi-admin模块下target文件夹,我们打开目录ruoyi-admin/taget,打开cmd,运行java -jar jar包名称…

MobileNetV4 论文学习

论文地址:https://arxiv.org/abs/2404.10518 代码地址:https://github.com/tensorflow/models/blob/master/official/vision/modeling/backbones/mobilenet.py 解决了什么问题? 边端设备的高效神经网络不仅能带来实时交互的体验&#xff0c…