Systèmes de fichiers#

Hide code cell source

import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import numpy as np
import seaborn as sns
import pandas as pd

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

Concepts fondamentaux#

Un système de fichiers est la couche logicielle qui structure l’espace d’un volume en objets (fichiers, répertoires) et maintient leurs métadonnées.

Blocs de données#

Le volume est découpé en blocs de taille fixe (512 o à 64 Kio selon le FS). Un fichier occupe un ou plusieurs blocs ; la différence entre la taille logique du fichier et l’espace alloué constitue la fragmentation interne.

Inodes#

Chaque fichier ou répertoire est représenté par un inode — une structure de métadonnées stockée dans une zone dédiée du volume. Un inode contient :

  • type (fichier régulier, répertoire, lien symbolique, périphérique…)

  • permissions (mode POSIX)

  • UID, GID propriétaires

  • taille logique en octets

  • horodatages : atime (dernier accès), mtime (dernière modification du contenu), ctime (dernier changement de métadonnées), crtime (création, ext4 uniquement)

  • compteur de liens durs

  • pointeurs vers les blocs de données

Ce que l’inode ne contient pas : le nom du fichier. Le nom est stocké dans le répertoire, qui est lui-même un fichier de type « directory » mappant des noms vers des numéros d’inodes.

Hide code cell source

import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import seaborn as sns

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

fig, ax = plt.subplots(figsize=(11, 6))
ax.set_xlim(0, 10)
ax.set_ylim(0, 6)
ax.axis("off")
ax.set_title("Structure d'un inode ext4 et ses blocs de données", fontweight="bold", pad=14)

# Inode box
inode_rect = mpatches.FancyBboxPatch((0.2, 1.0), 2.8, 4.5,
    boxstyle="round,pad=0.1", fc="#2980b9", ec="#1a5276", lw=2)
ax.add_patch(inode_rect)
ax.text(1.6, 5.3, "INODE #42", ha="center", va="center",
        fontsize=10, fontweight="bold", color="white")

fields = [
    "mode = 0644",
    "uid = 1000, gid = 1000",
    "size = 45 320 o",
    "links = 2",
    "atime / mtime / ctime",
    "blocs directs [0..11]",
    "indirect simple",
    "indirect double",
    "indirect triple",
]
for i, f in enumerate(fields):
    ax.text(1.6, 4.8 - i * 0.42, f,
            ha="center", va="center", fontsize=7.8, color="white", family="monospace")

# Blocs directs
colors_blk = ["#27ae60", "#27ae60", "#27ae60", "#e67e22"]
labels_blk  = ["Bloc 0\n4 Kio", "Bloc 1\n4 Kio", "...", "Bloc n\n(indirect)"]
for i, (c, l) in enumerate(zip(colors_blk, labels_blk)):
    x = 4.0 + i * 1.45
    rect = mpatches.FancyBboxPatch((x, 3.2), 1.1, 1.1,
        boxstyle="round,pad=0.05", fc=c, ec="white", lw=1.5)
    ax.add_patch(rect)
    ax.text(x + 0.55, 3.75, l, ha="center", va="center",
            fontsize=7.5, color="white", fontweight="bold")
    ax.annotate("", xy=(x, 3.75), xytext=(3.0, 3.2),
                arrowprops=dict(arrowstyle="->", color="#555", lw=1.2))

# Bloc indirect → blocs supplémentaires
for i in range(3):
    x = 4.5 + i * 1.4
    rect = mpatches.FancyBboxPatch((x, 1.2), 0.9, 0.9,
        boxstyle="round,pad=0.05", fc="#8e44ad", ec="white", lw=1.5)
    ax.add_patch(rect)
    ax.text(x + 0.45, 1.65, f"Blk\n{12+i}", ha="center", va="center",
            fontsize=7, color="white")
ax.annotate("", xy=(4.95, 2.1), xytext=(7.1 - 0.1, 3.2),
            arrowprops=dict(arrowstyle="->", color="#8e44ad", lw=1.2, linestyle="dashed"))
