Partilhar via


Anotações IRQL para drivers

Todos os desenvolvedores de drivers devem considerar níveis de solicitação de interrupção (IRQLs). Um IRQL é um número inteiro entre 0 e 31; PASSIVE_LEVEL, DISPATCH_LEVEL e APC_LEVEL são normalmente referidos simbolicamente, e os outros por seus valores numéricos. O aumento e redução do IRQL devem seguir uma rigorosa disciplina de pilha. Uma função deve ter como objetivo retornar ao mesmo IRQL em que foi chamada. Os valores do IRQL devem ser não decrescentes na pilha. E uma função não pode baixar o IRQL sem primeiro aumentá-lo. As anotações IRQL destinam-se a ajudar a aplicar essas regras.

Quando o código do driver tem anotações IRQL, as ferramentas de análise de código podem fazer uma inferência melhor sobre o intervalo de níveis em que uma função deve ser executada e podem encontrar erros com mais precisão. Por exemplo, você pode adicionar anotações que especificam o IRQL máximo no qual uma função pode ser chamada; se uma função é chamada em um IRQL mais alto, as ferramentas de análise de código podem identificar as inconsistências.

As funções do driver devem ser anotadas com o máximo de informações sobre o IRQL que sejam apropriadas. Se as informações adicionais estiverem disponíveis, isso ajuda as ferramentas de análise de código na verificação subsequente da função de chamada e da função chamada. Em alguns casos, adicionar uma anotação é uma boa maneira de suprimir um falso positivo. Algumas funções, como uma função utilitária, podem ser chamadas em qualquer IRQL. Neste caso, a ausência de uma anotação IRQL é a anotação apropriada.

Ao anotar uma função para IRQL, é especialmente importante considerar como a função pode evoluir, não apenas sua implementação atual. Por exemplo, uma função como implementada pode funcionar corretamente em um IRQL mais alto do que o designer pretendia. Embora seja tentador anotar a função com base no que o código realmente faz, o designer pode estar ciente de requisitos futuros, como a necessidade de reduzir o IRQL máximo para algum aprimoramento futuro ou requisito pendente do sistema. A anotação deve ser derivada da intenção do designer da função, não da implementação real.

Você pode usar as anotações na tabela a seguir para indicar o IRQL correto para uma função e seus parâmetros. Os valores de IRQL são definidos em Wdm.h.

Anotação IRQL Descrição
_IRQL_requires_max_(irql) O irql é o IRQL máximo no qual a função pode ser chamada.
_IRQL_requires_min_(IRQL) O irql é o IRQL mínimo no qual a função pode ser chamada.
_IRQL_requires_(IRQL) A função deve ser inserida no IRQL especificado pelo irql.
_IRQL_raises_(IRQL) A função termina no irql especificado, mas pode apenas ser chamada para aumentar (não baixar) o IRQL atual.
_IRQL_saves_ O parâmetro anotado salva o IRQL atual para restaurar mais tarde.
_IRQL_restores_ O parâmetro anotado contém um valor IRQL de IRQL_saves que deve ser restaurado quando a função retorna.
_IRQL_saves_global_ (tipo, param) O IRQL atual é salvo em um local interno às ferramentas de análise de código a partir do qual o IRQL deve ser restaurado. Esta anotação é usada para anotar uma função. O local é identificado por tipo e ainda mais refinado por parâmetro. Por exemplo, OldIrql poderia ser o tipo, e FastMutex poderia ser o parâmetro que mantinha esse valor IRQL antigo.
_IRQL_restores_global_ (tipo, param) O IRQL salvo pela função anotada com IRQL_saves_global é restaurado a partir de um local que é interno às ferramentas de Análise de Código.
_IRQL_always_function_min_(valor) O valor IRQL é o valor mínimo para o qual a função pode reduzir o IRQL.
_IRQL_always_function_max_(valor) O valor IRQL é o valor máximo para o qual a função pode elevar o IRQL.
_IRQL_requires_same_ A função anotada deve entrar e sair no mesmo IRQL. A função pode alterar o IRQL, mas deve restaurar o IRQL ao seu valor original antes de sair.
_IRQL_uses_cancel_ O parâmetro anotado é o valor de IRQL que deve ser restaurado por uma função de callback DRIVER_CANCEL. Na maioria dos casos, utilize a anotação IRQL_is_cancel.

