Freigeben über


Sperren von ausgelagertem Code oder Daten

Bestimmte Kernelmodustreiber, z. B. serielle und parallele Treiber, müssen nicht speicherresident sein, es sei denn, die von ihnen verwalteten Geräte sind geöffnet. Solange jedoch eine aktive Verbindung oder ein aktiver Port vorhanden ist, muss ein Teil des Treibercodes, der diesen Port verwaltet, resident sein, um das Gerät zu bedienen. Wenn der Port oder die Verbindung nicht verwendet wird, ist der Treibercode nicht erforderlich. Im Gegensatz dazu muss ein Treiber für einen Datenträger, der Systemcode, Anwendungscode oder die System paging-Datei enthält, immer speicherresident sein, da der Treiber ständig Daten zwischen seinem Gerät und dem System überträgt.

Ein Treiber für ein sporadisch verwendetes Gerät (z. B. ein Modem) kann Systemspeicher freigeben, wenn das verwaltete Gerät nicht aktiv ist. Wenn Sie in einem einzelnen Abschnitt den Code einfügen, der für die Bedienung eines aktiven Geräts zuständig sein muss, und wenn Ihr Treiber den Code im Arbeitsspeicher sperrt, während das Gerät verwendet wird, kann dieser Abschnitt als ausserstellbar festgelegt werden. Wenn das Gerät des Treibers geöffnet wird, bringt das Betriebssystem den auslagerungsfähigen Abschnitt in den Arbeitsspeicher, und der Treiber sperrt ihn dort, bis er nicht mehr benötigt wird.

Der System-CD-Audiotreibercode verwendet dieses Verfahren. Der Code für den Treiber wird entsprechend dem Hersteller des CD-Geräts in auslagerungsfähige Abschnitte gruppiert. Bestimmte Marken sind möglicherweise nie in einem bestimmten System vorhanden. Auch wenn eine CD-ROM auf einem System vorhanden ist, kann selten darauf zugegriffen werden, sodass das Gruppieren von Code in auslagerungsfähige Abschnitte nach CD-Typ sicherstellt, dass Code für Geräte, die nicht auf einem bestimmten Computer vorhanden sind, niemals geladen wird. Wenn jedoch auf das Gerät zugegriffen wird, lädt das System den Code für das entsprechende CD-Gerät. Anschließend ruft der Treiber die MmLockPagableCodeSection-Routine auf, wie unten beschrieben, um seinen Code im Arbeitsspeicher zu sperren, während das Gerät verwendet wird.

Um den auslagerungsfähigen Code in einen benannten Abschnitt zu isolieren, markieren Sie ihn mit der folgenden Compilerdirektive:

#pragma alloc_text(PAGE*Xxx, *RoutineName)

Der Name eines ausgelagerten Codeabschnitts muss mit den vier Buchstaben "PAGE" beginnen und kann von bis zu vier Zeichen gefolgt werden (hier als Xxx dargestellt), um den Abschnitt eindeutig zu identifizieren. Die ersten vier Buchstaben des Abschnittsnamens (d. h. "PAGE") müssen groß geschrieben werden. RoutineName identifiziert einen Einstiegspunkt, der in den ausgelagerten Abschnitt aufgenommen werden soll.

Der kürzeste gültige Name für einen ausgelagerten Codeabschnitt in einer Treiberdatei ist einfach PAGE. Beispielsweise identifiziert RdrCreateConnection die Pragma-Direktive im folgenden Codebeispiel als Einstiegspunkt in einem ausgelagerten Codeabschnitt namens PAGE.

#ifdef  ALLOC_PRAGMA 
#pragma alloc_text(PAGE, RdrCreateConnection) 
#endif 

Um den auslagerungsfähigen Treibercode in den Speicher zu integrieren und zu sperren, ruft ein Treiber MmLockPagableCodeSection auf und übergibt eine Adresse (in der Regel den Einstiegspunkt einer Treiberroutine), die sich im Auslagerungscodeabschnitt befindet. MmLockPagableCodeSection sperrt den gesamten Inhalt des Abschnitts, der die Routine enthält, auf die im Aufruf verwiesen wird. Mit anderen Worten, dieser Aufruf macht jede Routine, die demselben PAGEXxx-Bezeichner zugeordnet ist, resident und im Arbeitsspeicher gesperrt.

