Einführung in Kernel Dispatcher-Objekte

Der Kernel definiert eine Reihe von Objekttypen, die als Kernel dispatcher-Objekte oder nur Dispatcherobjekte bezeichnet werden. Dispatcherobjekte umfassen Timerobjekte, Ereignisobjekte, Semaphorobjekte, Mutex-Objekte und Threadobjekte.

Treiber können Verteilerobjekte als Synchronisierungsmechanismen innerhalb eines nichtarbiträren Threadkontexts verwenden, während sie in IRQL gleich PASSIVE_LEVEL ausführen.

Dispatcher-Objektzustände

Jeder vom Kernel definierte Dispatcherobjekttyp verfügt über einen Zustand, der entweder auf Signaled oder Auf Not-Signaled festgelegt ist.

Eine Gruppe von Threads kann ihre Vorgänge synchronisieren, wenn ein oder mehrere Threads KeWaitForSingleObject, KeWaitForMutexObject oder KeWaitForMultipleObjects aufrufen. Diese Funktionen verwenden Dispatcherobjektzeiger als Eingabe und warten, bis eine andere Routine oder ein Thread ein oder mehrere Dispatcherobjekte auf den Signalzustand festlegt.

Wenn ein Thread keWaitForSingleObject aufruft , um auf ein Dispatcherobjekt (oder KeWaitForMutexObject für einen Mutex) zu warten, wird der Thread in einen Wartezustand versetzt, bis das Dispatcherobjekt auf den Signaled-Zustand festgelegt ist. Ein Thread kann KeWaitForMultipleObjects aufrufen, um entweder darauf zu warten, dass ein oder alle Verteilerobjekte auf Signaled festgelegt werden.

Wenn ein Verteilerobjekt auf den Signaled-Zustand festgelegt ist, ändert der Kernel den Zustand jedes Threads, der darauf wartet, dass dieses Objekt bereit ist. (Synchronisierungstimer und Synchronisierungsereignisse sind Ausnahmen für diese Regel. Wenn ein Synchronisierungsereignis oder -timer signalisiert wird, wird nur ein wartenden Thread auf den Status Bereit festgelegt. Weitere Informationen finden Sie unter Timerobjekte und DPCs und Ereignisobjekte.) Die Ausführung eines Threads im Status Bereit wird gemäß seiner aktuellen Threadpriorität zur Laufzeit und der aktuellen Verfügbarkeit von Prozessoren für jeden Thread mit dieser Priorität geplant.

Wann können Treiber auf Dispatcherobjekte warten?

Im Allgemeinen können Treiber nur warten, bis Dispatcherobjekte festgelegt werden, wenn mindestens einer der folgenden Umstände zutrifft:

  • Der Treiber wird in einem nicht-biträren Threadkontext ausgeführt.

    Das heißt, Sie können den Thread identifizieren, der in einen Wartezustand wechselt. In der Praxis sind die einzigen Treiberroutinen, die in einem nicht-biträren Threadkontext ausgeführt werden, die Routinen DriverEntry, AddDevice, Reinitialize und Unload eines beliebigen Treibers sowie die Dispatchroutinen von Treibern der obersten Ebene. Alle diese Routinen werden direkt vom System aufgerufen.

  • Der Treiber führt eine vollständig synchrone E/A-Anforderung aus.

    Das heißt, dass während der Verarbeitung der E/A-Anforderung kein Treiber Vorgänge in die Warteschlange stellt, und kein Treiber kehrt zurück, bis der Treiber darunter die Verarbeitung der Anforderung abgeschlossen hat.

Darüber hinaus kann ein Treiber nicht in einen Wartezustand wechseln, wenn er mit oder über IRQL gleich DISPATCH_LEVEL ausgeführt wird.

Basierend auf diesen Einschränkungen müssen Sie die folgenden Regeln verwenden:

  • Die Routinen DriverEntry, AddDevice, Reinitialize und Unload eines beliebigen Treibers können auf Verteilerobjekte warten.

  • Die Dispatchroutinen eines Treibers der obersten Ebene können auf Verteilerobjekte warten.

  • Die Dispatchroutinen von Treibern auf niedrigerer Ebene können auf Dispatchobjekte warten, wenn der E/A-Vorgang synchron ist, z. B. Vorgänge zum Erstellen, Leeren, Herunterfahren und Schließen, einige Geräte-E/A-Steuerungsvorgänge sowie einige PnP- und Energievorgänge.

  • Die Dispatchroutinen von Treibern niedrigerer Ebene können nicht auf ein Verteilerobjekt warten, um asynchrone E/A-Vorgänge abzuschließen.

  • Eine Treiberroutine, die mit oder über IRQL DISPATCH_LEVEL ausgeführt wird, darf nicht warten, bis ein Verteilerobjekt auf den Signalzustand festgelegt wird.

  • Ein Treiber darf nicht versuchen, zu warten, bis ein Verteilerobjekt für den Abschluss eines Übertragungsvorgangs zu oder von einem Paginggerät auf den Signalzustand festgelegt wird.

  • Treiberverteilungsroutinen, die Lese-/Schreibanforderungen verarbeiten, können in der Regel nicht warten, bis ein Verteilerobjekt auf den Signalzustand festgelegt wird.

  • Eine Dispatchroutine für eine Geräte-E/A-Steuerungsanforderung kann nur warten, bis ein Verteilerobjekt auf den Signalzustand festgelegt wird, wenn der Übertragungstyp für den E/A-Steuerungscode METHOD_BUFFERED ist.

  • SCSI-Miniporttreiber sollten keine Kernelverteilerobjekte verwenden. SCSI-Miniporttreiber sollten nur SCSI-Portbibliotheksroutinen aufrufen.

Jede andere Standardtreiberroutine wird in einem beliebigen Threadkontext ausgeführt: der eines threads, der aktuell ist, wenn die Treiberroutine aufgerufen wird, um einen Vorgang in der Warteschlange zu verarbeiten oder einen Geräteunterbrechung zu behandeln. Darüber hinaus werden die meisten Standardtreiberroutinen mit einem erhöhten IRQL ausgeführt, entweder am DISPATCH_LEVEL oder für Gerätetreiber bei DIRQL.

Bei Bedarf kann ein Treiber einen dedizierten Gerätethread erstellen, der auf die anderen Routinen des Treibers (außer einer ISR- oder SynchCritSection-Routine ) warten kann, um ein Verteilerobjekt auf den Signalzustand festzulegen und auf den Not-Signaled Zustand zurückzusetzen.

Allgemein gilt: Wenn Sie davon ausgehen, dass Ihr neuer Gerätetreiber häufig länger als 50 Mikrosekunden anhalten muss, während er während E/A-Vorgängen auf Änderungen des Gerätezustands wartet, sollten Sie erwägen, einen Treiber mit einem dedizierten Gerätethread zu implementieren. Wenn der Gerätetreiber auch ein Treiber auf höchster Ebene ist, sollten Sie die Verwendung von Systemarbeitsthreads und die Implementierung einer oder mehrerer Workerthreadrückrufroutinen in Betracht ziehen. Weitere Informationen finden Sie unter PsCreateSystemThread und Verwalten von ineinandergreifenden Warteschlangen mit einem Driver-Created Thread.