Allozierung während des Bootens

Wenn Sie wirklich einen riesigen Puffer physikalisch zusammenhängenden Speichers benötigen, dann müssen Sie diesen allozieren, indem Sie den Speicher bereits während des Bootens anfordern. Diese Technik ist unelegant und unflexibel, gelingt aber auch am sichersten. Natürlich ist es Modulen nicht möglich, Speicher während des Bootens zu allozieren; nur Treiber, die direkt in den Kernel gelinkt sind, können das.

Die Allokation während des Bootens ist die einzige Möglichkeit, zusammenhängende Speicherseiten zu bekommen und dabei die Begrenzungen von get_free_pages hinsichtlich der Puffer-Größe – sowohl hinsichtlich der maximal zulässigen Größe als auch hinsichtlich der eingeschränkten Größenauswahl – zu umgehen. Speicher beim Booten zu allozieren ist eine “unsaubere Technik”, weil dabei sämtliche Speicherverwaltungsregelungen durch das Reservieren eines privaten Speicher-Pools umgangen werden.

Ein spürbares Problem mit der Allokation beim Booten besteht darin, daß es für normale Benutzer kaum möglich ist: Die Technik steht nur für Code zur Verfügung, der in das Kernel-Image eingelinkt ist; ein Gerätetreiber, der diese Art von Allokationstechnik verwendet, kann nur durch das erneute Kompilieren des Kernels und Rebooten des Computers installiert oder ersetzt werden. Glücklicherweise gibt es einige Workarounds für dieses Problem, die wir uns in Kürze anschauen werden.

Obwohl wir Ihnen hier nicht raten wollen, Ihren Speicher beim Booten zu allozieren, ist es doch erwähnenswert, daß dies in den ersten Linux-Versionen (bevor __GFP_DMA eingeführt wurde) die einzige Möglichkeit war, einen DMA-fähigen Puffer zu bekommen.

Einen dedizierten Puffer beim Booten bekommen

Wenn der Kernel gebootet wird, steht ihm der gesamte im System vorhandene physikalische Speicher zur Verfügung. Er initialisiert dann seine Subsysteme durch das Aufrufen der jeweiligen Initialisierungsfunktion, wobei der Initialisierungscode einen Speicher-Puffer zur privaten Verwendung allozieren kann, was die RAM-Menge für den normalen Systembetrieb entsprechend reduziert.

In der Version 2.4 des Kernels wird diese Art von Allokation durch eine der folgenden Funktionen durchgeführt:


#include <linux/bootmem.h>
void *alloc_bootmem(unsigned long size);
void *alloc_bootmem_low(unsigned long size);
void *alloc_bootmem_pages(unsigned long size);
void *alloc_bootmem_low_pages(unsigned long size);

Die Funktionen allozieren entweder ganze Seiten (wenn sie auf _pages enden) oder nicht auf Seitengrenzen ausgerichtete Speicherbereiche. Sie allozieren entweder unteren oder normalen Speicher (siehe die Behandlung von Speicherzonen weiter oben in diesem Kapitel). Eine normale Allokation gibt Speicheradressen oberhalb von MAX_DMA_ADDRESS zurück, unterer Speicher liegt an Adressen unter diesem Wert.

Dieses Interface wurde in der Version 2.3.23 des Kernels eingeführt. Frühere Versionen benutzten ein weniger ausgeklügeltes Interface, ähnlich dem, das in Unix-Büchern beschrieben wird. Im wesentlichen erwarteten die Initialisierungsfunktionen mehrerer Kernel-Subsysteme zwei unsigned long-Argumente, die die aktuellen Grenzen des freien Speicherbereiches repräsentierten. Jede dieser Funktionen konnte dann einen Teil von diesem Bereich abschneiden und die neue untere Grenze zurückgeben. Ein Treiber, der Speicher beim Booten allozierte, konnte also zusammenhängenden Speicher aus dem linearen Array des verfügbaren RAMs entnehmen.

Das Hauptproblem mit diesem älteren Mechanismus zur Bearbeitung von Allokationsanforderungen beim Booten bestand darin, daß nicht alle Initialisierungsfunktionen die untere Speichergrenze ändern konnten, so daß das Schreiben eines Treibers, der eine solche Allokation benötigte, normalerweise mit dem Liefern eines Kernel-Patches einherging. Andererseits kann alloc_bootmem von der Initialisierungsfunktion eines beliebigen Kernel-Subsystems aufgerufen werden, sofern dies während des Bootens geschieht.

