Busybox init

De Wiki doc


Busybox intègre init, un programme pouvant être amorcé directement par un noyau Linux au démarrage d'une machine. Il est ainsi possible de concevoir un système d'exploitation léger composé uniquement d'un noyau et de Busybox. L'adjonction d'outils supplémentaires sur cette base minimaliste pourra engendrer une distribution spécifiquement conçue pour un besoin particulier. Cette association s’avère donc particulièrement intéressante dans des systèmes embarqués tel que les appliances réseau comme OpenWRT ou DD-WRT.

Nous verrons comment construire un tel système en partant des sources de chaque programmes depuis une GNU/Linux Debian 12 Bookworm. Les compilations se feront avec l'ensemble des paramètres par défaut. Je recommande d'utiliser une machine (virtuelle amd64 dans mon cas) spécifiquement installée pour cet usage car un grand nombre de dépendances est nécessaire et il serait dommage de pourrir votre environnement de travail...

L'espace de travail sera le répertoire personnel de l'utilisateur root.

Linux

Installation des dépendances

apt install autoconf automake autotools-dev curl libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat-dev libelf-dev libssl-dev libncurses-dev dwarves

Téléchargement des sources du dernier noyau stable (01/11/2023)

# Code source
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.6.tar.xz -P ~
# Signature GPG
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.6.tar.sign -P ~

Note : le code source est également disponible dans nos fichiers.

Décompression de l'archive des sources

unxz -k ~/linux-6.6.tar.xz

Vérification de la signature GPG de l'archive

apt install gnupg2
gpg2 --locate-keys torvalds@kernel.org gregkh@kernel.org
gpg2 --tofu-policy good 38DBBDC86092693E
gpg2 --tofu-policy good 79BE3E4300411886
gpg2 --trust-model tofu --verify ~/linux-6.6.tar.sign

Note : la dernière commande doit vous renvoyer plusieurs lignes de résultat dont gpg: Bonne signature de « Greg Kroah-Hartman <gregkh@kernel.org> » [totale].

Désarchivage des sources

tar xvf ~/linux-6.6.tar
cd ~/linux-6.6/

Création d'une configuration de confection saine avec les paramètres par défaut et compilation avec 4 cœurs de processeur

make defconfig
make -j4
# Retour dans le répertoire personnel
cd ~

Le noyau compilé pour notre architecture x86 64bits se trouve à l'emplacement suivant : ~/linux-6.6/arch/x86/boot/bzImage.

Busybox

Téléchargement des sources

wget https://www.busybox.net/downloads/busybox-1.36.1.tar.bz2
wget https://www.busybox.net/downloads/busybox-1.36.1.tar.bz2.sha256

Note : le code source est également disponible dans nos fichiers.

Vérification d'intégrité

sha256sum -c ~/busybox-1.36.1.tar.bz2.sha256

Note : la vérification d'intégrité doit renvoyer Réussi.

Extraction de l'archive compressée

tar xvf ~/busybox-1.36.1.tar.bz2
cd ~/busybox-1.36.1/

Configuration par défaut et compilation du code avec lien statique afin d'embarquer les dépendances dans le binaire final

make defconfig
make -j4 LDFLAGS="--static"
# Retour dans le répertoire personnel
cd ~

Busybox est désormais disponible ici : ~/busybox-1.36.1/busybox

Média d'amorce

Nous avons dés à présent en notre possession tous les programmes de notre future système d'exploitation. Vous pouvez préparer les vôtres en vue de les intégrer dans les sections qui suivent.

Les méthodes d'amorçages peuvent varier selon les besoins et votre convenance. Je répertorie personnellement 3 cas d'usage :

  1. mémoire morte avec système de fichier classique type EXT4
  2. initramfs
  3. PXE

