Organisation des logischen Adressraums des Linux-Kernels

8

Laut "Write Great Code" ist der Speicher in fast allen Betriebssystemen zur Laufzeit in folgende Regionen unterteilt:

Betriebssystem | Stapel | Haufen | Text | Statisch | Speicher / BSS

[In zunehmender Adressmode]

Der Benutzerbereichsprozess verwendet einen höheren Speicherbereich für seine verschiedenen Arten von Datenobjekten.

Kernel-Space-Prozesse haben auch verschiedene Arten von Datenobjekten. Teilen diese Objekte die Speicherbereiche des Benutzerbereichs (Stapel, Heap usw.) oder haben sie eigene separate Unterabschnitte (Heap, Stapel usw.) in der OS-Region. Und wenn ja, in welcher Reihenfolge sind sie angeordnet . Vielen Dank,

gkt
quelle

Antworten:

5

Es ist falsch über die Bestellung. Das Betriebssystem befindet sich oben im Speicher, der im 32-Bit-Kernel im Allgemeinen über der 3-GB-Marke (0xC0000000) liegt, und im 64-Bit-Kernel ist es der halbe Wegpunkt von 0x8000000000000000 IIRC.

Die Position des Stapels und des Heaps wird zufällig ausgewählt. Es gibt keine wirkliche Regel für die Reihenfolge der Text- / Daten- / BSS-Segmente innerhalb des Hauptprogramms, und jede dynamische Bibliothek verfügt über einen eigenen Satz davon, sodass viele davon über den gesamten Speicher verteilt sind.

Damals, als Dinosaurier die Erde beherrschten (vor mehr als 20 Jahren), war der Programmadressraum linear (keine Löcher) und die Reihenfolge war Text, Daten, BSS, Stapel, Haufen. Es gab damals auch keine dynamischen Bibliotheken oder Threading. Das hat sich mit dem virtuellen Speicher geändert.

Kernel-Prozesse sind vollständig im Kernel-Teil des Adressraums enthalten. Der Benutzerteil wird ignoriert. Auf diese Weise kann der Kernel den Kontextwechsel zwischen Kernel-Threads beschleunigen, da die Seitentabellen nicht aktualisiert werden müssen, da alle Prozesse denselben Kernel-Teil der Seitentabellen verwenden.

psusi
quelle
4

Dies gilt nicht für „fast alle Betriebssysteme“. Die Arten der dargestellten Speicherbereiche sind ziemlich typisch, aber es gibt keinen Grund, warum sie in einer bestimmten Reihenfolge sein sollten, und es kann mehr als ein Stück einer bestimmten Art geben.

Unter Linux können Sie sehen Adressraum ist ein Prozesses , mit cat /proc/$pid/mapsdem $pidder Prozess - ID, zum Beispiel cat /proc/$$/mapsan der Schale schauen Sie laufen catvon oder cat /proc/self/mapszu Blick auf den cateigenen Mappings des Prozesses. Der Befehl pmaperzeugt eine etwas schönere Ausgabe.

08048000-08054000 r-xp 00000000 08:01 828061     /bin/cat
08054000-08055000 r--p 0000b000 08:01 828061     /bin/cat
08055000-08056000 rw-p 0000c000 08:01 828061     /bin/cat
08c7f000-08ca0000 rw-p 00000000 00:00 0          [heap]
b755a000-b7599000 r--p 00000000 08:01 273200     /usr/lib/locale/en_US.utf8/LC_CTYPE
b7599000-b759a000 rw-p 00000000 00:00 0 
b759a000-b76ed000 r-xp 00000000 08:01 269273     /lib/tls/i686/cmov/libc-2.11.1.so
b76ed000-b76ee000 ---p 00153000 08:01 269273     /lib/tls/i686/cmov/libc-2.11.1.so
b76ee000-b76f0000 r--p 00153000 08:01 269273     /lib/tls/i686/cmov/libc-2.11.1.so
b76f0000-b76f1000 rw-p 00155000 08:01 269273     /lib/tls/i686/cmov/libc-2.11.1.so
b76f1000-b76f4000 rw-p 00000000 00:00 0 
b770b000-b7712000 r--s 00000000 08:01 271618     /usr/lib/gconv/gconv-modules.cache
b7712000-b7714000 rw-p 00000000 00:00 0 
b7714000-b7715000 r-xp 00000000 00:00 0          [vdso]
b7715000-b7730000 r-xp 00000000 08:01 263049     /lib/ld-2.11.1.so
b7730000-b7731000 r--p 0001a000 08:01 263049     /lib/ld-2.11.1.so
b7731000-b7732000 rw-p 0001b000 08:01 263049     /lib/ld-2.11.1.so
bfbec000-bfc01000 rw-p 00000000 00:00 0          [stack]

