vmalloc und Freunde

Die nächste Funktion zur Allokation von Speicher, die wir Ihnen vorführen werden, ist vmalloc. Sie alloziert einen zusammenhängenden Speicherbereich im virtuellen Adreßraum. Obwohl die Seiten im physikalischen Speicher nicht notwendigerweise zusammenhängend sind (jede Seite wird mit einem neuen Aufruf von __get_free_page angefordert), sieht der Speicherbereich für den Kernel wie ein zusammenhängender Adreßbereich aus. vmalloc gibt 0 zurück, wenn ein Fehler aufgetreten ist, ansonsten einen Zeiger auf einen linearen Speicherbereich von mindestens der Größe size.

Die Prototypen dieser Funktion und ihrer Verwandten (ioremap, die aber streng genommen keine Allokationsfunktion ist, wird später behandelt) sehen folgendermaßen aus:


#include <linux/vmalloc.h>

void * vmalloc(unsigned long size);
void vfree(void * addr);
void *ioremap(unsigned long offset, unsigned long size);
void iounmap(void * addr);

Man sollte hier betonen, daß die von kmalloc und get_free_pages zurückgegebenen Adressen ebenfalls virtuelle Adressen sind. Die Werte werden noch einmal von der MMU (der Speicherverwaltungseinheit, die normalerweise ein Bestandteil der CPU ist) durchgeknetet.[1] vmalloc unterscheidet sich nicht dadurch, wie die Hardware alloziert wird, sondern dadurch, wie der Kernel die Allokation durchführt.

Der von kmalloc und get_free_pages verwendete (virtuelle) Adreßbereich enthält eine Eins-zu-Eins-Abbildung auf den physikalischen Speicher, möglicherweise um den konstanten Wert PAGE_OFFSET verschoben; die Funktionen müssen die Seitentabellen für diesen Adreßbereich nicht modifizieren. Der von vmalloc und ioremap verwendete Adreßbereich ist dagegen vollkommen synthetisch, und jede Allokation baut den (virtuellen) Speicherbereich auf, indem sie die Seitentabellen passend einrichtet.

Sie können diesen Unterschied beobachten, wenn Sie die Zeiger vergleichen, die von den Allokationsfunktionen zurückgegeben werden. Auf manchen Plattformen (wie etwa x86) sind die von vmalloc zurückgegebenen Adressen einfach höher als die von kmalloc zurückgegebenen. Auf anderen Plattformen (wie MIPS und IA-64) gehören sie zu einem vollständig anderen Adreßbereich. Die für vmalloc zur Verfügung stehenden Adressen liegen im Bereich von VMALLOC_START bis VMALLOC_END. Beide Symbole sind in <asm/pgtable.h> definiert.

Adressen, die von vmalloc alloziert wurden, können nicht außerhalb des Mikroprozessors verwendet werden, weil sie nur im Zusammenhang mit der MMU des Prozessors einen Sinn ergeben. Wenn ein Treiber eine echte physikalische Adresse benötigt (wie eine DMA-Adresse, die von Peripherie-Geräten verwendet wird, um den System-Bus zu steuern), können Sie vmalloc nicht ohne weiteres verwenden. vmalloc ist dann die richtige Wahl, wenn Sie Speicher für einen großen sequentiellen Puffer allozieren wollen, der nur in der Software existiert. Es ist wichtig zu wissen, daß vmalloc mehr Verwaltungsaufwand als __get_free_pages bedeutet, weil es sowohl den Speicher besorgen als auch die Seitentabellen aufbauen muß. Deswegen ist es nicht sinnvoll, vmalloc aufzurufen, wenn man nur eine Seite benötigt.

Der Systemaufruf create_module ist ein Beispiel für eine Funktion, die vmalloc verwendet; hier wird mit vmalloc Speicher für das zu erzeugende Modul angefordert. Code und Daten des Moduls werden später mit copy_from_user in den allozierten Speicherbereich kopiert, nachdem insmod den Code reloziert hat. Damit sieht es so aus, als ob das Modul in zusammenhängenden Speicher geladen worden ist. Indem Sie in /proc/ksyms nachschauen, können Sie überprüfen, daß die von Modulen exportierten Kernel-Symbole in einem anderen Speicherbereich liegen als die vom Kernel selbst exportierten Symbole.