Anotações para DRIVER_CANCEL

Existe uma diferença entre as anotações _IRQL_uses_cancel_ e _IRQL_is_cancel_. A anotação _IRQL_uses_cancel_ especifica claramente que o parâmetro anotado é o valor IRQL que deve ser restaurado por uma função callback DRIVER_CANCEL. A anotação _IRQL_is_cancel_ é uma anotação composta que inclui _IRQL_uses_cancel_ juntamente com várias outras anotações que garantem o comportamento correto de uma função utilitária de callback DRIVER_CANCEL. Por si só, a anotação _IRQL_uses_cancel_ só ocasionalmente é útil; por exemplo, se as restantes obrigações descritas por _IRQL_is_cancel_ já tiverem sido cumpridas de outra forma.

Anotação IRQL Descrição
_IRQL_is_cancel_ O parâmetro anotado é o IRQL passado como parte da chamada para uma função callback DRIVER_CANCEL. Essa anotação indica que a função é um utilitário que é chamado a partir de rotinas de cancelamento e que completa os requisitos para funções DRIVER_CANCEL, incluindo a liberação do spinlock de cancelamento.

Como as anotações IRQL interagem

As anotações de parâmetros IRQL interagem entre si mais do que outras anotações porque o valor IRQL é definido, redefinido, salvo e restaurado pelas várias funções chamadas.

Especificando IRQL máximo e mínimo

As anotações _IRQL_requires_max_ e _IRQL_requires_min_ especificam que a função não deve ser chamada a partir de um IRQL maior ou menor do que o valor especificado. Por exemplo, quando o PREfast vê uma sequência de chamadas de função que não alteram o IRQL, se encontrar uma função com um valor de _IRQL_requires_max_ que está abaixo de um _IRQL_requires_min_ próximo, ele relata um aviso na segunda chamada que encontrar. O erro pode realmente ocorrer na primeira chamada; A mensagem indica onde ocorreu a outra metade do conflito.

Se as anotações numa função mencionarem o IRQL e não aplicarem explicitamente _IRQL_requires_max_, a ferramenta de análise de código aplicará implicitamente a anotação _IRQL_requires_max_(DISPATCH_LEVEL), o que é normalmente correto, com raras exceções. A aplicação implícita disso como padrão elimina muita confusão de anotações e torna as exceções muito mais visíveis.

A anotação _IRQL_requires_min_(PASSIVE_LEVEL) está sempre implícita porque o IRQL não pode ser inferior; consequentemente, não existe uma regra explícita correspondente sobre o IRQL mínimo. Muito poucas funções têm um limite superior diferente de DISPATCH_LEVEL e um limite inferior diferente de PASSIVE_LEVEL.

Algumas funções são chamadas em um contexto no qual a função chamada não pode elevar com segurança o IRQL acima de algum máximo ou, mais frequentemente, não pode baixá-lo com segurança abaixo de algum mínimo. As anotações _IRQL_always_function_max_ e _IRQL_always_function_min_ ajudam o PREfast a encontrar casos em que isso ocorre involuntariamente.

Por exemplo, funções do tipo DRIVER_STARTIO são anotadas com _IRQL_always_function_min_(DISPATCH_LEVEL). Isso significa que, durante a execução de uma função DRIVER_STARTIO, é um erro baixar o IRQL abaixo de DISPATCH_LEVEL. Outras anotações indicam que a função deve ser inserida e saída em DISPATCH_LEVEL.

Especificando um IRQL explícito

