Il n’est pas facile de savoir combien de RAM un processus Linux utilise, surtout lorsqu’il faut tenir compte de la mémoire partagée. Heureusement, la commande pmap vous aide à y voir plus clair.

Cartographie de la mémoire

Sur les systèmes d’exploitation modernes, chaque processus vit dans sa propre région de mémoire allouée ou espace d’allocation. Les limites de la région allouée ne sont pas mappées directement aux adresses matérielles physiques. Le système d’exploitation crée un espace de mémoire virtuelle pour chaque processus et agit comme une couche d’abstraction en mappant la mémoire virtuelle à la mémoire physique.

Le noyau maintient une table de traduction pour chaque processus, à laquelle l’unité centrale accède. Lorsque le noyau modifie le processus en cours d’exécution sur un cœur de CPU particulier, il met à jour la table de traduction qui relie les processus et les cœurs de CPU.

LA MEILLEURE IMPRIMANTE POUR LINUX

Les avantages de l’abstraction

Ce schéma présente des avantages. L’utilisation de la mémoire est quelque peu encapsulée et protégée pour chaque processus dans l’espace utilisateur. Un processus ne « voit » la mémoire qu’en termes d’adresses de mémoire virtuelle. Cela signifie qu’il ne peut travailler qu’avec la mémoire qui lui a été attribuée par le système d’exploitation. À moins qu’il n’ait accès à une mémoire partagée, il ne connaît ni n’a accès à la mémoire allouée aux autres processus.

L’abstraction de la mémoire physique matérielle en adresses de mémoire virtuelle permet au noyau de modifier l’adresse physique à laquelle est mappée une mémoire virtuelle. Il peut échanger la mémoire sur le disque en modifiant l’adresse réelle vers laquelle pointe une région de mémoire virtuelle. Il peut également différer la fourniture de mémoire physique jusqu’à ce qu’elle soit réellement requise.

Tant que les demandes de lecture ou d’écriture de la mémoire sont traitées au fur et à mesure, le noyau est libre de jongler avec la table de correspondance comme il l’entend.

RAM à la demande

La table de correspondance et le concept de « RAM à la demande » ouvrent la possibilité d’une mémoire partagée. Le noyau essaiera d’éviter de charger la même chose en mémoire plus d’une fois. Par exemple, il chargera une fois une bibliothèque partagée en mémoire et la mappera aux différents processus qui doivent l’utiliser. Chacun des processus aura sa propre adresse unique pour la bibliothèque partagée, mais ils pointeront tous vers le même emplacement réel.

Si la région de mémoire partagée est accessible en écriture, le noyau utilise un schéma appelé copy-on-write. Si un processus écrit dans la mémoire partagée et que les autres processus partageant cette mémoire ne sont pas censés voir les changements, une copie de la mémoire partagée est créée au moment de la demande d’écriture.

Le noyau Linux 2.6.32, publié en décembre 2009, a donné à Linux une fonctionnalité appelée « Kernel SamePage Merging ». Cela signifie que Linux peut détecter des régions de données identiques dans des espaces d’adressage différents. Imaginez une série de machines virtuelles fonctionnant sur un seul ordinateur, et les machines virtuelles exécutent toutes le même système d’exploitation. L’utilisation d’un modèle de mémoire partagée et de la copie sur écriture permet de réduire considérablement les frais généraux sur l’ordinateur hôte.

Tout cela rend la gestion de la mémoire dans Linux sophistiquée et aussi optimale qu’elle peut l’être. Mais cette sophistication rend difficile l’observation d’un processus et la connaissance de son utilisation réelle de la mémoire.

L’utilitaire pmap

Le noyau expose une grande partie de ce qu’il fait avec la RAM à travers deux pseudo-fichiers dans le pseudo-système de fichiers d’informations système « /proc ». Il y a deux fichiers par processus, nommés en fonction de l’ID du processus ou PID de chaque processus : « /proc/maps » et « /proc//smaps ».

