Usando bloqueios de rotação: um exemplo
Minimizar o tempo que um driver mantém os bloqueios de rotação pode melhorar significativamente o desempenho do driver e do sistema em geral. Por exemplo, considere a figura a seguir, que mostra como um bloqueio de rotação de interrupção protege dados específicos do dispositivo que devem ser compartilhados entre um ISR e as rotinas StartIo e DpcForIsr em um computador SMP.
Enquanto o ISR do driver é executado em DIRQL em um processador, sua rotina StartIo é executada em DISPATCH_LEVEL em um segundo processador. O manipulador de interrupção do kernel contém o InterruptSpinLock para o ISR do driver, que acessa dados protegidos e específicos do dispositivo, como estado ou ponteiros para registros de dispositivo (SynchronizeContext), na extensão de dispositivo do driver. A rotina StartIo , que está pronta para acessar SynchronizeContext, chama KeSynchronizeExecution, passando um ponteiro para os objetos de interrupção associados, SynchronizeContext compartilhado e a rotina SynchCritSection do driver (AccessDevice na figura anterior).
Até que o ISR retorne, liberando o InterruptSpinLock do driver, KeSynchronizeExecutiongira no segundo processador, impedindo que AccessDevice toque em SynchronizeContext. No entanto, KeSynchronizeExecution também gera IRQL no segundo processador para SynchronizeIrql dos objetos de interrupção, impedindo assim que outra interrupção de dispositivo ocorra nesse processador para que AccessDevice possa ser executado em DIRQL assim que o ISR retornar. No entanto, interrupções dirql mais altas para outros dispositivos, interrupções de relógio e interrupções de falha de energia ainda podem ocorrer em qualquer processador.
Quando o ISR enfileira o DpcForIsr do driver e retorna, AccessDevice é executado no segundo processador no SynchronizeIrql dos objetos de interrupção associados e acessa SynchronizeContext. Enquanto isso, o DpcForIsr é executado em outro processador em DISPATCH_LEVEL IRQL. O DpcForIsr também está pronto para acessar SynchronizeContext, portanto, ele chama KeSynchronizeExecution usando os mesmos parâmetros que a rotina StartIo fez na Etapa 1.
Quando KeSynchronizeExecution adquire o bloqueio de rotação e executa o AccessDevice em nome da rotina StartIo , a rotina de sincronização fornecida pelo driver AccessDevice recebe acesso exclusivo ao SynchronizeContext. Como o AccessDevice é executado no SynchronizeIrql, o ISR do driver não pode adquirir o bloqueio de rotação e acessar a mesma área até que o bloqueio de rotação seja liberado, mesmo que o dispositivo interrompa em outro processador enquanto o AccessDevice estiver em execução.
AccessDevice retorna, liberando o bloqueio de rotação. A rotina StartIo retoma a execução em DISPATCH_LEVEL no segundo processador. KeSynchronizeExecution agora executa AccessDevice no terceiro processador, para que ele possa acessar SynchronizeContext em nome do DpcForIsr. No entanto, se uma interrupção de dispositivo tivesse ocorrido antes do DpcForIsr chamado KeSynchronizeExecution na Etapa 2, o ISR poderia ser executado em outro processador antes que KeSynchronizeExecution pudesse adquirir o bloqueio de rotação e executar o AccessDevice no terceiro processador.
Como mostra a figura anterior, enquanto uma rotina em execução em um processador mantém um bloqueio de rotação, todas as outras rotinas tentando adquirir esse bloqueio de rotação não fazem nenhum trabalho. Cada rotina tentando adquirir um bloqueio de rotação já mantido simplesmente gira em seu processador atual até que o titular libere o bloqueio de rotação. Quando um bloqueio de rotação é liberado, uma e apenas uma rotina pode adquiri-la; todas as outras rotinas atualmente tentando adquirir o mesmo bloqueio de rotação continuarão a girar.
O detentor de qualquer bloqueio de rotação é executado em um IRQL gerado: em DISPATCH_LEVEL para um bloqueio de rotação executivo ou em um DIRQL para um bloqueio de rotação de interrupção. Os chamadores de KeAcquireSpinLock e KeAcquireInStackQueuedSpinLock são executados em DISPATCH_LEVEL até que chamem KeReleaseSpinLock ou KeReleaseInStackQueuedSpinLock para liberar o bloqueio. Os chamadores de KeSynchronizeExecution geram automaticamente IRQL no processador atual para SynchronizeIrql dos objetos de interrupção até que a rotina SynchCritSection fornecida pelo chamador saia e KeSynchronizeExecution retorne o controle. Para obter mais informações, consulte Chamando rotinas de suporte que usam bloqueios de rotação.
Tenha em mente o seguinte fato sobre como usar bloqueios de rotação:
Todo o código executado em um IRQL inferior não pode fazer nenhum trabalho no conjunto de processadores ocupados por um titular de spin-lock e por outras rotinas tentando adquirir o mesmo bloqueio de rotação.
Consequentemente, minimizar o tempo que um driver mantém os bloqueios de rotação resulta em um desempenho significativamente melhor do driver e contribui significativamente para melhorar o desempenho geral do sistema.
Como mostra a figura anterior, o manipulador de interrupção de kernel executa rotinas em execução no mesmo IRQL em um computador multiprocessador por 1 a 0. O kernel também faz o seguinte:
Quando uma rotina de driver chama KeSynchronizeExecution, o kernel faz com que a rotina SynchCritSection do driver seja executada no mesmo processador do qual ocorreu a chamada para KeSynchronizeExecution (consulte Etapas 1 e 3).
Quando o ISR de um driver enfileira seu DpcForIsr, o kernel faz com que o DPC seja executado no primeiro processador disponível no qual o IRQL fica abaixo DISPATCH_LEVEL. Esse não é necessariamente o mesmo processador do qual ocorreu a chamada IoRequestDpc (consulte Etapa 2).
As operações de E/S controladas por interrupção de um driver podem tendem a ser serializadas em um computador uniprocessador, mas as mesmas operações podem ser verdadeiramente assíncronas em um computador SMP. Como mostra a figura anterior, o ISR de um driver pode ser executado em CPU4 em um computador SMP antes que seu DpcForIsr comece a processar um IRP para o qual o ISR já lidou com uma interrupção de dispositivo na CPU1.
Em outras palavras, você não deve assumir que um bloqueio de rotação de interrupção pode proteger dados específicos da operação que o ISR salva quando é executado em um processador de ser substituído pelo ISR quando ocorre uma interrupção de dispositivo em outro processador antes da execução da rotina DpcForIsr ou CustomDpc .
Embora um driver possa tentar serializar todas as operações de E/S controladas por interrupção para preservar os dados coletados pelo ISR, esse driver não seria executado muito mais rápido em um computador SMP do que em um computador uniprocessador. Para obter o melhor desempenho possível do driver enquanto permanecem portáteis em plataformas uniprocessador e multiprocessador, os drivers devem usar alguma outra técnica para salvar dados específicos da operação obtidos pelo ISR para processamento subsequente pelo DpcForIsr.
Por exemplo, um ISR pode salvar dados específicos da operação no IRP que ele passa para o DpcForIsr. Um refinamento dessa técnica é implementar um DpcForIsr que consulta uma contagem aumentada por ISR, processa o número de IRPs da contagem usando dados fornecidos pelo ISR e redefine a contagem para zero antes de retornar. É claro que a contagem deve ser protegida pelo bloqueio de rotação de interrupção do driver porque seu ISR e um SynchCritSection manteriam seu valor dinamicamente.