使用驱动程序提供的自旋锁
管理自己的 IRP 队列的驱动程序可以使用驱动程序提供的旋转锁(而不是系统取消旋转锁)来同步对队列的访问。 可以避免使用取消旋转锁来提高性能,除非绝对必要。 由于系统只有一个取消旋转锁,因此驱动程序有时可能需要等待该旋转锁可用。 使用驱动程序提供的旋转锁可以消除这种潜在的延迟,并使取消旋转锁可用于 I/O 管理器和其他驱动程序。 尽管系统在调用驱动程序的 Cancel 例程时仍会获取 取消 旋转锁,但驱动程序可以使用自己的旋转锁来保护其 IRP 队列。
即使驱动程序未排队等待 IRP,但以其他某种方式保留所有权,该驱动程序也必须为 IRP 设置 取消 例程,并且必须使用旋转锁来保护 IRP 指针。 例如,假设驱动程序将 IRP 标记为挂起,然后将 IRP 指针作为上下文传递到 IoTimer 例程。 驱动程序必须 设置取消例 程来取消计时器,并且必须在访问 IRP 时在 Cancel 例程和计时器回调中使用相同的旋转锁。
任何将自己的 IRP 排队并使用其自己的旋转锁的驱动程序都必须执行以下操作:
创建一个旋转锁来保护队列。
仅在保留此旋转锁时设置和清除 Cancel 例程。
如果在驱动程序取消 IRP 排队时 取消 例程开始运行,则允许 取消 例程完成 IRP。
获取用于保护 取消 例程中队列的锁。
若要创建旋转锁,驱动程序调用 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。 取消例程将完成 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 排队并重置取消例程时可能会取消 IRP,因此驱动程序必须检查 IoSetCancelRoutine 返回的值。 如果 IoSetCancelRoutine 返回 NULL,这表示 取消例程 已调用或即将调用,则取消排队例程允许 取消 例程完成 IRP。 然后,它会释放保护队列并返回的锁。
请注意在前面的例程中使用 InitializeListHead 。 驱动程序可以重新排队 IRP,以便 Cancel 例程可以取消排队,但调用 InitializeListHead 更简单,这会重新初始化 IRP 的 ListEntry 字段,使其指向 IRP 本身。 使用自引用指针非常重要,因为列表的结构可能会在 Cancel 例程获取旋转锁之前发生更改。 如果列表结构发生更改(可能使 原始值 ListEntry 无效), 取消 例程可能会在取消 IRP 排队时损坏列表。 但是,如果 ListEntry 指向 IRP 本身,则 Cancel 例程将始终使用正确的 IRP。
反过来,取消例程只需执行以下操作:
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 例程 之前获取全局取消旋转锁,因此 取消 例程的第一个任务是释放此旋转锁。 然后,它获取可保护驱动程序的 IRP 队列的旋转锁,从队列中删除当前 IRP,释放其旋转锁,使用STATUS_CANCELLED完成 IRP 且无优先级提升,并返回 。
有关取消旋转锁的详细信息,请参阅 取消 Windows 驱动程序中的逻辑 白皮书。