Lowest-Level 驱动程序中的 StartIo 例程

I/O 管理器对驱动程序调度例程的调用是满足设备 I/O 请求的第一个阶段。 StartIo 例程是第二个阶段。 具有 StartIo 例程的每个设备驱动程序都可能会从其 DispatchReadDispatchWrite 例程调用 IoStartPacket,并且通常为它在 DispatchDeviceControl 例程中支持的 I/O 控制代码的子集调用。 IoStartPacket 例程将 IRP 添加到设备的系统提供的设备队列,或者,如果队列为空,请立即调用驱动程序的 StartIo 例程来处理 IRP。

可以假定在调用驱动程序的 StartIo 例程时,目标设备不繁忙。 这是因为 I/O 管理器在两种情况下调用 StartIo :驱动程序的调度例程之一刚刚调用 IoStartPacket ,设备队列为空,或者驱动程序的 DpcForIsr 例程正在完成另一个请求,并且刚刚调用 IoStartNextPacket 以取消排队下一个 IRP。

在调用最高级别设备驱动程序中的 StartIo 例程之前,该驱动程序的调度例程应已探测并锁定用户缓冲区(如有必要)以在排队到 其 StartIo 例程的 IRP 中设置有效的映射缓冲区地址。 如果最高级别的设备驱动程序将其设备对象设置为直接 I/O(或既非缓冲也非直接 I/O),则驱动程序无法将延迟锁定用户缓冲区到它的 StartIo 例程中;每个 StartIo 例程都是在任意线程上下文且 IRQL 为 DISPATCH_LEVEL 时调用。

注释

驱动程序的 StartIo 例程要访问的任何缓冲区内存都必须锁定,或从驻留的系统空间内存中分配,并且必须能够在任意线程上下文中访问。