ax.text(6.5, 2.55, "bloc indirect\n(pointeurs)", ha="center", fontsize=7.5, color="#8e44ad")

plt.tight_layout()
plt.show()
_images/5f68a28f586bbaab9d943c8d5cd68407264a168e99a6dd4dcb8848a659e09638.png

Liens durs et liens symboliques#

Un lien dur (hard link) est une entrée de répertoire supplémentaire pointant vers le même inode. Le fichier n’est détruit que quand le compteur de liens tombe à zéro. Les liens durs ne peuvent pas traverser les systèmes de fichiers ni pointer sur des répertoires.

Un lien symbolique (symlink) est un fichier de type l dont le contenu est un chemin cible. Il peut traverser les systèmes de fichiers. Si la cible est supprimée, le lien devient « brisé » (dangling symlink).

# Lien dur
ln /etc/hosts /tmp/hosts_hard
ls -li /etc/hosts /tmp/hosts_hard
# 524289 -rw-r--r-- 2 root root 231 ... /etc/hosts
# 524289 -rw-r--r-- 2 root root 231 ... /tmp/hosts_hard
# ↑ même inode, compteur = 2

# Lien symbolique
ln -s /etc/hosts /tmp/hosts_sym
ls -li /tmp/hosts_sym
# 789012 lrwxrwxrwx 1 root root 10 ... /tmp/hosts_sym -> /etc/hosts
# ↑ inode différent, type 'l'

ext4#

ext4 est le système de fichiers par défaut de la plupart des distributions Linux. Il est l’aboutissement d’une lignée ext2 → ext3 → ext4.

Journalisation#

ext4 maintient un journal (journal séparé ou dans le volume) qui enregistre les opérations de métadonnées avant leur application. En cas de coupure de courant, fsck peut rejouer ou annuler le journal en quelques secondes, évitant un fsck complet (qui peut prendre des heures sur de grands volumes).

Modes de journalisation :

  • journal : données et métadonnées journalisées — le plus lent, le plus sûr

  • ordered (défaut) : données écrites avant les métadonnées

  • writeback : seules les métadonnées sont journalisées — le plus rapide, risque de corruption des données

Extents#

Depuis ext4, les blocs sont alloués sous forme d”extents — des plages contiguës de blocs décrites par (bloc de départ, longueur). Un extent peut décrire jusqu’à 128 Mio en une seule entrée d’inode, réduisant drastiquement la fragmentation et la taille des structures internes.

Commandes d’administration#

# Créer un ext4 avec des options
sudo mkfs.ext4 -L "data" -b 4096 -E stride=8,stripe-width=64 /dev/sdb1

# Informations sur le système de fichiers
sudo dumpe2fs -h /dev/sdb1
# Filesystem volume name:   data
# Filesystem UUID:          ...
# Filesystem magic number:  0xEF53
# Filesystem revision #:    1 (dynamic)
# Filesystem features:      has_journal ext_attr resize_inode dir_index
#                           filetype extent 64bit flex_bg sparse_super2
#                           huge_file dir_nlink extra_isize metadata_csum
# Inode count:              3276800
# Block count:              13107200
# Block size:               4096

# Modifier les paramètres (volume démonté ou en lecture seule recommandé)
sudo tune2fs -c 30 -i 1m /dev/sdb1   # fsck tous les 30 montages ou 1 mois
sudo tune2fs -e remount-ro /dev/sdb1  # comportement en cas d'erreur

# Vérification (volume démonté obligatoirement)
sudo fsck.ext4 -f /dev/sdb1

fsck en production

fsck doit être exécuté sur un volume démonté. Sur le volume racine, il s’exécute au démarrage avant le montage. Forcer fsck sur un volume monté en lecture-écriture peut corrompre les données.


XFS#

XFS est développé par SGI depuis 1993, intégré au noyau Linux en 2001. C’est le système de fichiers par défaut de RHEL/CentOS/Rocky Linux depuis la version 7.

