Semaphorobjekte

Jeder Treiber kann ein Semaphorobjekt verwenden, um Vorgänge zwischen seinen vom Treiber erstellten Threads und anderen Treiberroutinen zu synchronisieren. Beispielsweise kann sich ein treiberdedizierter Thread in einen Wartezustand versetzen, wenn keine ausstehenden E/A-Anforderungen für den Treiber vorhanden sind, und die Dispatchroutinen des Treibers können den Semaphor direkt nach der Warteschlange eines IRP auf den Signalzustand festlegen.

Die Dispatchroutinen von Treibern der obersten Ebene, die im Kontext des Threads ausgeführt werden, der einen E/A-Vorgang anfordert, können einen Semaphor verwenden, um eine Ressource zu schützen, die von den Dispatchroutinen gemeinsam genutzt wird. Niedrigere Treiberverteilungsroutinen für synchrone E/A-Vorgänge können auch einen Semaphor verwenden, um eine Ressource zu schützen, die von dieser Teilmenge der Dispatchroutinen oder mit einem vom Treiber erstellten Thread gemeinsam genutzt wird.

Jeder Treiber, der ein Semaphorobjekt verwendet, muss KeInitializeSemaphor aufrufen, bevor er auf wartet oder das Semaphor freigibt. Die folgende Abbildung veranschaulicht, wie ein Treiber mit einem Thread ein Semaphorobjekt verwenden kann.

Diagramm, das das Warten auf ein Semaphorobjekt veranschaulicht.

Wie die vorherige Abbildung zeigt, muss ein solcher Treiber den Speicher für das Semaphorobjekt bereitstellen, das resident sein sollte. Der Treiber kann die Geräteerweiterung eines vom Treiber erstellten Geräteobjekts, die Controllererweiterung verwenden, wenn er ein Controllerobjekt verwendet, oder einen vom Treiber zugewiesenen Nicht-Auslagerpool.

Wenn die AddDevice-Routine des Treibers KeInitializeSemaphor aufruft, muss ein Zeiger auf den residenten Speicher des Treibers für das Semaphorobjekt übergeben werden. Darüber hinaus muss der Aufrufer einen Count-Wert für das Semaphorobjekt angeben, wie in der vorherigen Abbildung gezeigt, das den Anfangszustand (ungleich null für Signaled) bestimmt.

Der Aufrufer muss auch einen Grenzwert für den Semaphor angeben. Dies kann eine der folgenden Sein:

  • Limit = 1

    Wenn dieses Semaphor auf den Signalzustand festgelegt ist, kann ein einzelner Thread, der darauf wartet, dass der Semaphor auf den Signalzustand festgelegt wird, zur Ausführung berechtigt und auf jede Ressource zugreifen, die durch den Semaphor geschützt ist.

    Dieser Semaphortyp wird auch als binärer Semaphor bezeichnet, da ein Thread entweder über exklusiven Zugriff auf die semaphorgeschützte Ressource verfügt oder nicht.

  • Grenzwert > 1

    Wenn dieses Semaphor auf den Signalzustand festgelegt ist, können einige Threads, die darauf warten, dass das Semaphorobjekt auf den Signalzustand festgelegt wird, für die Ausführung geeignet sein und auf jede Ressource zugreifen, die durch den Semaphor geschützt ist.

    Diese Art von Semaphor wird als Zählsemaphor bezeichnet, da die Routine, die das Semaphor auf den Signalzustand festlegt, auch angibt, wie viele wartende Threads ihre Zustände von Warten auf Bereit ändern können. Die Anzahl solcher wartenden Threads kann der Grenzwert sein, der bei der Initialisierung des Semaphors festgelegt wurde, oder eine Zahl, die kleiner als dieser voreingestellte Grenzwert ist.