Toute les démonstrations seront réalisées via Qemu. La mise en œuvre du réseau ne sera pas détaillée car j'utilise des scripts personnalisés avec mon système. La création d'une interface tap et son exploitation via la directive -device virtio-net-pci,netdev=network0,mac=$tap_mac -netdev tap,id=network0,ifname=$int_tap,script=no,downscript=no permet de lier la machine virtuel au réseau physique via l'adjonction d'un pont réseau.

L'étape 1 fera office de tronc commun aux autres sections afin de ne pas alourdir le document avec une redondance inutile et difficilement maintenable. Seule celle-ci nécessite l'utilisation d'un périphérique de type bloc, les deux autres peuvent êtres réalisées directement dans ~/rootfs si vous le désirez. La réalisation successive des trois étapes est toutefois possible. Il faudra simplement penser à remonter ~/rootfs afin de ne pas travailler dans un répertoire vide...

1. Amorçage en mémoire morte

Cette façon de faire permet de modifier simplement le contenu de votre distribution après coup. Il suffit pour se faire de monter le système de fichier en écriture pour y actualiser son contenu à votre guise.

Pour l'exemple, je créerai un fichier simulant un périphérique de type bloc du nom de busybox.dd au même titre qu'une mémoire morte. En condition réelle, remplacez celui-ci par votre périphérique physique : /dev/sda; /dev/mmcblk; /dev/nvme0n1...

Création et formatage de la mémoire racine

dd if=/dev/zero of=~/busybox.dd bs=1M count=1024
mkfs.ext4 ~/busybox.dd

Création et montage de l'environnement de travail

mkdir -p ~/rootfs
mount ~/busybox.dd ~/rootfs
cd ~/busybox-1.36.1/

Installation de l'arborescence du système Busybox dans notre système de fichiers avec lien statiques

make install CONFIG_PREFIX=../rootfs LDFLAGS="--static"
cd ~

Cette étape a créée les répertoires standards permettant d’accueillir les binaires usuels du système selon la Filesystem Hierarchy Standard (FHS). L'exécutable busybox précédemment compilé a été copié dans le nouveau /bin et des liens symboliques ont étés créés pointant vers celui-ci avec le nom de tous les utilitaires qu'il contient.

Création des points montages des pseudos systèmes de fichiers usuels, de la table de montages statiques et du répertoire accueillant notre futur script d'initialisation

mkdir -p ~/rootfs/proc ~/rootfs/sys ~/rootfs/dev
mkdir -p ~/rootfs/etc
touch ~/rootfs/etc/fstab
mkdir -p ~/rootfs/etc/init.d

Script d'initialisation du système

bash -c "cat > ~/rootfs/etc/init.d/rcS" << _EOF_
#!/bin/sh

# Message d'accueil
echo "Busybox ycharbi.fr"
# Pseudos systèmes de fichiers usuels
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs none /dev

# Configuration réseau
ip addr add 10.0.0.1/24 dev eth0
ip link set dev eth0 up
ip route add default via 10.0.0.254 dev eth0

# Clavier en AZERTY
loadkmap < /etc/fr.map
_EOF_

Attribution du droit d'exécution au script d'initialisation

chmod +x ~/rootfs/etc/init.d/rcS

Configuration des Télétypes (TTY)

bash -c "cat > ~/rootfs/etc/inittab" << _EOF_
::sysinit:/etc/init.d/rcS
ttyS0::respawn:/bin/sh
tty2::askfirst:-/bin/sh
tty3::askfirst:-/bin/sh
tty4::askfirst:-/bin/sh
tty4::respawn:/sbin/getty 38400 tty5
tty5::respawn:/sbin/getty 38400 tty6
::ctrlaltdel:/sbin/reboot
::shutdown:/sbin/swapoff -a
::shutdown:/bin/umount -a -r
::restart:/sbin/init
_EOF_

Ce fichier définit les TTY qui doivent êtres invoqués au lancement de init. Vous devrez probablement adapter ceci à votre besoin. La documentation de ces lignes ainsi que de la configuration appliquée par défaut en cas d'absence du fichier est disponible dans ~/busybox-1.36.1/examples/inittab.

