Fonctionnalités du noyau Linux (Episode 2) – Gestion de la mémoire

Nous avons vu dans l’épisode 1 l’architecture générale de l’environnement du noyau Linux, ainsi que ce qu’il réalise en matière de gestion de processus.

Dans l’épisode 2 nous allons voir comment le noyau gère la mémoire.

La mémoire dont nous parlons ici est la mémoire RAM physique installée sur l’ordinateur.

Nous avons vu dans l’épisode 1 que, lors du boot de l’ordinateur, le noyau est le premier programme monté en mémoire, et qu’il se verrouille dans une zone de haut privilège.

Le reste de la mémoire physique présente sur la machine est donc disponible pour que le noyau y fasse tourner des processus et y conserve des caches.

Pour faire tourner ces processus, la mémoire physique sera utilisée sous forme de mémoire virtuelle à base de pages.

Une page fait 4 Ko car c’est la taille standard pour la majorité des micro-processeurs, un bon compromis entre la gestion de la mémoire et les performances.

Pages physiques et pages virtuelles

La figure ci-dessus montre ce qui se passe réellement dans la machine.

La partie milieu est la mémoire physique existant sur la machine, découpée en pages de 4 Ko. C’est la seule mémoire qui existe réellement.

La partie gauche et la partie droite représentent 2 processus tournant sur la machine (par exemple Firefox et LibreOffice writer). Comme on peut le voir, l’empreinte mémoire des processus est également constituée de pages de 4 Ko, celles qui contiennent le code machine du programme à exécuter et les données (variables).

Chaque page virtuelle d’un processus est stockée dans une page physique de la mémoire physique. Le noyau maintient pour chaque processus une table de pages qui fait le lien entre les pages virtuelles et les pages physiques pour ce processus.

Ainsi l’assemblage des pages virtuelles d’un processus n’est qu’une vue de l’esprit. Les seules choses qui existent réellement dans la machine sont les pages physiques et les tables de pages, qui disent dans quel ordre lier les pages physiques pour constituer la vue processus.

On aurait pu penser que les processus sont exécutés dans une mémoire purement physique mais ce n’est pas le cas, notamment car on aurait très vite des limitations de place.

Au contraire, l’usage de cette mémoire virtualisée a d’énormes avantages.

Avantages d’utiliser de la mémoire virtuelle

Cette organisation de la mémoire permet de faire en sorte que chaque processus a son propre espace d’adresses virtuelles. Un processus donné ne peut donc pas interférer avec les autres processus. Chaque processus est dans un espace mémoire étanche.

Cela ne veut pas dire que les processus ne peuvent pas communiquer entre eux ; ils ne peuvent simplement pas communiquer directement. Ils devront passer par des mécanismes offerts spécialement pour cela par le noyau (pipes, sockets, IPC, signaux) que nous verrons dans les épisodes ultérieurs.

Cette organisation mémoire permet aussi de faire croire à un processus donné qu’il bénéficie de l’intégralité de la mémoire pour lui tout seul, et mieux, que cette mémoire est beaucoup plus grande que la taille de la mémoire physique réellement présente sur la machine.

Autre avantage, et non des moindres, les pages partagées par plusieurs processus ne sont chargées qu’en un seul exemplaire en mémoire physique. Le cas extrême est celui où l’on lance deux fois le même programme : le code à exécuter est le même, et donc n’est stocké qu’en un seul exemplaire en mémoire physique.

Enfin, ce mécanisme de pages virtuelles garantit que la mémoire physique est partagée équitablement entre tous les processus.

Mécanisme de demande de pages

Etant une vue de l’esprit, la mémoire virtuelle a une taille qui est supérieure à la taille de la mémoire physique installée sur la machine.

Le noyau doit donc gérer efficacement la mémoire physique : il ne va charger en mémoire que les pages réellement utilisées. Si vous avez un programme dont le code est volumineux, et qu’à une exécution donnée de ce code 90% du code écrit n’est pas exécuté, alors il ne sera pas chargé en mémoire.

