Compilation du noyau et de BusyBox
Technologie

Fabriquer son propre Linux (1) : compiler un noyau et BusyBox

Une arborescence de travail, cinq familles d'options noyau à activer en dur, et un BusyBox statique : les deux seuls binaires dont un système Linux a réellement besoin pour démarrer.

La meilleure façon de comprendre un système d'exploitation reste d'en fabriquer un. Pas une distribution : un Linux minimal, compilé à la main, qui démarre dans une machine virtuelle et sert une page web. Trois articles, et à la fin une image disque de quelques dizaines de mégaoctets dont on connaît chaque fichier.

Ce que l'on construit

Un système composé de trois éléments seulement : un noyau Linux compilé pour l'occasion, un binaire BusyBox statique qui fournit toutes les commandes, et un script /init qui joue le rôle de PID 1. Le tout démarre sous QEMU via GRUB, obtient une adresse en DHCP et répond en HTTP.

C'est l'exercice qui rend concrets tous les concepts du démarrage : ce qui doit être compilé en dur, à quoi sert vraiment l'initramfs, ce que fait le premier processus.

Une arborescence, cinq répertoires

~/rocket-lnx-demo/
├── src/         sources uniquement (noyau, BusyBox) — aucun binaire
├── build/       artefacts intermédiaires de compilation
├── out/         résultats finaux : vmlinuz, initramfs.img, disk.qcow2
├── initramfs/   racine de l'initramfs en construction
└── mnt/         points de montage temporaires

Cette séparation n'est pas cosmétique : elle garantit qu'on peut tout effacer et recommencer sans se demander ce qui était source et ce qui était produit. Sur un exercice qu'on refait plusieurs fois — et on le refait — c'est ce qui évite les résultats « qui marchent » sans qu'on sache pourquoi.

Compiler le noyau : ce qui n'est pas compilé n'existe pas

Configuration et compilation du noyau puis de BusyBox

Cinq familles d'options suffisent — mais elles doivent être compilées en dur, pas en modules : sans initramfs, personne ne chargerait ces modules.

make defconfig      # base saine pour x86_64
make menuconfig     # puis on active précisément ce qu'il faut

La règle qui gouverne cette étape : tout ce qui n'est pas compilé n'existe pas. Et comme le premier système n'aura pas d'initramfs pour charger des modules, tout doit être en dur (=y).

  • Console sérieCONFIG_SERIAL_8250 et CONFIG_SERIAL_8250_CONSOLE. Sans elle, QEMU lancé en -nographic n'affiche rien : la machine démarre peut-être très bien, on ne le saura jamais. C'est l'option qu'on oublie et qui coûte une heure.
  • Disque VirtIOCONFIG_VIRTIO, CONFIG_VIRTIO_PCI, CONFIG_VIRTIO_BLK : c'est ce qui fait apparaître /dev/vda. Sans elles, le noyau démarre et ne voit aucun disque.
  • RéseauCONFIG_VIRTIO_NET et CONFIG_E1000 : deux modèles de cartes, pour pouvoir basculer sur une carte émulée classique en cas de doute lors du débogage.
  • Système de fichiersCONFIG_EXT4_FS, faute de quoi la racine ne se monte pas.
  • devtmpfs et initramfs — pour que /dev se peuple automatiquement et que le noyau accepte une archive initrd.
make -j"$(nproc)"
cp arch/x86/boot/bzImage ~/rocket-lnx-demo/out/vmlinuz

Le noyau produit s'appelle bzImage — « big zImage », rien à voir avec bzip2 : c'est simplement le format d'image compressée capable de dépasser les limites de taille du format historique. Renommé vmlinuz, il sera chargé par GRUB.

BusyBox : toutes les commandes en un binaire

Un système a besoin de sh, mount, ls, ip, d'un client DHCP… Les compiler séparément représenterait des centaines de paquets. BusyBox réunit plus de trois cents commandes dans un exécutable unique : le programme regarde sous quel nom il a été appelé et se comporte en conséquence. Chaque commande n'est qu'un lien symbolique vers lui.

Settings ---> [*] Build static binary (no shared libs)
Networking Utilities ---> [*] udhcpc   [ ] tc

Deux choix méritent explication.

La compilation statique est obligatoire, pas préférable. Un binaire dynamique va chercher sa bibliothèque C au démarrage ; dans un initramfs qui ne contient pas de libc, il échoue immédiatement — et le message d'erreur, à ce stade, est particulièrement peu bavard. Statique, le binaire est autonome : il fonctionne partout.

udhcpc fournit un client DHCP minimal — indispensable pour que le système obtienne une adresse. Quant à tc, il est désactivé volontairement : inutile ici, et connu pour faire échouer les compilations statiques.

Où l'on en est

À l'issue de cette étape, deux fichiers seulement : un noyau et un BusyBox statique. Rien ne démarre encore — il manque un disque, un système de fichiers racine et un premier processus. C'est l'objet de l'article suivant.

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 — vous y êtes
  4. Fabriquer son Linux (2) : disque, rootfs et PID 1
  5. Fabriquer son Linux (3) : initramfs, GRUB et premier boot
Retour au blog