Artisan Numérique

/système/démarrage/kernel/ Customiser son initrd

La mise en oeuvre d'un kernel monolithique peut, à juste titre, rebuter par sa complexité. Et même si cette technique reste le meilleur moyen d'optimiser un kernel, il est malgré tout possible d'opter pour une approche plus douce en s'attaquant directement à initrd.

Principe

Pour démarrer, un kernel a besoin de ses modules s'il veut espérer causer avec les périphériques. Le problème est que ces modules sont justement sur une périphérique, le disque dur, à laquelle le kernel ne sait accéder. Le coup classique de la poule et de l'oeuf en somme.

La première solution a été de construire un kernel monolithique contenant déjà les modules vitaux pour le démarrage. Mais de nos jours construire un tel kernel reviendrait à lui coller énormément de modules pour prendre en compte tous les cas possible (USB, IDE, SATA, EXT3, EXT4, REISER, etc.). Notre monolithe deviendrait donc trop gros et trop lourd pour tenir entièrement en mémoire.

La seconde solution, quasi universellement adoptée, est de procéder à un démarrage en deux temps. Dans le premier temps, GRUB va fournir au kernel un fichier, initrd (pour initial RAM disk), qui contient quelque binaires, un ensemble de modules vitaux, un interpréteur de script et un script de démarrage. Le kernel va alors monter ce fichier en mémoire pour en faire sa racine et ainsi pouvoir lancer le script. Ce dernier est alors chargé de trouver les bons modules pour la configuration matérielle courante, monter le vrai système de fichier, le définir comme nouvelle racine du kernel et exécuter le second script de démarrage qu'elle contient. Le fichier initrd est alors démonté et la mémoire occupée libérée.

L'image initrd se présente sous la forme d'un fichier dans le dossier /boot. Le nom qu'il peut avoir est variable d'une distribution à l'autre. Les initrd Mandriva sont par exemple nommés initrd-X.Y.Z.img que l'on peut automatiser pour le kernel courant en initrd-$(uname -r).img. Pour les Debians c'est plutôt initrd.img-$(uname -r)).

Pour ce qui est du format de ce fichier, c'est aujourd'hui une simple archive CPIO (une concurrent de tar) optionnellement compressée avec gzip qui est directement décompressée dans disque virtuel utilisant initramfs. Dans des temps plus reculés, il s'agissait d'un vrai système de fichier EXT2 ou cramfs monté en mode "loop".

