Introduzione agli oggetti Mutex
Come suggerisce il nome, un oggetto mutex è un meccanismo di sincronizzazione progettato per garantire l'accesso a una singola risorsa condivisa tra un set di thread in modalità kernel. Solo i driver di livello più alto, ad esempio i driver di file system (FSD) che usano thread di lavoro esecutivi, probabilmente usano un oggetto mutex.
È possibile che un driver di livello più alto con thread creati dal driver o routine di callback del thread di lavoro possa usare un oggetto mutex. Tuttavia, qualsiasi driver con thread paginabili o routine di callback del thread di lavoro deve gestire le acquisizioni di, attese e versioni degli oggetti mutex molto attentamente.
Gli oggetti Mutex hanno funzionalità predefinite che forniscono thread di sistema (solo in modalità kernel) a vicenda esclusivi, senza deadlock alle risorse condivise nei computer SMP. Il kernel assegna la proprietà di un mutex a un singolo thread alla volta.
L'acquisizione della proprietà di un mutex impedisce il recapito delle normali chiamate di routine asincrone in modalità kernel (API). Il thread non verrà preceduto da un APC a meno che il kernel non problemi un interruzione software APC_LEVEL per eseguire un APC kernel speciale, ad esempio la routine di completamento I/O di gestione I/O che restituisce i risultati al richiedente originale di un'operazione di I/O
Un thread può acquisire la proprietà di un oggetto mutex già proprietario (proprietà ricorsiva), ma un oggetto mutex acquisito in modo ricorsivo non è impostato sullo stato signaled fino a quando il thread rilascia completamente la proprietà. Tale thread deve rilasciare in modo esplicito il mutex quante volte ha acquisito la proprietà prima che un altro thread possa acquisire il mutex.
Il kernel non consente mai a un thread proprietario di un mutex di causare una transizione alla modalità utente senza prima rilasciare il mutex e impostarlo sullo stato Signaled. Se un thread creato o creato da driver FSD che possiede un mutex tenta di restituire il controllo al gestore di I/O prima di rilasciare la proprietà del mutex, il kernel scende il sistema.
Qualsiasi driver che usa un oggetto mutex deve chiamare KeInitializeMutex una volta prima di attendere o rilasciare il relativo oggetto mutex. La figura seguente illustra come due thread di sistema potrebbero usare un oggetto mutex.
Come illustrato nella figura precedente, un driver che usa un oggetto mutex deve fornire l'archiviazione per l'oggetto mutex, che deve essere residente. Il driver può usare l'estensione del dispositivo di un oggetto dispositivo creato dal driver, l'estensione del controller se usa un oggetto controller o un pool non a pagine allocato dal driver.
Quando un driver chiama KeInitializeMutex (in genere dalla routine AddDevice ), deve passare un puntatore all'archiviazione del driver per l'oggetto mutex, che inizializza il kernel allo stato Signaled.
Dopo l'inizializzazione di un driver di questo livello superiore, può gestire l'accesso a una risorsa condivisa a vicenda, come illustrato nella figura precedente. Ad esempio, le routine di invio di un driver per operazioni e thread sincrono intrinsecamente possono usare un mutex per proteggere una coda creata dal driver per i provider di servizi di integrazione.
Poiché KeInitializeMuteximposta sempre lo stato iniziale di un oggetto mutex su Signaled (come illustrato nella figura precedente):
Una chiamata iniziale della routine di invio a KeWaitForSingleObject con il puntatore Mutex inserisce immediatamente il thread corrente nello stato pronto, fornisce la proprietà del thread del mutex e reimposta lo stato mutex su Not-Signaled. Non appena la routine di invio riprende l'esecuzione, può inserire in modo sicuro un'IRP nella coda protetta da mutex.
Quando un secondo thread (un'altra routine di invio, una routine di callback del thread di lavoro fornita dal driver o un thread creato dal driver) chiama KeWaitForSingleObject con il puntatore Mutex , il secondo thread viene inserito nello stato di attesa.
Quando la routine di invio termina l'accodamento dell'IRP come descritto nel passaggio 1, chiama KeReleaseMutex con il puntatore Mutex e un valore di attesa booleano, che indica se intende chiamare KeWaitForSingleObject (o KeWaitForMutexObject) con il Mutex non appena KeReleaseMutex restituisce il controllo.
Supponendo che la routine di invio abbia rilasciato la proprietà del mutex nel passaggio 3 (Wait impostata su FALSE), il mutex è impostato sullo stato Signaled by KeReleaseMutex. Il mutex attualmente non ha alcun proprietario, quindi il kernel determina se un altro thread è in attesa di tale mutex. In tal caso, il kernel rende il secondo thread (vedere il passaggio 2) il proprietario del mutex, eventualmente aumenta la priorità del thread al valore di priorità in tempo reale più basso e ne modifica lo stato in pronto.
Il kernel invia il secondo thread per l'esecuzione non appena è disponibile un processore: ovvero quando nessun altro thread con una priorità maggiore è attualmente nello stato pronto e non sono presenti routine in modalità kernel da eseguire in un irQL superiore. Il secondo thread (una routine di invio che accoda un'IRP o la routine di callback del thread di lavoro del driver o la dequeuzione di un thread creato dal driver) può ora accedere in modo sicuro alla coda di irp protetta dal mutex fino a quando non chiama KeReleaseMutex.
Se un thread acquisisce la proprietà di un oggetto mutex ricorsivamente, tale thread deve chiamare in modo esplicito KeReleaseMutex quante volte in attesa del mutex per impostare l'oggetto mutex sullo stato Signaled. Ad esempio, se un thread chiama KeWaitForSingleObject e quindi KeWaitForMutexObject con lo stesso puntatore Mutex, deve chiamare KeReleaseMutex due volte quando acquisisce il mutex per impostare tale oggetto mutex sullo stato Signaled.
La chiamata a KeReleaseMutex con il parametro Wait impostata su TRUE indica l'intenzione del chiamante di chiamare immediatamente una routine di supporto KeWaitXxx al ritorno da KeReleaseMutex.
Prendere in considerazione le linee guida seguenti per impostare il parametro Wait su KeReleaseMutex:
Una routine di driver paginabile o di thread paginabile eseguita in IRQL PASSIVE_LEVEL non deve mai chiamare KeReleaseMutex con il parametro Wait impostato su TRUE. Tale chiamata causa un errore di pagina irreversibile se il chiamante viene paginato tra le chiamate a KeReleaseMutex e KeWaitXxxObject.
Qualsiasi routine di driver standard eseguita in un irQL maggiore di PASSIVE_LEVEL non può attendere un intervallo diverso da zero su qualsiasi oggetto dispatcher senza arrestare il sistema. Tuttavia, tale routine può chiamare KeReleaseMutex se possiede il mutex durante l'esecuzione in un IRQL minore o uguale a DISPATCH_LEVEL.
Per un riepilogo degli IRQLs in cui vengono eseguite routine di driver standard, vedere Gestione delle priorità hardware.