L’outil pmap lit les informations de ces fichiers et affiche les résultats dans la fenêtre du terminal. Il est évident que nous devons fournir le PID du processus qui nous intéresse lorsque nous utilisons pmap.

Trouver l’ID du processus

Il existe plusieurs façons de trouver le PID d’un processus. Voici le code source d’un programme trivial que nous utiliserons dans nos exemples. Il est écrit en C. Tout ce qu’il fait est d’imprimer un message dans la fenêtre du terminal et d’attendre que l’utilisateur appuie sur la touche « Entrée ».

include

int main(int argc, char *argv[])
{
printf(« Programme de test Journal du Freenaute. ») ;
getc(stdin) ;
} // fin de main

Le programme a été compilé dans un exécutable appelé pm à l’aide du compilateur gcc :

gcc -o pm pm.c

Compilation du programme d’exemple

Comme le programme attendra que l’utilisateur appuie sur « Entrée », il restera en cours d’exécution aussi longtemps que nous le souhaitons.

./pm

Exécution du programme d’exemple

Le programme se lance, imprime le message et attend la frappe de la touche. Nous pouvons maintenant rechercher son PID. La commande ps liste les processus en cours d’exécution. L’option -e (show all processes) permet à ps de lister tous les processus. Nous allons passer la sortie par grep et filtrer les entrées qui ont « pm » dans leur nom.

ps -e | grep pm

Trouver l’ID du processus avec grep

Ceci liste toutes les entrées avec « pm » dans leur nom.

Nous pouvons être plus spécifiques en utilisant la commande pidof. Nous donnons à pidof le nom du processus qui nous intéresse sur la ligne de commande, et il essaie de trouver une correspondance. Si une correspondance est trouvée, pidof affiche le PID du processus correspondant.

pidof pm

Utilisation de pidof pour trouver l’ID du processus

La méthode pidof est plus propre lorsque vous connaissez le nom du processus, mais la méthode ps fonctionnera même si vous ne connaissez qu’une partie du nom du processus.

Utilisation de pmap

Avec notre programme de test en cours d’exécution, et une fois que nous avons identifié son PID, nous pouvons utiliser pmap comme ceci :

pmap 40919

Exécution de pmap sur le programme d’exemple

Les mappages de mémoire pour le processus sont listés pour nous.

La sortie standard de pmap

Voici la sortie complète de la commande :

40919 : ./pm
000056059f06c000 4K r—- pm
000056059f06d000 4K r-x– pm
000056059f06e000 4K r—- pm
000056059f06f000 4K r—- pm
000056059f070000 4K rw— pm
000056059fc39000 132K rw— [ anon ]
00007f97a3edb000 8K rw— [ anon ]
00007f97a3edd000 160K r—- libc.so.6
00007f97a3f05000 1616K r-x– libc.so.6
00007f97a4099000 352K r—- libc.so.6
00007f97a40f1000 4K —– libc.so.6
00007f97a40f2000 16K r—- libc.so.6
00007f97a40f6000 8K rw— libc.so.6
00007f97a40f8000 60K rw— [ anon ]
00007f97a4116000 4K r—- ld-linux-x86-64.so.2
00007f97a4117000 160K r-x– ld-linux-x86-64.so.2
00007f97a413f000 40K r—- ld-linux-x86-64.so.2
00007f97a4149000 8K r—- ld-linux-x86-64.so.2
00007f97a414b000 8K rw— ld-linux-x86-64.so.2
00007ffca0e7e000 132K rw— stack
00007ffca0fe1000 16K r—- [ anon ]
00007ffca0fe5000 8K r-x– [ anon ]
ffffffff600000 4K –x– [ anon ]
total 2756K

La première ligne est le nom du processus et son PID. Chacune des autres lignes indique une adresse mémoire mappée, et la quantité de mémoire à cette adresse, exprimée en kilo-octets. Les cinq caractères suivants de chaque ligne sont appelés permissions de mémoire virtuelle. Les permissions valides sont les suivantes

