Présentation des objets Mutex
Comme son nom l’indique, un objet mutex est un mécanisme de synchronisation conçu pour garantir un accès mutuellement exclusif à une ressource unique partagée entre un ensemble de threads en mode noyau. Seuls les pilotes de niveau supérieur, tels que les pilotes de système de fichiers (FSD) qui utilisent des threads de travail exécutifs, sont susceptibles d’utiliser un objet mutex.
Il est possible qu’un pilote de niveau supérieur avec des threads créés par le pilote ou des routines de rappel de thread de travail utilise un objet mutex. Toutefois, tout pilote avec des threads paginables ou des routines de rappel de threads de travail doit gérer très soigneusement les acquisitions, les attentes et les libérations de ses objets mutex.
Les objets Mutex ont des fonctionnalités intégrées qui fournissent des threads système (en mode noyau uniquement) un accès mutuellement exclusif et sans blocage aux ressources partagées dans les machines SMP. Le noyau attribue la propriété d’un mutex à un seul thread à la fois.
L’acquisition de la propriété d’un mutex empêche la remise d’appels de procédure asynchrone (APC) en mode noyau normaux. Le thread ne sera pas préempté par un APC, sauf si le noyau émet une interruption logicielle APC_LEVEL pour exécuter un APC de noyau spécial, comme la routine d’achèvement IRP du gestionnaire d’E/S qui retourne les résultats au demandeur d’origine d’une opération d’E/S
Un thread peut acquérir la propriété d’un objet mutex qu’il possède déjà (propriété récursive), mais un objet mutex acquis de manière récursive n’est pas défini à l’état Signaled tant que le thread n’a pas entièrement libéré sa propriété. Un tel thread doit libérer explicitement le mutex autant de fois qu’il a acquis la propriété avant qu’un autre thread puisse acquérir le mutex.
Le noyau n’autorise jamais un thread qui possède un mutex à provoquer une transition vers le mode utilisateur sans relâcher au préalable le mutex et le définir à l’état Signaled. Si un thread créé par un FSD ou un pilote propriétaire d’un mutex tente de renvoyer le contrôle au gestionnaire d’E/S avant de libérer la propriété du mutex, le noyau fait tomber le système.
Tout pilote qui utilise un objet mutex doit appeler KeInitializeMutex une fois avant d’attendre ou de libérer son objet mutex. La figure suivante montre comment deux threads système peuvent utiliser un objet mutex.
Comme le montre la figure précédente, un pilote qui utilise un objet mutex doit fournir le stockage de l’objet mutex, qui doit être résident. Le pilote peut utiliser l’extension d’appareil d’un objet d’appareil créé par le pilote, l’extension de contrôleur s’il utilise un objet contrôleur ou un pool non paginé alloué par le pilote.
Lorsqu’un pilote appelle KeInitializeMutex (généralement à partir de sa routine AddDevice ), il doit passer un pointeur vers le stockage du pilote pour l’objet mutex, que le noyau initialise à l’état Signaled.
Une fois qu’un pilote de niveau supérieur a été initialisé, il peut gérer l’accès mutuellement exclusif à une ressource partagée, comme illustré dans la figure précédente. Par exemple, les routines de répartition d’un pilote pour les opérations et les threads intrinsèquement synchrones peuvent utiliser un mutex pour protéger une file d’attente créée par le pilote pour les irps.
Étant donné que KeInitializeMutexdéfinit toujours l’état initial d’un objet mutex sur Signaled (comme le montre la figure précédente) :
L’appel initial d’une routine de répartition à KeWaitForSingleObject avec le pointeur Mutex place immédiatement le thread actuel dans l’état prêt, donne la propriété du thread au mutex et réinitialise l’état mutex à Non signalé. Dès que la routine de répartition reprend son exécution, elle peut insérer en toute sécurité un IRP dans la file d’attente protégée par mutex.
Lorsqu’un deuxième thread (une autre routine de répartition, une routine de rappel worker-thread fournie par le pilote ou un thread créé par le pilote) appelle KeWaitForSingleObject avec le pointeur Mutex , le deuxième thread est placé dans l’état d’attente.
Lorsque la routine de répartition termine la mise en file d’attente de l’IRP comme décrit à l’étape 1, elle appelle KeReleaseMutex avec le pointeur Mutex et une valeur d’attente booléenne, ce qui indique si elle a l’intention d’appeler KeWaitForSingleObject (ou KeWaitForMutexObject) avec le Mutex dès que KeReleaseMutex retourne le contrôle.
En supposant que la routine de répartition a libéré sa propriété du mutex à l’étape 3 (Wait défini sur FALSE), le mutex est défini sur l’état Signaled par KeReleaseMutex. Le mutex n’ayant actuellement aucun propriétaire, le noyau détermine si un autre thread attend ce mutex. Si c’est le cas, le noyau fait du deuxième thread (voir l’étape 2) le propriétaire mutex, augmente éventuellement la priorité du thread à la valeur de priorité en temps réel la plus faible et modifie son état sur prêt.
Le noyau distribue le deuxième thread pour l’exécution dès qu’un processeur est disponible, c’est-à-dire lorsqu’aucun autre thread ayant une priorité plus élevée n’est actuellement à l’état prêt et qu’il n’existe aucune routine en mode noyau à exécuter à un IRQL plus élevé. Le deuxième thread (une routine de répartition faisant la mise en file d’attente d’un IRP ou la routine de rappel de thread de travail du pilote ou la suppression d’un IRP créé par le pilote) peut désormais accéder en toute sécurité à la file d’attente protégée par mutex des IRP jusqu’à ce qu’il appelle KeReleaseMutex.
Si un thread acquiert la propriété d’un objet mutex de manière récursive, ce thread doit appeler explicitement KeReleaseMutex autant de fois qu’il a attendu sur le mutex afin de définir l’objet mutex à l’état Signaled. Par exemple, si un thread appelle KeWaitForSingleObject , puis KeWaitForMutexObject avec le même pointeur Mutex , il doit appeler KeReleaseMutex deux fois lorsqu’il acquiert le mutex afin de définir cet objet mutex à l’état Signaled.
L’appel de KeReleaseMutex avec le paramètre Wait défini sur TRUE indique l’intention de l’appelant d’appeler immédiatement une routine de support KeWaitXxx au retour de KeReleaseMutex.
Tenez compte des instructions suivantes pour définir le paramètre Wait sur KeReleaseMutex :
Un thread paginable ou une routine de pilote paginable qui s’exécute à IRQL PASSIVE_LEVEL ne doit jamais appeler KeReleaseMutex avec le paramètre Wait défini sur TRUE. Un tel appel entraîne une erreur de page irrécupérable si l’appelant se trouve être paginé entre les appels à KeReleaseMutex et keWaitXxxObject(s).
Toute routine de pilote standard qui s’exécute à un IRQL supérieur à PASSIVE_LEVEL ne peut pas attendre un intervalle différent de zéro sur les objets de répartiteur sans mettre le système en panne. Toutefois, une telle routine peut appeler KeReleaseMutex si elle possède le mutex lors de l’exécution à un IRQL inférieur ou égal à DISPATCH_LEVEL.
Pour obtenir un résumé des IRQL auxquels les routines de pilotes standard s’exécutent, consultez Gestion des priorités matérielles.