Einführung in Mutex-Objekte

Wie der Name schon sagt, ist ein Mutex-Objekt ein Synchronisierungsmechanismus, der den gegenseitig ausschließenden Zugriff auf eine einzelne Ressource sicherstellt, die von einer Reihe von Kernelmodusthreads gemeinsam genutzt wird. Nur Treiber der höchsten Ebene, z. B. Dateisystemtreiber (FSDs), die Executive Workerthreads verwenden, verwenden wahrscheinlich ein Mutex-Objekt.

Möglicherweise kann ein Treiber der höchsten Ebene mit vom Treiber erstellten Threads oder Workerthread-Rückrufroutinen ein Mutex-Objekt verwenden. Jeder Treiber mit auslagerungsfähigen Threads oder Workerthread-Rückrufroutinen muss jedoch die Käufe, Wartezeiten und Freigaben seiner Mutex-Objekte sehr sorgfältig verwalten.

Mutex-Objekte verfügen über integrierte Features, die Systemthreads (nur Kernelmodus) bieten, die sich gegenseitig ausschließenden, deadlockfreien Zugriff auf freigegebene Ressourcen auf SMP-Computern bieten. Der Kernel weist einem einzelnen Thread gleichzeitig den Besitz eines Mutex zu.

Der Erwerb des Besitzes eines Mutex verhindert die Übermittlung normaler asynchroner Prozeduraufrufe (asynchrone Prozeduraufrufe, APCs im Kernelmodus). Der Thread wird nicht von einem APC vorzeitig entfernt, es sei denn, der Kernel gibt einen APC_LEVEL Software-Interrupt aus, um einen speziellen Kernel-APC auszuführen, z. B. die IRP-Vervollständigungsroutine des E/A-Managers, die Ergebnisse an den ursprünglichen Anforderer eines E/A-Vorgangs zurückgibt.

Ein Thread kann den Besitz eines Mutex-Objekts erwerben, das er bereits besitzt (rekursiver Besitz), aber ein rekursiv erworbenes Mutex-Objekt wird erst auf den Signalzustand festgelegt, bis der Thread seinen Besitz vollständig freigibt. Ein solcher Thread muss den Mutex so oft freigeben, wie er den Besitz erworben hat, bevor ein anderer Thread den Mutex abrufen kann.

Der Kernel erlaubt nie, dass ein Thread, der einen Mutex besitzt, einen Übergang in den Benutzermodus bewirkt, ohne zuerst den Mutex loszulassen und ihn auf den Signalzustand festzulegen. Wenn ein von FSD erstellter oder treiber erstellter Thread, der einen Mutex besitzt, versucht, die Steuerung an den E/A-Manager zurückzugeben, bevor der Besitz des Mutex freigegeben wird, wird das System vom Kernel heruntergefahren.

Jeder Treiber, der ein Mutex-Objekt verwendet, muss KeInitializeMutex einmal aufrufen, bevor er sein Mutex-Objekt wartet oder freigibt. Die folgende Abbildung veranschaulicht, wie zwei Systemthreads ein Mutex-Objekt verwenden können.

Diagramm, das das Warten auf ein Mutex-Objekt veranschaulicht.

Wie die vorherige Abbildung zeigt, muss ein Treiber, der ein Mutex-Objekt verwendet, den Speicher für das Mutex-Objekt bereitstellen, das resident sein muss. Der Treiber kann die Geräteerweiterung eines vom Treiber erstellten Geräteobjekts, die Controllererweiterung verwenden, wenn er ein Controllerobjekt verwendet, oder einen nicht auslagerten Pool, der vom Treiber zugewiesen wird.

Wenn ein Treiber KeInitializeMutex aufruft (in der Regel aus seiner AddDevice-Routine ), muss er einen Zeiger auf den Treiberspeicher für das Mutex-Objekt übergeben, das der Kernel in den Signalzustand initialisiert.

Nachdem ein solcher Treiber der höchsten Ebene initialisiert wurde, kann er den sich gegenseitig ausschließenden Zugriff auf eine freigegebene Ressource verwalten, wie in der vorherigen Abbildung gezeigt. Beispielsweise können die Dispatchroutinen eines Treibers für inhärent synchrone Vorgänge und Threads einen Mutex verwenden, um eine vom Treiber erstellte Warteschlange für IRPs zu schützen.

