Cours 5 · Fichiers et périphériquesLeçon 1 sur 2
Système de fichiers
5 h de lecture9 sections Version PDF
Fichier, répertoire et chemin ; droits et propriétaires ; blocs, inodes et FAT ; montage, et journalisation en survol.
Sous Unix, on peut supprimer un fichier qu'un programme est en train de lire. Le programme continue de lire, jusqu'au bout, sans erreur — et l'espace n'est libéré qu'à sa fermeture. Sous Windows, la même opération est refusée : le fichier est verrouillé.
Cette différence n'est pas un détail d'implémentation. Elle révèle ce qu'est réellement un
fichier dans un système Unix : le nom et le contenu sont deux objets distincts. rm
n'efface rien, il retire un nom. Le contenu disparaît quand plus personne n'y renvoie.
Ce chapitre décrit cette organisation. Il ferme la boucle du cours : le chapitre 6 rangeait des pages sur un disque sans dire comment ce disque était structuré, et la mémoire virtuelle suppose exactement ce que nous allons construire.
Fichier, répertoire, chemin
Un fichier est une suite d'octets nommée et persistante. C'est tout — le système ne connaît
ni « fichier texte », ni « image ». L'extension .jpg est une convention entre applications, et
la commande file, qui reconnaît un format, le fait en examinant les premiers octets, pas le
nom.
Un répertoire est un fichier lui aussi, dont le contenu est une table de correspondances entre des noms et des numéros. Ces numéros sont ceux des inodes, et c'est la structure centrale du chapitre.
Répertoire /home/ana┌────────────┬────────┐│ nom │ inode │├────────────┼────────┤│ . │ 1204 │ lui-même│ .. │ 902 │ son parent│ rapport.md │ 3311 ││ copie.md │ 3311 │ ← le MÊME inode : deux noms, un seul contenu└────────────┴────────┘L'inode porte tout ce qu'on sait d'un fichier sauf son nom : taille, propriétaire, droits, dates, et les adresses des blocs de données. Le nom est dans le répertoire, pas dans l'inode — et c'est ce qui permet à deux entrées de désigner le même inode. C'est un lien physique, et l'inode compte ses références.
rm décrémente ce compteur et retire l'entrée du répertoire. Les blocs ne sont libérés que
lorsque le compteur atteint zéro et qu'aucun processus n'a le fichier ouvert. D'où le
comportement de l'introduction, et deux conséquences pratiques : la suppression ne détruit pas
les données, elle les rend seulement inaccessibles — ce qui fonde la récupération de fichiers et
justifie l'effacement sécurisé ; et un fichier volumineux ouvert par un processus continue
d'occuper le disque après un rm, ce qui explique bien des « disques pleins » où du ne trouve
rien.
Il faut distinguer le lien symbolique, qui est un fichier contenant un chemin. Il peut pointer sur un autre système de fichiers, mais il devient invalide si la cible disparaît, alors qu'un lien physique reste toujours valide.
Droits et propriétaires
Chaque fichier appartient à un utilisateur et à un groupe, et porte neuf bits de permission — trois catégories, trois droits :
-rwxr-xr-- 1 ana etudiants 4096 rapport.md │└┬┘└┬┘└┬┘ │ │ │ └── autres : r-- lecture seule │ │ └───── groupe : r-x lecture et exécution │ └──────── propriétaire : rwx └────────── type : - fichier, d répertoire, l lienL'écriture octale du chapitre 1 d'architecture prend ici tout son sens : chaque groupe de trois
bits est un chiffre octal, d'où chmod 754. Les valeurs se composent — 4 pour lire, 2 pour
écrire, 1 pour exécuter — et 755 ou 644 sont les deux motifs qu'on écrit sans réfléchir.
Le point qui trompe : sur un répertoire, les droits n'ont pas le même sens. r permet de
lister les noms, w d'y créer ou supprimer des entrées, et x de le traverser,
c'est-à-dire d'accéder à ce qu'il contient si l'on en connaît le nom. Un répertoire en r--
laisse voir les noms sans pouvoir ouvrir les fichiers ; en --x, il laisse ouvrir un fichier
dont on connaît le chemin exact sans pouvoir découvrir la liste. Et surtout, supprimer un
fichier ne demande aucun droit sur le fichier : c'est une modification du répertoire, donc
seul w sur le répertoire compte. Un fichier en lecture seule dans un répertoire où vous
écrivez est effaçable par vous.
Sur le disque : blocs et allocation
Le disque est découpé en blocs de taille fixe, typiquement 4 Kio — la même que la page du chapitre 6, et ce n'est pas un hasard. Reste à savoir quels blocs appartiennent à quel fichier, et trois réponses ont été essayées.
L'allocation contiguë met le fichier dans des blocs consécutifs. Lecture séquentielle très rapide, accès direct immédiat — et exactement la fragmentation externe du chapitre 6, plus l'impossibilité d'agrandir un fichier coincé entre deux voisins. On ne la trouve plus que sur les supports en lecture seule, comme les disques optiques.
L'allocation chaînée met dans chaque bloc l'adresse du suivant. Plus de fragmentation, mais l'accès direct devient impossible : atteindre le millième bloc demande de lire les 999 premiers. La variante FAT sort les pointeurs des blocs et les met dans une table unique, chargée en mémoire : l'accès direct redevient possible en parcourant la table plutôt que le disque. C'est l'organisation des clés USB et des cartes mémoire, et sa limite est que la table doit tenir en mémoire, donc grandit avec le disque.
L'allocation indexée est celle d'Unix. Chaque fichier a un inode qui contient un tableau d'adresses de blocs. L'accès direct est immédiat, il n'y a pas de fragmentation externe — et le tableau est de taille fixe, ce qui poserait une limite si l'on s'arrêtait là.
Les blocs indirects
La solution est élégante et se retrouve dans presque tous les systèmes de fichiers modernes. L'inode contient :
12 pointeurs DIRECTS → 12 blocs de données 1 pointeur INDIRECT SIMPLE → un bloc rempli de pointeurs 1 pointeur INDIRECT DOUBLE → un bloc de pointeurs vers des blocs de pointeurs 1 pointeur INDIRECT TRIPLE → un niveau de plusAvec des blocs de 4 Kio et des pointeurs de 4 octets, un bloc contient 1024 pointeurs. La capacité totale se calcule alors ainsi :
direct 12 × 4 Kio = 48 Kioindirect simple 1024 × 4 Kio = 4 Mioindirect double 1024 × 1024 × 4 Kio = 4 Gioindirect triple 1024 × 1024 × 1024 × 4 Kio = 4 TioL'intérêt de cette structure asymétrique est qu'elle s'adapte à la distribution réelle des tailles de fichiers. L'immense majorité des fichiers font moins de 48 Kio : leur inode suffit, et un accès demande une seule lecture. Les gros fichiers, rares, paient un ou deux accès supplémentaires — ce qui est acceptable puisqu'ils sont rares, et que le cache du système gardera de toute façon leurs blocs d'index.
Un utilisateur ne peut pas modifier un fichier appartenant à quelqu'un d'autre, mais il parvient à le supprimer. Comment est-ce possible ?
Le montage
Le chapitre 2 l'a annoncé : Unix n'a qu'un seul arbre. Le montage est l'opération qui greffe le système de fichiers d'un périphérique sur un répertoire existant, appelé point de montage.
mount /dev/sdb1 /media/cleAprès quoi /media/cle/photo.jpg désigne un fichier de la clé, atteint par les mêmes appels
système et les mêmes commandes que n'importe quel autre. Le contenu que le point de montage
avait avant l'opération est masqué tant que le montage dure — il n'est pas détruit, mais il
n'est plus atteignable.
C'est l'abstraction du chapitre 1 poussée à son terme : la table de montage, plus une couche
appelée système de fichiers virtuel qui définit une interface commune, permettent à ext4, à
NTFS, à un partage réseau et à /proc de coexister sous les mêmes commandes. Un programme qui
lit un fichier ne sait pas — et n'a pas besoin de savoir — sur quel type de support il se
trouve.
La journalisation
Une opération apparemment simple — créer un fichier — modifie plusieurs structures : l'inode, la table des blocs libres, l'entrée de répertoire. Une coupure de courant au milieu laisse un système incohérent : un bloc marqué occupé qu'aucun inode ne réclame, ou pire, un inode pointant vers un bloc réattribué à un autre fichier.
L'ancienne parade était de tout vérifier au démarrage — un parcours complet du disque, qui prenait des heures sur un gros volume.
La journalisation procède autrement : avant de modifier quoi que ce soit, le système écrit dans un journal ce qu'il s'apprête à faire, puis exécute, puis efface l'entrée du journal. Après une coupure, il suffit de relire le journal et de rejouer ou d'annuler les opérations inachevées — quelques secondes au lieu de quelques heures.
La plupart des systèmes ne journalisent que les métadonnées, pas les données. C'est un compromis explicite : la structure est garantie cohérente, mais un fichier peut se retrouver avec des blocs au contenu indéterminé. Journaliser aussi les données double les écritures.
Avec des blocs de 4 Kio et des pointeurs de 4 octets, quelle taille maximale un fichier peut-il atteindre en n'utilisant que les 12 pointeurs directs et l'indirect simple ?
À vous
L'exercice est le calcul d'examen du chapitre, écrit une fois pour toutes. Vous implémentez la résolution d'un déplacement en octets vers un numéro de bloc à travers la structure de l'inode : direct, indirect simple, indirect double — en comptant au passage combien d'accès disque la résolution a coûté.
C'est ce compteur qui rend la structure compréhensible. On voit que les premiers 48 Kio coûtent un accès, que le mégaoctet suivant en coûte deux, et pourquoi c'est le bon compromis compte tenu de la distribution réelle des tailles de fichiers.
Résolvez un déplacement en octets à travers l'inode, et comptez les accès disque.
const BLOC = 4096; // 4 Kio const POINTEUR = 4; // 4 octets par adresse const PAR_BLOC = BLOC / POINTEUR; // 1024 pointeurs dans un bloc d'index const DIRECTS = 12; // Capacités cumulées, en blocs de données. const CAP_DIRECT = DIRECTS; // 12 const CAP_SIMPLE = PAR_BLOC; // 1024 const CAP_DOUBLE = PAR_BLOC * PAR_BLOC; // 1 048 576 // Résout un déplacement en octets vers un bloc logique, en disant par où l'on // est passé et combien d'accès disque cela a coûté. function resoudre(octet) { const blocLogique = Math.floor(octet / BLOC); const dansLeBloc = octet % BLOC; if (blocLogique < CAP_DIRECT) { return { voie: "direct", indices: [blocLogique], dansLeBloc, acces: 1 }; } let reste = blocLogique - CAP_DIRECT; if (reste < CAP_SIMPLE) { // Un accès pour lire le bloc d'index, un pour la donnée. return { voie: "indirect simple", indices: [reste], dansLeBloc, acces: 2 }; } reste -= CAP_SIMPLE; if (reste < CAP_DOUBLE) { return null; // ← à écrire : indirect double } return { erreur: "au-delà de l'indirect double" }; } // ── À VOUS ──────────────────────────────────────────────────────────────── // 1. Complétez la branche « indirect double » : deux indices (quel bloc du // premier niveau, quelle entrée dedans) et le bon nombre d'accès. // 2. Écrivez tailleMax(), qui rend la taille maximale d'un fichier avec // directs + simple + double. // 3. Vérifiez qu'un fichier de moins de 48 Kio coûte toujours UN accès. function tailleMax() { return 0; // ← à écrire } const CAS = [0, 40000, 49152, 60000, 4 * 1024 * 1024, 100 * 1024 * 1024]; for (const o of CAS) { const r = resoudre(o); console.log(String(o).padStart(10) + " octets -> " + (r === null ? "à écrire" : r.erreur ?? (r.voie.padEnd(16) + " indices " + JSON.stringify(r.indices).padEnd(16) + " " + r.acces + " accès disque"))); } console.log("taille maximale :", tailleMax(), "octets");
En travaux pratiques
Un fichier n'est pas son nom
Séparer le nom, l'inode et le contenu — la distinction qui explique les liens, la suppression, l'espace qui ne se libère pas, et la récupération d'un fichier effacé.
- Les TP 2 et 3
- Les commandes ls -i, stat, lsof, df, du
- 1. Voir l'inode
Créez un fichier, affichez son inode et ses métadonnées. Listez ce qui est stocké dans l'inode et ce qui ne l'est pas. Où est le nom ?
- 2. Deux noms, un fichier
Créez un lien dur vers ce fichier. Comparez les inodes, modifiez par un nom, lisez par l'autre. Supprimez le premier nom : que devient le contenu ?
- 3. Le lien symbolique
Créez un lien symbolique vers le même fichier et refaites les mêmes essais. Supprimez la cible et observez. Établissez le tableau des différences.
- 4. L'espace qui ne revient pas
Créez un fichier d'un gigaoctet, ouvrez-le depuis un programme qui dort, supprimez-le pendant ce temps. Comparez ce que disent df et du. Expliquez.
- 5. Récupérer l'irrécupérable
Sans arrêter le programme précédent, retrouvez le contenu du fichier supprimé en passant par /proc. Vous venez de récupérer un fichier effacé.
- 6. Épuiser autre chose que l'espace
Sur une petite partition de test, créez des centaines de milliers de fichiers vides jusqu'à l'échec. Regardez df puis df -i, et expliquez le message.
- 7. Écriture atomique
Écrivez un programme qui remplace un fichier de configuration, et tuez-le au milieu de l'écriture. Constatez la corruption, puis réécrivez-le avec le motif écrire-puis-renommer et refaites l'essai.
- Vous savez dire où est stocké le nom d'un fichier, et ce n'est pas dans l'inode
- Vous récupérez le contenu d'un fichier supprimé encore ouvert
- Le disque est plein alors que df annonce de la place, et vous savez pourquoi
- Votre configuration n'est jamais corrompue, même tuée en pleine écriture
Ce que la suite en fait
Le chapitre 8 termine le cours en descendant sous le système de fichiers : les pilotes qui parlent au contrôleur, les tampons qui absorbent l'écart de vitesse, et l'ordonnancement des requêtes disque — car les blocs que ce chapitre a numérotés se trouvent physiquement quelque part, et l'ordre dans lequel on les demande change le temps total.
Les droits posés ici y reviendront aussi, du côté sécurité : ce sont eux qui portent l'isolation entre utilisateurs, et le chapitre montrera par quels mécanismes on va au-delà.
À retenir
Vous avez parcouru les 9 sections.
Marquez-la terminée pour faire avancer votre parcours, ou revenez sur un point avant de passer à la suite.