共用方式為


管理裝置佇列

I/O 管理員通常會(除了 FSD)在驅動程式呼叫 IoCreateDevice 時,建立相關聯的裝置佇列物件。 它也提供IoStartPacket和IoStartNextPacket,這些驅動程式可以呼叫這些驅動程式,讓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 放入特定類別驅動程式的裝置佇列為止。

藉由使用自己的裝置佇列將所有 IRP 透過 其 StartIo 例程進行處理,底層埠驅動程式會透過共用裝置(或總線)控制器/適配卡,將作業串行化至所有連結的裝置。 藉由有時將每個支援的裝置的 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 例程會呼叫 IoCompleteRequest 並使用連接埠驅動程序剛剛完成 I/O 處理的輸入 IRP,或者通過設定 I/O 狀態區塊為錯誤,或者滿足 I/O 要求。

請注意,上述序列表示 DpcForIsr 例程也必須判斷其正在完成目前 (輸入) IRP 的裝置,以便有效率地管理 IRP 的內部佇列。

如果埠驅動程式嘗試等待至其共用控制器/適配器閒置後,才從附加裝置佇列中取出保留的 IRP,驅動程式可能會導致有大量 I/O 需求的裝置被餓死,因為它會立即為現有 I/O 需求較輕的其他裝置提供服務。