Si un processus tente d’accéder (pour exécuter son code) à une page virtuelle non chargée en mémoire physique, c’est-à-dire non référencée dans la table de pages, le micro-processeur déclenche une faute de page. Le noyau charge alors la page virtuelle manquante dans une page physique libre.

Ce mécanisme de faute de page n’existe que sur les micro-processeurs équipés de MMU (Memory Management Unit), ce qui est le cas de tous les micro-processeurs modernes. Quoi qu’il en soit, le noyau Linux ne pourrait pas tourner sur une machine dont le micro-processeur n’aurait pas de MMU.

Donc, lors d’une faute de page, s’il reste une page physique libre, la page virtuelle est chargée en allant piocher le code manquant dans le binaire qui est sur le disque dur.

S’il ne reste plus de page physique libre, le noyau doit écraser une page physique existante. Si la page choisie n’a été accédée qu’en lecture, elle est écrasée. Le processus qui l’utilise la fera recharger, s’il en a ultérieurement encore besoin, lors d’une prochaine faute de page.

Si la page choisie pour l’écrasement a été accédée en écriture, elle est sauvegardée dans un fichier spécial appelé swap.

Le choix de la page à écraser résulte d’un algorithme appliqué par le noyau : jusqu’aux versions 5.x du noyau Linux, l’algorithme utilisé était LRU (Least Recently Used), c’est-à-dire que chaque page comportait un âge, mis à jour par le noyau à chaque fois que la page était accédée. Plus une page est accédée récemment, plus elle est jeune. Les vieilles pages sont de bonnes candidates pour l’écrasement ou le swapping.

L’algorithme a changé à partir de la version 6.1 du noyau Linux : on a quitté le LRU pour le MGLRU (Multi-Gen LRU). Les pages sont maintenant organisées en plusieurs générations. Chaque génération représente un groupe de pages avec une ancienneté d’accès similaire.

Concrètement, chaque page mémoire se voit attribuer un numéro de génération (exemple 0, 1, 2, 3). Plus le numéro est petit et plus la page est ancienne au sens des accès. Quand la mémoire vient à manquer, le noyau Linux supprime intégralement la génération la plus ancienne, d’un seul coup, sans avoir à la parcourir page par page. Il y a donc moins de latence puisqu’il n’y a plus à fouiller dans une liste de page immense, de plusieurs millions d’entrées.

Les caches

Nous avons vu jusqu’ici que la mémoire physique est utilisée pour loger le noyau Linux dans sa zone de hauts privilèges, et que la mémoire restante est utilisée pour gérer la mémoire virtuelle logeant les processus.

Sur une machine peu chargée en processus, il reste donc des pans entiers de mémoire physique disponible, inutilisée, surtout si les barrettes de RAM sont grosses.

Cet espace disponible est utilisé par le noyau pour faire du cache, et notamment du cache disque et du cache swap.

Cache disque : le noyau garde en mémoire les dernières données lues sur les périphériques de type bloc, comme les disques durs, etc. Les accès à ces types de périphériques étant très lents comparés aux accès mémoire, il est plus rentable de garder en mémoire les dernières données lues, surtout s’il y a besoin d’y accéder de nouveau.

Cache swap : lorsqu’une page a été swappée puis rechargée depuis le swap puis qu’il est à nouveau nécessaire de la swapper, le noyau sait grâce à ce cache qu’il peut l’écraser directement puisqu’elle est déjà dans le swap.

Une machine Linux sera donc d’autant plus rapide qu’elle a de RAM physique installée puisque l’intégralité de la RAM physique non utilisée par le noyau ou par les processus est utilisée en cache ultra rapide.

Nous arrivons à la fin de cet épisode 2 sur la gestion mémoire assurée par le noyau Linux. Nous avions commencé par affirmer au début de l’épisode 1 que l’écriture d’un noyau Unix était une tâche ardue, et l’épisode 1 consacrée à la gestion des processus avait commencé à le confirmer). Avec ce nouvel épisode consacrée à la gestion de la mémoire que doit assurer le noyau, cela se confirme.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *