Partilhar via


Filas de IRP Seguras contra Cancelamento

Os drivers que implementam as suas próprias filas de IRP devem usar a estrutura de fila de IRP segura para cancelamento . As filas de IRP à prova de cancelamento dividem a manipulação de IRP em duas partes.

  1. O controlador oferece um conjunto de rotinas de retorno de chamada que realizam operações padrão na fila de IRPs do controlador. As operações fornecidas incluem inserir e remover IRPs da fila e bloquear e desbloquear a fila. Veja Implementação da fila IRP Cancel-Safe.

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

Os drivers que usam filas de IRP seguras para cancelamento não implementam rotinas de Cancel para dar suporte ao cancelamento de IRP.

A estrutura garante que os drivers insiram e removem IRPs de sua fila atomicamente. Ele também garante que o cancelamento de IRP seja implementado corretamente. Os drivers que não usam o framework devem bloquear e desbloquear manualmente a fila antes de fazer inserções e remoções. Eles também devem evitar as condições de competição que podem resultar ao implementar uma rotina de 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 segura para cancelamento está disponível no Windows XP e nas versões posteriores do Windows. Drivers que também devem funcionar com o Windows 2000 e Windows 98/Me podem vincular à biblioteca Csq.lib que está incluída no Windows Driver Kit (WDK). A biblioteca Csq.lib fornece uma implementação dessa estrutura.

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

Você pode ver uma demonstração completa de como usar filas IRP seguras para cancelamento no diretório \src\general\cancel do WDK. Para obter mais informações sobre essas filas, consulte também o documento técnico Flow of Control for Cancel-Safe IRP Queuing.

Implementando a fila de IRP Cancel-Safe

Para implementar uma fila de 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 um ou outro.

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

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

  • Para permitir que o sistema bloqueie e desbloqueie a fila IRP, as duas rotinas seguintes devem ser usadas: CsqAcquireLock e CsqReleaseLock.

  • Uma rotina CsqCompleteCanceledIrp que conclui um IRP cancelado.

Os ponteiros para as rotinas do motorista são armazenados na estrutura IO_CSQ que descreve a fila. O driver atribui o espaço de armazenamento para a estrutura IO_CSQ. A estrutura IO_CSQ tem garantia de manter um tamanho fixo, para que um controlador possa incorporar com segurança a estrutura na extensão do seu dispositivo.

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

Em cada rotina de retorno de chamada, os drivers precisam apenas fornecer a funcionalidade essencial. Por exemplo, apenas 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 despacho 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 condutor pode utilizar o seu valor de retorno para indicar o resultado da operação; Se ele retornar um código de erro, a inserção falhou. Uma rotina CsqInsertIrp não retorna um valor, pelo que não existe uma maneira simples de indicar que uma inserção falhou. Além disso, o CsqInsertIrpEx usa um parâmetro adicional definido pelo driver, o InsertContext, que pode ser usado para especificar informações adicionais específicas do driver a serem utilizadas pela implementação da fila.

Os drivers podem usar CsqInsertIrpEx para implementar um tratamento de IRP mais sofisticado. Por exemplo, se não houver IRPs pendentes, a rotina CsqInsertIrpEx pode retornar um código de erro e o driver pode 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 controlador está isolado de todo o processamento 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 do 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 uma das primitivas de sincronização do sistema operativo para implementar as suas rotinas CsqAcquireLock e CsqReleaseLock . As primitivas 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 usa um bloqueio de rotação para implementar o bloqueio para a fila, o driver pode usar essa variável para armazenar o IRQL atual quando a fila estiver bloqueada.

Os condutores não são obrigados a 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 controladores, consulte Técnicas de sincronização.

Usando a fila de IRP do Cancel-Safe

Os drivers usam as seguintes rotinas do sistema ao enfileirar e desenfileirar 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 motorista.

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

Observe que tanto IoCsqInsertIrp quanto IoCsqInsertIrpEx podem ser chamados em qualquer fila segura para cancelamento, quer a fila tenha uma rotina CsqInsertIrp ou uma rotina CsqInsertIrpEx. IoCsqInsertIrp se comporta da mesma forma em ambos os casos. Se IoCsqInsertIrpEx for passada por uma fila que tenha uma rotina CsqInsertIrp, comportar-se-á de forma idêntica à 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.

Existem várias maneiras naturais de usar as rotinas IoCsqXxx para enfileirar e desenfileirar 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 obrigado a distinguir entre IRPs específicos, ele pode simplesmente retirá-los da fila na ordem em que foram enfileirados, da seguinte maneira:

    IoCsqRemoveNextIrp(IoCsq, NULL);

Como alternativa, o driver pode colocar em fila e retirar da fila 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);

O driver pode então retirar o mesmo IRP da fila usando o valor IO_CSQ_IRP_CONTEXT.

    IoCsqRemoveIrp(IoCsq, Irp, &ParticularIrpInQueue);

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