Geräteerweiterungen

Bei den meisten Treibern auf mittlerer und niedrigster Ebene ist die Geräteerweiterung die wichtigste Datenstruktur, die einem Geräteobjekt zugeordnet ist. Die interne Struktur ist treiberdefiniert und wird in der Regel für Folgendes verwendet:

  • Verwalten von Gerätestatusinformationen

  • Stellen Sie Speicher für alle vom Kernel definierten Objekte oder andere Systemressourcen bereit, z. B. Spinsperren, die vom Treiber verwendet werden.

  • Halten Sie alle Daten bereit, die der Treiber für die Ausführung seiner E/A-Vorgänge im Systemraum haben muss.

Da die meisten Bus-, Funktions- und Filtertreiber (Treiber der niedrigsten und mittleren Ebene) in einem beliebigen Threadkontext ausgeführt werden (der eines beliebigen Threads, der aktuell ist), ist eine Geräteerweiterung der primäre Ort jedes Treibers, um den Gerätezustand und alle anderen gerätespezifischen Daten beizubehalten, die der Treiber benötigt. Beispielsweise stellt jeder Treiber, der eine CustomTimerDpc - oder CustomDpc-Routine implementiert, in der Regel Speicher für die erforderlichen kerneldefinierten Timer- und/oder DPC-Objekte in einer Geräteerweiterung bereit.

Jeder Treiber mit einem ISR muss Speicher für einen Zeiger auf eine Reihe von kerneldefinierten Interruptobjekten bereitstellen, und die meisten Gerätetreiber speichern diesen Zeiger in einer Geräteerweiterung. Jeder Treiber bestimmt die Größe der Geräteerweiterung, wenn er ein Geräteobjekt erstellt, und jeder Treiber definiert den Inhalt und die Struktur seiner eigenen Geräteerweiterungen.

Die IoCreateDevice - und IoCreateDeviceSecure-Routinen des E/A-Managers weisen Arbeitsspeicher für das Geräteobjekt und die Erweiterung aus dem nicht auslagerten Speicherpool zu.

Jede Standardtreiberroutine, die einen IRP empfängt, erhält auch einen Zeiger auf ein Geräteobjekt, das das Zielgerät für den angeforderten E/A-Vorgang darstellt. Diese Treiberroutinen können über diesen Zeiger auf die entsprechende Geräteerweiterung zugreifen. In der Regel ist ein DeviceObject-Zeiger auch ein Eingabeparameter für den ISR eines Treibers auf der niedrigsten Ebene.

Die folgende Abbildung zeigt einen repräsentativen Satz treiberdefinierter Daten für die Geräteerweiterung eines Treiberobjekts der niedrigsten Ebene. Ein Treiber auf höherer Ebene bietet keinen Speicher für einen Interruptobjektzeiger, der von IoConnectInterrupt zurückgegeben und an KeSynchronizeExecution und IoDisconnectInterrupt übergeben wird. Ein Treiber auf höherer Ebene würde jedoch Speicher für die in der folgenden Abbildung gezeigten Timer- und DPC-Objekte bereitstellen, wenn der Treiber über eine CustomTimerDpc-Routine verfügt. Ein Treiber auf höherer Ebene kann auch Speicher für eine Executive-Spin-Sperre und eine ineinander verriegelte Arbeitswarteschlange bereitstellen.

Diagramm, das eine Beispielgeräteerweiterung für einen Treiber der niedrigsten Ebene veranschaulicht.

Zusätzlich zum Bereitstellen von Speicher für einen Interruptobjektzeiger muss ein Gerätetreiber der niedrigsten Ebene Speicher für eine Interrupt-Spinsperre bereitstellen, wenn sein ISR Interrupts für zwei oder mehr Geräte in verschiedenen Vektoren verarbeitet oder wenn er über mehrere ISR verfügt. Weitere Informationen zum Registrieren eines ISR finden Sie unter Registrieren eines ISR.

In der Regel speichern Treiber Zeiger auf ihre Geräteobjekte in ihren Geräteerweiterungen, wie in der Abbildung gezeigt. Ein Treiber kann auch eine Kopie der Ressourcenliste für das Gerät in der Erweiterung aufbewahren.

Ein Treiber auf höherer Ebene speichert in der Regel einen Zeiger auf das Geräteobjekt des nächstniedrigen Treibers in seiner Geräteerweiterung. Ein Treiber auf höherer Ebene muss einen Zeiger auf das Geräteobjekt des nächstniedrigen Treibers an IoCallDriver übergeben, nachdem er den E/A-Stapelspeicherort des nächstniedrigen Treibers in einem IRP eingerichtet hat, wie unter Behandeln von IRPs erläutert.

Beachten Sie außerdem, dass jeder Treiber auf höherer Ebene, der IRPs für Treiber auf niedrigerer Ebene ordnet, angeben muss, wie viele Stapelspeicherorte die neuen IRPs aufweisen sollen. Insbesondere wenn ein Treiber auf höherer Ebene IoMakeAssociatedIrp, IoAllocateIrp oder IoInitializeIrp aufruft, muss er auf das Zielgerätobjekt des Treibers der nächstniedrigen Ebene zugreifen, um seinen StackSize-Wert zu lesen, um die richtige StackSize als Argument für diese Supportroutinen anzugeben.

Während ein Treiber auf höherer Ebene Daten vom Geräteobjekt des Treibers der nächstniedrigen Ebene über den von IoAttachDeviceToDeviceStack zurückgegebenen Zeiger lesen kann, muss ein solcher Treiber die folgenden Implementierungsrichtlinien befolgen:

  • Versuchen Sie niemals, Daten in das Geräteobjekt des niedrigeren Treibers zu schreiben.

    Die einzigen Ausnahmen von dieser Richtlinie sind Dateisysteme, die DO_VERIFY_VOLUME in den Flags der Geräteobjekte von Wechselmedientreibern auf niedrigerer Ebene festlegen und löschen.

  • Versuchen Sie niemals, aus den folgenden Gründen auf die Geräteerweiterung des niedrigeren Treibers zuzugreifen:

    • Es gibt keine sichere Möglichkeit, den Zugriff auf eine einzelne Geräteerweiterung zwischen zwei Treibern zu synchronisieren.

    • Ein Paar von Treibern, die ein solches Backdoor-Kommunikationsschema implementieren, kann nicht einzeln aktualisiert werden, kein Zwischentreiber zwischen ihnen eingefügt werden, ohne die vorhandene Treiberquelle zu ändern, und kann nicht neu kompiliert und problemlos von einer Windows-Plattform zur nächsten verschoben werden.

Um ihre Interoperabilität mit Treibern niedrigerer Ebene von einer Windows-Plattform oder -Version zur nächsten zu erhalten, müssen Treiber auf höherer Ebene entweder die ihnen zugewiesenen IRPs wiederverwenden oder neue IRPs erstellen, und sie müssen IoCallDriver verwenden, um Anforderungen an Treiber auf niedrigerer Ebene zu kommunizieren.