Sie können den Code und die Lese- / Schreibdaten (Text und BSS) aus der ausführbaren Datei, dann dem Heap, dann einer Speicherzuordnungsdatei, dann etwas mehr Lese- / Schreibdaten, dann Code, Nur-Lese-Daten und Lese- sehen. Schreiben Sie Daten aus einer gemeinsam genutzten Bibliothek (wieder Text und BSS), mehr Lese- / Schreibdaten, eine andere gemeinsam genutzte Bibliothek (genauer gesagt den dynamischen Linker) und schließlich den Stapel des einzigen Threads.

Der Kernel-Code verwendet eigene Adressbereiche. Auf vielen Plattformen verwendet Linux den oberen Teil des Adressraums für den Kernel, häufig die oberen 1 GB. Im Idealfall würde dieser Speicherplatz ausreichen, um Kernelcode, Kerneldaten und den Systemspeicher (RAM) sowie jedes speicherabgebildete Gerät abzubilden. Auf typischen 32-Bit-PCs von heute ist dies nicht möglich, was Verzerrungen erfordert, die nur für Kernel-Hacker von Interesse sind.

Während der Kernelcode einen Systemaufruf verarbeitet, wird der Speicher des Prozesses idealerweise (wenn die oben genannten Verzerrungen nicht vorhanden sind) an denselben Adressen zugeordnet. Dadurch können Prozesse Daten an den Kernel übergeben und der Kernel kann direkt vom Zeiger lesen. Dies ist jedoch kein großer Gewinn, da die Zeiger ohnehin validiert werden müssen (damit der Prozess den Kernel nicht dazu verleiten kann, aus dem Speicher zu lesen, auf den der Prozess keinen Zugriff haben soll).

Die Speicherzonen im Linux-Kernel sind ziemlich komplex. Es gibt verschiedene Speicherpools, und bei den Hauptunterschieden geht es nicht darum, woher der Speicher stammt, sondern darum, mit wem er geteilt wird. Wenn Sie neugierig sind, beginnen Sie mit LDD3 .

Gilles 'SO - hör auf böse zu sein'
quelle
1

Keine Antwort, sondern ein FYI, das mehr Platz benötigt.

Ich denke nicht, dass Ihre Vorstellung vom logischen Adresslayout überhaupt richtig ist.

Sie können dieses Programm kompilieren und ausführen, um zu sehen, was ein Userland-Prozess für Adressen hat:

#include <stdio.h>
long global_initialized = 119234;
long global_uninitialized;
extern int _end, _edata, _etext;
int
main(int ac, char **av)
{
        long local;

        printf("main at 0x%lx\n", main);
        printf("ac at   0x%lx\n", &ac);
        printf("av at   0x%lx\n", &av);
        printf("av has  0x%lx\n", av);
        printf("initialized global at 0x%lx\n", &global_initialized);
        printf("global at             0x%lx\n", &global_uninitialized);
        printf("local at              0x%lx\n", &local);
        printf("_end at               0x%lx\n", &_end);
        printf("_edata at             0x%lx\n", &_edata);
        printf("_etext at             0x%lx\n", &_etext);
        return 0;
}

Auf dem von mir ausgeführten Red Hat Enterprise Server readelfkann angegeben werden, wo der Kernel (logisch) eine ausführbare Datei laden würde:

readelf -S where

Zeigt mir viele der gleichen Adressinformationen, die die Ausgabe von wheregibt.

Ich denke nicht, dass readelfes auf einem Linux-Kernel (/ boot / vmlinuz oder einem ähnlichen) problemlos funktioniert, und ich denke, dass der Kernel standardmäßig bei 0x80000000 in seinem eigenen Adressraum startet: Er wird trotz Verwendung eines nicht in einem Userland-Prozess zugeordnet Adresse über dem Userland-Stack oben bei 0x7fffffff (x86, 32-Bit-Adressierung).

Bruce Ediger
quelle
Danke für das Beispiel! Nur meine Anmerkung zum Linux-Teil - ich habe gerade dieses Beispiel ausprobiert, als where.cunter Ubuntu 11.04 gcc where.c -o where; meldet "main at 0x80483c4". Versucht readelf -S where, und es berichtet, sagen "[13] .text PROGBITS 08048310 ...", was ungefähr richtig aussieht? Obwohl ich auch "ac at 0xbfb035a0" und "local at 0xbfb0358c" bekomme, scheint dieser Adressbereich (0xbf ...) nicht von gemeldet zu werden readelf -S.
Sdaau
@sdaau - Die Argumente acund die avautomatische Variable localhaben wahrscheinlich bei jedem Aufruf unterschiedliche Adressen. Die meisten modernen Linux-Kernel verfügen über "Address Space Layout Randomization", um das Ausnutzen von Pufferüberläufen zu erschweren.
Bruce Ediger