Use as anotações _IRQL_raises_ ou _IRQL_requires_ para ajudar o PREfast a relatar melhor uma inconsistência que é descoberta com as anotações _IRQL_requires_max_ ou _IRQL_requires_min_ porque o PREfast então conhece o IRQL.

A anotação _IRQL_raises_ indica que uma função retorna com o IRQL definido como um novo valor. Quando você usa a anotação _IRQL_raises_, ela também define efetivamente a anotação _drv_maxFunctionIRQL para o mesmo valor IRQL. No entanto, se a função aumentar o IRQL acima do valor final e depois o reduzir para o valor final, deve ser adicionada uma anotação _IRQL_always_function_max_ explícita após a anotação _IRQL_raises_ para permitir o valor IRQL mais alto.

Aumentar ou reduzir o IRQL

A anotação _IRQL_raises_ indica que a função deve ser usada apenas para aumentar o IRQL e não deve ser usada para diminuir o IRQL, mesmo que a sintaxe da função o permita. KeRaiseIrql é um exemplo de uma função que não deve ser usada para diminuir o IRQL.

Salvando e restaurando IRQL

Use as anotações _IRQL_saves_ e _IRQL_restores_ para indicar que o IRQL atual (se é conhecido exatamente ou apenas aproximadamente) é salvo ou restaurado a partir do parâmetro anotado.

Algumas funções salvam e restauram o IRQL implicitamente. Por exemplo, a função do sistema ExAcquireFastMutex salva o IRQL em um local opaco associado ao objeto fast mutex que o primeiro parâmetro identifica; o IRQL salvo é restaurado pela função ExReleaseFastMutex correspondente para esse objeto mutex rápido. Para indicar essas ações explicitamente, use as anotações _IRQL_saves_global_ e _IRQL_restores_global_. Os parâmetros kind e param indicam onde o valor IRQL é salvo. O local onde o valor é salvo não precisa ser especificado com precisão, desde que as anotações que salvam e restauram o valor sejam consistentes.

Mantendo o mesmo IRQL

Você deve anotar todas as funções criadas pelo seu driver que alteram o IRQL, utilizando a anotação _IRQL_requires_same_ ou uma das outras anotações de IRQL, para indicar que a alteração no IRQL é esperada. Na ausência de anotações que indiquem qualquer alteração no IRQL, as ferramentas de análise de código emitirão um aviso para qualquer função que não saia no mesmo IRQL em que a função foi inserida. Se a alteração no IRQL for pretendida, adicione a anotação apropriada para suprimir o erro. Se a alteração no IRQL não for pretendida, o código deve ser corrigido.

Salvando e restaurando o IRQL para rotinas de cancelamento de E/S

Use a anotação _IRQL_uses_cancel_ para indicar que o parâmetro anotado é o valor IRQL que deve ser restaurado por uma função de retorno de chamada DRIVER_CANCEL. Esta anotação indica que a função é uma função utilitária chamada a partir de rotinas de cancelamento e que completa os requisitos estabelecidos para as funções DRIVER_CANCEL (ou seja, cumpre a responsabilidade do chamador).

Por exemplo, a declaração para o tipo de função de retorno de chamada DRIVER_CANCEL é apresentada a seguir. Um dos parâmetros é o IRQL que deve ser restaurado por esta função. As anotações indicam todos os requisitos de uma função cancelar.

// Define driver cancel routine type.  //    
__drv_functionClass(DRIVER_CANCEL)  
_Requires_lock_held_(_Global_cancel_spin_lock_)  
_Releases_lock_(_Global_cancel_spin_lock_)  
_IRQL_requires_min_(DISPATCH_LEVEL)  
_IRQL_requires_(DISPATCH_LEVEL)  
typedef  
VOID  
DRIVER_CANCEL (  
    _Inout_ struct _DEVICE_OBJECT *DeviceObject,  
    _Inout_ _IRQL_uses_cancel_ struct _IRP *Irp  
    );  
  
typedef DRIVER_CANCEL *PDRIVER_CANCEL;  

Anotações SAL 2.0 para controladores