Definieren und Verwenden eines Ereignisobjekts

Jeder Treiber, der ein Ereignisobjekt verwendet, muss KeInitializeEvent, IoCreateNotificationEvent oder IoCreateSynchronizationEvent aufrufen, bevor es wartet, legt fest, löscht oder setzt das Ereignis zurück. In der folgenden Abbildung wird veranschaulicht, wie ein Treiber mit einem Thread ein Ereignisobjekt für die Synchronisierung verwenden kann.

diagram illustrating waiting for an event object.

Wie in der vorherigen Abbildung dargestellt, muss ein solcher Treiber den Speicher für das Ereignisobjekt bereitstellen, das ansässig sein muss. Der Treiber kann die Geräteerweiterung eines vom Treiber erstellten Geräteobjekts, die Controllererweiterung verwenden, wenn es ein Controllerobjekt oder einen vom Treiber zugewiesenen nichtpaged pool verwendet.

Wenn der Treiber KeInitializeEvent aufruft, muss er einen Zeiger an den residenten Speicher des Treibers für das Ereignisobjekt übergeben. Darüber hinaus muss der Aufrufer den Anfangszustand (signalisiert oder nicht signalisiert) für das Ereignisobjekt angeben. Der Aufrufer muss auch den Ereignistyp angeben, der eine der folgenden Sein kann:

  • SyncEvent

    Wenn ein Synchronisierungsereignis auf den Signalstatus festgelegt ist, wird ein einzelner Thread, der darauf wartet, dass das Ereignis auf Not-Signaled zurückgesetzt wird, zur Ausführung berechtigt ist, und der Zustand des Ereignisses wird automatisch auf "Not-Signaled" zurückgesetzt.

    Dieser Ereignistyp wird manchmal als Autoclearing-Ereignis bezeichnet, da der Signaled-Zustand jedes Mal, wenn eine Wartezeit erfüllt ist, automatisch zurückgesetzt wird.

  • Benachrichtigungsereignis

    Wenn ein Benachrichtigungsereignis auf den Signalstatus festgelegt ist, werden alle Threads, die darauf warten, dass das Ereignis auf Not-Signaled zurückgesetzt wird, zur Ausführung berechtigt sein, und das Ereignis bleibt im Signalstatus, bis ein explizites Zurücksetzen auf Not-Signaled auftritt: d. h. es gibt einen Aufruf von KeClearEvent oder KeResetEvent mit dem angegebenen Ereigniszeiger .

Nur wenige Geräte- oder Zwischentreiber verfügen über einen einzelnen Treiber-dedizierten Thread, und zwar einen Satz von Threads, die ihre Vorgänge möglicherweise synchronisieren, indem sie auf ein Ereignis warten, das eine freigegebene Ressource schützt.

Die meisten Treiber, die Ereignisobjekte verwenden, um auf den Abschluss eines I/O-Vorgangs zu warten, legen den Eingabetyp auf NotificationEvent fest, wenn sie KeInitializeEvent aufrufen. Ein Ereignisobjekt, das für IRPs eingerichtet ist, die ein Treiber mit IoBuildSynchronousFsdRequest oder IoBuildDeviceIoControlRequest erstellt, wird fast immer als NotificationEvent initialisiert, da der Aufrufer auf das Ereignis auf die Benachrichtigung wartet, dass seine Anforderung von einem oder mehreren Treibern auf niedrigerer Ebene erfüllt wurde.

Nachdem sich der Treiber selbst initialisiert hat, kann sein dedizierter Thread, falls vorhanden, und andere Routinen ihre Vorgänge auf dem Ereignis synchronisieren. Beispielsweise kann ein Treiber mit einem Thread, der die Warteschlange von IRPs verwaltet, z. B. der System-Floppy-Controllertreiber, die IRP-Verarbeitung auf einem Ereignis synchronisieren, wie in der vorherigen Abbildung dargestellt:

  1. Der Thread, der ein IRP für die Verarbeitung auf dem Gerät dequeuiert hat, ruft KeWaitForSingleObject mit einem Zeiger auf den vom Treiber bereitgestellten Speicher für das initialisierte Ereignisobjekt auf.

  2. Andere Treiberroutine führen gerätespezifische I/O-Vorgänge aus, die zum Erfüllen des IRP erforderlich sind, und wenn diese Vorgänge abgeschlossen sind, ruft die DpcForIsr-Routine des Treibers KeSetEvent mit einem Zeiger auf das Ereignisobjekt auf, eine treiberbestimmte Prioritätssteigerung für den Thread (Increment, wie in der vorherigen Abbildung dargestellt) und ein boolescher Wait-Wert auf FALSE festgelegt. Durch Aufrufen von KeSetEvent wird das Ereignisobjekt auf den Signalstatus festgelegt, wodurch der Status des wartenden Threads so geändert wird, dass er bereit ist.

  3. Der Kernel verteilt den Thread für die Ausführung, sobald ein Prozessor verfügbar ist: das heißt, kein anderer Thread mit höherer Priorität befindet sich derzeit im bereiten Zustand und es gibt keine Kernelmodusroutinen, die bei einem höheren IRQL ausgeführt werden sollen.

    Der Thread kann jetzt das IRP abschließen, wenn der DpcForIsrioCompleteRequest mit dem IRP bereits nicht aufgerufen hat und ein weiteres IRP dequeue, das auf dem Gerät verarbeitet werden soll.

Das Aufrufen von KeSetEvent mit dem auf TRUE festgelegten Wait-Parameter gibt die Absicht des Aufrufers an, sofort eine KeWaitForSingleObject- oder KeWaitForMultipleObjects-Unterstützungsroutine für die Rückgabe von KeSetEvent aufzurufen.

Berücksichtigen Sie die folgenden Richtlinien zum FestlegendesWaitparametersaufKeSetEvent:

Eine pageable Thread- oder pageable Treiberroutine, die bei IRQL < DISPATCH_LEVEL ausgeführt wird, sollte KeSetEvent nie aufrufen, wobei der Wait-Parameter auf TRUE festgelegt ist. Ein solcher Aufruf verursacht einen schwerwiegenden Seitenfehler, wenn der Aufrufer zwischen den Aufrufen von KeSetEvent und KeWaitForSingleObject oder KeWaitForMultipleObjects ausgelagert wird.

Jede Standardtreiberroutine, die bei IRQL = DISPATCH_LEVEL ausgeführt wird, kann nicht auf ein Nichtzerointervall für alle Verteilerobjekte warten, ohne das System herunterzuschalten. Eine solche Routine kann jedoch KeSetEvent aufrufen, während sie bei einem IRQL kleiner oder gleich DISPATCH_LEVEL ausgeführt wird.

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

KeResetEvent gibt den vorherigen Zustand eines bestimmten Ereignisses zurück: Ob er auf "Signaled" festgelegt wurde oder nicht, wenn der Aufruf von KeResetEvent aufgetreten ist. KeClearEvent legt einfach den Status des angegebenen Ereignisses auf "Not-Signaled" fest.

Berücksichtigen Sie die folgende Richtlinie, um die vorherigen Supportroutinen aufzurufen:

Um eine bessere Leistung zu erzielen, sollte jeder Treiber KeClearEvent aufrufen, es sei denn, der Aufrufer benötigt die von KeResetEvent zurückgegebenen Informationen, um zu bestimmen, was als Nächstes ausgeführt werden soll.