Nur wenige Geräte- oder Zwischentreiber verfügen über einen einzelnen vom Treiber erstellten Thread. noch weniger Verfügen über einen Satz von Threads, die möglicherweise darauf warten, dass ein Semaphor abgerufen oder freigegeben wird. Nur wenige vom System bereitgestellte Treiber verwenden Semaphorobjekte, und von denen, die dies tun, verwenden noch weniger ein binäres Semaphor. Obwohl ein binärer Semaphor in der Funktionalität einem Mutex-Objekt ähnlich zu sein scheint, bietet ein binäres Semaphor nicht den integrierten Schutz vor Deadlocks, die ein Mutex-Objekt für Systemthreads hat, die auf SMP-Computern ausgeführt werden.

Nachdem ein Treiber mit einem initialisierten Semaphor geladen wurde, kann er Vorgänge auf dem Semaphor synchronisieren, der eine freigegebene Ressource schützt. Beispielsweise könnte ein Treiber mit einem dedizierten Gerätethread, der die Warteschlangen von IRPs verwaltet, z. B. der System-Floppycontrollertreiber, IRP-Warteschlangen auf einem Semaphor synchronisieren, wie in der vorherigen Abbildung gezeigt:

  1. Der Thread ruft KeWaitForSingleObject mit einem Zeiger auf den vom Treiber bereitgestellten Speicher für das initialisierte Semaphorobjekt auf, um sich in einen Wartezustand zu versetzen.

  2. IrPs kommen an, die Geräte-E/A-Vorgänge erfordern. Die Dispatchroutinen des Treibers fügen jede dieser IRP in eine verriegelte Warteschlange unter Spin-Lock-Steuerung ein und rufen KeReleaseSemaphore mit einem Zeiger auf das Semaphorobjekt, eine vom Treiber festgelegte Prioritätserhöhung für den Thread (Inkrement, wie in der vorherigen Abbildung gezeigt), eine Anpassung von 1, die der Anzahl des Semaphors hinzugefügt wird, während jedes IRP in die Warteschlange gestellt wird, und eine boolesche Wartezeit , die auf FALSE festgelegt ist. Eine Semaphoranzahl ungleich null legt das Semaphorobjekt auf den Signalzustand fest, wodurch der Status des wartenden Threads in Bereit geändert wird.

  3. Der Kernel sendet den Thread zur Ausführung, sobald ein Prozessor verfügbar ist. Das heißt, kein anderer Thread mit einer höheren Priorität befindet sich derzeit im Status Bereit, und es gibt keine Kernelmodusroutinen, die mit einem höheren IRQL ausgeführt werden können.

    Der Thread entfernt ein IRP aus der ineinandergreifenden Warteschlange unter Spin-Lock-Steuerung, übergibt ihn zur weiteren Verarbeitung an andere Treiberroutinen und ruft KeWaitForSingleObject erneut auf . Wenn der Semaphor weiterhin auf den Signalzustand festgelegt ist (d. h. seine Anzahl bleibt ungleich null, was angibt, dass sich mehr IRPs in der ineinandergreifenden Warteschlange des Treibers befinden), ändert der Kernel den Status des Threads erneut von warten in bereit.

    Durch die Verwendung eines Zählsemaphors auf diese Weise weiß ein solcher Treiberthread, dass ein IRP aus der ineinandergreifenden Warteschlange entfernt wird, wenn dieser Thread ausgeführt wird.

Spezifische Informationen zur Verwaltung von IRQL beim Aufrufen von KeReleaseSemaphore finden Sie im Abschnitt Hinweise von KeReleaseSemaphore.

Jede Standardtreiberroutine, die mit einem IRQL ausgeführt wird, der größer als PASSIVE_LEVEL ist, kann nicht auf ein Intervall ungleich null auf Verteilerobjekte warten, ohne das System herunterzusetzen. Weitere Informationen finden Sie unter Kernel Dispatcher Objects . Eine solche Routine kann jedoch KeReleaseSemaphore aufrufen, während sie mit einem IRQL-Wert ausgeführt wird, der kleiner oder gleich DISPATCH_LEVEL ist.

Eine Zusammenfassung der IRQLs, bei denen Standardtreiberroutinen ausgeführt werden, finden Sie unter Verwalten von Hardwareprioritäten. Informationen zu IRQL-Anforderungen einer bestimmten Supportroutine finden Sie auf der Referenzseite der Routine.