Da KeInitializeMutexden Anfangszustand eines Mutex-Objekts immer auf Signal festgelegt (wie die vorherige Abbildung zeigt):

  1. Der anfängliche Aufruf von KeWaitForSingleObject einer Dispatchroutine mit dem Mutex-Zeiger versetzt den aktuellen Thread sofort in den bereiten Zustand, verleiht dem Thread den Besitz des Mutex und setzt den Mutex-Zustand auf Not-Signaled zurück. Sobald die Dispatch-Routine wieder ausgeführt wird, kann sie eine IRP sicher in die mutex-geschützte Warteschlange einfügen.

  2. Wenn ein zweiter Thread (eine andere Dispatchroutine, vom Treiber bereitgestellte Workerthread-Rückrufroutine oder vom Treiber erstellte Thread) KeWaitForSingleObject mit dem Mutex-Zeiger aufruft, wird der zweite Thread in den Wartezustand versetzt.

  3. Wenn die Dispatchroutine die Warteschlangen des IRP wie in Schritt 1 beschrieben beendet, ruft sie KeReleaseMutex mit dem Mutex-Zeiger und einem Boolean Wait-Wert auf, der angibt, ob KeWaitForSingleObject (oder KeWaitForMutexObject) mit dem Mutex aufgerufen werden soll, sobald KeReleaseMutex die Steuerung zurückgibt.

  4. Unter der Annahme, dass die Dispatchroutine in Schritt 3 (Wartevorgang auf FALSE) den Besitz des Mutex freigegeben hat, wird der Mutex von KeReleaseMutex auf den Signalzustand festgelegt. Der Mutex hat derzeit keinen Besitzer, sodass der Kernel bestimmt, ob ein anderer Thread auf diesen Mutex wartet. Wenn dies der Fall ist, macht der Kernel den zweiten Thread (siehe Schritt 2) zum Mutex-Besitzer, erhöht möglicherweise die Priorität des Threads auf den niedrigsten Echtzeitprioritätswert und ändert seinen Zustand in ready.

  5. Der Kernel sendet den zweiten Thread zur Ausführung, sobald ein Prozessor verfügbar ist: Das heißt, wenn sich derzeit kein anderer Thread mit einer höheren Priorität im bereiten Zustand befindet und keine Kernelmodusroutinen für die Ausführung auf einem höheren IRQL vorhanden sind. Der zweite Thread (eine Dispatch-Routine, die eine IRP oder die Workerthread-Rückrufroutine des Treibers oder vom Treiber erstellte Thread-Dequeuuierung eines IRP)) kann jetzt sicher auf die mutex-geschützte Warteschlange von IRPs zugreifen, bis KeReleaseMutex aufgerufen wird.

Wenn ein Thread rekursiv den Besitz eines Mutex-Objekts erwirbt, muss dieser Thread KeReleaseMutex explizit aufrufen, solange er auf dem Mutex gewartet hat, um das Mutex-Objekt auf den Signalzustand festzulegen. Wenn ein Thread beispielsweise KeWaitForSingleObject und dann KeWaitForMutexObject mit demselben Mutex-Zeiger aufruft, muss er keReleaseMutex zweimal aufrufen, wenn er den Mutex abruft, um das Mutex-Objekt auf den Signalzustand festzulegen.

Das Aufrufen von KeReleaseMutex , wenn der Wait-Parameter auf TRUE festgelegt ist, gibt an, dass der Aufrufer beabsichtigt, sofort eine KeWaitXxx-Supportroutine bei der Rückkehr von KeReleaseMutex aufzurufen.

Beachten Sie die folgenden Richtlinien zum Festlegen des Wait-Parameters auf KeReleaseMutex:

Ein auslagerungsfähiger Thread oder eine auslagerungsfähige Treiberroutine, die bei IRQL PASSIVE_LEVEL ausgeführt wird, sollte Niemals KeReleaseMutex aufrufen, wobei der Wait-Parameter auf TRUE festgelegt ist. Ein solcher Aufruf verursacht einen schwerwiegenden Seitenfehler, wenn der Aufrufer zwischen den Aufrufen von KeReleaseMutex und KeWaitXxx-Objekt(en) ausgelagert wird.

Jede Standardtreiberroutine, die mit einem IRQL über PASSIVE_LEVEL ausgeführt wird, kann nicht auf ein nonzero-Intervall für Dispatcherobjekte warten, ohne das System herunterfahren zu müssen. Eine solche Routine kann jedoch KeReleaseMutex aufrufen, wenn sie den Mutex besitzt, während sie bei einer IRQL mit weniger als oder gleich DISPATCH_LEVEL ausgeführt wird.

Eine Zusammenfassung der IRQLs, bei denen Standardtreiberroutinen ausgeführt werden, finden Sie unter Verwalten von Hardwareprioritäten.