Verwenden der automatischen Synchronisierung
Fast der gesamte Code in einem frameworkbasierten Treiber befindet sich in Ereignisrückruffunktionen. Das Framework synchronisiert die meisten Rückruffunktionen eines Treibers automatisch wie folgt:
Das Framework synchronisiert immer das allgemeine Geräteobjekt, das Funktionale Geräteobjekt (FDO) und die PDO-Ereignisrückruffunktionen (Physical Device Object, PDO), sodass nur eine der Rückruffunktionen (außer EvtDeviceSurpriseRemoval, EvtDeviceQueryRemove und EvtDeviceQueryStop) gleichzeitig für jedes Gerät aufgerufen werden kann. Diese Rückruffunktionen unterstützen Plug & Play (PnP) und Energieverwaltungsereignisse und werden unter IRQL = PASSIVE_LEVEL aufgerufen.
Optional kann das Framework die Ausführung der Rückruffunktionen synchronisieren, die die E/A-Anforderungen eines Treibers verarbeiten, sodass diese Rückruffunktionen einzeln ausgeführt werden. Insbesondere kann das Framework die Rückruffunktionen für Warteschlangen-, Interrupt-, verzögerte Prozeduraufruf-, Timer-, Arbeitselement- und Dateiobjekte sowie die Rückruffunktion EvtRequestCancel des Anforderungsobjekts synchronisieren. Das Framework ruft die meisten dieser Rückruffunktionen unter IRQL = DISPATCH_LEVEL auf. Sie können jedoch erzwingen, dass die Rückruffunktionen für Warteschlangen- und Dateiobjekte bei IRQL = PASSIVE_LEVEL ausgeführt werden. (Rückruffunktionen für Arbeitselemente werden immer bei PASSIVE_LEVEL ausgeführt.)
Das Framework implementiert diese automatische Synchronisierung mithilfe einer Reihe interner Synchronisierungssperren. Das Framework stellt sicher, dass mindestens zwei Threads dieselbe Rückruffunktion nicht gleichzeitig aufrufen können, da jeder Thread warten muss, bis er eine Synchronisierungssperre abrufen kann, bevor eine Rückruffunktion aufgerufen wird. (Optional können Treiber diese Synchronisierungssperren bei Bedarf auch abrufen. Weitere Informationen finden Sie unter Verwenden von Frameworksperren.)
Ihr Treiber sollte objektspezifische Daten im Objektkontextbereich speichern. Wenn Ihr Treiber nur vom Framework definierte Schnittstellen verwendet, können nur Rückruffunktionen, die ein Handle für das Objekt erhalten, auf diese Daten zugreifen. Wenn das Framework Aufrufe mit den Rückruffunktionen des Treibers synchronisiert, wird jeweils nur eine Rückruffunktion aufgerufen, und auf den Kontextraum des Objekts kann jeweils nur eine Rückruffunktion zugreifen.
Sofern Ihr Treiber die Behandlung von Interrupts auf passiver Ebene nicht implementiert, muss Code, den Dienste unterbrechen und auf Interruptdaten zugreifen, am IRQL (DIRQL) des Geräts ausgeführt werden und eine zusätzliche Synchronisierung erfordert. Weitere Informationen finden Sie unter Synchronisieren von Interruptcode.
Wenn Ihr Treiber die automatische Synchronisierung der Rückruffunktionen aktiviert, die E/A-Anforderungen verarbeiten, synchronisiert das Framework diese Rückruffunktionen, sodass sie einzeln ausgeführt werden. In der folgenden Tabelle sind die Rückruffunktionen aufgeführt, die vom Framework synchronisiert werden.
Objekttyp | Synchronisierte Rückruffunktionen |
---|---|
Warteschlangenobjekt |
Anforderungshandler, EvtIoQueueState, EvtIoResume, EvtIoStop |
File-Objekt |
Alle Rückruffunktionen |
Anforderungsobjekt |
Optional kann das Framework diese Rückruffunktionen auch mit allen Interrupt-, DPC-, Arbeitselement- und Timerobjektrückruffunktionen synchronisieren, die Ihr Treiber für das Gerät bereitstellt (mit Ausnahme der EvtInterruptIsr-Rückruffunktion des Interruptobjekts). Um diese zusätzliche Synchronisierung zu aktivieren, muss der Treiber den AutomaticSerialization-Member der Konfigurationsstrukturen dieser Objekte auf TRUE festlegen.
Zusammenfassend stellt die automatische Synchronisierungsfunktion des Frameworks die folgenden Features bereit:
Das Framework synchronisiert immer die PnP- und Energieverwaltungsrückruffunktionen jedes Geräts.
Optional kann das Framework die Anforderungshandler einer E/A-Warteschlange und einige zusätzliche Rückruffunktionen synchronisieren (siehe die vorherige Tabelle).
Ein Treiber kann das Framework auffordern, Rückruffunktionen für Interrupt-, DPC-, Arbeitselement- und Timerobjekte zu synchronisieren.
Treiber müssen Code synchronisieren, der von Diensten unterbricht und auf Interruptdaten zugreift, indem sie die Unter Synchronisieren von Interruptcode beschriebenen Techniken verwenden.
Das Framework synchronisiert keine anderen Rückruffunktionen eines Treibers, z. B. die CompletionRoutine-Rückruffunktion des Treibers oder die Rückruffunktionen, die das E/A-Zielobjekt definiert. Stattdessen stellt das Framework zusätzliche Sperren bereit, die Treiber verwenden können, um diese Rückruffunktionen zu synchronisieren.
Auswählen eines Synchronisierungsbereichs
Sie können auswählen, dass das Framework alle Rückruffunktionen synchronisiert, die allen E/A-Warteschlangen eines Geräts zugeordnet sind. Alternativ können Sie festlegen, dass das Framework die Rückruffunktionen für jede E/A-Warteschlange eines Geräts separat synchronisiert. Die Synchronisierungsoptionen, die Ihrem Treiber zur Verfügung stehen, lauten wie folgt:
Synchronisierung auf Geräteebene
Das Framework synchronisiert die Rückruffunktionen, die die vorherige Tabelle enthält, für alle E/A-Warteschlangen des Geräts, sodass sie einzeln ausgeführt werden. Das Framework erreicht diese Synchronisierung, indem die Synchronisierungssperre des Geräts erworben wird, bevor eine Rückruffunktion aufgerufen wird.
Synchronisierung auf Warteschlangenebene
Das Framework synchronisiert die Rückruffunktionen, die die vorherige Tabelle enthält, für jede einzelne E/A-Warteschlange, sodass sie einzeln ausgeführt werden. Das Framework erreicht diese Synchronisierung, indem die Synchronisierungssperre der Warteschlange erworben wird, bevor eine Rückruffunktion aufgerufen wird.
Keine Synchronisierung
Das Framework synchronisiert nicht die Ausführung der Rückruffunktionen, die die vorherige Tabelle enthält, und ruft keine Synchronisierungssperre ab, bevor die Rückruffunktionen aufgerufen werden. Wenn eine Synchronisierung erforderlich ist, muss sie vom Treiber bereitgestellt werden.
Um anzugeben, ob das Framework Synchronisierung auf Geräteebene, Synchronisierung auf Warteschlangenebene oder keine Synchronisierung für Ihren Treiber bereitstellen soll, können Sie einen Synchronisierungsbereich für Ihr Treiberobjekt, Ihre Geräteobjekte oder Warteschlangenobjekte angeben. Das SynchronizationScope-Element der WDF_OBJECT_ATTRIBUTES Struktur eines Objekts identifiziert den Synchronisierungsbereich des Objekts. Die Werte des Synchronisierungsbereichs, die Ihr Treiber angeben kann, sind:
WdfSynchronizationScopeDevice
Das Framework wird synchronisiert, indem die Synchronisierungssperre eines Geräteobjekts abgerufen wird.
WdfSynchronizationScopeQueue
Das Framework wird synchronisiert, indem die Synchronisierungssperre eines Warteschlangenobjekts abgerufen wird.
WdfSynchronizationScopeNone
Das Framework synchronisiert sich nicht und ruft keine Synchronisierungssperre ab.
WdfSynchronizationScopeInheritFromParent
Das Framework ruft den SynchronizationScope-Wert des Objekts aus dem übergeordneten Objekt des Objekts ab.
Im Allgemeinen wird die Verwendung der Synchronisierung auf Geräteebene nicht empfohlen.
Weitere Informationen zu den Werten des Synchronisierungsbereichs finden Sie unter WDF_SYNCHRONIZATION_SCOPE.
Der Standardsynchronisierungsbereich für Treiberobjekte ist WdfSynchronizationScopeNone. Der Standardsynchronisierungsbereich für Geräte- und Warteschlangenobjekte ist WdfSynchronizationScopeInheritFromParent.
Wenn das Framework die Synchronisierung auf Geräteebene für alle Geräte bereitstellen soll, können Sie die folgenden Schritte ausführen:
Legen Sie SynchronizationScope in der WDF_OBJECT_ATTRIBUTES Struktur des Treiberobjekts des Treibers auf WdfSynchronizationScopeDevice fest.
Verwenden Sie den Standardwert WdfSynchronizationScopeInheritFromParent für jedes Geräteobjekt .
Alternativ können Sie die folgenden Schritte ausführen, um die Synchronisierung auf Geräteebene für einzelne Geräte bereitzustellen:
Verwenden Sie den Standardwert WdfSynchronizationScopeNone für das Treiberobjekt .
Legen Sie SynchronizationScope in der WDF_OBJECT_ATTRIBUTES Struktur einzelner Geräteobjekte auf WdfSynchronizationScopeDevice fest.
Wenn das Framework eine Synchronisierung auf Warteschlangenebene für ein Gerät bereitstellen soll, stehen die folgenden Techniken zur Verfügung:
Für Frameworkversionen 1.9 und höher sollten Sie die Synchronisierung auf Warteschlangenebene für einzelne Warteschlangen aktivieren, indem Sie WdfSynchronizationScopeQueue in der WDF_OBJECT_ATTRIBUTES Struktur des Warteschlangenobjekts festlegen. Dies ist die bevorzugte Technik.
Alternativ können Sie die folgenden Schritte in allen Frameworkversionen ausführen:
- Legen Sie SynchronizationScope in der WDF_OBJECT_ATTRIBUTES Struktur des Geräteobjekts auf WdfSynchronizationScopeQueue fest.
- Verwenden Sie den Standardwert WdfSynchronizationScopeInheritFromParent für die Warteschlangenobjekte jedes Geräts.
Wenn sie nicht möchten, dass das Framework die Rückruffunktionen synchronisiert, die die E/A-Anforderungen Ihres Treibers verarbeiten, verwenden Sie den Standardwert SynchronizationScope für Treiber, Geräte und Warteschlangenobjekte Ihres Treibers. In diesem Fall synchronisiert das Framework die E/A-Rückruffunktionen des Treibers nicht automatisch, und die Rückruffunktionen können unter IRQL <= DISPATCH_LEVEL aufgerufen werden.
Beachten Sie, dass beim Festlegen eines SynchronizationScope-Werts nur die Rückruffunktionen synchronisiert werden, die die vorherige Tabelle enthält. Wenn das Framework auch die Rückruffunktionen interrupt, DPC, Work-Item und Timerobjekt des Treibers synchronisieren soll, muss der Treiber das Element AutomaticSerialization der Konfigurationsstrukturen dieser Objekte auf TRUE festlegen.
Sie können AutomaticSerialization jedoch nur dann auf TRUE festlegen, wenn alle Rückruffunktionen, die Sie synchronisieren möchten, mit derselben IRQL ausgeführt werden. Die Auswahl einer Ausführungsebene, die als Nächstes beschrieben wird, kann zu inkompatiblen IRQL-Ebenen führen. In einer solchen Situation muss der Treiber Frameworksperren verwenden, anstatt automaticSerialization festzulegen. Weitere Informationen zu den Konfigurationsstrukturen für Interrupt-, DPC-, Arbeitselement- und Timerobjekte sowie zu Einschränkungen, die für das Festlegen der AutomaticSerialisierung in diesen Strukturen gelten, finden Sie unter WDF_INTERRUPT_CONFIG, WDF_DPC_CONFIG, WDF_WORKITEM_CONFIG und WDF_TIMER_CONFIG.
Wenn Sie AutomaticSerialization auf TRUE festlegen, sollten Sie Synchronisierung auf Warteschlangenebene auswählen.
Auswählen einer Ausführungsebene
Wenn ein Treiber einige Typen von Frameworkobjekten erstellt, kann er eine Ausführungsebene für das Objekt angeben. Die Ausführungsebene gibt die IRQL an, bei der das Framework die Ereignisrückruffunktionen des Objekts aufruft, die die E/A-Anforderungen eines Treibers verarbeiten.
Wenn ein Treiber eine Ausführungsebene bereitstellt, wirkt sich die angegebene Ebene auf die Rückruffunktionen für Warteschlangen- und Dateiobjekte aus. Normalerweise ruft das Framework diese Rückruffunktionen bei IRQL = DISPATCH_LEVEL auf, wenn der Treiber die automatische Synchronisierung verwendet. Durch Angeben einer Ausführungsebene kann der Treiber das Framework erzwingen, diese Rückruffunktionen unter IRQL = PASSIVE_LEVEL aufzurufen. Das Framework verwendet die folgenden Regeln, wenn der IRQL festgelegt wird, bei dem Warteschlangen- und Dateiobjektrückruffunktionen aufgerufen werden:
Wenn ein Treiber die automatische Synchronisierung verwendet, werden seine Warteschlangen- und Dateiobjektrückruffunktionen unter IRQL = DISPATCH_LEVEL aufgerufen, es sei denn, der Treiber fordert das Framework auf, seine Rückruffunktionen unter IRQL = PASSIVE_LEVEL aufzurufen.
Wenn ein Treiber keine automatische Synchronisierung verwendet und keine Ausführungsebene angibt, können die Rückruffunktionen der Warteschlange und des Dateiobjekts des Treibers unter IRQL <= DISPATCH_LEVEL aufgerufen werden.
Beachten Sie, dass das Framework diese Rückruffunktionen wahrscheinlich unter IRQL = PASSIVE_LEVEL aufrufbar ist, wenn Ihr Treiber Dateiobjektrückruffunktionen bereitstellt, z. B. der Dateiname.
Um eine Ausführungsebene anzugeben, muss Ihr Treiber einen Wert für das ExecutionLevel-Element der WDF_OBJECT_ATTRIBUTES Struktur eines Objekts angeben. Die Werte der Ausführungsebene, die Ihr Treiber angeben kann, sind:
WdfExecutionLevelPassive
Das Framework ruft die Rückruffunktionen des Objekts unter IRQL = PASSIVE_LEVEL auf.
WdfExecutionLevelDispatch
Das Framework kann die Rückruffunktionen des Objekts unter IRQL <= DISPATCH_LEVEL aufrufen. (Wenn der Treiber die automatische Synchronisierung verwendet, ruft das Framework immer die Rückruffunktionen unter IRQL = DISPATCH_LEVEL auf.)
WdfExecutionLevelInheritFromParent
Das Framework ruft den ExecutionLevel-Wert des Objekts vom übergeordneten Objekt ab.
Die Standardausführungsebene für Treiberobjekte ist WdfExecutionLevelDispatch. Die Standardausführungsebene für alle anderen Objekte ist WdfExecutionLevelInheritFromParent.
Weitere Informationen zu den Werten der Ausführungsebene finden Sie unter WDF_EXECUTION_LEVEL.
Die folgende Tabelle zeigt die IRQL-Ebene, auf der das Framework die Rückruffunktionen eines Treibers für Warteschlangenobjekte und Dateiobjekte aufrufen kann.
Synchronisierungsbereich | Ausführungsebene | IRQL von Warteschlangen- und Dateirückruffunktionen |
---|---|---|
WdfSynchronizationScopeDevice |
WdfExecutionLevelPassive |
PASSIVE_LEVEL |
WdfSynchronizationScopeDevice |
WdfExecutionLevelDispatch |
DISPATCH_LEVEL |
WdfSynchronizationScopeQueue |
WdfExecutionLevelPassive |
PASSIVE_LEVEL |
WdfSynchronizationScopeQueue |
WdfExecutionLevelDispatch |
DISPATCH_LEVEL |
WdfSynchronizationScopeNone |
WdfExecutionLevelPassive |
PASSIVE_LEVEL |
WdfSynchronizationScopeNone |
WdfExecutionLevelDispatch |
<= DISPATCH_LEVEL |
Sie können die Ausführungsebene für Treiber, Gerät, Datei, Warteschlange, Timer und allgemeine Objekte auf WdfExecutionLevelPassive oder WdfExecutionLevelDispatch festlegen. Für andere Objekte ist nur WdfExecutionLevelInheritFromParent zulässig.
Sie sollten WdfExecutionLevelPassive angeben, wenn:
Die Rückruffunktionen Ihres Treibers müssen Frameworkmethoden oder WDM-Routinen (Windows Driver Model) aufrufen, die nur unter IRQL = PASSIVE_LEVEL aufgerufen werden können.
Die Rückruffunktionen Ihres Treibers müssen auf auslagerungsfähigen Code oder Daten zugreifen. (Beispielsweise greifen Dateiobjektrückruffunktionen in der Regel auf auslagerungsfähige Daten zu.)
Anstatt WdfExecutionLevelPassive festzulegen, kann Ihr Treiber WdfExecutionLevelDispatch festlegen und eine Rückruffunktion bereitstellen, die Arbeitselemente erstellt, wenn einige Vorgänge unter IRQL = PASSIVE_LEVEL verarbeitet werden müssen.
Bevor Sie entscheiden, ob Ihr Treiber die Ausführungsebene eines Objekts auf WdfExecutionLevelPassive festlegen soll, sollten Sie den IRQL bestimmen, unter dem Ihr Treiber und andere Treiber im Treiberstapel aufgerufen werden. Betrachten Sie die folgenden Situationen:
Wenn sich Ihr Treiber ganz oben im Kernelmodustreiberstapel befindet, ruft das System den Treiber in der Regel unter IRQL = PASSIVE_LEVEL auf. Der Client eines solchen Treibers kann ein UMDF-basierter Treiber oder eine Benutzermodusanwendung sein. Die Angabe von WdfExecutionLevelPassive wirkt sich nicht negativ auf die Leistung des Treibers aus, da das Framework die Aufrufe Ihres Treibers für Arbeitselemente, die unter IRQL = PASSIVE_LEVEL aufgerufen werden, nicht in die Warteschlange stellen muss.
Wenn sich Ihr Treiber nicht ganz oben im Stapel befindet, ruft das System Ihren Treiber wahrscheinlich nicht unter IRQL = PASSIVE_LEVEL auf. Daher muss das Framework die Aufrufe Ihres Treibers für Arbeitselemente in die Warteschlange stellen, die später unter IRQL = PASSIVE_LEVEL aufgerufen werden. Dieser Prozess kann zu einer schlechten Treiberleistung führen, im Vergleich dazu, dass die Rückruffunktionen Ihres Treibers unter IRQL <= DISPATCH_LEVEL aufgerufen werden können.
Beachten Sie für DPC-Objekte und für Timerobjekte, die keine Timer auf passiver Ebene darstellen, dass Sie den AutomaticSerialization-Member der Konfigurationsstruktur nicht auf TRUE festlegen können, wenn Sie die Ausführungsebene des übergeordneten Geräts auf WdfExecutionLevelPassive festgelegt haben. Dies liegt daran, dass das Framework die Rückrufsynchronisierungssperren des Geräteobjekts bei IRQL = PASSIVE_LEVEL abruft und daher die Sperren nicht zum Synchronisieren der DPC- oder Timerobjektrückruffunktionen verwendet werden können, die bei IRQL = DISPATCH_LEVEL ausgeführt werden müssen. In einem solchen Fall sollte Ihr Treiber Framework-Spinsperren in allen Geräte-, DPC- oder Timerobjektrückruffunktionen verwenden, die miteinander synchronisiert werden müssen.
Beachten Sie außerdem, dass Sie für Timerobjekte , die Timer auf passiver Ebene darstellen, den Member AutomaticSerialization der Konfigurationsstruktur nur dann auf TRUE festlegen können, wenn die Ausführungsebene des übergeordneten Geräts auf WdfExecutionLevelPassive festgelegt ist.