Cancel-Safe IRP 佇列
實作自己 IRP 佇列的驅動程式應該使用 取消安全的 IRP 佇列 架構。 取消安全的 IRP 佇列會將 IRP 處理分割成兩個部分:
驅動程式提供一組回呼常式,可在驅動程式的 IRP 佇列上實作標準作業。 提供的作業包括插入和移除佇列中的 IRP,以及鎖定和解除鎖定佇列。 請參閱 實作Cancel-Safe IRP 佇列。
每當驅動程式需要實際插入或移除佇列中的 IRP 時,它會使用系統提供的IoCsqXxx常式。 這些常式會處理驅動程式的所有同步處理和 IRP 取消邏輯。
使用取消安全 IRP 佇列的驅動程式不會實作 Cancel 常式來支援 IRP 取消。
架構可確保驅動程式會以不可部分完成的方式從其佇列中插入和移除 IRP。 它也可確保已正確實作 IRP 取消。 不使用架構的驅動程式必須先手動鎖定和解除鎖定佇列,才能執行任何插入和刪除。 它們也必須避免在實作 Cancel 常式時所產生的競爭條件。 (如需可能發生的競爭條件描述,請參閱 同步處理 IRP Cancellation.)
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 插入佇列的下列其中一個常式: CsqInsertIrp 或 CsqInsertIrpEx。 CsqInsertIrpEx 是 CsqInsertIrp的擴充版本;佇列是使用其中一個或另一個來實作。
從佇列中移除指定 IRP 的 CsqRemoveIrp 常式。
CsqPeekNextIrp常式,會在佇列中指定的 IRP 後面傳回下一個 IRP 的指標。 這是系統從IoCsqRemoveNextIrp接收的 PeekCoNtext值的位置。 驅動程式可以以任何方式解譯該值。
下列兩個常式都允許系統鎖定和解除鎖定 IRP 佇列: CsqAcquireLock 和 CsqReleaseLock。
完成已取消 IRP 的 CsqCompleteCanceledIrp 常式。
驅動程式常式的指標會儲存在描述佇列 的IO_CSQ 結構中。 驅動程式會為 IO_CSQ 結構配置儲存體。 IO_CSQ結構保證維持固定大小,因此驅動程式可以在其裝置擴充功能內安全地內嵌結構。
驅動程式會使用 IoCsqInitialize 或 IoCsqInitializeEx 來初始化結構。 如果佇列實作CsqInsertIrp,請使用IoCsqInitialize;如果佇列實作CsqInsertIrpEx,請使用IoCsqInitializeEx。
驅動程式只需要在每個回呼常式中提供基本功能。 例如,只有 CsqAcquireLock 和 CsqReleaseLock 常式會實作鎖定處理。 系統會視需要自動呼叫這些常式來鎖定和解除鎖定佇列。
只要提供適當的分派常式,您就可以在驅動程式中實作任何類型的 IRP 佇列機制。 例如,驅動程式可以將佇列實作為連結清單或優先順序佇列。
CsqInsertIrpEx 提供比 CsqInsertIrp更彈性的佇列介面。 驅動程式可以使用其傳回值來指出作業的結果;如果傳回錯誤碼,插入就會失敗。 CsqInsertIrp常式不會傳回值,因此沒有簡單的方法來指出插入失敗。 此外, CsqInsertIrpEx 會採用額外的驅動程式定義 InsertCoNtext 參數,可用來指定佇列實作要使用的其他驅動程式特定資訊。
驅動程式可以使用 CsqInsertIrpEx 來實作更複雜的 IRP 處理。 例如,如果沒有擱置的 IRP, CsqInsertIrpEx 常式可以傳回錯誤碼,而且驅動程式可以立即處理 IRP。 同樣地,如果無法再將 IRP 排入佇列, CsqInsertIrpEx 可以傳回錯誤碼來指出該事實。
驅動程式會與所有 IRP 取消處理隔離。 系統會為佇列中的 IRP 提供 Cancel 常式。 此常式會呼叫 CsqRemoveIrp ,以從佇列中移除 IRP,而 CsqCompleteCanceledIrp 則會完成 IRP 取消作業。
下圖說明 IRP 取消的控制流程。
CsqCompleteCanceledIrp的基本實作如下所示。
VOID CsqCompleteCanceledIrp(PIO_CSQ Csq, PIRP Irp) {
Irp->IoStatus.Status = STATUS_CANCELLED;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
}
驅動程式可以使用任何作業系統的同步處理基本類型來實作其 CsqAcquireLock 和 CsqReleaseLock 常式。 可用的同步處理基本類型包括 微調鎖定 和 Mutex 物件。
以下是驅動程式如何使用微調鎖定來實作鎖定的範例。
/*
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 變數的指標傳遞至 CsqAcquireLock 和 CsqReleaseLock。 如果驅動程式使用微調鎖定來實作佇列的鎖定,驅動程式可以使用此變數來儲存佇列鎖定時目前的 IRQL。
驅動程式不需要使用微調鎖定。 例如,驅動程式可以使用 Mutex 來鎖定佇列。 如需驅動程式可用之同步處理技術的描述,請參閱 同步處理技術。
使用 Cancel-Safe IRP 佇列
當佇列和清除佇列 IRP 時,驅動程式會使用下列系統常式:
下列任一項,將 IRP 插入佇列: IoCsqInsertIrp 或 IoCsqInsertIrpEx。
IoCsqRemoveNextIrp 可移除佇列中的下一個 IRP。 驅動程式可以選擇性地指定索引鍵值。
下圖說明 IoCsqRemoveNextIrp的控制流程。
- IoCsqRemoveIrp 可從佇列中移除指定的 IRP。
下圖說明 IoCsqRemoveIrp的控制流程。
這些常式接著會分派給驅動程式提供的常式。
IoCsqInsertIrpEx常式可讓您存取CsqInsertIrpEx常式的擴充功能。 它會傳回 CsqInsertIrpEx所傳回的狀態值。 呼叫端可以使用此值來判斷 IRP 是否已成功排入佇列。 IoCsqInsertIrpEx 也允許呼叫者為 CsqInsertIrpEx的 InsertCoNtext 參數指定值。
請注意,不論佇列有CsqInsertIrp 常式或 CsqInsertIrpEx常式,都可以在任何取消安全佇列上呼叫IoCsqInsertIrp和IoCsqInsertIrpEx。 在任一情況下,IoCsqInsertIrp的行為都相同。 如果 IoCsqInsertIrpEx 傳遞了具有 CsqInsertIrp 常式的佇列,其行為會與 IoCsqInsertIrp相同。
下圖說明 IoCsqInsertIrp的控制流程。
下圖說明 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 時,系統會將其傳回給驅動程式。