Disque qcow2 attaché en NBD, partitionné et formaté
Technologie

Fabriquer son propre Linux (2) : le disque, le rootfs et le PID 1

Monter une image qcow2 comme un vrai disque avec NBD, créer un MBR et un ext4, peupler la racine avec BusyBox — et écrire le processus qui n'a pas le droit de mourir.

Un noyau et un BusyBox ne démarrent rien. Il faut maintenant un disque, une partition amorçable, une arborescence racine — et surtout le premier processus, celui que le noyau lancera et qui ne devra jamais s'arrêter.

Un disque qcow2, monté sans machine virtuelle

Création du disque, attachement NBD, partitionnement et formatage

NBD expose une image qcow2 comme un disque réel de l'hôte : on la partitionne et on la remplit sans démarrer aucune machine virtuelle.

L'astuce centrale de cette étape s'appelle NBD (Network Block Device). Le module noyau du même nom, associé à qemu-nbd, permet d'exposer une image disque qcow2 comme un périphérique bloc ordinaire de la machine hôte :

sudo modprobe nbd max_part=16
sudo qemu-nbd --connect=/dev/nbd0 out/disk.qcow2

Le paramètre max_part=16 autorise la reconnaissance des partitions — sans lui, /dev/nbd0p1 n'apparaît jamais. À partir de cet instant, le noyau de l'hôte considère l'image comme un vrai disque : fdisk, mkfs et mount fonctionnent dessus normalement.

Un avertissement s'impose : un périphérique NBD resté connecté est une source d'ennuis sérieux — image corrompue si on la manipule en parallèle, ou disque « occupé » sans raison apparente. Le qemu-nbd --disconnect final n'est pas optionnel.

MBR, ext4, et pourquoi ces choix

sudo fdisk /dev/nbd0        # table MBR, une partition primaire, marquée bootable
sudo partprobe /dev/nbd0
sudo mkfs.ext4 /dev/nbd0p1
sudo mount /dev/nbd0p1 mnt

Une table MBR et non GPT : on démarre en mode BIOS, avec le GRUB correspondant. C'est le schéma décrit dans l'article sur la chaîne de démarrage — secteur 0, 512 octets, signature 0x55AA. Une partition unique, marquée amorçable, formatée en ext4 — le système de fichiers pour lequel on a justement compilé le support en dur dans le noyau.

Tout se tient : chaque décision prise ici a son pendant dans la configuration du noyau. C'est l'intérêt de l'exercice.

Le système de fichiers racine, à la main

On crée l'arborescence attendue par un système Unix — /bin, /sbin, /etc, /proc, /sys, /dev, /tmp, /boot — puis on y copie le BusyBox statique et on crée les liens symboliques de chaque commande vers lui.

C'est le moment où l'on mesure à quel point un système Unix est peu de chose : un binaire, quelques centaines de liens, une poignée de répertoires vides. Tout le reste — un système de fichiers /proc peuplé, des périphériques dans /dev — est fabriqué au démarrage par le noyau lui-même.

Le réseau : un script appelé par le client DHCP

udhcpc ne configure rien tout seul. Il obtient un bail, puis appelle un script en lui passant les informations reçues dans des variables d'environnement, avec un argument indiquant l'événement : deconfig au démarrage, bound ou renew quand une adresse est obtenue.

Le script — quelques lignes — applique l'adresse à l'interface, installe la route par défaut si une passerelle a été fournie, et écrit /etc/resolv.conf avec les serveurs DNS. Rien de plus, mais rien de moins : sans ce script, la machine obtient une adresse que personne n'utilise.

C'est un excellent révélateur : sur un système classique, ce travail est fait par NetworkManager ou systemd-networkd, et personne ne sait plus ce qui se passe réellement.

PID 1 : le processus qui n'a pas le droit de mourir

Le noyau, une fois la racine montée, exécute un seul programme. Ce processus porte le PID 1 et jouit d'un statut particulier : s'il se termine, le noyau panique. C'est aussi lui qui hérite des processus orphelins.

Deux décisions, ici, méritent d'être soulignées.

Le fichier est /init, pas /sbin/init — et c'est délibéré. Dans un environnement BusyBox, /sbin/init est souvent un lien symbolique vers le binaire BusyBox, lequel peut lui-même renvoyer ailleurs : on obtient alors des boucles de liens et l'erreur « Too many levels of symbolic links », au pire moment. Un fichier réel supprime le problème.

Le script doit être robuste plutôt qu'élégant. Il monte /proc, /sys et /dev de façon idempotente — un montage déjà présent ne doit pas faire échouer le démarrage —, choisit automatiquement la première interface réseau disponible, lance le DHCP, écrit une page HTML, puis démarre le serveur HTTP au premier plan.

Ce dernier point est essentiel : le serveur devient le processus final. S'il passait en arrière-plan, le script se terminerait, PID 1 disparaîtrait, et le noyau paniquerait aussitôt. Sur un système minimal, le service est le PID 1.

Ce qui manque encore

Le disque contient un système complet, mais rien ne sait le démarrer : ni initramfs, ni chargeur. C'est l'objet du dernier article.

La page de référence est sur le wiki

Cet article ne remplace pas la documentation : il en reprend le fil et en explique les raisons. Le détail complet — toutes les commandes, tous les scripts, les options de configuration une par une — est publié sur mon wiki :

La série

Deux articles pour comprendre ce qui se passe au démarrage, trois pour fabriquer un système Linux complet à la main :

  1. Du bouton Power au noyau : la chaîne de démarrage x86
  2. initramfs et switch_root : le sas de démarrage
  3. Fabriquer son Linux (1) : noyau et BusyBox
  4. Fabriquer son Linux (2) : disque, rootfs et PID 1 — vous y êtes
  5. Fabriquer son Linux (3) : initramfs, GRUB et premier boot
Retour au blog