共用方式為


取消安全 IRP 隊列

實作自己 IRP 佇列的驅動程式應該使用 取消安全 IRP 佇列 框架。 取消安全的 IRP 佇列會將 IRP 處理分割成兩個部分:

  1. 驅動程式提供一組回呼例程,可在驅動程式的 IRP 佇列上實作標準作業。 提供的操作包括將 IRPs 插入佇列和從佇列中移除,以及鎖定和解除鎖定佇列。 請參閱 實作 Cancel-Safe IRP 佇列

  2. 每當驅動程式需要實際插入或移除佇列中的 IRP 時,就會使用系統提供的 IoCsqXxx 例程。 這些例程會處理驅動程式的所有同步處理和 IRP 取消邏輯。

使用取消安全 IRP 佇列的驅動程式不會實作 Cancel 例程來支援 IRP 取消。

架構確保驅動程式以原子方式從佇列中插入和移除 IRP。 它也可確保 IRP 取消被正確地執行。 不使用架構的驅動程序必須先手動鎖定和解除鎖定佇列,才能執行任何插入和刪除。 他們也必須避免在實作 Cancel 例程時所產生的競賽條件。 (如需可能發生之競賽狀況的描述,請參閱 同步化 IRP 取消。)

Windows XP 和更新版本的 Windows 隨附取消安全 IRP 佇列架構。 必須同時支援 Windows 2000 和 Windows 98/Me 的驅動程式可以連結到 Windows 驅動程式套件 (WDK) 中所包含的 Csq.lib 庫。 Csq.lib 連結庫提供此架構的實作。

IoCsqXxx 例程會在 Windows XP 和更新版本的 Wdm.h 和 Ntddk.h 中宣告。 驅動程式若也需在 Windows 2000 和 Windows 98/Me 上運行,則必須包含 Csq.h 以取得宣告。

您可以看到如何在 WDK 的 \src\general\cancel 目錄中使用取消安全 IRP 佇列的完整示範。 如需這些佇列的詳細資訊,另請參閱 IRP 佇列白皮書 Cancel-Safe 控制流程

實施 Cancel-Safe IRP 佇列

若要實作安全取消的 IRP 佇列,驅動程式必須提供下列例程:

  • 將 IRP 插入佇列的下列任一例程: CsqInsertIrpCsqInsertIrpExCsqInsertIrpExCsqInsertIrp 的擴充版本;佇列是使用其中一個或另一個來實作。

  • 從佇列中移除指定 IRP 的 CsqRemoveIrp 例程。

  • CsqPeekNextIrp 例程,傳回佇列中指定 IRP 之後下一個 IRP 的指標。 這是系統傳遞它從IoCsqRemoveNextIrp接收的 PeekContext 值的位置。 驅動程式可以以任何方式解譯該值。

  • 下列兩個例程都允許系統鎖定和解除鎖定 IRP 佇列: CsqAcquireLockCsqReleaseLock

  • 完成已取消 IRP 的 CsqCompleteCanceledIrp 例程。

驅動程式例程的指標會儲存在描述佇列 的IO_CSQ 結構中。 驅動程式會配置 IO_CSQ 結構的記憶體。 IO_CSQ結構保證維持固定大小,因此驅動程式可以在其裝置延伸模組內安全地內嵌結構。

驅動程式會使用 IoCsqInitializeIoCsqInitializeEx 來初始化結構。 如果佇列實作 CsqInsertIrp,請使用 IoCsqInitialize;如果佇列實作 CsqInsertIrpEx,請使用 IoCsqInitializeEx

驅動程式只需要在每個回呼例程中提供基本功能。 例如,只有 CsqAcquireLockCsqReleaseLock 例程會實作鎖定處理。 系統會視需要自動呼叫這些例程來鎖定和解除鎖定佇列。

只要提供適當的分派例程,您就可以在驅動程式中實作任何類型的 IRP 佇列機制。 例如,驅動程式可以將佇列實作為連結清單,或做為優先順序佇列。

CsqInsertIrpEx 提供比 CsqInsertIrp 更彈性的佇列介面。 驅動程式可以使用其傳回值來指出作業的結果;如果傳回錯誤碼,則插入失敗。 CsqInsertIrp 例程不會傳回值,因此沒有簡單的方法表示插入失敗。 此外, CsqInsertIrpEx 會採用額外的驅動程式定義 InsertContext 參數,可用來指定佇列實作要使用的其他驅動程式特定資訊。

驅動程式可以使用 CsqInsertIrpEx 來實作更複雜的 IRP 處理。 例如,如果沒有擱置的 IRP,CsqInsertIrpEx 例程可以傳回錯誤碼,而驅動程式可以立即處理 IRP。 同樣地,如果無法再將 IRP 排入佇列, CsqInsertIrpEx 可以傳回錯誤碼來指出該事實。

