Proteção detalhada
A partir do Windows XP, a proteção detalhada está disponível para drivers de modo kernel. Os drivers podem usar a proteção detalhada para acessar com segurança objetos na memória compartilhada do sistema que são criados e excluídos por outro driver do modo kernel.
Um objeto será considerado detalhado quando todos os acessos pendentes dele forem concluídos e nenhuma nova solicitação para acessá-lo for concedida. Por exemplo, um objeto compartilhado pode precisar ser detalhado para ser excluído e substituído por um novo.
O driver que tem o objeto compartilhado pode permitir que outros drivers adquiram e liberem proteção detalhada nele. Quando a proteção detalhada for válida, um driver diferente do proprietário poderá acessar o objeto sem o risco de que o proprietário exclua o objeto antes que o acesso seja concluído. Antes que o acesso comece, o driver que está acessando solicita proteção detalhada no objeto. Para um objeto de longa data, essa solicitação é quase sempre concedida. Depois que o acesso termina, o driver que está acessando libera sua proteção detalhada previamente adquirida no objeto.
Principais rotinas de proteção detalhada
Para começar a compartilhar um objeto, o driver que tem o objeto chama a rotina ExInitializeRundownProtection para inicializar a proteção detalhada no objeto. Depois dessa chamada, outros drivers que acessam o objeto podem adquirir e liberar proteção detalhada no objeto.
Um driver que acessa o objeto compartilhado chama a rotina ExAcquireRundownProtection para solicitar proteção detalhada no objeto. Depois que o acesso é concluído, esse driver chama a rotina ExReleaseRundownProtection para liberar proteção detalhada no objeto.
Se o driver proprietário determinar que o objeto compartilhado deve ser excluído, ele aguardará para excluir o objeto até que todos os acessos pendentes do objeto sejam encerrados.
Em preparação para excluir o objeto compartilhado, o driver proprietário chama a rotina ExWaitForRundownProtectionRelease para aguardar o detalhamento do objeto. Durante essa chamada, a rotina ExWaitForRundownProtectionRelease aguarda que todas as instâncias concedidas anteriormente de proteção detalhada no objeto sejam liberadas, mas impede que novas solicitações de proteção detalhada no objeto sejam concedidas. Depois que o último acesso protegido é concluído e todas as instâncias de proteção detalhada são liberadas, ExWaitForRundownProtectionRelease retorna e o driver proprietário pode excluir o objeto com segurança.
A rotina ExWaitForRundownProtectionRelease bloqueia a execução do thread do driver de chamada até que todos os drivers que mantêm proteção detalhada no objeto compartilhado liberem essa proteção. Para evitar que ExWaitForRundownProtectionRelease bloqueie a execução por períodos excessivamente longos, os threads dos drivers que acessam o objeto compartilhado devem evitar ser suspensos enquanto mantêm a proteção detalhada no objeto. Por esse motivo, os drivers de acesso devem chamar ExAcquireRundownProtection e ExReleaseRundownProtection dentro de uma região crítica ou protegida, ou durante a execução em IRQL = APC_LEVEL.
Formas de usar a proteção detalhada
A proteção detalhada é particularmente útil para conceder acesso a um objeto compartilhado que está quase sempre disponível, mas que, às vezes, pode precisar ser excluído e substituído. Os drivers que acessam dados ou que chamam rotinas nesse objeto não devem tentar acessar o objeto depois que ele é excluído. Caso contrário, esses acessos inválidos podem causar um comportamento imprevisível, corrupção de dados ou, até mesmo, falha do sistema.
Por exemplo, um driver antivírus costuma permanecer carregado na memória enquanto o sistema operacional está em execução. Às vezes, esse driver pode precisar ser descarregado e substituído por uma versão atualizada do driver. Outros drivers enviam solicitações de E/S para o driver antivírus a fim de acessar os dados e as rotinas nesse driver. Antes de enviar uma solicitação de E/S, um componente do kernel, como um gerenciador de filtros do sistema de arquivos, pode adquirir proteção detalhada para impedir o descarregamento prematuro do driver antivírus enquanto ele gerencia a solicitação de E/S. Após a conclusão da solicitação de E/S, a proteção detalhada pode ser liberada.
A proteção detalhada não serializa acessos a um objeto compartilhado. Se dois ou mais drivers de acesso puderem manter a proteção detalhada ao mesmo tempo em um objeto e os acessos ao objeto precisarem ser serializados, algum outro mecanismo, como um bloqueio de exclusão mútua, deverá ser usado para serializar os acessos.
A estrutura EX_RUNDOWN_REF
Uma estrutura EX_RUNDOWN_REF controla o status da proteção detalhada em um objeto compartilhado. Essa estrutura é opaca para os drivers. As rotinas de proteção detalhada fornecidas pelo sistema usam essa estrutura para contar o número de instâncias de proteção detalhada que estão atualmente em vigor no objeto. Essas rotinas também usam essa estrutura para controlar se o objeto está sendo executado ou se está em processo de detalhamento.
Para começar a compartilhar um objeto, o driver que possui o objeto chama ExInitializeRundownProtection para inicializar a estrutura EX_RUNDOWN_REF associada ao objeto. Depois da inicialização, o driver proprietário pode disponibilizar essa estrutura para outros drivers que exigem acesso ao objeto. Os drivers de acesso passam essa estrutura como um parâmetro para as chamadas ExAcquireRundownProtection e ExReleaseRundownProtection, que adquirem e liberam proteção detalhada no objeto. O driver proprietário passa essa estrutura como um parâmetro para a chamada ExWaitForRundownProtectionRelease que aguarda a execução do objeto para que ele possa ser excluído com segurança.
Comparação com bloqueios
A proteção detalhada é uma das várias maneiras de garantir o acesso seguro a um objeto compartilhado. Outra abordagem é usar um bloqueio de software de exclusão mútua. Se um driver exigir acesso a um objeto que está atualmente bloqueado por outro driver, o primeiro driver deverá aguardar que o segundo driver libere o bloqueio. No entanto, adquirir e liberar bloqueios pode se tornar um gargalo de desempenho, pois os bloqueios podem consumir grandes quantidades de memória. Se usados incorretamente, os bloqueios podem fazer com que os drivers que competem pelos mesmos objetos compartilhados fiquem travados. O trabalho para detectar e evitar travamentos normalmente exige desviar recursos computacionais substanciais.
Em contraste com os bloqueios, a proteção detalhada tem requisitos de tempo de processamento e memória relativamente leves. Uma contagem de referência simples é associada ao objeto para garantir que a exclusão do objeto seja adiada até que todos os acessos pendentes do objeto sejam concluídos. Com essa abordagem, é possível usar instruções de hardware atômicas e intertravadas no lugar de bloqueios de software de exclusão mútua para garantir o acesso seguro a um objeto. As chamadas para adquirir e liberar a proteção detalhada são geralmente muito rápidas. Os benefícios de usar um mecanismo leve, como a proteção detalhada, podem ser significativos para um objeto compartilhado que tem uma vida útil longa e é compartilhado entre muitos drivers.
Outras rotinas de proteção detalhada
Há várias outras rotinas de proteção disponíveis, além daquelas que foram mencionadas anteriormente. Essas rotinas adicionais podem ser usadas por alguns drivers.
A rotina ExReInitializeRundownProtection permite que uma estrutura EX_RUNDOWN_REF usada anteriormente seja associada a um novo objeto e inicialize a proteção detalhada nesse objeto.
A rotina ExRundownCompleted atualiza a estrutura EX_RUNDOWN_REF para indicar que a execução do objeto associado foi concluída.
As rotinas ExAcquireRundownProtectionEx e ExReleaseRundownProtectionEx são semelhantes a ExAcquireRundownProtection e ExReleaseRundownProtection. Essas quatro rotinas aumentam ou diminuem a contagem das instâncias de proteção detalhada que estão em vigor em um objeto compartilhado. Enquanto ExAcquireRundownProtection e ExReleaseRundownProtection aumentam e diminuem essa contagem em um, ExAcquireRundownProtectionEx e ExReleaseRundownProtectionEx aumentam e diminuem a contagem por quantidades arbitrárias.
Proteção detalhada com reconhecimento de cache
Uma referência de detalhamento é uma estrutura de dados compacta e rápida, mas pode causar contenção de cache quando muitos processadores tentam adquirir a referência ao mesmo tempo. Isso pode afetar o desempenho e a escalabilidade do driver.
Para evitar esse problema, você pode usar uma referência de detalhamento com reconhecimento de cache para distribuir o controle de referência entre várias linhas de cache. Isso reduz a contenção de cache e melhora o desempenho do driver em computadores com muitos processadores.
Para usar uma referência detalhada com reconhecimento de cache, realize estas etapas:
- Crie um objeto EX_RUNDOWN_REF_CACHE_AWARE seguindo um destes procedimentos:
- Chame ExAllocateCacheAwareRundownProtection. Observe que isso trata a inicialização.
- Como alternativa, para controlar a alocação de memória, chame ExSizeOfRundownProtectionCacheAware, aloque um buffer do tamanho retornado e passe esse buffer e tamanho para ExInitializeRundownProtectionCacheAware.
- Solicite proteção detalhada no objeto antes de acessá-lo chamando a rotina ExAcquireRundownProtectionCacheAware. Essa rotina retornará TRUE se a solicitação for concedida, ou FALSE se o objeto estiver sendo detalhado.
- Libere a proteção detalhada no objeto depois de acessá-lo chamando a rotina ExReleaseRundownProtectionCacheAware.
- Aguarde até que o objeto seja detalhado antes de excluí-lo chamando a rotina ExWaitForRundownProtectionReleaseCacheAware. Essa rotina bloqueia o thread atual até que todas as instâncias de proteção detalhada no objeto sejam liberadas.
- Se o driver tiver chamado ExAllocateCacheAwareRundownProtection anteriormente, ele deverá chamar ExFreeCacheAwareRundownProtection para liberar a referência detalhada.
Para reutilizar uma referência detalhada com reconhecimento de cache, realize estas etapas:
- Depois de chamar ExWaitForRundownProtectionReleaseCacheAware, chame ExRundownCompletedCacheAware para indicar que o detalhamento do objeto antigo foi concluído.
- Chame ExReInitializeRundownProtectionCacheAware para reinicializar a referência depois que o objeto associado for detalhado.
- Agora o driver pode chamar novamente ExAcquireRundownProtectionCacheAware.
Uma referência detalhada com reconhecimento de cache tem a vantagem de melhor desempenho e escalabilidade em situações específicas, mas consome mais memória do que uma referência detalhada regular. Você deve considerar essa compensação ao escolher entre os dois tipos de referências detalhadas.