Architecture#

XFS repose sur des Allocation Groups (AG) indépendants — des partitions internes du volume traitées en parallèle. Chaque AG gère ses propres inodes et son propre espace libre via des B-trees, ce qui offre d’excellentes performances sur les workloads parallèles.

Journalisation : XFS ne journalise que les métadonnées (équivalent du mode writeback d’ext4 mais avec des garanties supplémentaires via l’ordering). Le journal peut être placé sur un périphérique séparé pour les workloads intensifs.

Allocation différée (delayed allocation) : XFS regroupe les petites écritures en mémoire avant d’allouer les blocs définitifs, maximisant la contiguïté.

Commandes d’administration#

# Créer un XFS
sudo mkfs.xfs -L "data_xfs" -b size=4096 /dev/sdc1

# Informations
sudo xfs_info /mnt/data
# meta-data=/dev/sdc1   isize=512  agcount=4, agsize=3276800 blks
#          =             sectsz=512 attr=2, projid32bit=1
#          =             crc=1        finobt=1, sparse=1, rmapbt=0
# data     =             bsize=4096   blocks=13107200, imaxpct=25
# naming   =version 2   bsize=4096   ascii-ci=0, ftype=1
# log      =internal    bsize=4096   blocks=6400, version=2

# Réparer (volume démonté)
sudo xfs_repair /dev/sdc1

# Défragmenter (volume monté)
sudo xfs_fsr /mnt/data

# Étendre (après extension du volume sous-jacent)
sudo xfs_growfs /mnt/data

XFS ne peut pas être réduit

Contrairement à ext4, XFS ne supporte pas la réduction de taille. Planifiez la taille initiale en conséquence, ou utilisez LVM pour conserver la flexibilité (voir chapitre 7).


Btrfs#

Btrfs (B-tree FS, prononcé « Butter FS » ou « Better FS ») est développé depuis 2007 et intégré au noyau depuis 2.6.29. Il apporte des fonctionnalités avancées nativement absentes de ext4 et XFS.

Copy-on-Write (CoW)#

Le principe fondamental de Btrfs est le Copy-on-Write : lors de toute modification d’un bloc de données, Btrfs écrit le nouveau contenu dans un nouvel emplacement, met à jour les pointeurs, puis libère l’ancien emplacement. L’ancien état du fichier reste accessible jusqu’à ce qu’il soit explicitement libéré.

Ce mécanisme rend les snapshots quasi-instantanés et sans coût initial d’espace disque.

Sous-volumes et snapshots#

# Créer un système de fichiers Btrfs
sudo mkfs.btrfs -L "systeme" /dev/sdd1

# Créer un sous-volume
sudo btrfs subvolume create /mnt/btrfs/@home

# Lister les sous-volumes
sudo btrfs subvolume list /mnt/btrfs
# ID 256 gen 10 top level 5 path @home

# Snapshot en lecture-écriture
sudo btrfs subvolume snapshot /mnt/btrfs/@home /mnt/btrfs/snapshots/@home_20250101

# Snapshot en lecture seule (pour backup)
sudo btrfs subvolume snapshot -r /mnt/btrfs/@home /mnt/btrfs/snapshots/@home_ro

# Supprimer un snapshot
sudo btrfs subvolume delete /mnt/btrfs/snapshots/@home_20250101

Compression transparente#

# Monter avec compression zstd niveau 3
sudo mount -o compress=zstd:3 /dev/sdd1 /mnt/btrfs

# Vérifier la compression effective
sudo compsize /mnt/btrfs/@home
# Processed 12345 files
# Type       Perc     Disk Usage   Uncompressed Referenced
# TOTAL       62%      3.1G          5.0G         5.0G
# zstd        62%      3.1G          5.0G         5.0G

RAID intégré#

# Créer un Btrfs RAID 1 sur deux disques
sudo mkfs.btrfs -d raid1 -m raid1 /dev/sde /dev/sdf

