Mémoire, fichiers et entrées-sorties
Cours complet · informatique (MP2I/MPI), chapitre 8 · MP2I et MPI
Travailler ce chapitre sur Adloun Exercices corrigés de ce chapitre
8.1 Ce que le système fait pour vous
Le programme donne à cette partie un objectif mesuré : « donner un premier aperçu des liens qu'assurent les systèmes d'exploitation … entre les programmes et les ressources offertes par les machines qui les exécutent ». Et il écarte aussitôt l'essentiel de ce qu'un cours de système contiendrait : « le fonctionnement du matériel, l'architecture des ordinateurs, la conception des systèmes, la gestion des interfaces, les protocoles de communication, la virtualisation … sont hors programme ».
Restent trois choses, et elles suffisent à comprendre pourquoi un programme plante : comment il occupe la mémoire, comment il lit et écrit des fichiers, et — au chapitre chap:concurrence — comment plusieurs fils s'y bousculent. Le système Linux sert de support, « le plus propice pour introduire les éléments de ce programme ».
8.2 La mémoire d'un processus
8.2.1 Pile et tas
Un programme en cours d'exécution dispose de plusieurs zones de mémoire. Deux nous intéressent :
- la pile (stack) : les blocs d'activation des appels de fonction, empilés et dépilés automatiquement. Rapide, de taille bornée, gérée sans que vous n'y pensiez.
- le tas (heap) : la mémoire demandée explicitement par
malloc, rendue parfree. Vaste, à durée de vie choisie, entièrement à votre charge en C.
Le programme demande de distinguer ces deux notions, qu'on confond volontiers.
- La portée est syntaxique : c'est la région du texte du programme où un nom désigne cette variable. En C, les accolades la délimitent.
- La durée de vie est temporelle : c'est l'intervalle pendant lequel la case mémoire existe.
Elles ne coïncident pas. Un objet du tas survit à la fonction qui l'a créé — c'est tout son intérêt.
| Nature de la variable | Où elle vit | Durée de vie |
|---|---|---|
| globale | zone des globales | tout le programme |
| locale, paramètre | pile | l'appel de la fonction |
| allouée par `malloc` | tas | jusqu'au `free` |
int* faute(void) {
int x = 42;
return &x; /* x MEURT ici : l'adresse rendue ne désigne plus rien */
}
Le bloc d'activation est dépilé au retour ; la case existe encore physiquement, et un prochain appel la réutilisera. Le programme lira donc une valeur qui change toute seule. gcc -Wall avertit : function returns address of local variable. C'est un avertissement qu'on ne discute pas.
8.2.2 La protection mémoire
Le système attribue à chaque processus des plages d'adresses. Toute lecture ou écriture hors de ces plages est interceptée par le matériel, et le processus est tué : c'est la segmentation violation, que le programme cite explicitement.
int* p = NULL;
*p = 1; /* Segmentation fault */
| Accès fautif | Détecté ? | Conséquence |
|---|---|---|
| Hors du processus | oui, par le matériel | mort immédiate, message clair |
| Hors tableau, dans le processus | non | corruption silencieuse |
Le second cas est infiniment plus dangereux : t[7] sur un tableau de cases tombe presque toujours dans la mémoire du processus, et écrase une autre variable. La violation de segment, elle, vous arrête net et vous dit où.
Bonne pratique (Les outils qui voient ce que le compilateur ne voit pas)
gcc -fsanitize=address -g prog.c -o prog # instrumente les accès mémoire
valgrind ./prog # détecte fuites et lectures fautives
Ces outils ne sont pas exigibles, mais ils transforment un « ça plante parfois » en un message nommant le fichier et la ligne. Le programme demande d'ailleurs une « expérience » pratique du système, pas seulement une connaissance théorique.
8.3 Les fichiers
8.3.1 L'interface : un flux d'octets
Un fichier se présente au programme comme une suite d'octets munie d'une taille et d'un accès séquentiel : on l'ouvre, on lit ou on écrit à la position courante — qui avance — et on le ferme.
FILE* f = fopen("donnees.txt", "r"); /* "r" lecture, "w" écriture */
if (f == NULL) { /* le fichier n'existe pas, ou les droits manquent */ }
int x;
fscanf(f, "%d", &x);
fclose(f);
let f = open_in "donnees.txt" in
let ligne = input_line f in (* lève End_of_file en fin de fichier *)
close_in f
Le programme borne l'exigence : « les seules notions exigibles sont celles permettant à un programme de gérer l'ouverture, la fermeture et l'accès à un ou plusieurs fichiers ».
fopen rend NULL quand il échoue, et rien ne vous oblige à le regarder : le fscanf suivant déréférencera NULL. Et un fichier non fermé garde ses écritures en tampon : le programme se termine, le fichier est incomplet. Ces deux fautes n'apparaissent qu'à l'exécution, souvent seulement quand le disque est plein ou le fichier absent.
8.3.2 L'implémentation : blocs et nœuds d'index
Sur le disque, un fichier est un ensemble de blocs de taille fixe, pas nécessairement contigus. Un nœud d'index — l'inode — porte les métadonnées (taille, droits, dates) et la liste des blocs. Point capital : l'inode ne contient pas le nom du fichier.
Le programme demande de présenter « le partage de blocs (avec liens physiques ou symboliques) et l'organisation hiérarchique de l'espace de nommage ».
| Lien physique | Lien symbolique | |
|---|---|---|
| Ce qu'il est | un second nom du même inode | un fichier contenant un chemin |
| Si la cible est renommée | rien ne change | le lien pointe dans le vide |
| Peut viser un répertoire | non | oui |
| Peut franchir un disque | non | oui |
| Commande | `ln cible nom` | `ln -s cible nom` |
Un fichier n'est effacé que lorsque son dernier nom disparaît : rm retire un nom, pas un contenu.
Chaque inode porte des droits pour trois catégories — propriétaire, groupe, autres — et trois opérations : lecture , écriture , exécution .
-rw-r--r-- rapport.txt le propriétaire lit et écrit, les autres lisent
drwxr-xr-x travaux/ un répertoire ; x signifie « on peut le traverser »
Sur un répertoire, ne veut pas dire « exécutable » mais « traversable » : sans lui, on ne peut pas accéder à ce qu'il contient, même en connaissant les noms.
8.3.3 L'espace de nommage
Contrairement aux lettres de lecteur, un système de type Unix n'a qu'un arbre, de racine /. Un support amovible y est monté en un point : son contenu apparaît alors sous ce répertoire. Le programme attend « une expérience du montage d'un support de fichiers amovible, de la gestion des droits d'accès … de la création et du déplacement des parties de l'arborescence et de la gestion des liens ».
8.4 Flux standard, redirections, tubes
Tout processus démarre avec trois flux ouverts :
| Flux | C | OCaml | Rôle |
|---|---|---|---|
| entrée standard | `stdin` | `stdin` | ce que le programme lit |
| sortie standard | `stdout` | `stdout` | les résultats |
| sortie d'erreur | `stderr` | `stderr` | les messages d'erreur |
Parce qu'elles sont redirigées séparément. Un programme dont les résultats vont dans un fichier doit pouvoir encore vous parler à l'écran :
./analyse < donnees.txt > resultats.txt # les erreurs restent à l'écran
./analyse < donnees.txt > resultats.txt 2> journal.txt # séparées
Écrire ses messages d'erreur sur la sortie standard les mélange aux résultats et rend le fichier inexploitable. C'est une faute de conception fréquente, et elle ne se voit qu'au moment de la redirection.
Un tube (pipe) branche la sortie standard d'un programme sur l'entrée standard d'un autre : les deux s'exécutent en même temps, les données circulant au fil de l'eau.
./generer | ./filtrer | ./compter
Le programme demande explicitement que « le professeur expose également ses étudiants à la réalisation d'enchaînements de programmes via des tubes ».
Bonne pratique (Écrire des programmes qui se chaînent)
Un programme qui lit stdin et écrit stdout se combine avec tous les autres ; un programme qui ouvre lui-même "entree.txt" ne se combine avec rien. C'est le même principe que l'abstraction du chapitre chap:abstraction : dépendre d'un contrat — ici, un flux — et non d'une réalisation particulière.
8.5 Ce qu'il faut retenir
- Pile : automatique, bornée, durée de vie = l'appel. Tas : explicite, vaste, durée de vie choisie.
- Portée est syntaxique, durée de vie est temporelle : ne jamais rendre l'adresse d'une locale.
- La violation de segment protège ; l'accès hors tableau resté dans le processus, lui, corrompt en silence.
- Un fichier est un inode — droits, taille, blocs — et des noms qui le désignent.
rmretire un nom. - Trois flux, redirigeables séparément : les erreurs sur
stderr, toujours. Un tube fait circuler les données entre deux programmes qui tournent ensemble.