Objetos semáforos
Qualquer driver pode usar um objeto semáforo para sincronizar operações entre seus threads criados pelo driver e outras rotinas de driver. Por exemplo, um thread dedicado ao driver pode se colocar em um estado de espera quando não houver solicitações de E/S pendentes para o driver, e as rotinas de expedição do driver podem definir o semáforo para o estado Sinalizado logo após enfileirarem um IRP.
As rotinas de expedição de drivers de nível mais alto, que são executados no contexto do thread que solicita uma operação de E/S, podem usar um semáforo para proteger um recurso compartilhado entre as rotinas de expedição. Rotinas de expedição de driver de nível inferior para operações de E/S síncronas também podem usar um semáforo para proteger um recurso compartilhado entre esse subconjunto de rotinas de expedição ou com um thread criado pelo driver.
Qualquer driver que usa um objeto semáforo deve chamar KeInitializeSemaphore antes de aguardar ou liberar o semáforo. A figura a seguir ilustra como um driver com um thread pode usar um objeto semáforo.
Como mostra a figura anterior, esse driver deve fornecer o armazenamento para o objeto semáforo, que deve ser residente. O driver pode usar a extensão de dispositivo de um objeto de dispositivo criado pelo driver, a extensão do controlador se ele usar um objeto de controlador ou um pool nãopagado alocado pelo driver.
Quando a rotina AddDevice do driver chama KeInitializeSemaphore, ele deve passar um ponteiro para o armazenamento residente do driver para o objeto semáforo. Além disso, o chamador deve especificar uma Contagem para o objeto semáforo, conforme mostrado na figura anterior, que determina seu estado inicial (diferente de zero para Signaled).
O chamador também deve especificar um Limite para o semáforo, que pode ser um dos seguintes:
Limite = 1
Quando esse semáforo é definido como o estado Sinalizado, um único thread aguardando que o semáforo seja definido como o estado Sinalizado torna-se qualificado para execução e pode acessar qualquer recurso protegido pelo semáforo.
Esse tipo de semáforo também é chamado de semáforo binário porque um thread tem ou não tem acesso exclusivo ao recurso protegido por semáforo.
Limite > 1
Quando esse semáforo é definido como o estado Sinalizado, alguns threads aguardando o objeto semáforo ser definido como o estado Sinalizado tornam-se elegíveis para execução e podem acessar qualquer recurso protegido pelo semáforo.
Esse tipo de semáforo é chamado de semáforo de contagem porque a rotina que define o semáforo para o estado Sinalizado também especifica quantos threads de espera podem ter seus estados alterados de esperando para prontos. O número desses threads de espera pode ser o limite definido quando o semáforo foi inicializado ou algum número menor que esse Limite predefinido.
Poucos drivers intermediários ou de dispositivo têm um único thread criado pelo driver; ainda menos têm um conjunto de threads que podem aguardar a aquisição ou liberação de um semáforo. Poucos drivers fornecidos pelo sistema usam objetos de semáforo e, dos que usam, ainda menos usam um semáforo binário. Embora um semáforo binário pareça ser semelhante em funcionalidade a um objeto mutex, um semáforo binário não fornece a proteção interna contra deadlocks que um objeto mutex tem para threads do sistema em execução em computadores SMP.
Depois que um driver com um semáforo inicializado é carregado, ele pode sincronizar operações no semáforo que protege um recurso compartilhado. Por exemplo, um driver com um thread dedicado ao dispositivo que gerencia o enfileiramento de IRPs, como o driver do controlador de disquete do sistema, pode sincronizar a fila IRP em um semáforo, conforme mostrado na figura anterior:
O thread chama KeWaitForSingleObject com um ponteiro para o armazenamento fornecido pelo driver para que o objeto semáforo inicializado se coloque em um estado de espera.
Os IRPs começam a aparecer e exigem operações de E/S do dispositivo. As rotinas de expedição do driver inserem cada IRP em uma fila intertravada sob o controle spin-lock e chamam KeReleaseSemaphore com um ponteiro para o objeto semáforo, um aumento de prioridade determinado pelo driver para o thread (Incremento, conforme mostrado na figura anterior), um Ajuste de 1 que é adicionado à Contagem do semáforo à medida que cada IRP é enfileirado e uma Espera Booleana definida como FALSE. Uma contagem de semáforos diferente de zero define o objeto semáforo como o estado Sinalizado, alterando assim o estado do thread de espera para pronto.
O kernel despacha o thread para execução assim que um processador está disponível: ou seja, nenhum outro thread com prioridade mais alta está atualmente no estado pronto e não há rotinas de modo kernel a serem executadas em um IRQL mais alto.
O thread remove um IRP da fila intertravada sob o controle de bloqueio de rotação, passa-o para outras rotinas de driver para processamento adicional e chama KeWaitForSingleObject novamente. Se o semáforo ainda estiver definido como o estado Sinalizado (ou seja, sua Contagem permanecerá diferente de zero, indicando que mais IRPs estão na fila intertravada do driver), o kernel altera novamente o estado do thread de esperando para pronto.
Usando um semáforo de contagem dessa maneira, um thread de driver "sabe" que há um IRP a ser removido da fila intertravada sempre que esse thread é executado.
Para obter informações específicas para gerenciar o IRQL ao chamar KeReleaseSemaphore, consulte a seção Comentários de KeReleaseSemaphore.
Qualquer rotina de driver padrão executada em um IRQL maior que PASSIVE_LEVEL não pode esperar por um intervalo diferente de zero em nenhum objeto dispatcher sem derrubar o sistema; consulte Objetos do Dispatcher do Kernel para obter detalhes. No entanto, essa rotina pode chamar KeReleaseSemaphore durante a execução em um IRQL menor ou igual a DISPATCH_LEVEL.
Para obter um resumo das IRQLs nas quais as rotinas de driver padrão são executadas, consulte Gerenciando prioridades de hardware. Para requisitos irql de uma rotina de suporte específica, consulte a página de referência da rotina.