GRUB, installation sur le MBR et démarrage QEMU
Technologie

This article has not been translated into English yet. The French version is shown below.

Read in French

Fabriquer son propre Linux (3) : initramfs, GRUB et premier démarrage

Un initramfs cpio écrit à la main, un GRUB installé sur le MBR d'une image disque, un démarrage QEMU en console série — et un « Hello World » qui valide toute la chaîne d'un seul coup.

Le disque contient un noyau, un rootfs et un PID 1. Reste à fabriquer l'initramfs, à installer un chargeur de démarrage — puis à appuyer sur le bouton et regarder la chaîne complète se dérouler sous les yeux.

Un initramfs écrit à la main

Les distributions génèrent le leur avec des outils qui embarquent des centaines de modules « au cas où ». Ici, on l'écrit : une arborescence minimale, le BusyBox statique, et un script /init d'une trentaine de lignes qui fait exactement quatre choses :

  1. monter /proc, /sys et /dev ;
  2. attendre l'apparition du disque — quelques secondes au plus, car le périphérique VirtIO n'est pas toujours prêt à la milliseconde où le script démarre. Cette attente est le genre de détail qui distingue un initramfs qui fonctionne d'un initramfs qui fonctionne une fois sur deux ;
  3. monter le vrai système de fichiers racine sur /new_root, avec une vérification de cohérence — le fichier /init attendu est-il bien là ? — avant d'aller plus loin ;
  4. appeler switch_root.

L'archive est ensuite produite au format cpio compressé, le seul que le noyau sache lire nativement :

find . | cpio -H newc -o | gzip -9 > ../out/initramfs.img

Le format newc n'est pas facultatif : c'est celui que le noyau attend. Quelques centaines de kilo-octets suffisent — à comparer aux dizaines de mégaoctets d'un initramfs de distribution.

GRUB : une configuration entièrement explicite

Configuration GRUB, installation sur le MBR et démarrage sous QEMU

Un menu sans détection automatique, un GRUB installé sur le secteur 0 de l'image, et une VM qui démarre en console série.

set timeout=0
set default=0
menuentry "Rocket Linux Demo (final)" {
    set root=(hd0,msdos1)
    linux /boot/vmlinuz console=ttyS0 root=/dev/vda1 ro loglevel=7
    initrd /boot/initramfs.img
}

Chaque ligne est volontairement explicite — aucune détection automatique, ce qui est exactement ce qu'on veut pour comprendre :

  • set root=(hd0,msdos1) — premier disque BIOS, première partition MBR. On désigne, on ne devine pas ;
  • console=ttyS0 — la sortie va sur la console série, donc dans le terminal qui a lancé QEMU. Cela n'a de sens que parce que le support série a été compilé en dur dans le noyau ;
  • root=/dev/vda1 — le paramètre que lira l'initramfs pour savoir quoi monter ;
  • ro puis loglevel=7 — racine montée en lecture seule au départ, et journalisation maximale : en phase de mise au point, on veut tout voir.
sudo grub-install --target=i386-pc --boot-directory=mnt/boot /dev/nbd0

La cible i386-pc désigne le GRUB pour BIOS — pas l'UEFI. C'est cette commande qui écrit l'étage 1 dans les 446 octets du MBR et installe les étages suivants. La boucle est bouclée avec l'article sur la chaîne de démarrage.

Fermer proprement

sync
sudo umount mnt
sudo qemu-nbd --disconnect /dev/nbd0

Trois commandes, dans cet ordre, et aucune n'est facultative. Le sync force l'écriture effective des données encore en cache ; le démontage libère le système de fichiers ; la déconnexion NBD rend l'image cohérente. Démarrer une image encore attachée en NBD, c'est la corrompre.

Le premier démarrage

qemu-system-x86_64 -enable-kvm -m 256M -nographic \
  -drive file=out/disk.qcow2,format=qcow2,if=virtio \
  -netdev user,id=n1,hostfwd=tcp::8080-:80 \
  -device virtio-net-pci,netdev=n1

Les options qui comptent : -nographic renvoie la console série dans le terminal — cohérent avec le console=ttyS0 passé au noyau ; if=virtio donne le /dev/vda attendu ; et hostfwd=tcp::8080-:80 redirige le port 8080 de l'hôte vers le port 80 de la machine.

Le déroulé s'affiche alors intégralement : GRUB, la décompression du noyau, l'initramfs, le switch_root, puis le script /init qui obtient une adresse en DHCP et démarre le serveur. 256 Mo de mémoire suffisent largement, et le démarrage se compte en secondes.

La preuve, depuis l'hôte

curl --max-time 3 http://127.0.0.1:8080/
Hello World - Rocket Linux Demo

Cette ligne valide toute la chaîne d'un coup : le MBR, GRUB, un noyau compilé à la main, un initramfs écrit à la main, un switch_root, un PID 1 maison, un client DHCP et un serveur HTTP — le tout dans une image de quelques dizaines de mégaoctets.

Ce que l'exercice apprend

Il ne s'agit pas de remplacer une distribution : personne ne maintiendra un système pareil en production. L'intérêt est ailleurs.

  • On comprend enfin pourquoi l'initramfs existe — en s'en passant, puis en en écrivant un.
  • On sait ce que fait réellement PID 1, et pourquoi il n'a pas le droit de se terminer.
  • On mesure ce qu'apporte une distribution : gestion de paquets, mises à jour de sécurité, pilotes universels, init robuste — tout ce qu'on n'a pas ici.
  • Et devant un serveur qui ne démarre plus, on sait à quel étage regarder : la question « GRUB s'affiche-t-il ? » ou « voit-on l'invite initramfs ? » divise immédiatement le problème en deux.

C'est là tout l'intérêt d'un lab : passer une soirée sur un système qui ne sert à rien, pour ne plus jamais être perdu devant un système qui sert à quelque chose.

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
  5. Fabriquer son Linux (3) : initramfs, GRUB et premier boot — vous y êtes

Sources 12

Documents used in the writing of this article.

Back to the blog