Création du binaire de traduction clavier afin d'utiliser l'AZERTY

busybox dumpkmap > ~/rootfs/etc/fr.map

À ce stade, notre mémoire morte est prête.

Pour permettre son amorçage, un éventail de possibilités s'offre à vous : Grub; Systemd-boot; UEFI stub; Ipxe; Syslinux... Pour ma part et comme précisé en introduction, j'utiliserai Qemu afin de simplifier au maximum l'exposé.

Démontage du système de fichier

umount ~/rootfs

Note : si vous comptez poursuivre les étapes des sections suivantes, pensez à remonter ce système de fichier ou à en copier le contenu dans un autre répertoire de travail afin de ne pas recommencer à zéro.

Test du système avec Qemu (apt install --no-install-recommends qemu-system-x86)

qemu-system-x86_64 \
--enable-kvm \
-m 2048 \
-device virtio-balloon \
-kernel ~/linux-6.6/arch/x86/boot/bzImage \
-append "ro root=/dev/sda console=ttyS0 quiet" \
-cpu host -smp cores=2,threads=1,sockets=1 \
-serial mon:stdio \
-drive id=disk,file="${HOME}"/busybox.dd,format=raw,if=none \
-device ahci,id=ahci \
-device ide-hd,drive=disk,bus=ahci.0 \
-display none

Pour un affichage sans port console, il faut supprimer le paramètre console=ttyS0 de la directive -append; supprimer la directive -display none et remplacer la ligne ttyS0::respawn:/bin/sh par tty1::respawn:/bin/sh dans le inittab.

2. Amorçage en initramfs

L'utilisation d'un système de fichiers initial en mémoire à accès aléatoire apporte encore plus de légèreté à la solution. Un unique fichier compressé vient s'ajouter au noyau en cours d'exécution pour servir la racine construite précédemment. Le système est exécuté intégralement en mémoire vive et peut donc se voir distribué via des protocoles réseaux tel que TFTP ou HTTP.

Construction de l'archive compressée Initramfs

cd ~/rootfs && find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../initramfs.cpio.gz && cd ~

Test du système avec Qemu

qemu-system-x86_64 \
--enable-kvm \
-m 2048 \
-device virtio-balloon \
-kernel ~/linux-6.6/arch/x86/boot/bzImage \
-append "ro rootfstype=ramfs rdinit=/sbin/init console=ttyS0 quiet" \
-initrd ~/initramfs.cpio.gz \
-cpu host -smp cores=2,threads=1,sockets=1 \
-serial mon:stdio \
-display none

Les différences de lancement sont :

  • modification des paramètres noyau (cmdline) de la directive -append
  • ajout de la directive -initrd
  • les directives concernant le disque SATA ont étés supprimées


3. Amorçage initramfs via PXE

Cette méthode d'amorçage ne varie pas beaucoup de la précédente puisque elle réutilise les mêmes éléments à savoir le noyau et l'Initramfs. Le delta sera sur la syntaxe du chargeur d'amorçage réseau utilisé ainsi que quelques paramètres passés au noyau. Voici la section fonctionnelle pour Ipxe :

#!ipxe

set menu-timeout 10000
set submenu-timeout ${menu-timeout}
isset ${menu-defaut} || set menu-defaut Debian_Buster
set serveur_ip 10.0.0.100

menu
item --gap --           -------------DEMARRAGE EN RAM----------------
item busybox			Lancer Busybox

choose --timeout ${menu-timeout} --default ${menu-default} target && goto ${target}

:busybox
kernel http://${serveur_ip}/systemes/noyaux/busybox/bzImage ro initrd=initramfs.cpio.gz rootfstype=ramfs rdinit=/sbin/init console=ttyS0
initrd http://${serveur_ip}/systemes/noyaux/busybox/initramfs.cpio.gz
boot
# https://ipxe.org/cmd/kernel

Sources