在最低级驱动程序中处理 IRP

最低级别的物理驱动程序具有较高级别驱动程序不需要的某些标准例程。 最低级别驱动程序的标准例程集也因以下条件而异:

  • 每个驱动程序控制的设备的性质

  • 驱动程序是为直接 I/O 还是缓冲 I/O 设置其设备对象

  • 单个驱动程序的设计

为了说明标准驱动程序例程的角色,下图显示了示例 IRP 在由最低级别的大容量存储设备驱动程序处理时可能采用的路径。 图中的驱动程序具有以下特征:

  • 设备在每个 I/O 操作结束时生成中断,因此此驱动程序具有 ISR 和 DpcForIsr 例程。

  • 驱动程序具有 StartIo 例程,而不是为 IRP 设置内部队列并管理其自己的队列。

  • 驱动程序使用系统 DMA,因此它为直接 I/O 设置其设备对象的 标志 ,并具有 AdapterControl 例程。

说明通过最低级别驱动程序例程的 irp 路径的示意图。

如图所示,I/O 管理器创建一个 IRP,并将其发送到给定主要函数代码的驱动程序调度例程。 假设函数代码 IRP_MJ_READIRP_MJ_WRITE,则调度例程为 DDDispatchReadWrite

调用 IoGetCurrentIrpStackLocation

任何需要 IRP 参数的驱动程序例程都必须调用 IoGetCurrentIrpStackLocation 以获取驱动程序的 I/O 堆栈位置。 此类例程包括调度例程,这些例程处理多个主 I/O 函数代码 (IRP_MJ_*XXX) ,处理支持次要函数的函数 (IRP_MN_XXX) ,或处理设备 I/O 控制请求 (*IRP_MJ_DEVICE_CONTROL 和/或 IRP_MJ_INTERNAL_DEVICE_CONTROL) ,以及处理 IRP 的其他每个驱动程序例程。

此驱动程序的 I/O 堆栈位置是最低的,无限数量的更高级别的驱动程序的 I/O 堆栈位置显示为阴影。 为简单起见,上图未显示从 DispatchReadWriteStartIoAdapterControlDpcForIsr 例程调用 IoGetCurrentIrpStackLocation

调用 IoMarkIrpPending 和 IoStartPacket

示例驱动程序不会在其调度例程中完成 IRP,而是在其 StartIo 例程中处理 IRP。 在执行此操作之前,调度例程会调用 IoMarkIrpPending 来指示 IRP 尚未完成。 然后,它会调用 IoStartPacket 将 IRP 排队,以便驱动程序的 StartIo 例程进一步处理。 调度例程还会返回 NTSTATUS 值STATUS_PENDING。

下图演示了对 IoStartPacket 的调用。

说明调用 iostartpacket 的示意图。

如果驱动程序正忙于处理设备上的另一个 IRP, IoStartPacket 会将 IRP 插入到与设备对象关联的设备队列中。 驱动程序可以选择将 Key 值作为参数提供给 IoStartPacket ,以对设备队列中的 IRP 施加驱动程序确定的顺序。

如果驱动程序不忙且设备队列为空,I/O 管理器会立即调用其 StartIo 例程,传递输入 IRP。

对于大容量存储设备,最低级别的驱动程序在调用 IoStartPacket 时不需要提供 Cancel 例程,原因有两个:

  1. 分层在此类驱动程序上的文件系统通常处理文件 I/O 请求的取消。

  2. 大容量存储设备驱动程序可快速处理 IRP。

通常,分层驱动程序链中的最高级别驱动程序处理 IRP 的取消。

调用 AllocateAdapterChannel 和 MapTransfer

假设通过最低级别驱动程序例程演示 IRP 路径的图中显示的 StartIo 例程确定传输请求可以通过单个 DMA 操作完成,则 StartIo 例程使用驱动程序的 AdapterControl 例程和 IRP 的入口点调用 AllocateAdapterChannel

当系统 DMA 控制器可用时,I/O 管理器会调用驱动程序的 AdapterControl 例程来设置传输操作。 AdapterControl 例程调用 MapTransfer 来设置系统 DMA 控制器。 然后,驱动程序会针对 DMA 操作对其设备进行程序设置并返回 。 (有关使用 DMA 和适配器对象的详细信息,请参阅 输入/输出技术。)

从驱动程序的 ISR 调用 IoRequestDpc

当设备中断以指示其传输操作已完成时,驱动程序的 ISR 将停止设备生成中断并调用 IoRequestDpc,如图所示,该图演示了通过最低级别的驱动程序例程的 IRP 路径。

此调用会将驱动程序的 DpcForIsr 例程排队,以较低的硬件优先级 (IRQL) 完成尽可能多的传输操作。

调用 IoStartNextPacket 和 IoCompleteRequest

当 DpcForIsr 例程完成传输处理后,它会立即调用 IoStartNextPacket,以便驱动程序的 StartIo 例程将与设备队列中的下一个 IRP 一起调用(如果有 IRP 已排队)。 DpcForIsr 例程还会设置刚刚完成的 IRP 的 I/O 状态块,然后为 IRP 调用 IoCompleteRequest

下图演示了此驱动程序对 IoStartNextPacketIoCompleteRequest 的调用。

调用 iostartnextpacket 和 iocompleterequest。

驱动程序应调用 IoStartNextPacketIoStartNextPacketByKey ,以便尽快开始下一个请求的 I/O 操作,最好是在调用 IoCompleteRequest 之前。

如果为设备排队的任何 IRP, IoStartNextPacket 会调用 KeRemoveDeviceQueue 以从队列中删除下一个 IRP。 然后,I/O 管理器调用驱动程序的 StartIo 例程,传递取消排队的 IRP。 如果设备队列中当前没有 IRP, IoStartNextPacket 仅返回给调用方。

在 IRP 中设置 I/O 状态块

在调用 IoCompleteRequest 之前,每个最低级别的驱动程序都必须设置 IRP 的 I/O 状态块。 (在上图中,第二个阴影区域表示状态块。) I/O 状态块向更高级别的驱动程序并最终向 I/O 操作的原始请求者提供信息。 在上图中,在驱动程序之上分层的任何更高级别的驱动程序都可能已设置一个 IoCompletion 例程,用于读取此驱动程序设置的 I/O 状态块。 较高级别的驱动程序通常不会修改设备驱动程序已完成的 IRP 中的 I/O 状态块,除非较高级别的驱动程序正在重试 IRP,在这种情况下,它会重新初始化 I/O 状态块。

在调用 IoCompleteRequest 之前,完成 IRP 而不将其发送到下一个较低驱动程序的每个较高级别的驱动程序也必须在该 IRP 中设置 I/O 状态块。 为了获得良好的整体 I/O 吞吐量,较高级别的驱动程序应在每个 IRP 的自己的 I/O 堆栈位置检查参数,如果参数无效,则应设置 I/O 状态块并完成请求本身。 驱动程序应尽可能避免将无效请求传递给链中较低的驱动程序。

假设上图中的传输操作成功, DpcForIsr 例程(如图所示,演示了通过最低级别驱动程序例程的 IRP 路径)在 “状态” 中设置STATUS_SUCCESS,并在 “信息 ”中为 IRP 的 I/O 状态块设置传输的字节数。

许多标准驱动程序例程还返回 NTSTATUS 类型值。 有关 NTSTATUS 常量(如 STATUS_SUCCESS)的详细信息,请参阅 日志记录错误