Systèmes d'exploitation — fondations · L2 · Section 9/12
Gestion de la mémoire
Progression
#Gestion de la mémoire
Prérequis
- C: pointeurs,
malloc/free, notion de segment.- Chapitre Processus: espace d'adressage par processus.
Objectifs d'apprentissage
- Décrire la traduction adresse virtuelle vers adresse physique (tables, TLB, défaut de page).
- Lire
/proc/<pid>/mapsetpmappour identifier les segments d'un processus.- Expliquer le copy-on-write de fork et la mesurer.
#0. La mémoire telle que le cours la présente
Le raisonnement du cours part d'une contrainte simple: l'espace mémoire est fini, et les programmes sont en concurrence pour l'utiliser. Or le partage du temps — la décision d'exécuter plusieurs processus « simultanément » en découpant le processeur en tranches — implique le partage de la mémoire: plusieurs processus doivent tenir en mémoire en même temps, ou du moins y revenir assez vite.
La réponse d'Unix tient en un mot: la mémoire virtuelle. C'est un espace beaucoup plus grand que la mémoire réelle, représenté sur disques, entre lesquels le noyau fait des échanges qui doivent rester rapides et fiables. Le cours en donne la représentation mentale la plus utile:
- une page en mémoire, une page sur disque;
- une mémoire virtuelle, une mémoire réelle;
- et un mécanisme de récupération de l'espace inutilisé.
Deux conséquences pratiques découlent de cette architecture, et elles sont visibles depuis la ligne de commande.
La partition d'échange. La mémoire virtuelle a besoin d'un support: c'est la partition swap, « zone dédiée à l'échange entre mémoire vive et disque dur ». Le cours recommande de la placer en début de disque, plus rapide lorsque le disque est organisé en cylindres, et le TP 3 en fait créer une de 500 Mo lors de l'installation d'Ubuntu dans la machine virtuelle — aux côtés d'une partition /home de 500 Mo et d'une partition racine / occupant le reste.
L'observation de la pression mémoire. Le cours cite top, qui affiche « l'utilisation de la zone d'échange » à côté de l'utilisation de la mémoire. C'est l'indicateur direct du mécanisme: tant que le swap reste calme, tout tient en mémoire réelle; quand il s'agite, le noyau fait des allers-retours vers le disque.
1free -h # mémoire réelle et swap, en unités lisibles2swapon --show # les zones d'échange actives3top # la ligne "MiB Swap" dans l'en-tête#1. Pagination et tables de pages
Pour un processus, la mémoire semble linéaire, privée et abondante. En réalité, le noyau et l'unité de gestion mémoire (MMU) orchestrent une traduction permanente entre les adresses virtuelles vues par le programme et les adresses physiques réelles. C'est cette indirection qui donne à la fois l'isolation (modifier l'adresse 0x5000 n'affecte que vous) et la flexibilité (agrandir le tas, charger une bibliothèque, swapper).
La mémoire virtuelle est découpée en pages de taille fixe (4 Ko sur la plupart des architectures courantes). Chaque page virtuelle possède une entrée dans une table de pages qui indique où se trouve le contenu en mémoire physique, ainsi que les permissions (lecture, écriture, exécution). Si une page n'est pas présente, l'accès déclenche un défaut de page: le noyau suspend le processus, charge la page depuis le disque (fichier mappé ou zone de swap), met à jour l'entrée puis reprend l'exécution à l'identique, l'instruction fautive est rejouée, pas avortée. Les processeurs modernes ajoutent un cache de traduction (TLB) pour éviter de relire la table à chaque accès.
Si l'accès est interdit (écrire une page en lecture seule, exécuter une page non exécutable), le défaut devient une protection: le noyau délivre SIGSEGV. C'est la mécanique derrière tout « segmentation fault ».
#2. Disposition et allocation
L'espace virtuel d'un processus classique se compose d'une zone texte (code, en lecture seule), d'une zone de données initialisées, d'un segment bss (données initialisées à zéro, allouées sans occuper de place dans le binaire), d'un tas qui grandit vers les adresses hautes via brk ou mmap, et d'une pile qui croît vers le bas. La carte se lit directement:
1cat /proc/self/maps2# une ligne par plage: adresse début-fin, permissions, chemin (bibliothèque, binaire)3pmap -x $$ # la même vue, avec tailles Résidente/partagéeObservation attendue: les bibliothèques partagées (libc, ld) apparaissent en r-xp pour le code, r--p pour les données en lecture seule; le tas suit le binaire; la pile est en haut, marquée [stack].
malloc et free ne font pas apparaître ou disparaître des pages physiques instantanément: ils gèrent des blocs dans le tas, tandis que le noyau fournit ou récupère des pages entières selon les besoins. C'est pour cela qu'un programme peut conserver une consommation apparente élevée même après avoir libéré des structures volumineuses: les pages ne retournent pas nécessairement au noyau.
#3. Partage maîtrisé
La mémoire virtuelle facilite le partage sans compromettre la sécurité. Deux processus peuvent mapper le même fichier en lecture seule (mmap avec MAP_SHARED) et bénéficier d'une mise à jour simultanée. Les bibliothèques partagées sont chargées une seule fois en mémoire physique, puis référencées par chaque processus qui les utilise. fork exploite également la stratégie copy-on-write: tant que parent et enfant lisent seulement les pages héritées, elles restent partagées. La duplication n'intervient qu'au moment de la première écriture.
1int fd = open("archive.bin", O_RDONLY);2void *view = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);3if (view == MAP_FAILED) { perror("mmap"); return 1; }4process(view);5munmap(view, size);#Atelier: voir le copy-on-write, pas seulement le croire
Écrivez un programme qui alloue un tableau de 500 Mo avec malloc, pré-initialise ses données (chaque octet écrit une fois), appelle fork, puis parent et enfant modifient chacun une moitié du tableau. Avant, pendant, après: affichez VmRSS et les compteurs de pages partagées/privées depuis /proc/self/smaps_rollup (ou lisez /proc/<pid>/smaps pour le détail).
Observations attendues: juste après fork, la résidente de l'enfant est quasi nulle et la quasi-totalité des pages est « shared »; à mesure que chacun écrit sa moitié, la part « private » grimpe (jusqu'à environ 250 Mo de chaque côté) et la part partagée chute: vous venez de regarder le noyau dupliquer les pages une à une, à la demande.
#Exercices
1. Pourquoi le partage du temps implique-t-il le partage de la mémoire?
2. Qu'est-ce que la mémoire virtuelle, et sur quel support repose-t-elle concrètement? Comment appelle-t-on ce support, et où le cours recommande-t-il de le placer?
3. Quels états d'un processus relèvent directement de la gestion mémoire?
4. Quelle commande affiche l'utilisation de la mémoire réelle et de la zone d'échange? Pourquoi un système qui « swappe » en permanence est-il lent, et non simplement plus lent?
5. Décrire le déroulement d'un défaut de page: que fait le noyau, et que devient l'instruction fautive?
6. En quoi le copy-on-write de fork permet-il de dupliquer un processus volumineux à coût quasi nul? Comment le vérifier?
Corrections détaillées
1. Parce que plusieurs processus doivent être résidents en mémoire en même temps pour que le processeur puisse passer de l'un à l'autre. Si un seul processus tenait la mémoire, il n'y aurait rien à exécuter pendant qu'il attend une entrée-sortie, et le temps partagé serait impossible. Le cours énonce donc que « le partage du temps implique le partage de la mémoire », et que l'espace étant fini, les programmes sont en concurrence pour l'utiliser.
2. La mémoire virtuelle est un espace beaucoup plus grand que la mémoire réelle, représenté sur disques, entre lesquels le noyau fait des échanges rapides et fiables. Le support concret de ces échanges est la partition d'échange, ou partition swap: une « zone dédiée à l'échange entre mémoire vive et disque dur ». Le cours recommande de la placer en début de disque, plus rapide lorsque le disque est organisé en cylindres; le TP d'installation en fait créer une de 500 Mo.
3. L'état « en attente de l'arrivée d'une partie de la mémoire virtuelle », cité par le cours parmi les quatre états d'un processus. C'est l'état d'un processus dont une page a été sortie sur disque et doit être ramenée avant que l'exécution puisse reprendre.
4. free -h affiche la mémoire réelle et la zone d'échange sous forme lisible; top en montre une ligne dans son en-tête, et swapon --show liste les zones d'échange actives. Un système qui swappe en permanence est beaucoup plus lent, et non simplement plus lent, parce qu'un accès à une page sortie sur disque coûte des ordres de grandeur de plus qu'un accès en mémoire réelle, et parce que chaque défaut de page suspend le processus et fait travailler le noyau: le temps machine est passé à déplacer des pages au lieu d'exécuter du code utile.
5. Un accès à une page non présente déclenche un défaut de page. Le noyau suspend le processus, charge la page depuis le disque — fichier mappé ou zone de swap —, met à jour l'entrée de la table de pages, puis reprend l'exécution. Point essentiel: l'instruction fautive est rejouée, pas avortée. Le programme ne sait pas qu'il a été interrompu; c'est cette transparence qui rend le mécanisme utilisable. Si l'accès est interdit — écriture sur une page en lecture seule, exécution d'une page non exécutable — le défaut devient une protection et le noyau délivre SIGSEGV.
6. fork ne copie pas immédiatement les pages du parent: le noyau marque leurs entrées comme copy-on-write et les partage. Tant que l'un des deux processus se contente de lire, la page reste unique en mémoire physique; la duplication n'intervient qu'à la première écriture. Un processus de 500 Mo se duplique donc à coût quasi nul. Vérification: lisez VmRSS et les compteurs de pages partagées/privées dans /proc/self/smaps_rollup avant, pendant et après le fork — juste après, la résidente de l'enfant est quasi nulle et presque tout est marqué « shared »; à mesure que chacun écrit, la part « private » grimpe et la part partagée chute.