# Statut
sudo btrfs filesystem df /mnt/raid
# Data, RAID1: total=10.00GiB, used=8.50GiB
# Metadata, RAID1: total=512.00MiB, used=120.00MiB

tmpfs et ramfs#

tmpfs#

tmpfs est un système de fichiers en mémoire virtuelle : il peut utiliser à la fois la RAM et le swap. Sa taille est dynamique (pas de pré-allocation).

# Monter un tmpfs de 512 Mo
sudo mount -t tmpfs -o size=512M tmpfs /mnt/tmp

# Usage système automatique
# /tmp     → tmpfs (défaut sur systemd, configurable via /etc/systemd/tmp.conf)
# /run     → tmpfs (montage automatique au démarrage)
# /dev/shm → tmpfs (mémoire partagée POSIX)

ramfs#

ramfs est plus simple : pas de limite de taille, pas d’utilisation du swap, pas de statistiques. Il peut remplir la RAM jusqu’à l’OOM killer. Usage réservé aux cas très spécifiques (initramfs, pilotes de noyau).


Montage#

Syntaxe de mount#

# Montage basique
sudo mount /dev/sdb1 /mnt/data

# Monter avec des options
sudo mount -o ro,noexec,nosuid /dev/sdb1 /mnt/data

# Monter par UUID
sudo mount UUID="1234abcd-ef56-..." /mnt/data

# Montage en lecture seule (filesystem corrompu)
sudo mount -o ro,errors=remount-ro /dev/sdb1 /mnt/data

Options de montage importantes#

Option

Effet

ro / rw

Lecture seule / lecture-écriture

noexec

Interdit l’exécution de binaires

nosuid

Ignore les bits setuid/setgid

nodev

Ignore les nœuds de périphérique

relatime

Met atime à jour seulement si < mtime

noatime

Désactive la mise à jour de atime

lazytime

Met atime à jour seulement en mémoire

defaults

rw,suid,dev,exec,auto,nouser,async

Bind mounts#

Un bind mount réexpose une partie de l’arborescence à un autre point de montage. Très utilisé dans les conteneurs (chroot, namespaces).

# Remonter /home dans un chroot
sudo mount --bind /home /mnt/chroot/home

# Bind mount en lecture seule
sudo mount --bind /etc /mnt/chroot/etc
sudo mount -o remount,ro,bind /mnt/chroot/etc

/etc/fstab#

/etc/fstab définit les montages automatiques au démarrage. Chaque ligne suit le format :

<périphérique>  <point-de-montage>  <type>  <options>  <dump>  <pass>
  • dump : 0 = pas de sauvegarde par dump, 1 = inclus

  • pass : ordre du fsck au démarrage (0 = pas de fsck, 1 = rootfs, 2 = autres)

Exemple complet#

# /etc/fstab

# Système racine — par UUID
UUID=5678efgh-1234-abcd-5678-000000000001  /           ext4    defaults,relatime       0 1

# EFI System Partition
UUID=A1B2-C3D4                             /boot/efi   vfat    umask=0077              0 2

# Swap
UUID=abcd1234-ef56-7890-abcd-000000000002  none        swap    sw                      0 0

# Données XFS avec noatime
UUID=9abc0123-def4-5678-9abc-000000000003  /data       xfs     defaults,noatime        0 2

# tmpfs pour /tmp
tmpfs                                      /tmp        tmpfs   defaults,size=2G,mode=1777  0 0

# Bind mount en lecture seule
/srv/www                                   /var/www    none    bind,ro                 0 0

systemd.automount#

systemd peut générer des unités .automount à partir de /etc/fstab pour monter les volumes à la demande :

UUID=...   /mnt/archive   xfs   defaults,x-systemd.automount,x-systemd.idle-timeout=60   0 2

Le volume est monté au premier accès et démonté après 60 secondes d’inactivité.

Vérification avant reboot

