Introdução aos objetos Mutex

Como o nome sugere, um objeto mutex é um mecanismo de sincronização projetado para garantir o acesso mutuamente exclusivo a um único recurso que é compartilhado entre um conjunto de threads no modo kernel. Somente drivers de nível mais alto, como FSDs (drivers do sistema de arquivos) que usam threads de trabalho executivo, provavelmente usarão um objeto mutex.

Possivelmente, um driver de nível mais alto com threads criados pelo driver ou rotinas de retorno de chamada de thread de trabalho pode usar um objeto mutex. No entanto, qualquer driver com threads pagináveis ou rotinas de retorno de chamada de thread de trabalho deve gerenciar as aquisições, esperas e versões de seus objetos mutex com muito cuidado.

Os objetos Mutex têm recursos internos que fornecem threads do sistema (somente no modo kernel) mutuamente exclusivos e sem deadlock para recursos compartilhados em computadores SMP. O kernel atribui a propriedade de um mutex a um único thread por vez.

Adquirir a propriedade de um mutex impede a entrega de APCs (chamadas de procedimento assíncronas) no modo kernel normal. O thread não será precedido por um APC, a menos que o kernel emita uma interrupção de software APC_LEVEL para executar um APC de kernel especial, como a rotina de conclusão de IRP do gerente de E/S que retorna resultados para o solicitante original de uma operação de E/S

Um thread pode adquirir a propriedade de um objeto mutex que ele já possui (propriedade recursiva), mas um objeto mutex adquirido recursivamente não é definido para o estado Signaled até que o thread libere completamente sua propriedade. Esse thread deve liberar explicitamente o mutex tantas vezes quanto adquiriu a propriedade antes que outro thread possa adquirir o mutex.

O kernel nunca permite que um thread que possua um mutex cause uma transição para o modo de usuário sem primeiro liberar o mutex e defini-lo para o estado Sinalizado. Se qualquer thread criado por FSD ou criado por driver que possua um mutex tentar retornar o controle para o gerenciador de E/S antes de liberar a propriedade do mutex, o kernel derrubará o sistema.

Qualquer driver que usa um objeto mutex deve chamar KeInitializeMutex uma vez antes de aguardar ou liberar seu objeto mutex. A figura a seguir ilustra como dois threads do sistema podem usar um objeto mutex.

diagrama ilustrando a espera de um objeto mutex.

Como mostra a figura anterior, um driver que usa um objeto mutex deve fornecer o armazenamento para o objeto mutex, 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 um driver chama KeInitializeMutex (normalmente de sua rotina AddDevice ), ele deve passar um ponteiro para o armazenamento do driver para o objeto mutex, que o kernel inicializa para o estado Sinalizado.

Depois que um driver de nível mais alto for inicializado, ele poderá gerenciar o acesso mutuamente exclusivo a um recurso compartilhado, conforme mostrado na figura anterior. Por exemplo, as rotinas de expedição de um driver para operações e threads inerentemente síncronos podem usar um mutex para proteger uma fila criada pelo driver para IRPs.

Como KeInitializeMutexsempre define o estado inicial de um objeto mutex como Signaled (como mostra a figura anterior):

  1. A chamada inicial de uma rotina de expedição para KeWaitForSingleObject com o ponteiro Mutex coloca o thread atual imediatamente no estado pronto, fornece a propriedade do thread do mutex e redefine o estado mutex para Not-Signaled. Assim que a rotina de expedição retomar a execução, ela poderá inserir com segurança um IRP na fila protegida por mutex.

  2. Quando um segundo thread (outra rotina de expedição, rotina de retorno de chamada de thread de trabalho fornecida pelo driver ou thread criado pelo driver) chama KeWaitForSingleObject com o ponteiro Mutex , o segundo thread é colocado no estado de espera.

  3. Quando a rotina de expedição termina de enfileirar o IRP conforme descrito na etapa 1, ele chama KeReleaseMutex com o ponteiro Mutex e um valor de espera booliana, o que indica se ele pretende chamar KeWaitForSingleObject (ou KeWaitForMutexObject) com o Mutex assim que KeReleaseMutex retorna o controle.

  4. Supondo que a rotina de expedição liberou sua propriedade do mutex na etapa 3 (aguarde definida como FALSE), o mutex é definido como o estado Sinalizado por KeReleaseMutex. Atualmente, o mutex não tem proprietário, portanto, o kernel determina se outro thread está esperando por esse mutex. Nesse caso, o kernel faz do segundo thread (consulte a etapa 2) o proprietário do mutex, possivelmente aumenta a prioridade do thread para o menor valor de prioridade em tempo real e altera seu estado para pronto.

  5. O kernel despacha o segundo thread para execução assim que um processador está disponível: ou seja, quando 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 segundo thread (uma rotina de expedição enfileirando um IRP ou a rotina de retorno de chamada de thread de trabalho do driver ou thread criado pelo driver que desativa um IRP) agora pode acessar com segurança a fila protegida por mutex de IRPs até chamar KeReleaseMutex.

Se um thread adquirir a propriedade de um objeto mutex recursivamente, esse thread deverá chamar explicitamente KeReleaseMutex quantas vezes ele esperou no mutex para definir o objeto mutex para o estado Signaled. Por exemplo, se um thread chamar KeWaitForSingleObject e, em seguida, KeWaitForMutexObject com o mesmo ponteiro Mutex , ele deverá chamar KeReleaseMutex duas vezes quando adquirir o mutex para definir esse objeto mutex para o estado Signaled.

Chamar KeReleaseMutex com o parâmetro Wait definido como TRUE indica a intenção do chamador de chamar imediatamente uma rotina de suporte keWaitXxx no retorno de KeReleaseMutex.

Considere as seguintes diretrizes para definir o parâmetro Wait como KeReleaseMutex:

Um thread paginável ou uma rotina de driver paginável que é executado no IRQL PASSIVE_LEVEL nunca deve chamar KeReleaseMutex com o parâmetro Wait definido como TRUE. Essa chamada causará uma falha fatal na página se o chamador for paginado entre as chamadas para KeReleaseMutex e KeWaitXxxObject(s).

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. No entanto, essa rotina pode chamar KeReleaseMutex se ele possuir o mutex 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.