MmLockPagableCodeSection gibt ein Handle zurück, das beim Entsperren des Abschnitts (durch Aufrufen der MmUnlockPagableImageSection-Routine ) verwendet werden soll oder wenn der Treiber den Abschnitt an zusätzlichen Stellen im Code sperren muss.

Ein Treiber kann auch selten verwendete Daten als ausgelagert behandeln, sodass sie ebenfalls ausgelagert werden können, bis das unterstützte Gerät aktiv ist. Der Systemmixertreiber verwendet z. B. auslagerungsfähige Daten. Dem Mixergerät ist keine asynchrone E/A zugeordnet, sodass dieser Treiber seine Daten ausserierbar machen kann.

Der Name eines ausgelagerten Datenabschnitts muss mit den vier Buchstaben "PAGE" beginnen und kann mit bis zu vier Zeichen gefolgt werden, um den Abschnitt eindeutig zu identifizieren. Die ersten vier Buchstaben des Abschnittsnamens (d. h. "PAGE") müssen groß geschrieben werden.

Vermeiden Sie das Zuweisen identischer Namen zu Code- und Datenabschnitten. Um den Quellcode besser lesbar zu machen, weisen Treiberentwickler dem Auslagerungscodeabschnitt in der Regel den Namen PAGE zu, da dieser Name kurz ist und in zahlreichen alloc_text Pragma-Direktiven enthalten sein kann. Anschließend werden allen auslagerungsfähigen Datenabschnitten (z. B. PAGEDATA für data_seg, PAGEBSS für bss_seg usw.) längere Namen zugewiesen, die der Treiber möglicherweise benötigt.

Beispielsweise definieren die ersten beiden Pragma-Direktiven im folgenden Codebeispiel zwei seitenfähige Datenabschnitte: PAGEDATA und PAGEBSS. PAGEDATA wird mithilfe der data_seg Pragma-Direktive deklariert und enthält initialisierte Daten. PAGEBSS wird mithilfe der bss_seg Pragma-Direktive deklariert und enthält nicht initialisierte Daten.

#pragma data_seg("PAGEDATA")
#pragma bss_seg("PAGEBSS")

INT Variable1 = 1;
INT Variable2;

CHAR Array1[64*1024] = { 0 };
CHAR Array2[64*1024];

#pragma data_seg()
#pragma bss_seg()

In diesem Codebeispiel Variable1 werden und Array1 explizit initialisiert und daher im Abschnitt PAGEDATA platziert. Variable2 und Array2 werden implizit null initialisiert und im Abschnitt PAGEBSS platziert.

Die implizite Initialisierung globaler Variablen auf 0 reduziert die Größe der ausführbaren Datei auf dem Datenträger und wird gegenüber der expliziten Initialisierung auf 0 (null) bevorzugt. Eine explizite Nullinitialisierung sollte vermieden werden, außer in Fällen, in denen dies erforderlich ist, um eine Variable in einem bestimmten Datenabschnitt zu platzieren.

Um einen Datenabschnitt speicherresident zu machen und ihn im Arbeitsspeicher zu sperren, ruft ein Treiber MmLockPagableDataSection auf und übergibt ein Datenelement, das im Auslagerungsdatenabschnitt angezeigt wird. MmLockPagableDataSection gibt ein Handle zurück, das in nachfolgenden Sperr- oder Entsperranforderungen verwendet werden soll.

Um die auslagerungsfähige status eines gesperrten Abschnitts wiederherzustellen, rufen Sie MmUnlockPagableImageSection auf, und übergeben Sie dabei den handle-Wert, der von MmLockPagableCodeSection oder MmLockPagableDataSection zurückgegeben wird. Die Unload-Routine eines Treibers muss MmUnlockPagableImageSection aufrufen, um jedes Handle freizugeben, das er für sperrbare Code- und Datenabschnitte erhalten hat.

Das Sperren eines Abschnitts ist ein teurer Vorgang, da der Speicher-Manager seine geladene Modulliste durchsuchen muss, bevor die Seiten im Arbeitsspeicher gesperrt werden. Wenn ein Treiber einen Abschnitt von vielen Stellen in seinem Code sperrt, sollte er nach dem ersten Aufruf des MmLockPagableXxx-Abschnittsdas effizientere MmLockPagableSectionByHandle verwenden.