Comme nous l'avons vu plus haut cette archive contient des modules (dans /lib/modules/$(uname -r)), des binaires (par exemple la gestion du splash), l'interpréteur de commande nash (qui n'est pas un shell !) et le script de démarrage /init

Ce qu'il faut bien comprendre à ce stade est que fondamentalement le couple kernel et initrd forme en mémoire un système Linux complet et opérationnel. Pour le rendre autonome, il suffirait juste de modifier le fichier /init pour qu'il exécute un shell de type busybox qui aurait été ajouté à l'archive. C'est d'ailleurs le système utilisé par Mandriva pour son mode Rescue présent sur les CD d'installation.

Dans le cas qui nous intéresse, le script init a juste pour travail de trouver les bons modules pour que le système puisse poursuivre son démarrage. On pourrait s'attendre à quelque chose d'évolué mais en réalité il s'agit simplement d'une série de chargement et d'attente de l'apparition des périphériques en /dev.

La première conséquence est que du coup un certains nombre de modules inutiles son chargés. Par exemple dans mon cas, le module piix alors que pata_piix a déjà pris en charge le contrôleur de disque.

La seconde conséquence est que le démarrage prend plus de temps pour rien. Par exemple initrd est capable de vous faire démarrer sur un disque USB, ce qui est très bien. Mais si vous ne le faites jamais, il va non seulement pré-charger toute la chaîne USB, mais aussi attendre un certain temps que d'éventuelles disques soient initialisés.

Enfin, dernière conséquence, certains modules génèrent des erreurs. Par exemple, encore sur l'U810, il tente d'utiliser le module generic_ide incompatible avec la plate-forme et échoue donc après quelques secondes.

L'objectif est donc de modifier l'image initrd de sorte à rendre le démarrage aussi efficace ou presque aussi rapide que pour un kernel monolithique.

Modification de l'image

De la modification de votre initrd peut résulter un système complètement inutilisable. Avant de poursuivre il est donc essentiel de créer une copie de sauvegarde de votre future ancienne image.

Pour cela, commencez par recopier votre initrd courant par un cp /boot/boot/initrd-$(uname -r).img /initrd-$(uname -r).backup.img (à adapter pour les autres distributions). Ensuite, dans /boot/grub/menu.lst, dupliquez le bloc correspondant au lancement de votre kernel, donnez-lui un nouveau nom, et modifiez la ligne correspondant au initrd pour pointer sur votre copie. cela doit au final donner quelque chose comme cela :

title Sauvegarde-2.6.27
kernel (hd0,0)/boot/vmlinuz-2.6.27-desktop-0.uc1mnb BOOT_IMAGE=2.6.27-desktop-0.uc1mnb root=/dev/sda1
initrd (hd0,0)/boot/initrd-2.6.27-desktop-0.uc1mnb.backup.img
fragment de /boot/grub/menu.lst

Ceci fait, relancez et testez que ce kernel alternatif fonctionne. Si c'est le cas, redémarrez à nouveau pour reprendre le kernel à modifier.

Maintenant que nous avons assuré nos arrières, nous pouvons commencer à hacker notre image en toute tranquillité. Pour cela, la première chose à faire est de la décompresser :

rootmkdir initrd
rootcd initrd
rootgzip -dc /boot/initrd-$(uname -r).img | cpio -id --no-absolute-filenames
15184 blocks
rootls
bin/  dev/  etc/  firmware/  init*  lib/  proc/  sbin@  sys/  sysroot/  usr/

L'image initrd est une archive cpio compressée. On utilise donc gzip pour décompresser vers la sortie standard puis cpio pour désarchiver le résultat. Cela se termine par une structure de fichiers qui rappelle celle d'une racine linux classique avec tous les modules dans /lib/modules/$(uname -r).

Lorsque le kernel effectue cette même opération au démarrage, il envoie tout cela sur un disque virtuel utilisant la mémoire comme espace de stockage (ramdisk). A ce stade, la racine est donc celle de ce disque et Linux exécute en premier lieu le script init qui s'y trouve.

C'est ce script qui contient les différents chargements de modules que nous allons chercher à modifier. Par exemple pour le cas "generic-ide" il faut commencer par localiser le code problématique :

# ...
echo "Loading ide_generic module"
modprobe -q ide_generic
# ...

Maintenant que nous tenons le coupable, il suffit simplement de commenter cette portion pour ne plus avoir l'erreur au démarrage. Ensuite nous pouvons recréer notre image.

création et compression de l'image
rootfind . | cpio --dereference -H newc -o | gzip -9 > /boot/initrd-$(uname -r).img
15184 blocks

La phase de recompression (gzip -9) est complètement optionnelle. Son but est de réduire l'espace disque occupé mais n'influe en rien sur l'espace pris en mémoire. Après c'est à vous de donner votre réponse à l'éternelle question : la compression prend du temps CPU pour rien ou la compression permet d'économiser des accès disques ?

Ceci fait, il faut redémarrer pour voir si tout fonctionne correctement.

Il est possible de continuer d'éliminer des portions de codes inutiles dans ce script. Par exemple on remarque qu'il y a toute une phase coûteuse en temps d'attente de stabilisation des périphériques USB. C'est sûrement très bien si l'on démarre sur un disque USB mais ne sert à rien dans le cas contraire. De même tous les modules USB sont chargés en mémoire : ehci-hcd pour l'USB 2.0, uhci-hcd pour le mode USB Host mais aussi ohci-hcd pour les périphériques USB 1.1. Personnellement je n'ai plus aucune périphérique en 1.1, je peux donc commenter ce chargement sans trop de crainte, cela m'évitera de la blacklister pour rien.

Dernier point, il est aussi possible, dans le dossier initrd, de supprimer directement des modules dans lib/modules/$(uname -r)/ pour éviter leur chargement. De la même manière vous pouvez ajouter des modules non prévus par votre distribution (un pilote disque particulier par exemple) et ajouter dans le script init la commande modprobe correspondante.

Automatisation

Le script d'initialisation initrd/init d'une distribution ne bouge pas tant que cela d'une mise à jour à l'autre du kernel. Il est peut donc être très pratique de dispose d'un script qui "patch" l'initrd courant avec nos modifications. C'est là que nous allons utiliser notre copie d'initrd faite plus haut.

nous commençons dans le dossier initrd crée et peuplée plus haut
rootcd ..
rootmkdir initrd_old
rootcd initrd_old
rootgzip -dc /boot/initrd-$(uname -r).backup.img | cpio -id --no-absolute-filenames
rootcd ..
rootdiff -Nautr initrd_old initrd > initrd.patch
création du patch

Maintenant que vous avez votre patch, il est possible de l'appliquer, lorsqu'une mise à jour de kernel est installée, par le script suivant :

#! /bin/sh
cp -f /boot/initrd-$(uname -r).img /boot/initrd-$(uname -r)-backup.img
mkdir initrd_new
cd initrd_new
gzip -dc /boot/initrd-$(uname -r).img | cpio -id
patch -p1 < ../initrd.patch
find ./ | cpio --dereference -H newc -o | gzip -9 > /boot/initrd-$(uname -r).img
rm -rf initrd_new
update_initrd.sh

Conclusion

La modification d'initrd m'a permis de faire descendre le démarrage du noyau à peu prés au même niveau que ce que j'obtenais avec un kernel monolithique, soit maintenant moins de 5 secondes

Cette méthode est sans aucun doute plus "bidouille" que la version pure et dure d'un paramétrage précis et recompilation du kernel mais présente le gros avantage d'être accessible à tous et surtout de permettre de continuer à profiter sans trop de manipulations des mises à jours d'une distribution.