Speicher, der mit vmalloc alloziert wurde, wird mit vfree wieder freigegeben, genau wie kfree mit kmalloc allozierten Speicher wieder freigibt.

Wie vmalloc baut ioremap neue Seitentabellen auf, alloziert aber im Gegensatz zu vmalloc keinen Speicher. Der Rückgabewert von ioremap ist eine virtuelle Adresse, die verwendet werden kann, um auf den angegebenen physikalischen Adreßbereich zuzugreifen; die angeforderte virtuelle Adresse wird schließlich von iounmap wieder freigegeben. Beachten Sie, daß der Rückgabewert von ioremap nicht auf allen Plattformen sicher dereferenziert werden kann; statt dessen sollten Funktionen wie readb verwendet werden. In “the Section called Direkt abgebildeter Speicher in Kapitel 8>” in Kapitel 8> finden Sie die Details dazu.

ioremap ist besonders nützlich, wenn die (physikalische) Adresse eines PCI-Puffers in den (virtuellen) Kernel Space abgebildet werden soll. Beispielsweise kann die Funktion für den Zugriff auf den Frame-Buffer eines Videogeräts verwendet werden. Solche Puffer werden normalerweise an hohen physikalischen Adressen abgebildet, die außerhalb des Adreßbereichs liegen, für den der Kernel beim Booten Seitentabellen aufbaut. Wir gehen in “the Section called Die PCI-Schnittstelle in Kapitel 15” in Kapitel 15 näher auf PCI-Fragen ein.

Wenn Ihr Treiber über verschiedene Architekturen hinweg portabel sein soll, dann sollten Sie von ioremap zurückgegebene Adressen nicht direkt dereferenzieren, sondern immer readb und die anderen in the Section called I/O-Speicher verwenden in Kapitel 8 in Kapitel 8 eingeführten I/O-Funktionen verwenden. Das liegt daran, daß manche Plattformen wie die Alpha-Plattform PCI-Speicherbereiche nicht direkt in den Adreßbereich abbilden können, weil es Unterschiede zwischen der PCI-Spezifikation und den Alpha-Prozessoren hinsichtlich des Datentransports gibt.

Es gibt fast keine Einschränkung, wieviel Speicher vmalloc allozieren und ioremap verfügbar machen kann, auch wenn vmalloc sich weigert, mehr Speicher zu allozieren, als physikalisch verfügbar ist, um typische Fehler oder Tippfehler von Programmierern aufzudecken. Sie sollten aber nicht vergessen, daß das Anfordern von zuviel Speicher mit vmalloc zu den gleichen Problemen führt wie mit kmalloc.

Sowohl ioremap als auch vmalloc sind seitenorientiert (sie arbeiten mit Modifikationen der Seitentabellen), weswegen die relozierte oder allozierte Größe auf die nächste Seitengrenze aufgerundet wird. Außerdem fängt die Linux 2.0-Implementierung von ioremap gar nicht erst an, eine physikalische Adresse abzubilden, die nicht auf einer Seitengrenze liegt. Neuere Kernel erlauben dies durch das “Abrunden” der abzubildenden Adresse und geben einen Offset auf die erste abgebildete Seite zurück.

vmalloc kann leider nicht zur Interrupt-Zeit verwendet werden, weil es intern kmalloc(GFP_KERNEL) verwendet, um Speicherplatz für die Seitentabellen anzufordern, und deswegen schlafen gehen kann. Das sollte aber kein Problem sein — wenn die Verwendung von __get_free_page für einen Interrupt-Handler nicht gut genug ist, dann liegt vermutlich etwas beim Design im Argen.

Ein scull mit virtuellen Adressen: scullv

