Freigeben über


Definieren und Verwenden eines Ereignisobjekts

Jeder Treiber, der ein Ereignisobjekt verwendet, muss KeInitializeEvent, IoCreateNotificationEvent oder IoCreateSynchronizationEvent aufrufen, bevor das Ereignis gewartet, festgelegt, gelöscht oder zurückgesetzt wird. Die folgende Abbildung veranschaulicht, wie ein Treiber mit einem Thread ein Ereignisobjekt für die Synchronisierung verwenden kann.

Diagramm, das das Warten auf ein Ereignisobjekt veranschaulicht.

Wie die vorherige Abbildung zeigt, muss ein solcher Treiber den Speicher für das Ereignisobjekt 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 vom Treiber zugewiesenen Nicht-Auslagerpool.

Wenn der Treiber KeInitializeEvent aufruft, muss er einen Zeiger auf 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 wie folgt aussehen kann:

  • SynchronizationEvent

    Wenn ein Synchronisierungsereignis auf den Signalzustand festgelegt ist, wird ein einzelner Thread, der darauf wartet, dass das Ereignis auf Not-Signaled zurückgesetzt wird, für die Ausführung berechtigt, und der Status des Ereignisses wird automatisch auf Not-Signaled zurückgesetzt.

    Diese Art von Ereignis wird manchmal als autoclearing-Ereignis bezeichnet, da sein Signalstatus bei jeder Wartezeit automatisch zurückgesetzt wird.

  • Benachrichtigungsereignis

    Wenn ein Benachrichtigungsereignis auf den Status Signaled festgelegt ist, werden alle Threads, die auf das Zurücksetzen des Ereignisses auf Not-Signaled gewartet haben, für die Ausführung berechtigt, und das Ereignis bleibt im Signalzustand, bis eine explizite Zurücksetzung auf Not-Signaled erfolgt: Das heißt, 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, geschweige denn über eine Reihe 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 E/A-Vorgangs zu warten, legen den Eingabetyp beim Aufrufen von KeInitializeEvent auf NotificationEvent fest. Ein Ereignisobjekt, das für IRPs eingerichtet ist, das ein Treiber mit IoBuildSynchronousFsdRequest oder IoBuildDeviceIoControlRequest erstellt, wird fast immer als NotificationEvent initialisiert, da der Aufrufer auf das Ereignis wartet, auf die Benachrichtigung, dass seine Anforderung von einem oder mehreren Treibern auf niedrigerer Ebene erfüllt wurde.

Nachdem der Treiber sich selbst initialisiert hat, können dessen treiberspezifischer Thread (falls vorhanden) und andere Routinen ihre Vorgänge für das Ereignis synchronisieren. Beispielsweise kann ein Treiber mit einem Thread, der die Warteschlangen von IRPs verwaltet, z. B. der System-Diskettencontrollertreiber, die IRP-Verarbeitung für ein Ereignis synchronisieren, wie in der vorherigen Abbildung gezeigt:

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

  2. Andere Treiberroutinen führen die E/A-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, einer vom Treiber bestimmten Prioritätserhöhung für den Thread (Inkrement, wie in der vorherigen Abbildung gezeigt) und einer auf FALSE festgelegten booleschen Wartezeit auf. Beim Aufrufen von KeSetEvent wird das Ereignisobjekt auf den Signalzustand festgelegt, wodurch der Status des wartenden Threads in "Ready" 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 bereiten Zustand, und es gibt keine Kernelmodusroutinen, die mit einem höheren IRQL ausgeführt werden können.

    Der Thread kann nun die IRP abschließen, wenn der DpcForIsr nicht bereits IoCompleteRequest mit dem IRP aufgerufen hat, und kann eine andere IRP zur Verarbeitung auf dem Gerät ausstellen.

Das Aufrufen von KeSetEvent , wenn der Wait-Parameter auf TRUE festgelegt ist, gibt an, dass der Aufrufer beabsichtigt, sofort ein KeWaitForSingleObject - oder KeWaitForMultipleObjects-Unterstützungsroutin bei der Rückkehr von KeSetEvent aufzurufen.

Beachten Sie die folgenden Richtlinien zum Festlegen desWait-Parameters aufKeSetEvent:

Eine auslagerungsfähige Thread- oder auslagerungsfähige Treiberroutine, die bei IRQL < DISPATCH_LEVEL ausgeführt wird, sollte niemals KeSetEvent aufrufen, wenn 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 mit IRQL = DISPATCH_LEVEL ausgeführt wird, kann nicht auf ein nonzero-Intervall für Verteilerobjekte warten, ohne das System herunterfahren zu müssen. Eine solche Routine kann jedoch KeSetEvent aufrufen, während eine IRQL ausgeführt wird, die kleiner als oder gleich DISPATCH_LEVEL ist.

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

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

Beachten Sie die folgende Richtlinie für den Aufruf der vorherigen Supportroutinen:

Um die Leistung zu verbessern, 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 zu tun ist.