Organisation de l'espace d'adressage logique du noyau Linux

8

Selon "Write Great Code" dans presque toute la mémoire d'exécution du système d'exploitation est organisée en régions suivantes:

OS | Pile | Tas | Texte | Statique | Stockage / BSS

[En augmentant la mode d'adresse]

Le processus de l'espace utilisateur utilise une région de mémoire supérieure pour ses différents types d'objets de données.

Les processus spatiaux du noyau ont également différents types d'objets de données. Ces objets partagent-ils les régions de mémoire de l'espace utilisateur (pile, segment de mémoire, etc.) ou ont-ils leurs propres sous-sections distinctes (segment de mémoire, pile, etc.) situées dans la région du système d'exploitation? Dans l'affirmative, quel est l'ordre dans lequel ils sont organisés . Merci,

gkt
la source

Réponses:

5

La commande est fausse. Le système d'exploitation est situé en haut de la mémoire, qui est généralement au-dessus de la marque de 3 Go (0xC0000000) dans le noyau 32 bits, et dans le noyau 64 bits, il est à mi-chemin de 0x8000000000000000 IIRC.

L'emplacement de la pile et du tas est aléatoire. Il n'y a pas de véritable règle sur l'ordre des segments texte / données / bss dans le programme principal, et chaque bibliothèque dynamique a son propre ensemble de ceux-ci, donc il y en a beaucoup dispersés dans toute la mémoire.

À l'époque où les dinosaures régnaient sur la Terre (il y a plus de 20 ans), l'espace d'adressage du programme était linéaire (sans trous) et l'ordre était le texte, les données, les bss, la pile, le tas. Il n'y avait pas non plus de bibliothèques dynamiques ni de threading à l'époque. Tout cela a changé avec la mémoire virtuelle.

Les processus du noyau sont entièrement contenus dans la partie noyau de l'espace d'adressage; la partie utilisateur est ignorée. Cela permet au noyau d'accélérer le changement de contexte entre les threads du noyau car il n'a pas à mettre à jour les tables de pages car tous les processus partagent la même partie du noyau des tables de pages.

psusi
la source
4

Ce n'est pas le cas de «presque tous les OS». Les types de zones de mémoire représentés sont assez typiques, mais il n'y a aucune raison pour qu'ils soient dans un ordre particulier, et il peut y avoir plus d'un morceau d'un type donné.

Sous Linux, vous pouvez regarder l'espace d'adressage d'un processus avec cat /proc/$pid/mapsoù se $pidtrouve l'ID du processus, par exemple cat /proc/$$/mapspour regarder le shell à catpartir duquel vous exécutez , ou cat /proc/self/mapspour regarder les catpropres mappages du processus. La commande pmapproduit une sortie légèrement plus agréable.

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]

Vous pouvez voir le code et les données en lecture-écriture (texte et BSS) de l'exécutable, puis le tas, puis un fichier mappé en mémoire, puis un peu plus de données en lecture-écriture, puis du code, des données en lecture seule et en lecture- écrire des données à partir d'une bibliothèque partagée (texte et BSS à nouveau), davantage de données en lecture-écriture, une autre bibliothèque partagée (plus précisément, l'éditeur de liens dynamique), et enfin la pile du seul thread.

Le code du noyau utilise ses propres plages d'adresses. Sur de nombreuses plates-formes, Linux utilise la partie supérieure de l'espace d'adressage pour le noyau, souvent le 1 Go supérieur. Idéalement, cet espace serait suffisant pour mapper le code du noyau, les données du noyau et la mémoire système (RAM) et chaque périphérique mappé en mémoire. Sur les PC 32 bits typiques d'aujourd'hui, ce n'est pas possible, ce qui nécessite des contorsions qui n'intéressent que les pirates du noyau.

Pendant que le code du noyau gère un appel système, idéalement (lorsque les contorsions susmentionnées ne sont pas en place), la mémoire du processus est mappée aux mêmes adresses. Cela permet aux processus de transmettre des données au noyau, et le noyau peut lire directement à partir du pointeur. Ce n'est pas un gros gain, cependant, car les pointeurs doivent être validés de toute façon (afin que le processus ne puisse pas inciter le noyau à lire dans la mémoire auquel le processus n'est pas censé avoir accès).

Les zones de mémoire à l'intérieur de l'espace du noyau Linux sont assez complexes. Il existe plusieurs pools de mémoire différents, et les principales distinctions ne concernent pas l'origine de la mémoire, mais plutôt avec qui elle est partagée. Si vous êtes curieux à leur sujet, commencez par LDD3 .

Gilles 'SO- arrête d'être méchant'
la source
1

Pas une réponse, mais un FYI qui a besoin de plus d'espace.

Je ne pense pas que votre conception de la disposition des adresses logiques soit correcte.

Vous pouvez compiler et exécuter ce programme pour voir ce qu'un processus de l'espace utilisateur a pour les adresses:

#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;
}

Le serveur Red Hat Enterprise que j'utilise, a readelf, qui peut être utilisé pour dire où le noyau chargerait (logiquement) un fichier exécutable:

readelf -S where

Me montre beaucoup des mêmes informations d'adressage que la sortie de wheredonne.

Je ne pense pas que readelfcela fonctionnera facilement sur un noyau Linux (/ boot / vmlinuz ou certains autres), et je pense que le noyau par défaut démarre à 0x80000000 dans son propre espace d'adressage: il n'est pas mappé dans un processus utilisateur, malgré l'utilisation d'un adresse au-dessus du sommet de la pile userland à 0x7fffffff (x86, adressage 32 bits).

Bruce Ediger
la source
Merci pour l'exemple! Juste ma note sur la partie Linux - je viens d'essayer cet exemple comme where.c, sur Ubuntu 11.04 en utilisant gcc where.c -o where; signale "principal à 0x80483c4". Essayé readelf -S where, et il rapporte, dire "[13] .text PROGBITS 08048310 ..." qui ressemble à peu près non? Bien que j'obtienne également "ac at 0xbfb035a0" et "local at 0xbfb0358c", cette plage d'adresses (0xbf ...) ne semble pas être signalée par readelf -S.
sdaau
@sdaau - Les arguments acet avet la variable automatique localauront probablement des adresses différentes à chaque appel. La plupart des noyaux Linux modernes ont une "randomisation de la disposition de l'espace d'adressage" pour rendre plus difficile l'exploitation des dépassements de tampon.
Bruce Ediger