Im Modul scullv finden Sie Beispiel-Code zur Verwendung von vmalloc. Wie scullp ist auch dieses Modul eine zusammengestrichene Version von scull, die eine andere Allokationsfunktion verwendet, um für das Gerät Platz zum Speichern von Daten zu schaffen.

Das Modul alloziert Speicher in Blocks à 16 Seiten. Diese Allokation wird in größeren Blocks durchgeführt, um eine bessere Performance als mit scullp zu erzielen und vorzuführen, daß etwas, das bei den anderen Allokationstechniken zu lange dauert, doch noch durchführbar ist. Das Allozieren von mehr als einer Seite mit __get_free_pages birgt die Gefahr des Scheiterns, und selbst wenn es gelingt, wird es wahrscheinlich langsam sein. Wie wir schon gesehen haben, ist vmalloc schneller als andere Funktionen, wenn es darum geht, mehrere Seiten anzufordern, aber langsamer, wenn nur eine Seite benötigt wird, weil zusätzlich noch die Seitentabellen aufgebaut werden müssen. scullv hat genau das gleiche Design wie scullp. order gibt die Größenordnung jeder Allokation an und hat einen Default von 4. Der einzige Unterschied zwischen scullv und scullp liegt in der Allokationsverwaltung. Diese Zeilen verwenden vmalloc, um neuen Speicher zu holen:


/* Ein Quantum mit virtuellen Adressen allozieren */
if (!dptr->data[s_pos]) {
    dptr->data[s_pos] =
        (void *)vmalloc(PAGE_SIZE << dptr->order);
    if (!dptr->data[s_pos])
        goto nomem;
    memset(dptr->data[s_pos], 0, PAGE_SIZE << dptr->order);
}

Und diese geben ihn wieder frei:


/* Quantum-Menge freigeben */
for (i = 0; i < qset; i++)
    if (dptr->data[i])
        vfree(dptr->data[i]);

Wenn Sie beide Module mit eingeschalteten Debugging-Informationen kompilieren, können Sie das Allozieren beobachten, indem Sie die in /proc erzeugten Dateien auslesen. Die folgenden Momentaufnahmen stammen von zwei verschiedenen Systemen:


salma% cat /tmp/bigfile > /dev/scullp0; head -5 /proc/scullpmem

Device 0: qset 500, order 0, sz 1048576
  item at e00000003e641b40, qset at e000000025c60000
       0:e00000003007c000
       1:e000000024778000
salma% cat /tmp/bigfile > /dev/scullv0; head -5 /proc/scullvmem

Device 0: qset 500, order 4, sz 1048576
  item at e0000000303699c0, qset at e000000025c87000
       0:a000000000034000
       1:a000000000078000
salma% uname -m
ia64

rudo% cat /tmp/bigfile > /dev/scullp0; head -5 /proc/scullpmem

Device 0: qset 500, order 0, sz 1048576
  item at c4184780, qset at c71c4800
       0:c262b000
       1:c2193000
rudo%  cat /tmp/bigfile > /dev/scullv0; head -5 /proc/scullvmem

Device 0: qset 500, order 4, sz 1048576
  item at c4184b80, qset at c71c4000
       0:c881a000
       1:c882b000
rudo% uname -m
i686

Die Werte zeigen zwei verschiedene Verhaltensweisen. Auf IA-64 werden physikalische und virtuelle Adressen auf vollständig unterschiedliche Adreßbereiche (0xE und 0xA) abgebildet, während vmalloc auf x86-Computern virtuelle Adressen direkt über der für physikalischen Speicher verwendeten Abbildung zurückgibt.

Fußnoten

[1]

Einige Architekturen reservieren Bereiche von “virtuellen” Adressen für den Zugriff auf den physikalischen Speicher. Wenn dies passiert, macht der Linux-Kernel davon Gebrauch, und die Adressen des Kernels und von get_free_pages liegen in einem dieser Speicherbereiche. Der Unterschied ist für Gerätetreiber und anderen Code, der nicht direkt am Speicherverwaltungssubsystem des Kernels beteiligt ist, transparent.