L'objectif ici est de créer à la main un système d'exploitation se basant sur le noyau linux.
Cette procédure vient illustrer les explications du démarrage d'un système données sur cette page : https://wiki.ev1.fr/fr/Système/Linux/Démarrage-OS-Linux
Une vidéo de démonstration du résultat de la procédure décrite ci-dessous est disponible ici : https://www.youtube.com/watch?v=s0VWe5Bx01Q
Cette procédure part du principe que l’on travaille depuis un hôte Ubuntu 24.04 LTS servant à la fois de poste de build et de machine d’exécution pour la VM de démonstration.
L’hôte joue trois rôles clés :
sudo apt update
sudo apt install -y \
build-essential bc bison flex libssl-dev libelf-dev libncurses-dev \
qemu-kvm qemu-utils \
grub-pc-bin grub-common \
e2fsprogs \
musl-tools \
git wget cpio rsync
Ces paquets sont indispensables pour configurer et compiler un noyau Linux moderne :
build-essential : compilateur GCC, linker, makebc : calculs utilisés lors de la compilation du noyaubison, flex : génération des parseurs (Kconfig, etc.)libssl-dev : support cryptographique (signatures, hash, modules)libelf-dev : manipulation des fichiers ELF (vmlinux, modules)libncurses-dev : interface menuconfigSans eux, la compilation du noyau échoue ou est incomplète.
Ces paquets permettent de démarrer et piloter la VM qui hébergera le système minimal :
qemu-kvm : moteur de virtualisation avec accélération matérielleqemu-utils : création et manipulation d’images disque (qcow2, raw)KVM est utilisé pour obtenir un environnement rapide, reproductible et proche du matériel réel.
On installe explicitement les outils GRUB côté hôte pour :
Paquets concernés :
grub-pc-bingrub-commonObjectif : aucune magie, GRUB est installé et configuré consciemment.
e2fsprogs : création et vérification de systèmes de fichiers ext4Utilisé pour formater le root filesystem de la VM.
musl-tools : compilation de binaires statiques basés sur musl libcPermet de produire des binaires très légers, idéaux pour un initramfs minimal.
git : récupération des sources (kernel, outils)wget : téléchargement directcpio : création de l’initramfsrsync : synchronisation propre des arborescencesCe sont les outils de base pour assembler proprement le système.
Avant d’aller plus loin, on vérifie que la machine hôte supporte bien la virtualisation matérielle.
kvm-ok || true
lsmod | grep kvm || true
kvm-ok : vérifie la présence des extensions CPU (Intel VT-x / AMD-V)lsmod | grep kvm : confirme que les modules KVM sont chargés dans le noyau hôteSi KVM est absent, QEMU fonctionnera, mais sans accélération, ce qui dégrade fortement l’expérience.
Cette étape valide que l’hôte est apte à compiler, booter et exécuter le mini-système Linux construit dans la suite de la procédure.
L’objectif ici est de disposer d’une arborescence claire, lisible et reproductible, permettant de séparer sans ambiguïté :
Cette séparation est volontairement stricte : elle facilite le debug, évite les confusions, et permet de repartir de zéro proprement à tout moment.
rm -rf ~/rocket-lnx-demo
mkdir -p ~/rocket-lnx-demo/{src,build,out,initramfs,mnt}
cd ~/rocket-lnx-demo
rm -rf ~/rocket-lnx-demo
Supprime toute version précédente du projet afin de garantir un environnement propre et déterministe.
Aucune hypothèse n’est faite sur un état antérieur.
mkdir -p ~/rocket-lnx-demo/{src,build,out,initramfs,mnt}
Crée l’ensemble des répertoires nécessaires au projet.
cd ~/rocket-lnx-demo
Point d’entrée unique pour toutes les étapes suivantes.
src/ — SourcesContient exclusivement les sources :
Ce répertoire ne contient aucun binaire compilé.
build/ — CompilationRépertoire dédié aux artefacts intermédiaires de compilation :
makePeut être supprimé sans impact sur les sources ni sur les résultats finaux.
out/ — Résultats finauxContient les produits finis du projet :
bzImage)C’est ce répertoire qui sera utilisé par QEMU pour le démarrage de la VM.
initramfs/ — Racine de l’initramfsArborescence représentant le filesystem initial chargé en mémoire au boot :
/init/bin, /sbinSon contenu est empaqueté via
cpiopour produire l’initramfs.
mnt/ — Points de montage temporairesRépertoire réservé aux montages temporaires :
Aucun fichier pérenne n’y est stocké.
Cette arborescence reflète volontairement une logique proche des systèmes de build industriels :
Une fois cette structure en place, l’ensemble du projet peut être compris, nettoyé ou reconstruit sans ambiguïté.
Cette base est indispensable pour aborder sereinement la suite :
compilation du noyau, création de l’initramfs, construction du disque et chaîne de boot complète.
Cette étape consiste à récupérer, configurer et compiler un noyau Linux adapté à notre environnement cible :
une machine virtuelle QEMU/KVM, démarrée en console série, avec un root filesystem minimal.
L’objectif n’est pas de produire un noyau générique de distribution, mais un noyau :
On cherche volontairement la sobriété fonctionnelle, pas l’exhaustivité.
On récupère les sources officielles du noyau Linux, puis on les prépare pour la configuration et la compilation.
cd ~/rocket-lnx-demo/src
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.6.56.tar.xz
tar -xf linux-6.6.56.tar.xz
cd linux-6.6.56
Détail des commandes :
cd ~/rocket-lnx-demo/src
Placement dans le répertoire dédié aux sources, conformément à l’arborescence définie.
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.6.56.tar.xz
Téléchargement des sources officielles du noyau Linux 6.6.56 (LTS) depuis kernel.org.
tar -xf linux-6.6.56.tar.xz
Extraction complète du code source du noyau.
cd linux-6.6.56
Répertoire racine du noyau pour toutes les opérations suivantes.
Cette étape définit exactement les capacités du noyau au démarrage.
Tout ce qui n’est pas compilé n’existe pas.
make defconfig
make menuconfig
make defconfig
Génère une configuration de base saine pour l’architecture x86_64.
make menuconfig
Interface interactive permettant d’activer précisément les fonctionnalités requises.
Les options suivantes doivent être activées en dur (Y).
-nographic)Options :
CONFIG_SERIAL_8250=yCONFIG_SERIAL_8250_CONSOLE=yChemin :
Device Drivers
└─ Character devices
└─ Serial drivers
└─ 8250/16550 and compatible serial support
Rôle : permettre l’affichage complet des logs kernel via la console série QEMU.
Options :
CONFIG_VIRTIO=yCONFIG_VIRTIO_PCI=yCONFIG_VIRTIO_BLK=yChemins :
Device Drivers -> Virtio drivers -> Virtio driver support / PCI driver
Device Drivers -> Block devices -> Virtio block driver
Rôle : rendre visible le disque virtuel (/dev/vda) fourni par QEMU.
Options :
CONFIG_VIRTIO_NET=yCONFIG_E1000=yRôle : assurer la connectivité réseau avec plusieurs types de cartes émulées.
Option :
CONFIG_EXT4_FS=yRôle : permettre le montage du root filesystem.
Options :
CONFIG_DEVTMPFS=yCONFIG_DEVTMPFS_MOUNT=yRôle : création automatique des périphériques dans /dev.
Option :
CONFIG_BLK_DEV_INITRD=yRôle : autoriser le chargement d’un initramfs au démarrage.
make -j"$(nproc)"
cp arch/x86/boot/bzImage ~/rocket-lnx-demo/out/vmlinuz
make -j"$(nproc)"
Compilation parallèle du noyau en utilisant tous les cœurs CPU disponibles.
cp arch/x86/boot/bzImage ~/rocket-lnx-demo/out/vmlinuz
Copie du noyau final prêt à être utilisé par GRUB et QEMU.
À l’issue de cette étape, le noyau Linux est fonctionnel, minimal et maîtrisé, prêt pour l’initramfs et la suite de la chaîne de boot.
Cette étape consiste à construire BusyBox, qui fournira l’ensemble des outils utilisateurs essentiels du système :
sh, ls, mount, ip, udhcpc, etc.
BusyBox est souvent décrit comme le couteau suisse de l’initramfs :
Le choix d’un binaire statique est volontaire et structurant :
BusyBox sera la colonne vertébrale utilisateur du système démarré.
On commence par récupérer les sources officielles de BusyBox, puis on les prépare pour la configuration et la compilation.
cd ~/rocket-lnx-demo/src
wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2
tar -xf busybox-1.36.1.tar.bz2
cd busybox-1.36.1
Détail des commandes :
cd ~/rocket-lnx-demo/src
Placement dans le répertoire des sources, commun à l’ensemble du projet.
wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2
Téléchargement de BusyBox 1.36.1, version stable et éprouvée.
tar -xf busybox-1.36.1.tar.bz2
Extraction complète des sources.
cd busybox-1.36.1
Répertoire racine de BusyBox pour la configuration et la compilation.
Cette étape définit quelles commandes BusyBox fournira et comment elles seront construites.
make defconfig
make menuconfig
make defconfig
Génère une configuration par défaut cohérente pour l’architecture cible.
make menuconfig
Interface interactive permettant de :
Les choix suivants sont déterminants pour la stabilité du système.
Settings --->
[Y] Build static binary (no shared libs)
Produit un binaire totalement autonome, indispensable dans un initramfs sans libc externe.
Networking Utilities --->
[Y] udhcpc
Fournit un client DHCP minimal permettant d’obtenir automatiquement une configuration réseau.
tc (désactivé)Networking Utilities --->
[N] tc
Désactivé volontairement :
Une fois la configuration validée, on procède à la compilation puis à l’installation dans un répertoire intermédiaire (staging).
make clean
make -j"$(nproc)"
rm -rf ~/rocket-lnx-demo/build/busybox-install
make CONFIG_PREFIX=~/rocket-lnx-demo/build/busybox-install install
Détail :
make clean
Nettoyage complet de toute compilation précédente.
make -j"$(nproc)"
Compilation de BusyBox en utilisant tous les cœurs CPU disponibles.
rm -rf ~/rocket-lnx-demo/build/busybox-install
Suppression préalable du répertoire d’installation pour garantir un état propre.
make CONFIG_PREFIX=~/rocket-lnx-demo/build/busybox-install install
Installation de BusyBox dans un répertoire staging, sans impacter le système hôte.
Ce répertoire sera ensuite copié dans l’initramfs.
file ~/rocket-lnx-demo/build/busybox-install/bin/busybox
Résultat attendu :
ELF 64-bit ... statically linked
Cette vérification est non négociable.
Si le binaire n’est pas statique, le système ne démarrera pas correctement.
BusyBox est prêt à être intégré dans l’initramfs et à fournir l’ensemble des commandes utilisateurs du système minimal.
On utilisera pour porter la page web,le binaire se trouvera ici sur notre système :
/sbin/httpd -f -v -p 0.0.0.0:80 -h /www
À cette étape, on crée le disque virtuel qui servira de support de boot à la VM.
Ce disque contiendra, dans la suite de la procédure :
/boot avec le noyau et l’initramfsIci, on part volontairement de zéro, avec un disque neuf, afin de garantir :
Important : si un
/dev/nbd0était déjà connecté à une image précédente, on le déconnecte avant de continuer.
sudo qemu-nbd --disconnect /dev/nbd0 2>/dev/null || true
rm -f ~/rocket-lnx-demo/out/disk.qcow2
qemu-img create -f qcow2 ~/rocket-lnx-demo/out/disk.qcow2 2G
/dev/nbd0sudo qemu-nbd --disconnect /dev/nbd0 2>/dev/null || true
qemu-nbd : permet de connecter une image disque QEMU à un périphérique bloc Linux via NBD (Network Block Device).
C’est très pratique pour partitionner/formatter une image comme si c’était un vrai disque.
--disconnect /dev/nbd0 : déconnecte l’image éventuellement attachée à /dev/nbd0.
Sans cette étape, on peut se retrouver à :
2>/dev/null : masque le message d’erreur si /dev/nbd0 n’était pas connecté.
|| true : garantit que la commande ne fait pas échouer la procédure si rien n’était connecté.
Objectif : s’assurer que
/dev/nbd0est libre et dans un état neutre.
rm -f ~/rocket-lnx-demo/out/disk.qcow2
rm -f : supprime le fichier s’il existe, sans poser de question.qemu-img create -f qcow2 ~/rocket-lnx-demo/out/disk.qcow2 2G
qemu-img : outil de gestion des images disque QEMU (création, conversion, informations, etc.).
create : crée une nouvelle image.
-f qcow2 : format qcow2, choisi car :
~/rocket-lnx-demo/out/disk.qcow2 : emplacement de l’image finale dans out/ (artefacts finaux).
2G : taille logique du disque virtuel.
Résultat : on obtient une image disque neuve, prête à être connectée via NBD pour :
À l’issue de cette étape, le disque virtuel existe, mais il est vide (aucune partition, aucun filesystem).
La prochaine étape consistera à le connecter, le partitionner et le préparer pour accueillir le système.
À cette étape, on connecte l’image disque qcow2 nouvellement créée au système hôte via le mécanisme NBD (Network Block Device).
L’objectif est fondamental :
NBD est un outil clé dans cette démarche, car il permet de traiter une image disque comme un périphérique bloc classique (/dev/nbdX).
sudo modprobe nbd max_part=16
sudo qemu-nbd --connect=/dev/nbd0 ~/rocket-lnx-demo/out/disk.qcow2
lsblk /dev/nbd0
sudo modprobe nbd max_part=16
modprobe nbd
Charge le module noyau NBD (Network Block Device), indispensable pour exposer une image disque QEMU comme un périphérique bloc.
max_part=16
Indique au noyau que chaque périphérique NBD peut gérer jusqu’à 16 partitions (/dev/nbd0p1, /dev/nbd0p2, etc.).
Sans ce module,
/dev/nbd0n’existe pas et l’image qcow2 ne peut pas être attachée.
sudo qemu-nbd --connect=/dev/nbd0 ~/rocket-lnx-demo/out/disk.qcow2
qemu-nbd
Outil permettant d’exposer une image disque QEMU via NBD.
--connect=/dev/nbd0
Associe explicitement l’image disque au périphérique /dev/nbd0.
~/rocket-lnx-demo/out/disk.qcow2
Image disque qcow2 créée à l’étape précédente.
À partir de ce moment, le noyau considère l’image comme un disque réel.
lsblk /dev/nbd0
lsblk
Affiche l’arborescence des périphériques blocs.
/dev/nbd0
Cible explicitement le disque attaché pour vérifier :
Résultat attendu :
/dev/nbd0À ce stade :
Une erreur ici impacte l’image disque, pas un disque physique,
mais la rigueur reste exactement la même.
À l’issue de cette étape, l’image qcow2 est attachée et prête pour :
À cette étape, on transforme l’image disque (attachée via NBD) en un disque utilisable par un boot BIOS/GRUB :
On choisit volontairement :
sudo fdisk /dev/nbd0
fdiskfdisk est un outil interactif permettant de manipuler une table de partitions MBR./dev/nbd0, c’est-à-dire l’image qcow2 exposée comme un disque.Point d’attention :
fdisk écrit directement dans l’image disque. Si on se trompe de device, on partitionne autre chose.
fdisko
n
p
1
a
w
Explication commande par commande :
o
Crée une nouvelle table de partitions MBR (DOS) vide.
➜ Réinitialise totalement la table de partitions (objectif “disque neuf”).
n
Crée une nouvelle partition.
p
Indique que la partition est primaire (primary).
➜ Avec MBR, on dispose de 4 partitions primaires max (suffisant ici).
1
Numéro de partition : partition 1.
(Entrée) sur “First sector”
Accepte la valeur par défaut (alignement correct).
(Entrée) sur “Last sector”
Accepte la valeur par défaut : la partition prend tout le disque.
a
Active le flag bootable sur la partition sélectionnée (ici 1).
➜ Utile pour certains BIOS / comportements GRUB en mode MBR.
w
Écrit les modifications sur le disque et quitte fdisk.
À la sortie, le disque dispose de :
/dev/nbd0p1 bootableUne fois fdisk terminé, le noyau hôte doit être informé de la nouvelle table de partitions, puis on formate la partition.
sudo partprobe /dev/nbd0
lsblk /dev/nbd0
sudo mkfs.ext4 -F -L ROCKET_ROOT /dev/nbd0p1
sudo partprobe /dev/nbd0
partprobe demande au noyau de relire la table de partitions du disque./dev/nbd0p1 immédiatement, sans reboot ni replug.Sans
partprobe, il arrive quemkfséchoue car la partition n’est pas encore reconnue.
lsblk /dev/nbd0
/dev/nbd0 existe/dev/nbd0p1 existeOn doit voir un arbre du type :
nbd0
nbd0p1sudo mkfs.ext4 -F -L ROCKET_ROOT /dev/nbd0p1
mkfs.ext4 : crée un système de fichiers ext4.-F : force le formatage même si l’outil détecte quelque chose d’ambigu (sécurité pour un environnement de démo).-L ROCKET_ROOT : définit un label lisible (ROCKET_ROOT)./dev/nbd0p1 : cible la partition créée à l’étape précédente.À l’issue, la partition contient un filesystem ext4 propre, prêt à être monté et peuplé.
Étape suivante logique : monter la partition (dans ~/rocket-lnx-demo/mnt) et y construire le root filesystem minimal + /boot.
À cette étape, on construit le root filesystem minimal qui sera monté comme / par le noyau Linux après la phase initramfs.
L’objectif est clair :
/usr éclaté, symlinks implicites, init de distribution)sudo umount ~/rocket-lnx-demo/mnt 2>/dev/null || true
sudo mount /dev/nbd0p1 ~/rocket-lnx-demo/mnt
# Arbo de base (pas de /usr symlink chelou : on reste simple)
sudo mkdir -p ~/rocket-lnx-demo/mnt/{boot,bin,sbin,etc,proc,sys,dev,run,tmp,www}
sudo chmod 1777 ~/rocket-lnx-demo/mnt/tmp
sudo umount ~/rocket-lnx-demo/mnt 2>/dev/null || true
Garantit que le point de montage est libre avant de monter la partition.
Si rien n’est monté, l’erreur est volontairement ignorée.
sudo mount /dev/nbd0p1 ~/rocket-lnx-demo/mnt
Monte la partition ext4 fraîchement créée comme rootfs en construction.
sudo mkdir -p ~/rocket-lnx-demo/mnt/{boot,bin,sbin,etc,proc,sys,dev,run,tmp,www}
Crée l’arborescence minimale indispensable :
/boot : noyau, initramfs, GRUB/bin, /sbin : commandes essentielles/etc : configuration système/proc, /sys : pseudo-filesystems noyau/dev : périphériques/run : runtime temporaire/tmp : fichiers temporaires/www : contenu HTTP de démonstrationsudo chmod 1777 ~/rocket-lnx-demo/mnt/tmp
Applique les permissions standards de /tmp :
ette arborescence volontairement plate et explicite évite tous les pièges classiques des systèmes modernes.
On injecte l’ensemble des outils utilisateurs dans le root filesystem.
sudo cp -a ~/rocket-lnx-demo/build/busybox-install/* ~/rocket-lnx-demo/mnt/
cp -a : copie récursive en conservant :
~/rocket-lnx-demo/build/busybox-install/*
Contient le résultat de l’installation BusyBox “staging”.
~/rocket-lnx-demo/mnt/
Racine du futur système Linux.
Après cette commande, le rootfs dispose déjà de :
/bin/busybox/bin/shBusyBox fonctionne via un principe simple :
un seul binaire, appelé sous différents noms via des liens symboliques.
BusyBox va créer beaucoup de symlinks. C’est normal et attendu.
Le point clé ici :
/sbin/initsudo chroot ~/rocket-lnx-demo/mnt /bin/busybox --install -s
chroot ~/rocket-lnx-demo/mnt
Exécute la commande comme si /mnt était /.
/bin/busybox --install -s
Demande à BusyBox de :
/bin, /sbin, etc.)Chaque appel à ls, mount, ip, sh, etc. invoquera BusyBox.
sudo ln -sf /bin/busybox ~/rocket-lnx-demo/mnt/sbin/httpd
httpd/sbin/httpdsudo ls -l ~/rocket-lnx-demo/mnt/bin/sh
sudo ls -l ~/rocket-lnx-demo/mnt/sbin/httpd
sudo file ~/rocket-lnx-demo/mnt/bin/busybox
À vérifier :
bin/sh → lien symbolique vers BusyBoxsbin/httpd → lien symbolique validebusybox → binaire ELF statiquement liéLe root filesystem est fonctionnel, minimal, et prêt à :
/bootinit personnaliséCette étape met en place la configuration réseau automatique du système minimal via DHCP.
Dans un environnement BusyBox sans NetworkManager ni systemd-networkd, c’est udhcpc (client DHCP BusyBox) qui joue ce rôle.
L’objectif est de :
Cette configuration est indispensable pour :
udhcpc demande une configuration réseau à un serveur DHCPudhcpc appelle alors un script (default.script) pour appliquer ces paramètresCe script est donc le point central de l’initialisation réseau.
default.script)On installe ici un script minimal, robuste et lisible, parfaitement adapté à un système BusyBox.
sudo mkdir -p ~/rocket-lnx-demo/mnt/etc/udhcpc
sudo tee ~/rocket-lnx-demo/mnt/etc/udhcpc/default.script > /dev/null <<'EOF'
#!/bin/sh
# Minimal udhcpc script for BusyBox systems
[ -z "$interface" ] && exit 0
case "$1" in
deconfig)
ip addr flush dev "$interface" 2>/dev/null
ip link set "$interface" up 2>/dev/null
;;
bound|renew)
ip addr flush dev "$interface" 2>/dev/null
ip addr add "$ip/$subnet" dev "$interface" 2>/dev/null
# Router (default gateway)
if [ -n "${router:-}" ]; then
ip route del default 2>/dev/null || true
ip route add default via "$router" dev "$interface" 2>/dev/null || true
fi
# DNS
: > /etc/resolv.conf
for ns in $dns; do
echo "nameserver $ns" >> /etc/resolv.conf
done
;;
esac
exit 0
EOF
sudo chmod 0755 ~/rocket-lnx-demo/mnt/etc/udhcpc/default.script
#!/bin/sh
[ -z "$interface" ] && exit 0
/bin/sh (BusyBox)Le script est appelé avec un événement en premier argument :
deconfig : perte de bail DHCPbound : bail DHCP obtenurenew : bail renouvelécase "$1" in
deconfigip addr flush dev "$interface"
ip link set "$interface" up
Évite les configurations résiduelles.
bound / renewip addr flush dev "$interface"
ip addr add "$ip/$subnet" dev "$interface"
ip route del default || true
ip route add default via "$router" dev "$interface"
: > /etc/resolv.conf
for ns in $dns; do
echo "nameserver $ns" >> /etc/resolv.conf
done
Permet la résolution de noms immédiatement après le boot.
sudo chmod 0755 ~/rocket-lnx-demo/mnt/etc/udhcpc/default.script
udhcpc l’utiliseLe script est directement écrit dans :
/etc/udhcpc/default.script
udhcpcle détectera automatiquement lors de son exécution au boot.
Le système dispose maintenant d’un réseau fonctionnel automatique, prêt à :
Cette étape installe le processus PID 1 du système minimal.
Dans un Linux “normal” (distribution), PID 1 est généralement systemd (ou parfois init).
Ici, on veut exactement l’inverse :
Point clé : PID 1 doit être un fichier réel /init, pas /sbin/init.
Cela évite le piège classique rencontré dans les environnements BusyBox :
boucles de symlinks et erreurs du type “Too many levels of symbolic links”.
En pratique :
/init (dans le rootfs ou initramfs selon la phase)/init monte /proc, /sys, /devhttpd en foreground (processus final)PID 1 a des particularités importantes :
C’est pour cela que le script :
|| true)/initOn crée /init dans le rootfs.
On force l’écrasement du fichier existant (si présent) pour garantir un état propre.
sudo rm -f ~/rocket-lnx-demo/mnt/init
sudo tee ~/rocket-lnx-demo/mnt/init > /dev/null <<'EOF'
#!/bin/sh
# PID 1 : doit être robuste. On évite de paniquer pour une bricole.
set -u
export PATH=/bin:/sbin
log() { echo "[rocket] $*"; }
log "init (/init) starting"
# --- Pseudo-filesystems (idempotent) ---
mountpoint -q /proc || mount -t proc proc /proc 2>/dev/null || true
mountpoint -q /sys || mount -t sysfs sysfs /sys 2>/dev/null || true
mountpoint -q /dev || mount -t devtmpfs devtmpfs /dev 2>/dev/null || true
mkdir -p /run /www /etc
mountpoint -q /run || mount -t tmpfs tmpfs /run 2>/dev/null || true
log "cmdline:"
cat /proc/cmdline 2>/dev/null || true
log "ifaces:"
ls -l /sys/class/net 2>/dev/null || true
# --- Réseau ---
ip link set lo up 2>/dev/null || true
IFACE=""
for d in /sys/class/net/*; do
n="${d##*/}"
[ "$n" = "lo" ] && continue
IFACE="$n"
break
done
log "selected iface: ${IFACE:-<none>}"
if [ -n "${IFACE:-}" ]; then
ip link set "$IFACE" up 2>/dev/null || true
log "dhcp on $IFACE"
udhcpc -i "$IFACE" -q -t 5 -n -s /etc/udhcpc/default.script 2>/dev/null || true
# QEMU user-net DNS
echo "nameserver 10.0.2.3" > /etc/resolv.conf
fi
log "ip a:"
ip a 2>/dev/null || true
log "ip r:"
ip r 2>/dev/null || true
# --- Page web ---
echo "Hello World - Rocket Linux Demo" > /www/index.html
# --- Bannière ---
echo
echo "============================================================"
echo " @ROCKET LINUX — MINIMAL BOOT DEMO"
echo
echo " Kernel + initramfs + busybox + httpd"
echo " démonstration de la chaîne de démarrage Linux"
echo
echo " Projet : Rocket-LNX-Demo"
echo " Wiki : https://wiki.ev1.fr"
echo "============================================================"
echo
log "start httpd (:80)"
exec /sbin/httpd -f -v -p 0.0.0.0:80 -h /www
EOF
sudo chmod 0755 ~/rocket-lnx-demo/mnt/init
sudo chown root:root ~/rocket-lnx-demo/mnt/init
# Vérif : /init doit être un FICHIER, pas un lien
sudo ls -l ~/rocket-lnx-demo/mnt/init
sudo file ~/rocket-lnx-demo/mnt/init
set -u
export PATH=/bin:/sbin
set -u : une variable non définie devient une erreur
➜ évite des comportements silencieux bizarres
➜ mais on contrebalance avec || true sur les commandes non critiques
PATH : on force /bin et /sbin (BusyBox)
➜ évite les surprises liées à un PATH incomplet
log() { echo "[rocket] $*"; }
Permet d’identifier les logs du système en console série, sans ambiguïté.
mountpoint -q /proc || mount -t proc proc /proc ...
mountpoint -q /sys || mount -t sysfs sysfs /sys ...
mountpoint -q /dev || mount -t devtmpfs devtmpfs /dev ...
/proc : infos noyau / processus/sys : interface sysfs (matériel, drivers, réseau…)/dev : périphériques (devtmpfs)Le pré-test mountpoint -q évite de re-monter inutilement.
mkdir -p /run /www /etc
mountpoint -q /run || mount -t tmpfs tmpfs /run ...
/run : runtime volatile (PID files, sockets éventuels)/www : racine du serveur HTTP de démo/etc : fichiers de conf (resolv.conf, scripts udhcpc)cat /proc/cmdline
ls -l /sys/class/net
Très utile quand on doit comprendre “pourquoi ça ne boote pas”.
ip link set lo up
...
for d in /sys/class/net/*; do
n="${d##*/}"
[ "$n" = "lo" ] && continue
IFACE="$n"
break
done
lo (loopback)eth0 sous QEMU)Permet de ne pas coder en dur le nom d’interface.
udhcpc -i "$IFACE" -q -t 5 -n -s /etc/udhcpc/default.script
echo "nameserver 10.0.2.3" > /etc/resolv.conf
udhcpc :
-i : interface-q : mode discret-t 5 : 5 tentatives-n : abandon si échec (évite de bloquer indéfiniment)-s : script à exécuter pour appliquer la conf10.0.2.3 : DNS fourni par défaut dans QEMU user-net
➜ garantit une résolution de noms même si le DHCP ne fournit rien
ip a
ip r
Affiche :
Permet de confirmer immédiatement que le DHCP a fonctionné.
echo "Hello World - Rocket Linux Demo" > /www/index.html
Prépare un contenu HTTP minimal pour valider la chaîne de bout en bout.
exec /sbin/httpd -f -v -p 0.0.0.0:80 -h /www
exec remplace PID 1 par httpd
➜ le système reste vivant tant que le serveur tourne
➜ aucun sous-processus inutile
options httpd :
-f : foreground (important en démo)-v : logs verbeux-p 0.0.0.0:80 : écoute sur toutes les interfaces, port 80-h /www : racine du siteLes commandes suivantes garantissent :
sudo chmod 0755 ~/rocket-lnx-demo/mnt/init
sudo chown root:root ~/rocket-lnx-demo/mnt/init
# Vérif : /init doit être un FICHIER, pas un lien
sudo ls -l ~/rocket-lnx-demo/mnt/init
sudo file ~/rocket-lnx-demo/mnt/init
Résultat attendu :
ls -l montre un fichier exécutablefile indique un script shell texteNotre système possède un PID 1 fonctionnel, minimal et robuste, qui initialise l’environnement et expose un service HTTP.
Cette étape construit l’initramfs : un petit système de fichiers chargé en RAM par le noyau au démarrage.
L’initramfs joue un rôle crucial dans la chaîne de boot :
Ici, l’initramfs est volontairement minimal et explicite :
/init dédié à l’initramfsswitch_root vers /init du rootfs, qui deviendra PID 1Cela sépare proprement :
On crée une arborescence minimale pour l’initramfs.
Elle doit contenir :
/init exécutable/newroot)rm -rf ~/rocket-lnx-demo/initramfs/*
mkdir -p ~/rocket-lnx-demo/initramfs/{bin,proc,sys,dev,newroot}
cp ~/rocket-lnx-demo/build/busybox-install/bin/busybox ~/rocket-lnx-demo/initramfs/bin/
ln -s busybox ~/rocket-lnx-demo/initramfs/bin/sh
Détail :
rm -rf ~/rocket-lnx-demo/initramfs/*
Remet l’initramfs à zéro pour garantir un build propre.
mkdir -p .../{bin,proc,sys,dev,newroot}
/bin : contiendra BusyBox et sh/proc, /sys, /dev : pseudo-filesystems nécessaires au boot/newroot : point de montage du vrai rootfs (sur disque)cp .../busybox .../initramfs/bin/
Copie BusyBox dans l’initramfs : on veut un environnement autonome.
ln -s busybox .../initramfs/bin/sh
Crée le shell /bin/sh attendu par de nombreux scripts.
➜ ici c’est un lien vers BusyBox (classique).
/init de l’initramfsCe /init est exécuté par le noyau dans l’initramfs.
Son unique mission : monter le rootfs final et faire switch_root.
cat > ~/rocket-lnx-demo/initramfs/init <<'EOF'
#!/bin/sh
set -e
export PATH=/bin:/sbin
BB=/bin/busybox
$BB echo "[initramfs] stage0 mounts"
$BB mount -t proc proc /proc
$BB mount -t sysfs sysfs /sys
$BB mount -t devtmpfs devtmpfs /dev 2>/dev/null || true
# Attendre le disque virtio
for i in 1 2 3 4 5; do
[ -b /dev/vda1 ] && break
$BB echo "[initramfs] waiting for /dev/vda1..."
$BB sleep 1
done
$BB echo "[initramfs] mount rootfs"
$BB mount -t ext4 /dev/vda1 /newroot
$BB mkdir -p /newroot/{proc,sys,dev}
$BB mount --move /proc /newroot/proc
$BB mount --move /sys /newroot/sys
$BB mount --move /dev /newroot/dev
$BB echo "[initramfs] sanity: /newroot/init"
$BB ls -l /newroot/init 2>/dev/null || true
$BB echo "[initramfs] switch_root -> /init"
exec $BB switch_root /newroot /init
EOF
chmod +x ~/rocket-lnx-demo/initramfs/init
set -e
export PATH=/bin:/sbin
set -e : si une commande critique échoue, le script s’arrêtePATH : garantit que BusyBox est trouvéBB=/bin/busybox
Ici, chaque commande est appelée via BusyBox ($BB mount, $BB echo, etc.). Ça évite toute ambiguïté et assure qu’on utilise bien les applets BusyBox.
$BB mount -t proc proc /proc
$BB mount -t sysfs sysfs /sys
$BB mount -t devtmpfs devtmpfs /dev 2>/dev/null || true
/proc : indispensable pour avoir visibilité noyau/processus/sys : nécessaire pour l’accès aux infos matérielles / drivers/dev : expose les périphériques (notamment /dev/vda1)devtmpfs est tolérant (|| true) car selon config noyau, il peut déjà être monté.
for i in 1 2 3 4 5; do
[ -b /dev/vda1 ] && break
$BB echo "[initramfs] waiting for /dev/vda1..."
$BB sleep 1
done
Dans certains boots, le device /dev/vda1 n’apparaît pas instantanément.
On attend quelques secondes, en loggant l’état. Cela évite un échec bête “device not found”.
$BB mount -t ext4 /dev/vda1 /newroot
Monte la partition root (ext4) dans /newroot.
C’est la future racine /.
$BB mkdir -p /newroot/{proc,sys,dev}
$BB mount --move /proc /newroot/proc
$BB mount --move /sys /newroot/sys
$BB mount --move /dev /newroot/dev
mount --move déplace les montages existants sans les démonter/remonter$BB ls -l /newroot/init 2>/dev/null || true
On vérifie que /newroot/init existe bien :
c’est le futur PID 1 (script créé à l’étape 10).
exec $BB switch_root /newroot /init
switch_root remplace la racine / par /newroot/init dans ce nouveau contexteexec remplace le processus courant (pas de processus inutile)À partir d’ici, le contrôle passe au /init du rootfs.
On empaquette l’arborescence dans un initramfs au format newc, compressé en gzip.
cd ~/rocket-lnx-demo/initramfs
find . -print0 | cpio --null -ov --format=newc | gzip > ~/rocket-lnx-demo/out/initramfs.img
Détail :
cd ~/rocket-lnx-demo/initramfs
On se place à la racine du futur initramfs.
find . -print0
Liste tous les fichiers de manière robuste (gestion des espaces via \0).
cpio --null -ov --format=newc
Crée l’archive initramfs :
--null : accepte l’entrée en \0-o : mode “output”-v : verbeux (utile en debug)--format=newc : format standard initramfs moderne| gzip > .../initramfs.img
Compression gzip et écriture dans out/.
Résultat : ~/rocket-lnx-demo/out/initramfs.img
C’est l’initramfs qui sera chargé par GRUB et le noyau.
On dispose d’un initramfs minimal, explicite, qui monte le rootfs final et effectue un switch_root vers /init (PID 1 réel).
À cette étape, on copie sur le disque (rootfs) les deux artefacts qui seront utilisés au démarrage :
vmlinuz)initramfs.img)Ces fichiers doivent être placés dans /boot, car :
Sans ces fichiers au bon endroit, GRUB n’a rien à charger : pas de noyau, pas d’initramfs, pas de boot.
sudo cp ~/rocket-lnx-demo/out/vmlinuz ~/rocket-lnx-demo/mnt/boot/vmlinuz
sudo cp ~/rocket-lnx-demo/out/initramfs.img ~/rocket-lnx-demo/mnt/boot/initramfs.img
sudo ls -lh ~/rocket-lnx-demo/mnt/boot/vmlinuz ~/rocket-lnx-demo/mnt/boot/initramfs.img
sudo cp ~/rocket-lnx-demo/out/vmlinuz ~/rocket-lnx-demo/mnt/boot/vmlinuz
sudo : la partition montée appartient à root, on copie donc avec les droits adaptéscp : copie du fichier final~/rocket-lnx-demo/out/vmlinuz : noyau compilé à l’étape 2~/rocket-lnx-demo/mnt/boot/vmlinuz : destination sur le disque, dans /bootRésultat : /boot/vmlinuz existe sur le rootfs.
sudo cp ~/rocket-lnx-demo/out/initramfs.img ~/rocket-lnx-demo/mnt/boot/initramfs.img
~/rocket-lnx-demo/out/initramfs.img : initramfs packagé à l’étape 11/boot/initramfs.imgRésultat : /boot/initramfs.img existe sur le rootfs.
sudo ls -lh ~/rocket-lnx-demo/mnt/boot/vmlinuz ~/rocket-lnx-demo/mnt/boot/initramfs.img
ls : vérifie l’existence des fichiers-l : affiche en format détaillé-h : tailles lisibles (K/M/G)On vérifie immédiatement :
Le répertoire /boot doit exister sur le rootfs (créé à l’étape 8).
Si /boot n’existe pas, c’est un indicateur que l’arborescence rootfs est incomplète.
Le disque contient un /boot complet avec :
vmlinuzinitramfs.imgCette étape verrouille définitivement la chaîne de boot.
On installe GRUB en mode BIOS (i386‑pc) directement dans le MBR du disque qcow2, puis on fournit un grub.cfg déterministe, sans détection automatique.
Objectifs :
grub> ou grub rescue>GRUB doit :
grub.cfgOn crée un fichier grub.cfg minimal et explicite.
sudo mkdir -p ~/rocket-lnx-demo/mnt/boot/grub
sudo tee ~/rocket-lnx-demo/mnt/boot/grub/grub.cfg > /dev/null <<'EOF'
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
}
EOF
set timeout=0
set default=0
timeout=0 : démarrage immédiat, aucune attentedefault=0 : première entrée du menu (index 0)menuentry "Rocket Linux Demo (final)" {
set root=(hd0,msdos1)
hd0 : premier disque BIOS (le qcow2)msdos1 : première partition MBROn force la localisation exacte du root.
Aucune détection automatique.
linux /boot/vmlinuz console=ttyS0 root=/dev/vda1 ro loglevel=7
/boot/vmlinuz : noyau copié à l’étape 12console=ttyS0 : sortie console série (QEMU -nographic)root=/dev/vda1 : rootfs finalro : root monté en lecture seule au bootloglevel=7 : verbosité maximale du noyauinitrd /boot/initramfs.img
switch_root vers /initOn installe GRUB directement sur /dev/nbd0, c’est-à-dire le disque virtuel.
Modules forcés :
évitefilesystem unknown,
évitegrub>sansnormal,
évite les boots partiels.
sudo grub-install -v --target=i386-pc --boot-directory=/home/guillaume/rocket-lnx-demo/mnt/boot --recheck --force --modules="biosdisk part_msdos ext2 normal linux" /dev/nbd0
--target=i386-pc
➜ GRUB BIOS (pas EFI)
--boot-directory=.../mnt/boot
➜ indique où se trouve /boot sur le rootfs monté
--recheck
➜ force la redétection des disques BIOS
--force
➜ autorise l’installation sur un disque non “standard” (image qcow2 via NBD)
--modules="..."
Modules intégrés en dur dans GRUB :
biosdisk : accès disque BIOSpart_msdos : partitions MBRext2 : lecture ext2/3/4normal : mode normal GRUBlinux : chargement du noyau LinuxCette liste est volontairement explicite pour éviter toute surprise.
/dev/nbd0sudo ls -R ~/rocket-lnx-demo/mnt/boot/grub | head -n 50
sudo cat ~/rocket-lnx-demo/mnt/boot/grub/grub.cfg
grub.cfgSi ces vérifications passent, le disque est bootable sans interaction.
Maintenant :
Cette étape finalise proprement tout le travail effectué sur l’image disque.
Elle est courte, mais critique : une erreur ici peut invalider tout le boot.
Objectifs :
Cette étape doit toujours être réalisée avant de lancer la VM.
sync
sudo umount ~/rocket-lnx-demo/mnt
sudo qemu-nbd --disconnect /dev/nbd0
sync
sync force l’écriture de tous les buffers en mémoire vers le disque.
garantit que :
sont effectivement écrits dans l’image qcow2.
Sans
sync, un démontage immédiat peut laisser des écritures en attente.
sudo umount ~/rocket-lnx-demo/mnt
/dev/nbd0p1Si cette commande échoue :
sudo qemu-nbd --disconnect /dev/nbd0
/dev/nbd0À partir de ce moment :
/dev/nbd0 n’existe plusNe jamais :
/dev/nbd0 attachésyncCes erreurs sont typiquement responsables de :
L'image disque est propre, cohérente et prête à booter.
Cette étape lance effectivement la machine virtuelle à partir du disque qcow2 construit dans les étapes précédentes.
Objectifs :
Les deux commandes suivantes sont volontairement proches :
/dev/vda-nographic)Résultat attendu :
http://127.0.0.1:8080
qemu-system-x86_64 -enable-kvm -m 256M -drive file=~/rocket-lnx-demo/out/disk.qcow2,format=qcow2,if=virtio -nographic -netdev user,id=n1,hostfwd=tcp::8080-:80 -device virtio-net-pci,netdev=n1
qemu-system-x86_64
Lance une VM x86_64 complète (BIOS, CPU, périphériques).
-enable-kvm
Active l’accélération matérielle KVM (beaucoup plus rapide).
-m 256M
Alloue 256 Mo de RAM à la VM (largement suffisant pour ce système minimal).
-drive file=...,format=qcow2,if=virtio
if=virtio : disque virtio rapide et moderne/dev/vda-nographic
console=ttyS0-netdev user,id=n1,hostfwd=tcp::8080-:80
8080 sur l’hôte80 dans la VM-device virtio-net-pci,netdev=n1
C’est la commande normale à utiliser une fois tout validé.
qemu-system-x86_64 -enable-kvm -m 256M -drive file=~/rocket-lnx-demo/out/disk.qcow2,format=qcow2,if=virtio -nographic -netdev user,id=n1,hostfwd=tcp::8080-:80 -device e1000,netdev=n1
e1000 ?e1000 émule une carte Intel e1000Utile si :
Comme le noyau a été compilé avec virtio-net + e1000, aucun rebuild n’est nécessaire.
curl -v --max-time 3 http://127.0.0.1:8080/
Ou dans un navigateur : http://127.0.0.1:8080
Attendu :
Hello World - Rocket Linux Demo