最低级驱动程序中的 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 例程与其他驱动程序例程共享对状态信息或 硬件资源的 访问,则状态信息或资源必须受到旋转锁的保护。 (请参阅 Spin Locks.)

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

  • 为 IRP 分配序列号,以防驱动程序在处理 IRP 时必须记录设备 I/O 错误。

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

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

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

  • 如果驱动程序控制可移动媒体设备,请在对设备进行 I/O 编程之前检查媒体更改,并在媒体发生更改时通知其过度文件系统。

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

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

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

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

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

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

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

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

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

一般情况下,使用缓冲 I/O 的驱动程序具有比使用直接 I/O 的 启动 Io 例程更简单。 使用缓冲 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 的物理设备驱动程序在 Irp-AssociatedIrp.SystemBuffer> 的每个 IRP 中找到的系统空间缓冲区或从 I/O 管理器分配的系统空间缓冲区传输数据。

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

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

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

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

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

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

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

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

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

因此,物理设备驱动程序向或从系统空间缓冲区传输数据,每个驱动程序在 IRP-AssociatedIrp.SystemBuffer 中的 Irp-AssociatedIrp.SystemBuffer> 中为设备控制请求找到该缓冲区。 即使是将设备对象设置为直接 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 传输编程。