Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Em vez de o sistema cancelar o bloqueio de rotação, os drivers que gerenciam suas próprias filas de IRPs podem usar um bloqueio de rotação fornecido pelo driver para sincronizar o acesso às filas. Você pode melhorar o desempenho evitando o uso do bloqueio de rotação de cancelamento, exceto quando necessário. Como o sistema tem apenas um bloqueio de rotação de cancelamento, às vezes, um driver pode ter que esperar que esse bloqueio de rotação fique disponível. O uso de um bloqueio de rotação fornecido pelo driver elimina esse possível atraso e disponibiliza o bloqueio de rotação de cancelamento para o gerente de E/S e outros drivers. Although the system still acquires the cancel spin lock when it calls the driver's Cancel routine, a driver can use its own spin lock to protect its queue of IRPs.
Even if a driver doesn't queue pending IRPs, but retains ownership in some other way, that driver must set a Cancel routine for the IRP and must use a spin lock to protect the IRP pointer. For example, suppose a driver marks an IRP pending, then passes the IRP pointer as context to an IoTimer routine. Nessa situação, o driver:
- Must set a Cancel routine that cancels the timer.
- Must use the same spin lock in both the Cancel routine and the timer callback when accessing the IRP.
Qualquer driver que enfileira seus próprios IRPs e usa seu próprio bloqueio de rotação deve executar as seguintes etapas:
Crie um bloqueio de rotação para proteger a fila.
Set and clear the Cancel routine only while holding this spin lock.
If the Cancel routine starts running while the driver is dequeuing an IRP, allow the Cancel routine to complete the IRP.
Acquire the lock that protects the queue in the Cancel routine.
To create the spin lock, the driver calls KeInitializeSpinLock. In the following example, the driver saves the spin lock in a DEVICE_CONTEXT structure along with the queue it created:
typedef struct {
LIST_ENTRYirpQueue;
KSPIN_LOCK irpQueueSpinLock;
...
} DEVICE_CONTEXT;
VOID InitDeviceContext(DEVICE_CONTEXT *deviceContext)
{
InitializeListHead(&deviceContext->irpQueue);
KeInitializeSpinLock(&deviceContext->irpQueueSpinLock);
}
To queue an IRP, the driver acquires the spin lock, calls InsertTailList, and then marks the IRP pending, as in the following example:
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;
}
As the example shows, the driver holds its spin lock while it sets and clears the Cancel routine. The sample queuing routine contains two calls to IoSetCancelRoutine.
The first call sets the Cancel routine for the IRP. However, because the IRP might be canceled while the queuing routine is running, the driver must check the Cancel member of the IRP.
If Cancel is set, then cancellation has been requested, and the driver must make a second call to IoSetCancelRoutine to see whether the previously set Cancel routine was called.
If the IRP is canceled but the Cancel routine isn't called yet, the current routine dequeues the IRP and completes it with STATUS_CANCELLED.
If the IRP is canceled and the Cancel routine is already called, the current routine marks the IRP pending and returns STATUS_PENDING. The Cancel routine completes the IRP.
O exemplo a seguir mostra como remover um IRP da fila criada anteriormente:
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;
}
No exemplo, o driver adquire o bloqueio de rotação associado antes de acessar a fila. Enquanto mantém o bloqueio de rotação, o driver verifica se a fila não está vazia e tira o próximo IRP da fila. Then it calls IoSetCancelRoutine to reset the Cancel routine for the IRP. Because the IRP could be canceled while the driver dequeues the IRP and resets the Cancel routine, the driver must check the value returned by IoSetCancelRoutine. If IoSetCancelRoutine returns NULL, which indicates that the Cancel routine either is or will soon be called, the dequeuing routine lets the Cancel routine complete the IRP. Em seguida, ele libera o bloqueio que protege a fila e retorna.
Note the use of InitializeListHead in the preceding routine. The driver could requeue the IRP, so that the Cancel routine can dequeue it, but it's simpler to call InitializeListHead, which reinitializes the IRP's ListEntry field so that it points to the IRP itself. Using the self-referencing pointer is important because the structure of the list could change before the Cancel routine acquires the spin lock. If the list structure changes, possibly making the original value of ListEntry invalid, the Cancel routine could corrupt the list when it dequeues the IRP. But if ListEntry points to the IRP itself, the Cancel routine always uses the correct IRP.
The Cancel routine does the following steps:
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;
}
The I/O manager always acquires the global cancel spin lock before it calls a Cancel routine, so the first task of the Cancel routine is to release this spin lock. Em seguida, ele adquire o bloqueio de rotação que protege a fila de IRPs do driver, remove o IRP atual da fila, libera seu bloqueio de rotação, conclui o IRP com STATUS_CANCELLED e sem aumento de prioridade e retorna.
Para obter mais informações sobre como cancelar bloqueios de rotação, consulte o white paper Cancelar Lógica em Drivers do Windows .
The I/O manager always acquires the global cancel spin lock before it calls a Cancel routine, so the first task of the Cancel routine is to release this spin lock. Em seguida, ele adquire o bloqueio de rotação que protege a fila de IRPs do driver, remove o IRP atual da fila, libera seu bloqueio de rotação, conclui o IRP com STATUS_CANCELLED e sem aumento de prioridade e retorna.
Para obter mais informações sobre como cancelar bloqueios de rotação, consulte o white paper Cancelar Lógica em Drivers do Windows .