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

前面我们已经介绍了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 的调用结果。

  • 21
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值