r : La mémoire mappée peut être lue par le processus.
w : La mémoire mappée peut être écrite par le processus.
x : Le processus peut exécuter toutes les instructions contenues dans la mémoire mappée.
s : La mémoire mappée est partagée, et les modifications apportées à la mémoire partagée sont visibles par tous les processus qui partagent la mémoire.
R : Il n'y a pas de réservation d'espace d'échange pour cette mémoire mappée.

La dernière information sur chaque ligne est le nom de la source du mappage. Il peut s’agir d’un nom de processus, d’un nom de bibliothèque ou d’un nom de système tel que stack ou heap.

L’affichage étendu

L’option -x (étendue) fournit deux colonnes supplémentaires.

pmap -x 40919

Utilisation de l’option étendue -X avec pmap

Les colonnes portent des titres. Nous avons déjà vu les colonnes « Address », « Kbytes », « Mode », et « Mapping ». Les nouvelles colonnes sont appelées « RSS » et « Dirty ».

La sortie étendue de pmap

Voici le résultat complet :

40919 : ./pm
Adresse Kbytes RSS Dirty Mode Mapping
000056059f06c000 4 4 0 r—- pm
000056059f06d000 4 4 0 r-x– pm
000056059f06e000 4 4 0 r—- pm
000056059f06f000 4 4 4 r—- pm
000056059f070000 4 4 4 rw— pm
000056059fc39000 132 4 4 rw— [ anon ]
00007f97a3edb000 8 4 4 rw— [ anon ]
00007f97a3edd000 160 160 0 r—- libc.so.6
00007f97a3f05000 1616 788 0 r-x– libc.so.6
00007f97a4099000 352 64 0 r—- libc.so.6
00007f97a40f1000 4 0 0 —– libc.so.6
00007f97a40f2000 16 16 16 r—- libc.so.6
00007f97a40f6000 8 8 8 rw— libc.so.6
00007f97a40f8000 60 28 28 rw— [ anon ]
00007f97a4116000 4 4 0 r—- ld-linux-x86-64.so.2
00007f97a4117000 160 160 0 r-x– ld-linux-x86-64.so.2
00007f97a413f000 40 40 0 r—- ld-linux-x86-64.so.2
00007f97a4149000 8 8 8 r—- ld-linux-x86-64.so.2
00007f97a414b000 8 8 8 rw— ld-linux-x86-64.so.2
00007ffca0e7e000 132 12 12 rw— [ stack ]
00007ffca0fe1000 16 0 0 r—- [ anon ]
00007ffca0fe5000 8 4 0 r-x– [ anon ]
ffffffff600000 4 0 0 –x– [ anon ]


total kB 2756 1328 96

RSS : C'est la taille de l'ensemble résident. C'est la quantité de mémoire qui est actuellement en RAM, et qui n'a pas été échangée.
Dirty (sale) : La mémoire "sale" a été modifiée depuis que le processus - et le mappage - a commencé.

Montrez-moi tout

Le -X (encore plus qu’étendu) ajoute des colonnes supplémentaires à la sortie. Notez la majuscule « X ». Une autre option appelée -XX (encore plus que -X ) vous montre tout ce que pmap peut obtenir du noyau. Comme -X est un sous-ensemble de -XX, nous allons décrire la sortie de -XX .

pmap -XX 40919

Utiliser l’option -XX show me everything avec pmap

La sortie s’enroule horriblement dans une fenêtre de terminal et est pratiquement indéchiffrable. Voici la sortie complète :

40919 : ./pm
Address Perm Offset Device Inode Size KernelPageSize MMUPageSize Rss Pss Shared_Clean Shared_Dirty Private_Clean Private_Dirty Referenced Anonymous LazyFree AnonHugePages ShmemPmdMapped FilePmdMapped Shared_Hugetlb Private_Hugetlb Swap SwapPss Locked THPeligible VmFlags Mapping

