web-dev-qa-db-fra.com

Chargement des bibliothèques partagées et RAM utilisation

Je me demande comment Linux gère les bibliothèques partagées. (en fait, je parle de Maemo Fremantle, une distribution basée sur Debian publiée en 2009 fonctionnant sur 256 Mo de RAM).

Supposons que nous ayons deux exécutables reliant à libQtCore.so.4 et utilisant ses symboles (utilisant ses classes et fonctions). Par souci de simplicité, appelons-les a et b. Nous supposons que les deux exécutables sont liés aux mêmes bibliothèques.

Nous lançons d'abord a. La bibliothèque doit être chargée. Est-il chargé en entier ou est-il chargé en mémoire uniquement dans la partie requise (comme nous n'utilisons pas chaque classe, seul le code concernant les classes utilisées est chargé)?

Ensuite, nous lançons b. Nous supposons que a est toujours en cours d'exécution. b lie également à libQtCore.so.4 et utilise certaines des classes que a utilise, mais aussi certaines qui ne sont pas utilisées par a. La bibliothèque sera-t-elle chargée deux fois (séparément pour a et séparément pour b)? Ou utiliseront-ils le même objet déjà dans la RAM. Si b n'utilise pas de nouveaux symboles et que a est déjà en cours d'exécution, le RAM utilisé par les bibliothèques partagées augmentera-t-il (ou la différence sera-t-elle négligeable)

42
marmistrz

REMARQUE: je vais supposer que votre machine possède une unité de mappage de mémoire (MMU). Il existe une version Linux (µClinux) qui ne nécessite pas de MMU, et cette réponse ne s'applique pas là-bas.

Qu'est-ce qu'une MMU? C'est du matériel - partie du processeur et/ou du contrôleur de mémoire. Comprendre la liaison de bibliothèque partagée ne vous oblige pas à comprendre exactement comment fonctionne un MMU, juste qu'un MMU permet qu'il y ait une différence entre logique adresses mémoire (celles utilisées par les programmes) et physique adresses mémoire (celles réellement présentes sur le bus mémoire). La mémoire est décomposée en pages, généralement de taille 4K sous Linux. Avec 4k pages, les adresses logiques 0–4095 sont la page 0, les adresses logiques 4096–8191 sont la page 1, etc. Le MMU mappe ceux-ci aux pages physiques de RAM, et chaque page logique peut être typiquement mappé à 0 ou 1 page physique. Une page physique donnée peut correspondre à plusieurs pages logiques (c'est ainsi que la mémoire est partagée: plusieurs pages logiques correspondent à la même page physique). Notez que cela s'applique quel que soit le système d'exploitation; c'est une description du matériel .

Lors du changement de processus, le noyau modifie les mappages de pages MMU, de sorte que chaque processus ait son propre espace. L'adresse 4096 du processus 1000 peut être (et est généralement) complètement différente de l'adresse 4096 du processus 1001. .

À peu près chaque fois que vous voyez une adresse, c'est une adresse logique. Les programmes d'espace utilisateur ne traitent presque jamais les adresses physiques.

Maintenant, il existe également plusieurs façons de créer des bibliothèques. Disons qu'un programme appelle la fonction foo() dans la bibliothèque. Le CPU ne sait rien des symboles ou des appels de fonction, il sait juste comment sauter à une adresse logique et exécuter le code qu'il y trouve. Il y a plusieurs façons de procéder (et des choses similaires s'appliquent lorsqu'une bibliothèque accède à ses propres données globales, etc.):

  1. Il pourrait coder en dur une adresse logique pour l'appeler. Cela nécessite que la bibliothèque toujours soit chargée à la même adresse logique exacte. Si deux bibliothèques nécessitent la même adresse, la liaison dynamique échoue et vous ne pouvez pas lancer le programme. Les bibliothèques peuvent nécessiter d'autres bibliothèques, ce qui nécessite essentiellement que chaque bibliothèque du système ait des adresses logiques uniques. C'est très rapide, cependant, si cela fonctionne. (C'est ainsi que a.out a fait les choses, et le genre de configuration que fait la pré-liaison, en quelque sorte).
  2. Il pourrait coder en dur une fausse adresse logique et indiquer à l'éditeur de liens dynamique de modifier la bonne lors du chargement de la bibliothèque. Cela coûte un peu de temps lors du chargement des bibliothèques, mais après cela, c'est très rapide.
  3. Cela pourrait ajouter une couche d'indirection: utilisez un registre CP pour contenir l'adresse logique à laquelle la bibliothèque est chargée, puis accédez à tout en tant que décalage à partir de ce registre. Cela impose un coût de performance à chaque accès.

