使用Driver-Supplied微調鎖定
管理自己的 IRP 佇列的驅動程式可以使用驅動程式提供的微調鎖定,而不是系統取消微調鎖定,以同步存取佇列。 您可以避免使用取消微調鎖定來改善效能,但絕對必要時除外。 因為系統只有一個取消微調鎖定,所以驅動程式有時可能需要等待該微調鎖定可供使用。 使用驅動程式提供的微調鎖定可消除此潛在延遲,並讓取消微調鎖定可供 I/O 管理員和其他驅動程式使用。 雖然系統在呼叫驅動程式的 Cancel 常式時仍會取得取消微調鎖定,但驅動程式可以使用自己的微調鎖定來保護其 IRP 佇列。
即使驅動程式不會排入擱置的 IRP 佇列,但以其他方式保留擁有權,該驅動程式也必須設定 IRP 的 Cancel 常式,而且必須使用微調鎖定來保護 IRP 指標。 例如,假設驅動程式將 IRP 標示為擱置中,然後將 IRP 指標當做內容傳遞至 IoTimer 常式。 驅動程式必須設定 Cancel 常式來取消計時器,而且必須在存取 IRP 時,在 Cancel 常式和計時器回呼中使用相同的微調鎖定。
任何將自己的 IRP 排入佇列並使用自己的微調鎖定的驅動程式都必須執行下列動作:
建立微調鎖定來保護佇列。
只有在保留此微調鎖定時,才設定並清除 Cancel 常式。
如果 取消 常式在驅動程式取消佇列 IRP 時開始執行,請允許 Cancel 常式完成 IRP。
取得在 Cancel 常式中保護佇列的鎖定。
若要建立微調鎖定,驅動程式會呼叫 KeInitializeSpinLock。 在下列範例中,驅動程式會將微調鎖定儲存在 DEVICE_CONTEXT 結構中,以及已建立的佇列:
typedef struct {
LIST_ENTRYirpQueue;
KSPIN_LOCK irpQueueSpinLock;
...
} DEVICE_CONTEXT;
VOID InitDeviceContext(DEVICE_CONTEXT *deviceContext)
{
InitializeListHead(&deviceContext->irpQueue);
KeInitializeSpinLock(&deviceContext->irpQueueSpinLock);
}
若要將 IRP 排入佇列,驅動程式會取得微調鎖定、呼叫 InsertTailList,然後將 IRP 標示為擱置中,如下列範例所示:
NTSTATUS QueueIrp(DEVICE_CONTEXT *deviceContext, PIRP Irp)
{
PDRIVER_CANCEL oldCancelRoutine;
KIRQL oldIrql;
NTSTATUS status;
KeAcquireSpinLock(&deviceContext->irpQueueSpinLock, &oldIrql);
// Queue the IRP and call IoMarkIrpPending to indicate
// that the IRP may complete on a different thread.
// N.B. It is okay to call these inside the spin lock
// because they are macros, not functions.
IoMarkIrpPending(Irp);
InsertTailList(&deviceContext->irpQueue, &Irp->Tail.Overlay.ListEntry);
// Must set a Cancel routine before checking the Cancel flag.
oldCancelRoutine = IoSetCancelRoutine(Irp, IrpCancelRoutine);
ASSERT(oldCancelRoutine == NULL);
if (Irp->Cancel) {
// The IRP was canceled. Check whether our cancel routine was called.
oldCancelRoutine = IoSetCancelRoutine(Irp, NULL);
if (oldCancelRoutine) {
// The cancel routine was NOT called.
// So dequeue the IRP now and complete it after releasing the spin lock.
RemoveEntryList(&Irp->Tail.Overlay.ListEntry);
// Drop the lock before completing the request.
KeReleaseSpinLock(&deviceContext->irpQueueSpinLock, oldIrql);
Irp->IoStatus.Status = STATUS_CANCELLED;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_PENDING;
} else {
// The Cancel routine WAS called.
// As soon as we drop our spin lock, it will dequeue and complete the IRP.
// So leave the IRP in the queue and otherwise do not touch it.
// Return pending since we are not completing the IRP here.
}
}
KeReleaseSpinLock(&deviceContext->irpQueueSpinLock, oldIrql);
// Because the driver called IoMarkIrpPending while it held the IRP,
// it must return STATUS_PENDING from its dispatch routine.
return STATUS_PENDING;
}
如範例所示,驅動程式會在設定並清除 Cancel 常式時保留其微調鎖定。 範例佇列常式包含 兩次 IoSetCancelRoutine呼叫。
第一個呼叫會設定 IRP 的 Cancel 常式。 不過,因為佇列常式執行時可能已經取消 IRP,所以驅動程式必須檢查 IRP 的 Cancel 成員。
如果已設定 Cancel ,則要求取消,而且驅動程式必須對 IoSetCancelRoutine 進行第二次呼叫,以查看是否已呼叫先前設定的 Cancel 常式。
如果 IRP 已取消,但尚未呼叫 Cancel 常式,則目前的常式會清除 IRP 佇列,並使用STATUS_CANCELLED完成它。
如果已取消 IRP,且已呼叫 Cancel 常式,則目前的傳回會標示 IRP 暫止並傳回STATUS_PENDING。 Cancel常式將會完成 IRP。
下列範例示範如何從先前建立的佇列中移除 IRP:
PIRP DequeueIrp(DEVICE_CONTEXT *deviceContext)
{
KIRQL oldIrql;
PIRP nextIrp = NULL;
KeAcquireSpinLock(&deviceContext->irpQueueSpinLock, &oldIrql);
while (!nextIrp && !IsListEmpty(&deviceContext->irpQueue)) {
PDRIVER_CANCEL oldCancelRoutine;
PLIST_ENTRY listEntry = RemoveHeadList(&deviceContext->irpQueue);
// Get the next IRP off the queue.
nextIrp = CONTAINING_RECORD(listEntry, IRP, Tail.Overlay.ListEntry);
// Clear the IRP's cancel routine.
oldCancelRoutine = IoSetCancelRoutine(nextIrp, NULL);
// IoCancelIrp() could have just been called on this IRP. What interests us
// is not whether IoCancelIrp() was called (nextIrp->Cancel flag set), but
// whether IoCancelIrp() called (or is about to call) our Cancel routine.
// For that, check the result of the test-and-set macro IoSetCancelRoutine.
if (oldCancelRoutine) {
// Cancel routine not called for this IRP. Return this IRP.
ASSERT(oldCancelRoutine == IrpCancelRoutine);
} else {
// This IRP was just canceled and the cancel routine was (or will be)
// called. The Cancel routine will complete this IRP as soon as we
// drop the spin lock, so do not do anything with the IRP.
// Also, the Cancel routine will try to dequeue the IRP, so make
// the IRP's ListEntry point to itself.
ASSERT(nextIrp->Cancel);
InitializeListHead(&nextIrp->Tail.Overlay.ListEntry);
nextIrp = NULL;
}
}
KeReleaseSpinLock(&deviceContext->irpQueueSpinLock, oldIrql);
return nextIrp;
}
在此範例中,驅動程式會在存取佇列之前取得相關聯的微調鎖定。 按住微調鎖定時,它會檢查佇列不是空的,並取得佇列的下一個 IRP。 然後它會呼叫 IoSetCancelRoutine 來重設 IRP 的 Cancel 常式。 因為當驅動程式清除 IRP 佇列並重設 Cancel 常式時,可能會取消 IRP,所以驅動程式必須檢查 IoSetCancelRoutine所傳回的值。 如果 IoSetCancelRoutine 傳回 Null,這表示 Cancel 常式已呼叫或即將呼叫,則清除佇列常式可讓 Cancel 常式完成 IRP。 然後,它會釋放保護佇列並傳回的鎖定。
請注意上述常式中的 InitializeListHead 用法。 驅動程式可以重新佇列 IRP,讓 Cancel 常式可以取消佇列,但呼叫 InitializeListHead會比較簡單,這會重新初始化 IRP 的 ListEntry 欄位,使其指向 IRP 本身。 使用自我參考指標很重要,因為在 Cancel 常式取得微調鎖定之前,清單的結構可能會變更。 如果清單結構變更,可能會使 ListEntry 的原始值無效, Cancel 常式可能會在取消佇列 IRP 時損毀清單。 但如果 ListEntry 指向 IRP 本身, 則 Cancel 常式一律會使用正確的 IRP。
接著,Cancel常式只會執行下列動作:
VOID IrpCancelRoutine(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
DEVICE_CONTEXT *deviceContext = DeviceObject->DeviceExtension;
KIRQL oldIrql;
// Release the global cancel spin lock.
// Do this while not holding any other spin locks so that we exit at the right IRQL.
IoReleaseCancelSpinLock(Irp->CancelIrql);
// Dequeue and complete the IRP.
// The enqueue and dequeue functions synchronize properly so that if this cancel routine is called,
// the dequeue is safe and only the cancel routine will complete the IRP. Hold the spin lock for the IRP
// queue while we do this.
KeAcquireSpinLock(&deviceContext->irpQueueSpinLock, &oldIrql);
RemoveEntryList(&Irp->Tail.Overlay.ListEntry);
KeReleaseSpinLock(&deviceContext->irpQueueSpinLock, oldIrql);
// Complete the IRP. This is a call outside the driver, so all spin locks must be released by this point.
Irp->IoStatus.Status = STATUS_CANCELLED;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return;
}
I/O 管理員一律會在呼叫 Cancel 常式之前取得全域取消微調鎖定,因此 Cancel 常式的第一個工作是釋放此微調鎖定。 然後,它會取得可保護驅動程式 IRP 佇列的微調鎖定、從佇列中移除目前的 IRP、釋放其微調鎖定、完成具有STATUS_CANCELLED且沒有優先權提升的 IRP,然後傳回。
如需取消微調鎖定的詳細資訊,請參閱 Windows 驅動程式中的取消邏輯 白皮書。