56059f06c000 r--p 00000000 08:03 393304 4 4 4 4 4 0 0 4 0 4 0 0 0 0 0 0 0 0 rd mr mw me dw sd pm
56059f06d000 r-xp 00001000 08:03 393304 4 4 4 4 4 0 0 4 0 0 4 0 0 0 0 0 0 0 rd ex mr mw me dw sd pm
56059f06e000 r--p 00002000 08:03 393304 4 4 4 4 4 0 0 4 0 4 0 0 0 0 0 0 0 rd mr mw me dw sd pm
56059f06f000 r--p 00002000 08:03 393304 4 4 4 4 4 0 0 0 4 4 0 0 0 0 0 0 0 rd mr mw me dw ac sd pm
56059f070000 rw-p 00003000 08:03 393304 4 4 4 4 4 0 0 0 4 4 4 0 0 0 0 0 0 rd wr mr mw me dw ac sd pm
56059fc39000 rw-p 00000000 00:00 0 132 4 4 4 4 0 0 0 4 4 0 0 0 0 0 0 rd wr mr mw me ac sd [heap]
7f97a3edb000 rw-p 00000000 00:00 0 8 4 4 4 4 0 0 0 4 4 0 0 0 0 0 0 0 rd wr mr mw me ac sd
7f97a3edd000 r--p 00000000 08:03 264328 160 4 4 160 4 160 0 0 0 160 0 0 0 0 0 0 0 0 0 rd mr mw me sd libc.so.6
7f97a3f05000 r-xp 00028000 08:03 264328 1616 4 4 788 32 788 0 0 0 788 0 0 0 0 0 0 0 0 0 0 0 rd ex mr mw me sd libc.so.6
7f97a4099000 r--p 001bc000 08:03 264328 352 4 4 64 1 64 0 0 0 64 0 0 0 0 0 0 0 0 0 0 0 0 rd mr mw me sd libc.so.6
7f97a40f1000 ---p 00214000 08:03 264328 4 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 mr mw me sd libc.so.6
7f97a40f2000 r--p 00214000 08:03 264328 16 4 4 16 16 0 0 0 16 16 16 0 0 0 0 0 0 0 0 0 rd mr mw me ac sd libc.so.6
7f97a40f6000 rw-p 00218000 08:03 264328 8 4 4 8 8 0 0 0 8 8 8 0 0 0 0 0 0 0 0 rd wr mr mw me ac sd libc.so.6
7f97a40f8000 rw-p 00000000 00:00 0 60 4 4 28 28 0 0 0 28 28 28 0 0 0 0 0 0 0 0 rd wr mr mw me ac sd
7f97a4116000 r--p 00000000 08:03 264305 4 4 4 4 0 4 0 0 0 4 0 0 0 0 0 0 0 0 rd mr mw me dw sd ld-linux-x86-64.so.2
7f97a4117000 r-xp 00001000 08:03 264305 160 4 4 160 11 160 0 0 0 160 0 0 0 0 0 0 0 0 0 rd ex mr mw me dw sd ld-linux-x86-64.so.2
7f97a413f000 r--p 00029000 08:03 264305 40 4 4 40 1 40 0 0 0 40 0 0 0 0 0 0 0 0 0 rd mr mw me dw sd ld-linux-x86-64.so.2
7f97a4149000 r--p 00032000 08:03 264305 8 4 4 8 8 0 0 0 8 8 8 0 0 0 0 0 0 0 0 rd mr mw me dw ac sd ld-linux-x86-64.so.2
7f97a414b000 rw-p 00034000 08:03 264305 8 4 4 8 8 0 0 0 8 8 8 0 0 0 0 0 0 0 rd wr mr mw me dw ac sd ld-linux-x86-64.so.2
7ffca0e7e000 rw-p 00000000 00:00 0 132 4 4 12 12 0 0 0 0 12 12 12 0 0 0 0 0 0 0 rd wr mr mw me gd ac [stack]
7ffca0fe1000 r--p 00000000 00:00 0 16 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 rd mr pf io de dd sd [vvar]
7ffca0fe5000 r-xp 00000000 00:00 0 8 4 4 4 0 4 0 0 0 0 0 4 0 0 0 0 0 0 0 0 rd ex mr mw me de sd [vdso]