Diese Art, Speicher zu allozieren, hat mehrere Nachteile, darunter den, daß die Puffer nie wieder freigegeben werden können. Nachdem sich ein Treiber den Speicher geholt hat, gibt es keine Möglichkeit, diesen wieder in den Pool freier Seiten zurückzugeben; dieser Pool wird erzeugt, nachdem alle physikalischen Allokationen abgeschlossen sind. Wir empfehlen nicht, an den internen Datenstrukturen der Speicherverwaltung herumzuhacken. Ein Vorteil dieser Technik besteht aber darin, daß ein zusammenhängender physikalischer Speicherbereich bereitgestellt wird, der DMA-geeignet ist. Dies ist derzeit die einzige sichere Möglichkeit, in einem Standard-Kernel einen Puffer von mehr als 32 aufeinanderfolgenden Seiten zu allozieren, weil der maximale Wert von order, den get_free_pages akzeptiert, bei 5 liegt. Wenn Sie aber viele Seiten brauchen und diese nicht zusammenhängen müssen, dann ist vmalloc mit Abstand die beste Funktion für diesen Zweck.

Wenn Sie sich dazu entschließen sollten, Speicher beim Booten zu allozieren, dann müssen Sie init/main.c in den Kernel-Quellen ändern. Mehr zu main.c finden Sie in Kapitel 16>.

Beachten Sie, daß diese “Allokation” nur in Mehrfachen der Seitengröße geschehen kann, auch wenn die Anzahl der Seiten keine Zweierpotenz sein muß.

Der bigphysarea-Patch

Ein anderer Ansatz, große, zusammenhängende Speicherbereiche für Treiber zur Verfügung zu stellen, besteht im Anwenden des bigphysarea-Patch. Dieser inoffizielle Patch ist schon seit einigen Jahren im Netz zu finden. Er hat einen so guten Ruf und ist so nützlich, daß einige Distributionen ihn defaultmäßig auf die installierten Kernel-Images anwenden. Der Patch alloziert den Speicher beim Booten und stellt ihn den Gerätetreibern zur Laufzeit zur Verfügung. Sie müssen dann eine Kommandozeilenoption an den Kernel übergeben, mit der Sie angeben, wieviel Speicher tatsächlich beim Booten reserviert werden soll.

Der Patch wird derzeit an der Adresse http://www.polyware.nl/˜middelink/En/hob-v4l.html gepflegt. Hier finden Sie auch die dazugehörige Dokumentation, die die Allokations-Schnittstelle für Gerätetreiber beschreibt. Der Zoran 36120-Framegrabber-Treiber im 2.4-Kernel (in drivers/char/zr36120.c) verwendet die bigphysarea-Erweiterung, wenn diese verfügbar ist, und ist damit ein gutes Beispiel, wie diese Schnittstelle benutzt wird.

Reservieren von hohen RAM-Adressen

Die letzte (und möglicherweise einfachste) Option zum Allozieren zusammenhängender Speicherbereiche besteht darin, einen Speicherbereich am Ende des physikalischen Speichers zu reservieren (bigphysarea macht das dagegen am Anfang des physikalischen Speichers). Dazu müssen Sie dem Kernel eine Kommandozeilenoption übergeben, mit der die Menge des zu verwaltenden Speichers begrenzt wird. Beispielsweise verwendet einer der Autoren mem=126M, um zwei Megabytes in einem System zu reservieren, das in Wirklichkeit 128 MByte RAM hat. Zur Laufzeit kann dieser Speicher dann alloziert und von Gerätetreibern verwendet werden.

Das allocator-Modul, das Sie im Beispiel-Code auf der O'Reilly-FTP-Site finden, enthält eine Allokations-Schnittstelle, mit der nicht vom Linux-Kernel verwendeter hoher Speicher verwaltet werden kann. Das Modul wird detailliert in ">" in Kapitel 13> beschrieben.

Der Vorteil von allocator gegenüber dem bigphysarea-Patch besteht darin, daß es nicht notwendig ist, die offiziellen Kernel-Quellen zu ändern. Der Nachteil ist die Notwendigkeit einer Kommandozeilenoption, mit der Sie die erreichbare Menge an RAM im System verändern. Außerdem ist allocator in Situationen ungeeignet, in denen hoher Speicher nicht verwendet werden kann, etwa als DMA-Puffer für ISA-Geräte.