Cancel-Safe filas IRP

Os drivers que implementam sua própria fila IRP devem usar a estrutura de fila IRP com segurança de cancelamento . Filas IRP com segurança de cancelamento dividem a manipulação de IRP em duas partes:

  1. O driver fornece um conjunto de rotinas de retorno de chamada que implementam operações padrão na fila IRP do driver. As operações fornecidas incluem inserir e remover IRPs da fila e bloquear e desbloquear a fila. Consulte Implementando o Cancel-Safe fila IRP.

  2. Sempre que o driver precisar realmente inserir ou remover um IRP da fila, ele usará as rotinas IoCsqXxx fornecidas pelo sistema. Essas rotinas lidam com toda a sincronização e a lógica de cancelamento de IRP para o driver.

Os drivers que usam filas IRP com segurança de cancelamento não implementam Rotinas de cancelamento para dar suporte ao cancelamento de IRP.

A estrutura garante que os drivers insiram e removam IRPs de sua fila atomicamente. Ele também garante que o cancelamento do IRP seja implementado corretamente. Os drivers que não usam a estrutura devem bloquear e desbloquear manualmente a fila antes de executar inserções e exclusões. Eles também devem evitar as condições de corrida que podem resultar na implementação de uma rotina cancelar . (Para obter uma descrição das condições de corrida que podem surgir, consulte Sincronizando o cancelamento de IRP.)

A estrutura de fila IRP com segurança de cancelamento está incluída no Windows XP e versões posteriores do Windows. Os drivers que também devem funcionar com o Windows 2000 e o Windows 98/Me podem vincular à biblioteca Csq.lib incluída no WDK (Windows Driver Kit). A biblioteca Csq.lib fornece uma implementação dessa estrutura.

As rotinas do IoCsqXxx são declaradas no Windows XP e versões posteriores do Wdm.h e Ntddk.h. Os drivers que também devem trabalhar com o Windows 2000 e o Windows 98/Me devem incluir Csq.h para as declarações.

Você pode ver uma demonstração completa de como usar filas IRP cancel-safe no diretório \src\general\cancel do WDK. Para obter mais informações sobre essas filas, consulte também o white paper Fluxo de Controle para Cancel-Safe Enfileiramento IRP .

Implementando a fila do IRP Cancel-Safe

Para implementar uma fila IRP segura para cancelamento, os drivers devem fornecer as seguintes rotinas:

  • Uma das seguintes rotinas para inserir IRPs na fila: CsqInsertIrp ou CsqInsertIrpEx. CsqInsertIrpEx é uma versão estendida do CsqInsertIrp; a fila é implementada usando uma ou outra.

  • Uma rotina CsqRemoveIrp que remove o IRP especificado da fila.

  • Uma rotina CsqPeekNextIrp que retorna um ponteiro para o próximo IRP seguindo o IRP especificado na fila. É aí que o sistema passa o valor PeekContext que recebe de IoCsqRemoveNextIrp. O driver pode interpretar esse valor de qualquer maneira.

  • Ambas as seguintes rotinas para permitir que o sistema bloqueie e desbloqueie a fila IRP: CsqAcquireLock e CsqReleaseLock.

  • Uma rotina CsqCompleteCanceledIrp que conclui um IRP cancelado.

Os ponteiros para as rotinas do driver são armazenados na estrutura IO_CSQ que descreve a fila. O driver aloca o armazenamento para a estrutura IO_CSQ . A estrutura IO_CSQ tem a garantia de permanecer um tamanho fixo, para que um driver possa inserir com segurança a estrutura dentro de sua extensão de dispositivo.

O driver usa IoCsqInitialize ou IoCsqInitializeEx para inicializar a estrutura. Use IoCsqInitialize se a fila implementar CsqInsertIrp ou IoCsqInitializeEx se a fila implementar CsqInsertIrpEx.

Os drivers precisam fornecer apenas a funcionalidade essencial em cada rotina de retorno de chamada. Por exemplo, somente as rotinas CsqAcquireLock e CsqReleaseLock implementam o tratamento de bloqueio. O sistema chama automaticamente essas rotinas para bloquear e desbloquear a fila conforme necessário.

Você pode implementar qualquer tipo de mecanismo de enfileiramento IRP em seu driver, desde que as rotinas de expedição apropriadas sejam fornecidas. Por exemplo, o driver pode implementar a fila como uma lista vinculada ou como uma fila de prioridade.

CsqInsertIrpEx fornece uma interface mais flexível para a fila do que csqInsertIrp. O driver pode usar seu valor retornado para indicar o resultado da operação; se retornar um código de erro, a inserção falhará. Uma rotina CsqInsertIrp não retorna um valor, portanto, não há uma maneira simples de indicar que uma inserção falhou. Além disso, CsqInsertIrpEx usa um parâmetro InsertContext definido pelo driver adicional que pode ser usado para especificar informações adicionais específicas do driver a serem usadas pela implementação da fila.

Os drivers podem usar CsqInsertIrpEx para implementar uma manipulação irp mais sofisticada. Por exemplo, se não houver IRPs pendentes, a rotina CsqInsertIrpEx poderá retornar um código de erro e o driver poderá processar o IRP imediatamente. Da mesma forma, se os IRPs não puderem mais ser enfileirados, o CsqInsertIrpEx poderá retornar um código de erro para indicar esse fato.

O driver é isolado de toda a manipulação de cancelamento de IRP. O sistema fornece uma rotina Cancelar para IRPs na fila. Essa rotina chama CsqRemoveIrp para remover o IRP da fila e CsqCompleteCanceledIrp para concluir o cancelamento do IRP.

O diagrama a seguir ilustra o fluxo de controle para cancelamento de IRP.

diagrama ilustrando o fluxo de controle para cancelamento de irp.

Uma implementação básica de CsqCompleteCanceledIrp é a seguinte.

VOID CsqCompleteCanceledIrp(PIO_CSQ Csq, PIRP Irp) {
  Irp->IoStatus.Status = STATUS_CANCELLED;
  Irp->IoStatus.Information = 0;

  IoCompleteRequest(Irp, IO_NO_INCREMENT);
}

Os drivers podem usar qualquer um dos primitivos de sincronização do sistema operacional para implementar suas rotinas CsqAcquireLock e CsqReleaseLock . Os primitivos de sincronização disponíveis incluem bloqueios de rotação e objetos mutex.

Aqui está um exemplo de como um driver pode implementar o bloqueio usando bloqueios de rotação.

/* 
  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);
}

O sistema passa um ponteiro para uma variável IRQL para CsqAcquireLock e CsqReleaseLock. Se o driver usar um bloqueio de rotação para implementar o bloqueio para a fila, o driver poderá usar essa variável para armazenar o IRQL atual quando a fila estiver bloqueada.

Os drivers não são necessários para usar bloqueios de rotação. Por exemplo, o driver pode usar um mutex para bloquear a fila. Para obter uma descrição das técnicas de sincronização disponíveis para drivers, consulte Técnicas de sincronização.

Usando a fila do IRP Cancel-Safe

Os drivers usam as seguintes rotinas do sistema ao enfileirar e desativar IRPs:

O diagrama a seguir ilustra o fluxo de controle para IoCsqRemoveNextIrp.

diagrama ilustrando o fluxo de controle para iocsqremovenextirp.

O diagrama a seguir ilustra o fluxo de controle para IoCsqRemoveIrp.

diagrama ilustrando o fluxo de controle para iocsqremoveirp.

Essas rotinas, por sua vez, despacham para rotinas fornecidas pelo driver.

A rotina IoCsqInsertIrpEx fornece acesso aos recursos estendidos de uma rotina CsqInsertIrpEx . Ele retorna o valor status retornado por CsqInsertIrpEx. O chamador pode usar esse valor para determinar se o IRP foi enfileirado com êxito ou não. IoCsqInsertIrpEx também permite que o chamador especifique um valor para o parâmetro InsertContext de CsqInsertIrpEx.

Observe que IoCsqInsertIrp e IoCsqInsertIrpEx podem ser chamados em qualquer fila cancel-safe, se a fila tem uma rotina CsqInsertIrp ou uma rotina CsqInsertIrpEx . IoCsqInsertIrp se comporta da mesma forma em ambos os casos. Se IoCsqInsertIrpEx for passado por uma fila que tenha uma rotina CsqInsertIrp , ela se comportará de forma idêntica a IoCsqInsertIrp.

O diagrama a seguir ilustra o fluxo de controle para IoCsqInsertIrp.

diagrama ilustrando o fluxo de controle para iocsqinsertirp.

O diagrama a seguir ilustra o fluxo de controle para IoCsqInsertIrpEx.

diagrama ilustrando o fluxo de controle para iocsqinsertirpex.

Há várias maneiras naturais de usar as rotinas do IoCsqXxx para enfileirar e desativar IRPs. Por exemplo, um driver pode simplesmente enfileirar IRPs para serem processados na ordem em que são recebidos. O driver pode enfileirar um IRP da seguinte maneira:

    status = IoCsqInsertIrpEx(IoCsq, Irp, NULL, NULL);

Se o driver não for necessário para distinguir entre IRPs específicos, ele poderá simplesmente desempacotá-los na ordem em que foram enfileirados, da seguinte maneira:

    IoCsqRemoveNextIrp(IoCsq, NULL);

Como alternativa, o driver pode enfileirar e desativar IRPs específicos. As rotinas usam a estrutura de IO_CSQ_IRP_CONTEXT opaca para identificar IRPs específicos na fila. O driver enfileira o IRP da seguinte maneira:

    IO_CSQ_IRP_CONTEXT ParticularIrpInQueue;
    IoCsqInsertIrp(IoCsq, Irp, &ParticularIrpInQueue);

Em seguida, o driver pode remover o mesmo IRP usando o valor IO_CSQ_IRP_CONTEXT .

    IoCsqRemoveIrp(IoCsq, Irp, &ParticularIrpInQueue);

O driver também pode ser necessário para remover IRPs da fila com base em um critério específico. Por exemplo, o driver pode associar uma prioridade a cada IRP, de modo que os IRPs de prioridade mais alta sejam desemqueados primeiro. O driver pode passar um valor PeekContext para IoCsqRemoveNextIrp, que o sistema passa de volta para o driver quando solicita o próximo IRP na fila.