ffffffff600000 –xp 00000000 00:00 0 4 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ex [vsyscall]
==== ============== =========== ==== === ============ ============ ============= ============= ========== ========= ======== ============= ============== ============= ============== =============== ==== ======= ====== ===========
2756 92 92 1328 157 1220 0 12 96 1328 96 0 0 0 0 0 0 0 0 0 0 KB

Il y a beaucoup d’informations ici. Voici ce que contiennent les colonnes :

Adresse : L'adresse de départ de ce mappage. Ceci utilise l'adressage de la mémoire virtuelle.
Perm : Les permissions de la mémoire.
Offset : Si la mémoire est basée sur un fichier, l'offset de ce mappage dans le fichier.
Device (Périphérique) : Le numéro de périphérique Linux, exprimé en chiffres majeurs et mineurs. Vous pouvez voir les numéros de périphériques sur votre ordinateur en exécutant la commande lsblk.
Inode : L'inode du fichier auquel le mappage est associé. Par exemple, dans notre exemple, il pourrait s'agir de l'inode qui contient des informations sur le programme pm.
Size (Taille) : La taille de la région mappée en mémoire.
KernelPageSize : La taille de la page utilisée par le noyau.
MMUPageSize : La taille de la page utilisée par l'unité de gestion de la mémoire.
Rss : C'est la taille de l'ensemble résident. C'est la quantité de mémoire qui est actuellement dans la RAM, et qui n'a pas été transférée.
Pss : Il s'agit de la taille de partage proportionnelle. C'est la taille partagée privée ajoutée à la (taille partagée divisée par le nombre de parts).
Shared_Clean : La quantité de mémoire partagée avec d'autres processus qui n'a pas été modifiée depuis la création de la cartographie. Notez que même si la mémoire est partageable, si elle n'a pas été réellement partagée, elle est toujours considérée comme de la mémoire privée.
Shared_Dirty : La quantité de mémoire partagée avec d'autres processus qui a été modifiée depuis la création de la cartographie.
Private_Clean : La quantité de mémoire privée - non partagée avec d'autres processus - qui n'a pas été modifiée depuis la création de la cartographie.
Private_Dirty : La quantité de mémoire privée qui a été modifiée depuis la création du mappage.
    Référencé : La quantité de mémoire actuellement marquée comme référencée ou accédée.
    Anonyme : Mémoire qui n'a pas de périphérique vers lequel échanger. C'est-à-dire qu'elle n'est pas sauvegardée dans un fichier.
    LazyFree : Les pages qui ont été marquées comme MADV_FREE. Ces pages ont été marquées comme disponibles pour être libérées et réclamées, même si elles peuvent contenir des modifications non écrites. Toutefois, si des modifications ultérieures surviennent après que le MADV_FREE a été défini sur la cartographie de la mémoire, l'indicateur MADV_FREE est supprimé et les pages ne seront pas récupérées tant que les modifications n'auront pas été écrites.
    AnonHugePages : Il s'agit de pages de mémoire "énormes" non soutenues par des fichiers (plus grandes que 4 Ko).
    ShmemPmdMapped : Mémoire partagée associée aux pages énormes. Elles peuvent également être utilisées par les systèmes de fichiers qui résident entièrement en mémoire.
    FilePmdMapped : Le répertoire intermédiaire de page est l'un des schémas de pagination disponibles pour le noyau. Il s'agit du nombre de pages adossées à des fichiers pointées par des entrées PMD.
    Shared_Hugetlb : Les Translation Lookaside Tables, ou TLB, sont des caches mémoire utilisés pour optimiser le temps d'accès aux emplacements mémoire de l'espace utilisateur. Ce chiffre représente la quantité de RAM utilisée dans les TLB qui sont associées à d'énormes pages de mémoire partagée.
    Private_Hugetlb : Ce chiffre représente la quantité de RAM utilisée dans les TLB associés à des pages mémoire privées.
    Swap : La quantité de swap utilisée.
    SwapPss : La taille du partage proportionnel de l'espace de pagination. Il s'agit de la quantité de swap composée de pages de mémoire privée swapées ajoutée à la (taille partagée divisée par le nombre de parts.)
    Verrouillé : Les mappages de mémoire peuvent être verrouillés pour empêcher le système d'exploitation de paginer la mémoire de tas ou hors tas.
    THPeligible : Il s'agit d'un drapeau indiquant si le mappage est éligible pour l'allocation de pages énormes transparentes. 1 signifie vrai, 0 signifie faux. Les pages énormes transparentes sont un système de gestion de la mémoire qui réduit la surcharge des recherches de pages TLB sur les ordinateurs disposant d'une grande quantité de RAM.
    VmFlags : Voir la liste des drapeaux ci-dessous.
    Mapping : Le nom de la source du mappage. Il peut s'agir d'un nom de processus, d'un nom de bibliothèque ou de noms de système tels que stack ou heap.