通常,任何较低级别设备驱动程序的 StartIo 例程都负责用输入的 IRP 调用 IoGetCurrentIrpStackLocation,然后进行任何与请求相关的必要处理,以启动设备上的 I/O 操作。 特定于请求的处理可能包括:

  • 设定或更新驱动程序所维护的当前请求的状态信息。 状态信息可能存储在目标设备对象的设备扩展中,或者存储在驱动程序分配的非分页池中的其他位置。

    例如,如果设备驱动程序维护用于当前传输操作的 InterruptExpected 布尔值,则其 StartIo 例程可能会将此变量设置为 TRUE。 如果驱动程序对当前操作具有超时计数器,则其 StartIo 例程可能会设置此值,或者 StartIo 例程可能会将驱动程序的 CustomTimerDpc 例程排队。

    如果 StartIo 例程与其他驱动程序例程共享对状态信息或 硬件资源 的访问权限,则状态信息或资源必须受到旋转锁的保护。 (请参阅 旋转锁

    如果 StartIo 例程与驱动程序 的 InterruptService 例程共享对状态信息或资源的访问权限, StartIo 必须使用 KeSynchronizeExecution 调用访问状态或资源信息的 SynchCritSection 例程。 (请参阅 使用关键部分

  • 在处理 IRP 的过程中,如果驱动程序必须记录设备 I/O 错误,应为 IRP 分配一个序列号。

    有关详细信息,请参阅 日志记录错误

  • 如有必要,将驱动程序的 I/O 堆栈位置中的参数转换为特定于设备的值。

    例如,磁盘驱动程序可能需要计算传输作的物理磁盘地址的起始扇区或字节偏移量,以及传输请求的长度是跨越特定扇区边界还是超过其物理设备的传输容量。

  • 如果驱动程序控制可移动媒体设备,应该在为设备进行 I/O 编程之前检查介质更改,如果介质发生变化,则通知其上层文件系统。

    有关详细信息,请参阅 支持可移动媒体

  • 如果设备使用 DMA,请检查请求的 Length(要传输的字节数,可以在 IRP 的驱动程序的 I/O 堆栈位置中找到)是否应拆分为部分传输操作,如输入/输出技术中所述,假设紧密耦合的更高级别驱动程序不会为设备驱动程序预先拆分大型传输。

    此类设备驱动程序的 StartIo 例程还可以负责调用 KeFlushIoBuffers;如果驱动程序使用基于数据包的 DMA,则使用驱动程序的 AdapterControl 例程调用 AllocateAdapterChannel

    有关更多详细信息,请参阅 适配器对象和 DMA 以及 维护缓存一致性

  • 如果设备使用 PIO,请使用 MmGetSystemAddressForMdlSafe 将在 Irp->MdlAddress 中描述的缓冲区基本虚拟地址映射到系统空间地址。

    对于读取请求,设备驱动程序的 StartIo 例程可以负责在 PIO 操作开始之前调用 KeFlushIoBuffers。 有关详细信息,请参阅 维护缓存一致性

  • 如果非 WDM 驱动程序使用控制器对象,则调用 IoAllocateController 注册其 ControllerControl 例程。

  • 如果驱动程序要处理可取消的 IRP,请检查输入的 IRP 是否已被取消。

  • 如果在处理完成之前可以取消输入的 IRP,则 StartIo 例程必须调用 IoSetCancelRoutine 并传入 IRP 和驱动程序 Cancel 例程的入口点。 StartIo 例程必须获取取消旋转锁才能调用 IoSetCancelRoutine。 或者,驱动程序可以使用 IoSetStartIoAttributesStartIo 例程的 NonCancelable 属性设置为 TRUE。 这可以防止系统尝试取消通过调用 IoStartPacket 传递给 StartIo 的 IRP。

一般情况下,使用缓冲 I/O 的驱动程序的 StartIo 例程比使用直接 I/O 的例程更简单。 使用缓冲 I/O 的驱动程序会为每个传输请求传输少量数据,而使用直接 I/O(无论是 DMA 还是 PIO)的驱动程序会将大量数据传输到系统内存中可以跨越物理页边界的锁定缓冲区或从锁定缓冲区传输大量数据。

分层在物理设备驱动程序之上的更高级别的驱动程序通常设置其设备对象以匹配其各自的设备驱动程序。 但是,最高级别的驱动程序(尤其是文件系统驱动程序)既不能为直接 I/O 设置设备对象,也不能为缓冲 I/O 设置设备对象。

为缓冲 I/O 设置其设备对象的驱动程序可以依赖 I/O 管理器在发送到驱动程序的所有 IRP 中传递有效的缓冲区。 为直接 I/O 设置设备对象的较低级别驱动程序可以依赖链中的最高级别驱动程序,在所有通过任何中间驱动程序发送到基础较低级设备驱动程序的 IRP 中传递有效的缓冲区。

在 StartIo 例程中使用缓冲 I/O

如果驱动程序的 DispatchReadDispatchWriteDispatchDeviceControl 例程确定请求有效并调用 IoStartPacket,则 I/O 管理器调用驱动程序的 StartIo 例程,以在设备队列为空时立即处理 IRP。 如果队列不为空, IoStartPacket 会将 IRP 排队。 最终,从驱动程序的 DpcForIsrCustomDpc 例程调用 IoStartNextPacket 会导致 I/O 管理器取消排队 IRP 并调用驱动程序的 StartIo 例程。

StartIo 例程调用 IoGetCurrentIrpStackLocation,并确定必须执行哪些作才能满足请求。 在对物理设备进行编程以执行 I/O 请求之前,它会以任何方式预处理 IRP。

如果访问物理设备(或设备扩展)必须与 InterruptService 例程同步, StartIo 例程必须调用 SynchCritSection 例程来执行必要的设备编程。 有关详细信息,请参阅 使用关键节

使用缓冲 I/O 的物理设备驱动程序将数据传输到系统空间缓冲区或从中传输,该缓冲区由 I/O 管理器分配,驱动程序在Irp-AssociatedIrp.SystemBuffer>的每个 IRP中找到它。

在 StartIo 例程中使用直接 I/O

如果驱动程序的 DispatchReadDispatchWriteDispatchDeviceControl 例程确定请求有效并调用 IoStartPacket,则 I/O 管理器调用驱动程序的 StartIo 例程,以在设备队列为空时立即处理 IRP。 如果队列不为空, IoStartPacket 会将 IRP 排队。 最终,从驱动程序的 DpcForIsrCustomDpc 例程调用 IoStartNextPacket 会导致 I/O 管理器取消排队 IRP 并调用驱动程序的 StartIo 例程。

StartIo 例程调用 IoGetCurrentIrpStackLocation,并确定必须执行哪些作才能满足请求。 它以任何必要方式预处理 IRP,例如将大型 DMA 传输请求拆分为部分传输范围,以及保存必须拆分的传入传输请求的 长度 的状态。 然后,它会将物理设备设置为执行 I/O 请求。

如果访问物理设备(或设备扩展)必须与驱动程序的 ISR 同步, StartIo 例程必须使用驱动程序提供的 SynchCritSection 例程来执行必要的编程。 有关详细信息,请参阅 使用关键节

使用直接 I/O 的任何驱动程序读取数据或从锁定缓冲区写入数据(由内存描述符列表 (MDL)描述,驱动程序在 Irp-MdlAddress> 的 IRP 中找到的数据。 此类驱动程序通常对设备控制请求使用缓冲 I/O。 有关详细信息,请参阅 在 StartIo 例程中处理 I/O 控制请求

MDL 类型是驱动程序不直接访问的不透明类型。 相反,使用 PIO 的驱动程序通过调用MmGetSystemAddressForMdlSafe并将Irp->MdlAddress作为参数来重新映射用户空间缓冲区。 使用 DMA 的驱动程序还会传递 Irp-MdlAddress> 给例程函数,以便在传输操作期间将缓冲区地址重新映射到其设备的逻辑地址范围。

除非紧密耦合的更高级别驱动程序拆分基础设备驱动程序的大型 DMA 传输请求,否则最低级别设备驱动程序的StartIo例程必须将每个大于其设备在单个传输操作中能够管理的传输请求拆分。 使用系统 DMA 的驱动程序需要拆分过大的传输请求,以使系统 DMA 控制器或其设备能够在单个传输操作中进行处理。

如果设备是从属 DMA 设备,其驱动程序必须通过系统 DMA 控制器与驱动程序分配的适配器对象(表示 DMA 通道)和驱动程序提供的 AdapterControl 例程同步传输。 总线主 DMA 设备的驱动程序还必须使用驱动程序分配的适配器对象来同步其传输,如果使用系统的基于数据包的 DMA 支持,则必须提供 AdapterControl 例程;如果使用系统的分散/聚集支持,则必须提供 AdapterListControl 例程。

根据驱动程序的设计,它可以将物理设备上的传输和设备控制作与控制器对象同步,并提供 ControllerControl 例程。

有关详细信息,请参阅 适配器对象和 DMA控制器对象

处理 StartIo 例程中的 I/O 控制请求

通常,只有一部分设备 I/O 控制请求从驱动程序的 DispatchDeviceControl 或 DispatchInternalDeviceControl 例程传递,以便驱动程序的 StartIo 例程进一步处理。 驱动程序的 StartIo 例程只能处理需要设备状态更改或返回有关当前设备状态的易失性信息的有效设备控制请求。

每个新驱动程序必须支持与相同类型设备的其他所有驱动程序相同的公共 I/O 控制代码集。 系统将 IRP_MJ_DEVICE_CONTROL 请求的公共特定于设备的 I/O 控制代码定义为缓冲请求。

因此,物理设备驱动程序会向/从每个驱动程序在 Irp-AssociatedIrp.SystemBuffer> 的 IRP 中找到的系统空间缓冲区进行数据传输,以便进行设备控制请求。 即使是为直接 I/O 设置其设备对象的驱动程序也使用缓冲 I/O 来满足具有公共 I/O 控制代码的设备控制请求。

每个 I/O 控件代码的定义确定是否缓冲为该请求传输的数据。 对于驱动程序特定的 IRP_MJ_INTERNAL_DEVICE_CONTROL 请求,任何专门定义的 I/O 控制代码在配对驱动程序之间可以通过方法缓冲、方法直接或方法无使用来定义代码。 一般情况下,如果紧密耦合的更高级别的驱动程序必须为该请求分配缓冲区,则不应使用方法定义任何专用定义的 I/O 控制代码。

将设备编程以执行 I/O 操作

通常,最低级别的设备驱动程序中的 StartIo 例程必须使用 KeSynchronizeExecution 调用驱动程序提供的 SynchCritSection 例程,以同步对与驱动程序的 ISR 共享的任何内存或设备寄存器的访问。 驱动程序的 StartIo 例程使用 SynchCritSection 例程在 DIRQL 上对物理设备进行 I/O 编程。 有关详细信息,请参阅 使用关键节

在调用 KeSynchronizeExecution 之前, StartIo 例程必须执行请求所需的任何预处理。 预处理可能包括计算初始部分传输范围,并保存有关其他驱动程序例程的原始请求的任何状态信息。

如果设备驱动程序使用 DMA,则其 StartIo 例程通常使用驱动程序提供的 AdapterControl 例程调用 AllocateAdapterChannel。 在这些情况下, StartIo 例程将物理设备编程的责任推迟到 AdapterControl 例程。 反过来,它可以调用 KeSynchronizeExecution ,让驱动程序提供的 SynchCritSection 例程程序设备进行 DMA 传输。