Das an MmLockPagableSectionByHandle übergebene Handle ist das Handle, das vom vorherigen Aufruf von MmLockPagableCodeSection oder MmLockPagableDataSection zurückgegeben wurde.

Der Speicher-Manager verwaltet eine Anzahl für jedes Abschnittshandle und erhöht diese Anzahl jedes Mal, wenn ein Treiber MmLockPagableXxx für diesen Abschnitt aufruft. Durch einen Aufruf von MmUnlockPagableImageSection wird die Anzahl erhöht. Während der Zähler für jedes Abschnittshandle ungleich null ist, bleibt dieser Abschnitt im Arbeitsspeicher gesperrt.

Das Handle für einen Abschnitt ist gültig, solange der Treiber geladen wird. Daher sollte ein Treiber mmLockPagableXxxSection nur einmal aufrufen. Wenn der Treiber zusätzliche Sperraufrufe erfordert, sollte er MmLockPagableSectionByHandle verwenden.

Wenn ein Treiber eine MmLockPagableXxx-Routine für einen bereits gesperrten Abschnitt aufruft, erhöht der Speicher-Manager die Referenzanzahl für den Abschnitt. Wenn der Abschnitt ausgelagert wird, wenn die Sperrroutine aufgerufen wird, wird der Speicher-Manager im Abschnitt ausgelagert und seine Verweisanzahl auf 1 festgelegt.

Die Verwendung dieser Technik minimiert die Auswirkungen des Treibers auf Systemressourcen. Wenn der Treiber ausgeführt wird, kann er den Code und die Daten, die resident sein müssen, im Arbeitsspeicher sperren. Wenn keine E/A-Anforderungen für das Gerät ausstehen (d. h. wenn das Gerät geschlossen ist oder das Gerät nie geöffnet wurde), kann der Treiber denselben Code oder dieselben Daten entsperren, sodass es ausgelagert werden kann.

Nachdem jedoch ein Treiber über Verbindungsunterbrechungen verfügt, muss jeder Treibercode, der während der Interruptverarbeitung aufgerufen werden kann, immer speicherresident sein. Während einige Gerätetreiber bei Bedarf ausgelagert oder in den Arbeitsspeicher gesperrt werden können, müssen sich einige Kernsätze eines solchen Treibercodes und die Daten dauerhaft im Systembereich befinden.

Beachten Sie die folgenden Implementierungsrichtlinien zum Sperren eines Code- oder Datenabschnitts.

  • Die primäre Verwendung der Xxx-Routinen mm(Un)Lock besteht darin, normalerweise nicht ausgelagerten Code oder Daten als ausgelagerten Code oder Daten ausgelagert und als nicht ausgelagerten Code oder Daten zu ermöglichen. Treiber wie der serielle Treiber und der parallele Treiber sind gute Beispiele: Wenn keine geöffneten Handles für ein Gerät vorhanden sind, das ein Treiber verwaltet, werden Teile des Codes nicht benötigt und können ausgelagert bleiben. Der Redirector und der Server sind auch gute Beispiele für Treiber, die diese Technik verwenden können. Wenn keine aktiven Verbindungen vorhanden sind, können beide Komponenten ausgelagert werden.

  • Der gesamte auslagerungsfähige Abschnitt ist im Arbeitsspeicher gesperrt.

  • Ein Abschnitt für Code und ein Abschnitt für Daten pro Treiber ist effizient. Viele benannte, ausserierbare Abschnitte sind im Allgemeinen ineffizient.

  • Halten Sie rein ausserbbare Abschnitte und ausgelagerte, aber bei Bedarf gesperrte Abschnitte getrennt.

  • Denken Sie daran, dass MmLockPagableCodeSection und MmLockPagableDataSection nicht häufig aufgerufen werden sollten. Diese Routinen können zu einer hohen E/A-Aktivität führen, wenn der Speicher-Manager den Abschnitt lädt. Wenn ein Treiber einen Abschnitt von mehreren Speicherorten im Code sperren muss, sollte er MmLockPagableSectionByHandle verwenden.