Les VmFlags – drapeaux de mémoire virtuelle – seront un sous-ensemble de la liste suivante.

rd : Lisible.
wr : Ecrit.
ex : Exécutable.
sh : Partagé.
mr : Peut être lu.
mw : Peut écrire.
me : Peut exécuter.
ms : Peut partager.
gd : Le segment de pile croît vers le bas.
pf : Plage de numéros de trame de page pure. Les numéros de cadre de page sont une liste des pages de la mémoire physique.
dw : Écriture désactivée dans le fichier mappé.
lo : Les pages sont verrouillées en mémoire.
io : Zone d'E/S mappée en mémoire.
sr : Lecture séquentielle conseillée (par la fonction madvise()).
rr : Conseils de lecture aléatoire fournis.
dc : Ne pas copier cette région mémoire si le processus est bifurqué.
de : Ne pas étendre cette région de mémoire lors du remappage.
ac : La zone est responsable.
nr : L'espace d'échange n'est pas réservé pour cette zone.
ht : La zone utilise de grandes pages TLB.
sf : Défaut de page synchrone.
ar : Indicateur spécifique à l'architecture.
wf : Effacer cette région mémoire si le processus est bifurqué.
dd : Ne pas inclure cette région mémoire dans les vidages du noyau.
sd : drapeau "soft dirty".
mm : Zone de carte mixte.
hg : Indicateur d'avis de page énorme.
nh : Pas d'indicateur d'avis de page énorme.
mg : Drapeau de conseil pour les fusions.
bt : Page gardée ARM64 d'instabilité de température de biais.
mt : Les balises d'extension de marquage de la mémoire ARM64 sont activées.
um : Suivi manquant de Userfaultfd.
uw : Suivi de protection par défaut de l'utilisateur.

La gestion de la mémoire est compliquée

Et travailler à rebours à partir de tableaux de données pour comprendre ce qui se passe réellement est difficile. Mais au moins pmap vous donne l’image complète afin que vous ayez la meilleure chance de comprendre ce que vous devez savoir.

Il est intéressant de noter que notre programme d’exemple a été compilé en un exécutable binaire de 16 Ko, et pourtant il utilise (ou partage) quelque 2756 Ko de mémoire, presque entièrement à cause des bibliothèques d’exécution.

Une dernière astuce est que vous pouvez utiliser pmap et les commandes pidof ensemble, combinant les actions de trouver le PID du processus et de le passer à pmap en une seule commande :

pmap $(pidof pm)