Après modification de /etc/fstab, vérifier sans redémarrer : sudo mount -a (monte tous les systèmes non encore montés) et sudo systemctl daemon-reload.


Quotas disque#

Les quotas limitent l’espace disque et le nombre d’inodes par utilisateur, groupe ou projet.

Quotas ext4#

# Activer dans /etc/fstab (options usrquota,grpquota)
# UUID=...  /data  ext4  defaults,usrquota,grpquota  0 2

# Initialiser les fichiers de quota
sudo quotacheck -cugm /data
# quotacheck: Scanning /dev/sdb1 [/data] done
# quotacheck: Old file not found.

# Activer les quotas
sudo quotaon /data

# Éditer le quota d'un utilisateur
sudo edquota -u alice
# Disk quotas for user alice (uid 1000):
#   Filesystem  blocks  soft  hard  inodes  soft  hard
#   /dev/sdb1   45320   0     0     1234    0     0
# → Modifier soft et hard :
#   /dev/sdb1   45320   4000000  5000000  1234  0  0

# Rapport global
sudo repquota -a
# *** Report for user quotas on device /dev/sdb1
# User            used    soft    hard  grace    used  soft  hard  grace
# ----------------------------------------------------------------------
# root      --   35320       0       0              10     0     0
# alice     --   45320 4000000 5000000            1234     0     0

Quotas projet XFS#

XFS supporte les quotas par projet (répertoire), plus flexibles que les quotas par utilisateur.

# Activer dans /etc/fstab (option pquota)
# Assigner un projet
sudo xfs_quota -x -c 'project -s -p /data/projet_a 42' /data
sudo xfs_quota -x -c 'limit -p bsoft=10g bhard=12g 42' /data

Inodes et liens — inspection#

Commande stat#

stat /etc/passwd
#   File: /etc/passwd
#   Size: 2345            Blocks: 8          IO Block: 4096   regular file
# Device: fd01h/64769d    Inode: 524289      Links: 1
# Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
# Access: 2025-01-10 08:23:01.000000000 +0100
# Modify: 2025-01-09 14:12:33.000000000 +0100
# Change: 2025-01-09 14:12:33.000000000 +0100
#  Birth: 2024-12-01 09:00:00.000000000 +0100

Numéros d’inodes#

# Afficher les numéros d'inodes
ls -li /etc/passwd /etc/shadow
# 524289 -rw-r--r-- 1 root root 2345 ... /etc/passwd
# 524290 -rw-r----- 1 root shadow 1234 ... /etc/shadow

# Trouver tous les liens durs d'un inode
find / -inum 524289 2>/dev/null

Saturation des inodes#

Un volume peut être plein en inodes sans être plein en espace. Symptôme : No space left on device alors que df -h montre de l’espace libre.

df -i
# Filesystem      Inodes  IUsed   IFree IUse% Mounted on
# /dev/sda3      3276800  98765 3178035    3% /
# /dev/sdb1       131072 131072       0  100% /var/spool/mail  ← saturé !

Utilisation réelle des partitions#

import psutil
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

parts = psutil.disk_partitions(all=False)
data = []
for p in parts:
    try:
        u = psutil.disk_usage(p.mountpoint)
        data.append({
            "mount": p.mountpoint,
            "total": u.total / 1e9,
            "used":  u.used  / 1e9,
            "free":  u.free  / 1e9,
            "pct":   u.percent,
        })
    except PermissionError:
        pass

data.sort(key=lambda x: x["total"], reverse=True)
labels = [d["mount"] for d in data]
used   = [d["used"]  for d in data]
free   = [d["free"]  for d in data]
pcts   = [d["pct"]   for d in data]

fig, ax = plt.subplots(figsize=(10, max(3, len(data) * 0.7 + 1)))
y = np.arange(len(labels))
bars_used = ax.barh(y, used, color="#e74c3c", label="Utilisé (Go)")
bars_free = ax.barh(y, free, left=used, color="#2ecc71", label="Libre (Go)")