Presque personne n'utilise plus # 1, du moins pas sur les systèmes à usage général. Il est impossible de conserver cette liste d'adresses logiques unique sur les systèmes 32 bits (il n'y en a pas assez) et un cauchemar administratif sur les systèmes 64 bits. La pré-liaison en quelque sorte le fait, par système.

L'utilisation de # 2 ou # 3 dépend de la construction de la bibliothèque avec le GCC -fPIC (code indépendant de la position). # 2 est sans, # 3 est avec. Généralement, les bibliothèques sont construites avec -fPIC, donc # 3 est ce qui se passe.

Pour plus de détails, voir le Ulrich Drepper's Comment écrire des bibliothèques partagées (PDF) .

Donc, enfin, on peut répondre à votre question:

  1. Si la bibliothèque est construite avec -fPIC (comme cela devrait presque certainement l'être), la grande majorité des pages sont exactement les mêmes pour chaque processus qui la charge. Vos processus a et b peuvent bien charger la bibliothèque à différentes adresses logiques, mais celles-ci pointeront vers les mêmes pages physiques: la mémoire sera partagée. De plus, les données dans RAM correspondent exactement à ce qui se trouve sur le disque, donc elles ne peuvent être chargées qu'en cas de besoin par le gestionnaire de défauts de page.
  2. Si la bibliothèque est construite sans -fPIC, il s'avère que la plupart des pages de la bibliothèque auront besoin de modifications de liens et seront différentes. Par conséquent, elles doivent être des pages physiques distinctes (car elles contiennent des données différentes). Cela signifie qu'ils ne sont pas partagés. Les pages ne correspondent pas à ce qui se trouve sur le disque, donc je ne serais pas surpris si la bibliothèque entière était chargée. Il peut bien sûr être ensuite échangé sur le disque (dans le fichier d'échange).

Vous pouvez l'examiner avec l'outil pmap, ou directement en vérifiant divers fichiers dans /proc. Par exemple, voici une sortie (partielle) de pmap -x sur deux bcs nouvellement créés. Notez que les adresses affichées par pmap sont, comme typique, des adresses logiques:

pmap -x 14739
Address           Kbytes     RSS   Dirty Mode  Mapping
00007f81803ac000     244     176       0 r-x-- libreadline.so.6.2
00007f81803e9000    2048       0       0 ----- libreadline.so.6.2
00007f81805e9000       8       8       8 r---- libreadline.so.6.2
00007f81805eb000      24      24      24 rw--- libreadline.so.6.2


pmap -x 17739
Address           Kbytes     RSS   Dirty Mode  Mapping
00007f784dc77000     244     176       0 r-x-- libreadline.so.6.2
00007f784dcb4000    2048       0       0 ----- libreadline.so.6.2
00007f784deb4000       8       8       8 r---- libreadline.so.6.2
00007f784deb6000      24      24      24 rw--- libreadline.so.6.2

Vous pouvez voir que la bibliothèque est chargée en plusieurs parties et pmap -x vous donne des détails sur chacun séparément. Vous remarquerez que les adresses logiques sont différentes entre les deux processus; on peut raisonnablement s'attendre à ce qu'ils soient les mêmes (puisque c'est le même programme en cours d'exécution, et les ordinateurs sont généralement prévisibles comme ça), mais il existe une fonctionnalité de sécurité appelée randomisation de la disposition de l'espace d'adressage qui les aléatoirement intentionnellement.

La différence de taille (kilo-octets) et de taille résidente (RSS) montre que le segment de bibliothèque entier n'a pas été chargé. Enfin, vous pouvez voir que pour les mappages plus grands, dirty est 0, ce qui signifie qu'il correspond exactement à ce qui se trouve sur le disque.

Vous pouvez réexécuter avec pmap -XX, et cela vous montrera - selon la version du noyau que vous exécutez, car la sortie -XX varie selon la version du noyau - que le premier mappage a un Shared_Clean sur 176, qui correspond exactement à RSS. Shared mémoire signifie que les pages physiques sont partagées entre plusieurs processus, et comme il correspond au RSS, cela signifie que toute la bibliothèque qui est en mémoire est partagée (consultez la section Voir aussi ci-dessous pour plus d'explications sur le partage contre privé):

pmap -XX 17739
         Address Perm   Offset Device   Inode  Size  Rss Pss Shared_Clean Shared_Dirty Private_Clean Private_Dirty Referenced Anonymous AnonHugePages Swap KernelPageSize MMUPageSize Locked                   VmFlagsMapping
    7f784dc77000 r-xp 00000000  fd:00 1837043   244  176  19          176            0             0             0        176         0             0    0              4           4      0       rd ex mr mw me sd  libreadline.so.6.2
    7f784dcb4000 ---p 0003d000  fd:00 1837043  2048    0   0            0            0             0             0          0         0             0    0              4           4      0             mr mw me sd  libreadline.so.6.2
    7f784deb4000 r--p 0003d000  fd:00 1837043     8    8   8            0            0             0             8          8         8             0    0              4           4      0       rd mr mw me ac sd  libreadline.so.6.2
    7f784deb6000 rw-p 0003f000  fd:00 1837043    24   24  24            0            0             0            24         24        24             0    0              4           4      0    rd wr mr mw me ac sd  libreadline.so.6.2


Voir aussi

57
derobert