管理设备队列

I/O 管理器通常 (FSD 除外,) 驱动程序调用 IoCreateDevice 时创建关联的设备队列对象。 它还提供 IoStartPacketIoStartNextPacket,驱动程序可以调用它们,让 I/O 管理器将 IRP 插入关联的设备队列或调用其 StartIo 例程。

因此,驱动程序为 IRP 设置其自己的设备队列对象很少 (或特别有用的) 。 可能的候选项是驱动程序,例如 SCSI 端口驱动程序,它们必须协调来自一些紧密耦合类驱动程序的传入 IRP,这些驱动程序适用于通过单个控制器或总线适配器提供服务的异类设备。

换句话说,磁盘阵列控制器的驱动程序更有可能使用驱动程序创建的控制器对象,而不是 () 设置补充设备队列对象,而加载项总线适配器和一组类驱动程序的驱动程序使用补充设备队列的可能性略高。

将补充设备队列与 StartIo 例程配合使用

通过调用 IoStartPacketIoStartNextPacket,驱动程序的 Dispatch 和 DpcForIsr (或 CustomDpc) 例程使用 I/O 管理器在创建设备对象时创建的设备队列同步对其 StartIo 例程的调用。 对于具有 StartIo 例程的端口驱动程序, IoStartPacketIoStartNextPacket 在端口驱动程序的共享设备控制器/适配器的设备队列中插入和删除 IRP。 如果端口驱动程序还设置补充设备队列来保存来自紧密耦合的较高级别类驱动程序的请求,则必须将传入的 IRP“排序”到其补充设备队列中,通常是在其 StartIo 例程中。

在尝试将该 IRP 插入相应的队列之前,端口驱动程序必须确定每个 IRP 属于哪个补充设备队列。 指向目标设备对象的指针随 IRP 一起传递到驱动程序的 Dispatch 例程。 驱动程序应保存指针以用于“排序”传入的 IRP。 请注意,传递给 StartIo 例程的设备对象指针是驱动程序自己的设备对象,它表示设备控制器/适配器,因此不能用于此目的。

在排队任何 IRP 后,驱动程序对其共享控制器/适配器进行编程以执行请求。 因此,端口驱动程序可以先到先得的原则处理所有设备的传入请求,直到调用 KeInsertDeviceQueue 将 IRP 放入特定类驱动程序的设备队列。

通过对其 StartIo 例程处理的所有 IRP 使用自己的设备队列,基础端口驱动程序通过共享设备 (或总线) 控制器/适配器将操作序列化到所有附加设备。 有时,通过在单独的设备队列中保留每个受支持设备的 IRP,此端口驱动程序会禁止处理已繁忙设备的 IRP,同时增加通过共享硬件执行 I/O 的其他设备的 I/O 吞吐量。

为了响应从端口驱动程序的 Dispatch 例程调用 IoStartPacket ,I/O 管理器会立即调用该驱动程序的 StartIo 例程,或者将 IRP 放入与端口驱动程序共享控制器/适配器的设备对象关联的设备队列中。

端口驱动程序必须维护它通过共享设备控制器/适配器服务的每个异类设备自身的状态信息。

使用补充设备队列设计类/端口驱动程序时,请记住以下事项:

  • 驱动程序无法轻松获取指向由位于自身之上的任何驱动程序创建的设备对象的指针,但设备堆栈顶部的设备对象除外。

    根据设计,I/O 管理器不提供用于获取此类指针的支持例程。 此外,驱动程序的加载顺序使得较低级别的驱动程序无法获取较高级别驱动程序的设备对象的指针,这些对象尚未在任何较低级别的驱动程序添加其设备时创建。

    尽管 IoGetAttachedDeviceReference 返回指向驱动程序堆栈中最高级别设备对象的指针,但驱动程序应仅使用此指针来指定对其堆栈的 I/O 请求的目标。 驱动程序不应尝试读取或写入设备对象。

  • 驱动程序不能使用指向由其上层层的任何驱动程序创建的设备对象的指针,除非将请求发送到其自己的设备堆栈的顶部。

    无法以多处理器安全的方式在两个驱动程序之间同步对单个设备对象的访问 (及其设备扩展) 。 两个驱动程序都不能对另一个驱动程序当前正在执行的 I/O 处理做出任何假设。

即使对于紧密耦合的类/端口驱动程序,每个类驱动程序也应使用指向端口驱动程序的设备对象的指针, () 仅使用 IoCallDriver 传递 IRP。 基础端口驱动程序必须维护其自己的状态(可能是在端口驱动程序的设备扩展中),它针对任何紧密耦合的类驱动程序 () 的设备 () 处理的请求。

跨驱动程序例程管理补充设备队列

对于一组紧密耦合的类驱动程序,任何在补充设备队列中排队 IRP 的端口驱动程序也必须有效地处理以下情况:

  1. 其 Dispatch 例程已将特定设备的 IRP 插入到该设备的驱动程序创建的设备队列中。

  2. 其他设备的 IRP 将继续进入,使用 IoStartPacket 排队到驱动程序的 StartIo 例程,并通过共享设备控制器进行处理。

  3. 设备控制器不会变为空闲状态,但驱动程序创建的设备队列中保留的每个 IRP 也必须尽快排队到驱动程序的 StartIo 例程。

因此,每当端口驱动程序完成 IRP 时,端口驱动程序的 DpcForIsr 例程必须尝试将 IRP 从特定设备的驱动程序内部设备队列传输到共享适配器/控制器的设备队列中,如下所示:

  1. DpcForIsr 例程调用 IoStartNextPacket,让 StartIo 例程开始处理排队到共享设备控制器的下一个 IRP。

  2. DpcForIsr 例程调用 KeRemoveDeviceQueue 来取消下一个 IRP (如果有任何) 其内部设备队列中持有代表其即将完成 IRP 的设备。

  3. 如果 KeRemoveDeviceQueue 返回非 NULL 指针, 则 DpcForIsr 例程使用刚刚取消排队的 IRP 调用 IoStartPacket ,使其排队到共享设备控制器/适配器。 否则,调用 KeRemoveDeviceQueue 只会将设备队列对象的状态重置为 Not-Busy,而 DpcForIsr 例程会省略对 IoStartPacket 的调用。

  4. 然后, DpcForIsr 例程使用端口驱动程序刚刚完成 I/O 处理的输入 IRP 调用 IoCompleteRequest ,方法是设置 I/O 状态块并出错,或者满足 I/O 请求。

请注意,前面的序列意味着 DpcForIsr 例程还必须确定它要为其完成当前 (输入) IRP 的设备,以便有效地管理 IRP 的内部队列。

如果端口驱动程序尝试等待其共享控制器/适配器处于空闲状态,然后才取消其补充设备队列中保留的 IRP 的排队,则驱动程序可能会使 I/O 需求量大的设备耗尽,同时它立即为当前 I/O 需求实际上较轻的其他设备提供服务。