for i, (u, p) in enumerate(zip(used, pcts)):
    color = "#c0392b" if p > 85 else "#7f8c8d"
    ax.text(u + max(free[i] * 0.02, 0.1), i, f"{p:.1f}%",
            va="center", fontsize=8, color=color, fontweight="bold")

ax.set_yticks(y)
ax.set_yticklabels(labels, fontsize=9)
ax.set_xlabel("Espace (Go)")
ax.set_title("Utilisation des systèmes de fichiers montés", fontweight="bold")
ax.legend(loc="lower right")
plt.tight_layout()
plt.show()
_images/e48f56da5b465ca497cd92fa9fb827dfb6996a07b0ae3b835cef2e7691326d17.png

Comparaison des systèmes de fichiers#

Hide code cell source

import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

categories = [
    "Performance\nséquentielle",
    "Performance\naléatoire",
    "Snapshots\nnatifs",
    "Scalabilité\n(grands volumes)",
    "Maturité /\nStabilité",
    "Fonctionnalités\navancées",
]
N = len(categories)
angles = np.linspace(0, 2 * np.pi, N, endpoint=False).tolist()
angles += angles[:1]

ext4  = [4, 3, 2, 3, 5, 2]
xfs   = [5, 4, 2, 5, 4, 3]
btrfs = [3, 3, 5, 4, 3, 5]

for v in [ext4, xfs, btrfs]:
    v += v[:1]

fig, ax = plt.subplots(figsize=(7, 7), subplot_kw=dict(polar=True))
ax.set_theta_offset(np.pi / 2)
ax.set_theta_direction(-1)
ax.set_xticks(angles[:-1])
ax.set_xticklabels(categories, size=8.5)
ax.set_ylim(0, 5)
ax.set_yticks([1, 2, 3, 4, 5])
ax.set_yticklabels(["1", "2", "3", "4", "5"], size=7)

colors = ["#3498db", "#e67e22", "#27ae60"]
fss    = [("ext4", ext4), ("XFS", xfs), ("Btrfs", btrfs)]
for (name, vals), color in zip(fss, colors):
    ax.plot(angles, vals, color=color, linewidth=2, label=name)
    ax.fill(angles, vals, color=color, alpha=0.15)

ax.legend(loc="upper right", bbox_to_anchor=(1.3, 1.1), fontsize=10)
ax.set_title("Comparaison ext4 / XFS / Btrfs", fontweight="bold", pad=20)
plt.tight_layout()
plt.show()
_images/4cdca51da52d1fcc4878fabd2099ddfb38eb78aad134be75e21a54fc98bc952c.png

Résumé#

Les systèmes de fichiers Linux forment un écosystème diversifié adapté à des besoins distincts.

Points essentiels :

  • L’inode est le cœur de tout système de fichiers POSIX : il contient les métadonnées mais jamais le nom du fichier.

  • ext4 est le choix sûr et universel : journalisation robuste, outils matures, compatible partout.

  • XFS excelle sur les workloads parallèles et les grands volumes ; il ne supporte pas la réduction.

  • Btrfs apporte CoW, snapshots et RAID natifs au prix d’une complexité plus élevée ; recommandé quand ces fonctionnalités sont exploitées.

  • tmpfs est monté sur /tmp et /run dans les systèmes systemd modernes ; ses données disparaissent au reboot.

  • /etc/fstab devrait utiliser des UUID ou des labels plutôt que des noms de périphériques.

  • Les quotas (utilisateur, groupe, projet XFS) protègent contre la saturation accidentelle des volumes.

  • Surveiller df -i : un volume peut être plein en inodes sans être plein en espace.

Pour aller plus loin

Le chapitre 7 présente LVM, qui abstrait les volumes physiques pour permettre le redimensionnement à chaud des systèmes de fichiers vus ici. Le chapitre 8 couvre RAID et les stratégies de sauvegarde — Btrfs et LVM y jouent un rôle central via leurs snapshots.