驅動程式不受影響於所有 IRP 取消操作的處理。 系統會為佇列中的 IRP 提供 Cancel 例程。 此例程會呼叫 CsqRemoveIrp 從佇列中移除 IRP,而 CsqCompleteCanceledIrp 則完成 IRP 取消。

下圖說明 IRP 取消的控制流程。

圖表說明 irp 取消的控制流程。

CsqCompleteCanceledIrp 的基本實作如下所示。

VOID CsqCompleteCanceledIrp(PIO_CSQ Csq, PIRP Irp) {
  Irp->IoStatus.Status = STATUS_CANCELLED;
  Irp->IoStatus.Information = 0;

  IoCompleteRequest(Irp, IO_NO_INCREMENT);
}

驅動程式可以使用任何作系統的同步處理基本類型來實作其 CsqAcquireLockCsqReleaseLock 例程。 可用的同步原語包括 自旋鎖互斥物件

以下是設備驅動程式如何使用旋轉鎖實現鎖定的範例。

/* 
  The driver has previously initialized the SpinLock variable with
  KeInitializeSpinLock.
 */

VOID CsqAcquireLock(PIO_CSQ IoCsq, PKIRQL PIrql)
{
    KeAcquireSpinLock(SpinLock, PIrql);
}

VOID CsqReleaseLock(PIO_CSQ IoCsq, KIRQL Irql)
{
    KeReleaseSpinLock(SpinLock, Irql);
}

系統會將 IRQL 變數的指標傳遞至 CsqAcquireLockCsqReleaseLock。 如果驅動程式使用自旋鎖來實作佇列的鎖定,則驅動程式可以在鎖定佇列時使用此變數儲存當前的 IRQL。

驅動程式不需要使用自旋鎖。 例如,驅動程式可以使用 Mutex 來鎖定佇列。 如需驅動程式可用之同步處理技術的描述,請參閱 同步處理技術

使用 Cancel-Safe IRP 佇列

當佇列和取消佇列 IRP 時,驅動程式會使用下列系統例程:

下圖說明 IoCsqRemoveNextIrp 的控制流程。

圖表說明 iocsqremovenextirp 的控制流程。

下圖說明 IoCsqRemoveIrp 的控制流程。

圖表說明 iocsqremoveirp 的控制流程。

這些例程然後會指派給由驅動程式提供的例程。

IoCsqInsertIrpEx 例程可讓您存取 CsqInsertIrpEx 例程的擴充功能。 它會傳回 CsqInsertIrpEx 所傳回的狀態值。 呼叫端可以使用此值來判斷 IRP 是否已成功排入佇列。 IoCsqInsertIrpEx 也允許呼叫端為 CsqInsertIrpEx 的 InsertContext 參數指定值。

請注意,無論佇列有 CsqInsertIrp 例程或 CsqInsertIrpEx 例程,都可以在任何取消安全佇列上呼叫 IoCsqInsertIrpIoCsqInsertIrpEx。 在任一情況下,IoCsqInsertIrp 的行為都相同。 如果 IoCsqInsertIrpEx 已傳遞具有 CsqInsertIrp 例程的佇列,其行為會與 IoCsqInsertIrp 相同。

下圖說明 IoCsqInsertIrp 的控制流程。

圖表說明 iocsqinsertirp 的控制流程。

下圖說明 IoCsqInsertIrpEx 的控制流程。

圖表說明 iocsqinsertirpex 的控制流程。

有數種自然方式可以使用 IoCsqXxx 例程來排入佇列和清除佇列 IRP。 例如,驅動程式可以簡單地將 IRP 排入佇列,按接收它們的順序進行處理。 驅動程式可以將 IRP 排入佇列,如下所示:

    status = IoCsqInsertIrpEx(IoCsq, Irp, NULL, NULL);

如果驅動程式不需要區分特定的 IRP,它就可以依照佇列的順序將其清除佇列,如下所示:

    IoCsqRemoveNextIrp(IoCsq, NULL);

或者,驅動程式可以將特定 IRP 排入佇列並出列。 例程會使用不透明 IO_CSQ_IRP_CONTEXT 結構來識別佇列中的特定 IRP。 驅動程式會將 IRP 排入佇列,如下所示:

    IO_CSQ_IRP_CONTEXT ParticularIrpInQueue;
    IoCsqInsertIrp(IoCsq, Irp, &ParticularIrpInQueue);

然後,驅動程式可以使用 IO_CSQ_IRP_CONTEXT 值來從佇列中移除相同的 IRP。

    IoCsqRemoveIrp(IoCsq, Irp, &ParticularIrpInQueue);

驅動程式可能也需要根據特定準則從佇列中移除 IRP。 例如,驅動程式可能會將優先順序賦予給每個 IRP,讓較高優先順序的 IRP 先出佇列。 驅動程式可能會將 PeekContext 值傳遞至 IoCsqRemoveNextIrp,當系統要求佇列中的下一個 IRP 時,此值會傳回給驅動程式。