Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
Os drivers que gerem as suas próprias filas de IRPs podem usar um spin lock fornecido pelo driver, em vez do spin lock de cancelamento do sistema, para sincronizar o acesso às filas. Você pode melhorar o desempenho evitando o uso do spin lock de cancelamento, exceto quando absolutamente necessário. Como o sistema tem apenas um spin lock, um driver às vezes pode ter que esperar que esse spin lock se torne disponível. O uso de um bloqueio de rotação fornecido pelo driver elimina esse atraso potencial e torna o bloqueio de rotação de cancelamento disponível para o gerenciador de E/S e outros drivers. Embora o sistema ainda adquira o bloqueio de rotação de cancelamento quando chama a rotina de cancelamento do driver, um driver pode usar seu próprio bloqueio de rotação para proteger sua fila de IRPs.
Mesmo que um driver não enfileire IRPs pendentes, mas retenha a posse de alguma outra forma, esse driver deve definir uma rotina Cancel para o IRP e deve usar um spin lock para proteger o ponteiro IRP. Por exemplo, suponha que um driver marque um IRP como pendente e, em seguida, passe o ponteiro IRP como contexto para uma rotina IoTimer. O driver deve definir uma rotina Cancel que cancela o temporizador e deve usar o mesmo bloqueio de rotação na rotina Cancel e no retorno de chamada do temporizador ao acessar o IRP.
Qualquer driver que enfileire seus próprios IRPs e use seu próprio bloqueio de rotação deve fazer o seguinte:
Crie um bloqueio de rotação para proteger a fila.
Defina e remova a rotina Cancelar apenas enquanto mantém pressionado este bloqueio de rotação.
Se a rotina Cancelar começar a ser executada enquanto o driver estiver enfileirando um IRP, permita que a rotina Cancelar conclua o IRP.
Adquira o bloqueio que protege a fila na rotina Cancelar .
Para criar o bloqueio de rotação, o driver chama KeInitializeSpinLock. No exemplo a seguir, o driver guarda o spin lock numa estrutura DEVICE_CONTEXT junto com a fila que criou:
typedef struct {
LIST_ENTRYirpQueue;
KSPIN_LOCK irpQueueSpinLock;
...
} DEVICE_CONTEXT;
VOID InitDeviceContext(DEVICE_CONTEXT *deviceContext)
{
InitializeListHead(&deviceContext->irpQueue);
KeInitializeSpinLock(&deviceContext->irpQueueSpinLock);
}
Para enfileirar um IRP, o driver adquire o spin lock, chama InsertTailList e, em seguida, assinala o IRP como pendente, como no exemplo a seguir:
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;
}
Como mostra o exemplo, o driver mantém seu bloqueio de rotação enquanto define e limpa a rotina Cancelar . A rotina de enfileiramento de exemplo contém duas chamadas para IoSetCancelRoutine.
A primeira chamada define a rotina Cancel para o IRP. No entanto, como o IRP pode ter sido cancelado enquanto a rotina de enfileiramento está em execução, o driver deve verificar o membro Cancel do IRP.
Se Cancelar estiver definido, o cancelamento foi solicitado e o driver deverá fazer uma segunda chamada para IoSetCancelRoutine para ver se a rotina Cancel definida anteriormente foi chamada.
Se o IRP tiver sido cancelado, mas a rotina Cancelar ainda não tiver sido chamada, a rotina atual retira o IRP da fila e completa-o com STATUS_CANCELLED.
Se o IRP tiver sido cancelado e a rotina Cancelar já tiver sido chamada, o retorno atual marcará o IRP pendente e retornará STATUS_PENDING. A rotina Cancelar completará o 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. Ao segurar o bloqueio de rotação, ele verifica se a fila não está vazia e tira o próximo IRP da fila. Em seguida, ele chama IoSetCancelRoutine para redefinir a rotina Cancel para o IRP. Como o IRP pode ser cancelado enquanto o controlador desenfileira o IRP e redefine a rotina Cancelar, o controlador deve verificar o valor retornado por IoSetCancelRoutine. Se IoSetCancelRoutine retornar NULL, o que indica que a rotina Cancel foi ou será chamada em breve, a rotina de enfileiramento permitirá que a rotina Cancel conclua o IRP. Em seguida, ele libera o bloqueio que protege a fila e retorna.
Observe o uso de InitializeListHead na rotina anterior. O driver pode adicionar novamente o IRP à fila, para que a rotina Cancel possa removê-lo da fila, mas é mais fácil chamar InitializeListHead, que reinicializa o campo ListEntry do IRP para que isto aponte para o próprio IRP. Usar o ponteiro de autorreferência é importante porque a estrutura da lista pode mudar antes que a rotina Cancel adquira o bloqueio de rotação. E se a estrutura da lista mudar, possivelmente tornando o valor original de ListEntry inválido, a rotina Cancel poderá corromper a lista quando desfilar o IRP. Mas se ListEntry aponta para o IRP em si, então a rotina Cancel sempre usará o IRP correto.
A rotina Cancelar , por sua vez, simplesmente faz o seguinte:
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;
}
O gestor de E/S adquire sempre o spin lock de cancelamento global antes de chamar uma rotina Cancelar, portanto, a primeira tarefa da rotina Cancelar é libertar esse spin lock. Em seguida, ele adquire o bloqueio de giro que protege a fila de IRPs do driver, remove o IRP atual da fila, libera esse bloqueio de giro, completa 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 nos drivers do Windows .