Artisan Numérique

/système/kernel/ Compiler un kernel monolitique

L'arrivée des NetBook donnent clairement une nouvelle forme de légitimité à Linux. Il suffit d'avoir utilisé tour à tour une distribution quelconque et un Windows Vista sur une de ces bestioles pour se rendre à quel point le pingouin se sent à l'aise lorsqu'il est un peu à l'étroit.

Dans la même idée, ces machines redonnent sens à de "vieilles" technique visant à optimiser un kernel pour coller au plus prés des ressources. Et l'une de ces techniques est la création d'un kernel monolithique.

Monolithique, modulaire et micro noyaux

Le principe "monolithique" n'est en soit pas réservé à Linux (ici le GNU n'est pas nécessaire ;-)). Il s'agit en fait d'une des trois grandes catégories d'architectures de noyau, avec le modulaire et le micro.

Dans l'architecture monolithique, toutes les domaines de compétence sont regroupés dans un seul bloc binaire, le noyau, chargé au démarrage. Les domaines étant liés au noyau de manière statique, leur inter-communication se fait par simple "appel de fonction" comme dans n'importe quel programme en C. C'est rapide, efficace mais pose malgré tout deux problèmes. Le premier est qu'un tel noyau deviendrait vit obèse s'il devait prendre en charge la fulltitude de périphériques existant. Le second est qu'il est très difficile à maintenir car toutes ces domaines tendent humainement à se lier les unes aux autres pour former un sauvage plat de spaghetti d'appels de fonctions.

L'architecture "modulaire" va chercher à décharger le noyau de toutes les domaines variant d'une plate-forme (ex. telle périphérique) ou d'un paramétrage (ex. tel mode de gestion mémoire) à l'autre. Les domaines sont ici des modules sous la forme de fichier indépendant qui cette fois sont liés dynamiquement au noyau plus ou moins comme une simple librairie. la communication inter-domaine est donc à peu prés aussi rapides que pour l'architecture monolithique avec un noyau cette fois beaucoup plus petit. Le problème d'obésité est donc réglé mais pas celui de la maintenance, car les modules restent liés les uns aux autres par de simples appels de fonction.

L'architecture micro-noyau va quant à elle encore plus loin que le modulaire en délaissant le concept trop laxiste de librairies dynamique au profit d'un protocole de communication inter-processsus spécialisé qui fonctionne un peu à la manière de Corba ou DBUS. Les domaines sont connus par leurs interfaces, composée d'un nom de service (ex. "carte WIFI") et d'une liste normalisée de fonction (ex. "scanner réseaux"). En fonction de sa configuration le noyau va pouvoir ainsi charger le module qui convient pour un service donné (ex. "carte WIFI Atheros 5k"). Mais plus important, ce protocole assure l'étanchéité entre les domaines qui ne se connaissent plus que par leur "interface". Cela limite grandement la dépendance entre modules et rend l'ensemble plus maintenable au détriment d'une relative perte de vitesse.

Un exemple connu de noyau micro-kernel est Mach qui sert de base au noyau Darwin de MacOS. C'est aussi l'architecture du noyau de Windows NT. Comme quoi l'association classique faite entre micro-kernel, portabilité et performance n'est pas forcement une évidence.

Linux quant à lui appartient aussi bien à une architecture monolithique que modulaire car il est possible pour chaque module de décider s'il va être lié au kernel de manière statique (mode monolithique) ou dynamique (mode modulaire).

De l'intérêt du monolithique

Le monolithique peut aujourd'hui, à juste titre, être considéré comme obsolète car au fond l'objectif de toute distribution est d'être compatible avec le plus large éventail de configurations matérielles. Cependant, l'omniscience du modulaire est aussi sa faiblesse. En effet un tel noyau ne connaît par définition que peu de choses du monde. Lorsqu'il démarre il ne sait pas ce qu'est un disque SATA, un système de fichier EXT3. Pour s'en sortir ce noyau a besoin d'un fichier contenant une série des modules de survie qui va au moins lui permettre de monter la partition principale qui contient le reste des modules. Ce fichier c'est initrd. Ainsi pour démarrer, ce noyau modulaire va devoir chercher ce fichier, le décompresser, le monter à la racine, tester les modules qui s'y trouvent pour trouver le contrôleur de disque, puis le système de fichier, monter la partition, et enfin, poursuivre son démarrage sur cette dernière.

Ensuite pour chaque nouveau domaine, il va devoir trouver le bon module, le charger en mémoire, lire sa table des symboles (car c'est librairie dynamiquement liée), l'initialiser puis passer au suivant, et ainsi de suite jusqu'à ce qu'enfin, le login apparaisse.

Sur une machine de bureau moderne, toute cette mise en route prends une quinzaine de secondes. Mais sur un NetBook comme l'U810 avec son Atom à 800Mhz et son disque dur à 4200rpm, c'est 80 secondes... Et une bonne partie de ce temps est soit lié à initrd, soit au choix des modules, soit à leur chargement. Et c'est du coup là que revient en force le kernel monolithique qui sait déjà quel matériel est présent, qui charge tout en une seule fois en mémoire et ne pose plus de questions après.

C'est donc un tel noyau que nous allons chercher à construire. Pour être exact nous allons créer un noyau hybride car il reste des choses qu'il est plus sage de laisser en module comme par exemple la partie spécifique de la gestion de l'USB. En effet ce sont des fonctions que l'on apprécie d'avoir sous la main (ex. brancher à chaud une clef Wifi) mais que l'on n'utilise pas forcément en permanence. L'idée est donc :

  • De désactiver tous les modules qui ne nous concernent pas (ex. pas carte Applicom sur mon portable...).
  • De ne laisser en module que les fonctions qui sont optionnelles mais utilisées (ex. USB).
  • De mettre toutes les autres fonctions (utilisées et non optionnelles) dans le noyau.

Paramétrage du noyau

Pour les sources, le plus simple est d'utiliser celles du noyau courant de votre distribution mais vous pouvez aussi prendre une version originale sur kernel.org. Dans un cas comme dans l'autre, on va se dire que les sources sont décompressées dans le dossier /usr/src/linux.

Avant de commencer nous devons modifier version du prochain kernel (uname -r) en ajoutant par exemple un -custom. Pour se faire, il faut éditer le Makefile qui se trouve à la racine des sources :

VERSION = 2
PATCHLEVEL = 6
SUBLEVEL = 27
EXTRAVERSION = -custom

Ensuite nous pouvons récupérer la configuration de l'actuel noyau (d'où l'avantage d'utiliser les sources du noyau de votre distribution) :

rootmake oldconfig

Nous allons maintenant éditer le fichier .config qui est le paramétrage du noyau. C'est lui qui permet d'activer ou désactiver des fonctionnalités non-utiles par le biais de modules générés ou pas.

Pour éditer la configuration du kernel nous avons plusieurs options :

  • A la main avec vi .config... il faut être motivé...
  • via la commande "make config", mais là aussi, c'est assez rebutant (une série d'une centaine de questions sont posées et si on en manque une, on recommence...)
  • Via la commande make menuconfig, là c'est déjà plus utilisable, des menus, en texte
  • Enfin via la commande make xconfig, une version graphique en Qt.

Personnellement je préfère la méthode "menuconfig", mais chacun son style.

Maintenant comme Linux peut fonctionner en mode modulaire ou monolithique, un module peut être compilé en tant que fichier indépendant (.ko pour kernel object et ko.z pour la version gzippée) ou directement "lié" au kernel.

Une fois l'interface compilée et lancée, le jeu commence. Les [ ] signifie que le module ou la section est désactivé (non compilé), [*] que c'est activé ET lié au kernel (mode monolithique) et [m] que l'on cherche à produire un module indépendant (.ko.z). On se déplace avec les flèches, la touche n permet de désactiver quel que soit l'état d'origine et espace permet de passer d'un état à l'autre. Enfin, l'astuce excellente de Scorpio810, la touche / permet à tout moment de faire une recherche dans les différentes sections de l'aide.

Toute la science repose maintenant sur la capacité à savoir si tel ou tel module est utilisé par votre système. Déjà ne pas hésiter à utiliser l'aide en ligne du configurateur qui donne de très bon indices (genre "si vous ne savez pas à quoi ça sert, désactivez!" ;-)).

L'autre outil est le classique lsmod. En effet, vous êtes actuellement dans la version "modulaire" du noyau, vous avez donc par cette fonction une liste des modules utiles à votre système. Ceci dit ils ne le sont pas tous loin de là, et c'est aussi un des aspects non optimum des kernels monolithique : charger des modules qui ne servent à rien et encombrent la mémoire.

Une fois que l'on a une liste de module "vitaux", il faut les transmettre dans la configuration. Le problème est généralement de savoir où cela se trouve. Une version aussi bourrine qu'efficace consiste à tester dans tous les Makefile avec les commande find/grep. Par exemple pour chercher le symbole kernel du module ide-cs

rootfind . -name "Makefile" -exec cat {} \; | grep ide-cs
obj-(CONFIG_ATA_PIIX)    += ata_piix.o

Une fois que l'on a le symbole responsable de la compilation ou non du module, il suffit d'ouvrir le fichier .config avec un éditeur de texte pour le localiser et ainsi comprendre où est-ce qu'il se trouve dans la configuration via menuconfig. Une autre option consiste à utiliser ce script PERL qui permet de tout faire en une seule passe. Il vous renvoie la liste des symboles qui semble non nécessaires et ceux qui lui semble l'être.

Avant de terminer, il peut être intéressant aussi de régler certaines options du noyau pour améliorer les performances. Ce genre de chose se trouve dans General setup ou Processor type and feature. On peut désactiver des choses sans intérêt pour nous comme le debugging, le profiling, l'IPV6, etc., ou encore sélectionner un type de processeur plus adapté. Par exemple, une légende urbaine peut être vraie tend à dire que l'Atom est mieux pris en charge par un code compilé en mode "Core2" que "Pentium4", c'est le moment de tester...

Enfin vous pouvez fixer certaines options un peu violente pour une petite machine comme le fait de gérer plus de 4go de ram lorsque l'on en aura jamais plus de 1go...

Au delà de toute optimisation, les points fondamentaux à vérifier sont :

  • Que le pilote de votre contrôleur de disque soit intégré au noyau. Par exemple pour mon chipset Intel PATA géré dans le nouveau mode unifié ATA cela veut dire dans Device Drivers que ATA/ATAPI/MFM/RLL soit désactivé, que Serial ATA and Parallel ATA soit activé, que dans cette sous-section, ATA SFF support soit activé ainsi que le module Intel PATA MPIIX support. Activé voulant dire intégré au noyau (avec le [*]).
  • Que le système de fichier de la partition qui contient linux soit elle aussi intégré au noyau, par exemple EXT3.
  • Qu'Initramfs soit désactivé pour ne pas utiliser de fichier initrd.

Une fois que la toutouille est terminée, nous pouvons passer à la compilation. Sélectionnez "sauver", et "quitter"

Compilation et installation du noyau

C'est bon, tout est configuré, il ne reste plus qu'à compiler. L'avantage est que nous avons tellement éliminé de module que cette étape sera un peu plus rapide que d'habitude. Cependant il est toujours possible d'utiliser distcc.

rootmake bzImage

et parce qu'il reste encore des modules malgré tout
rootmake modules

installation des modules dans <kbd>/lib/modules</kbd>
rootmake modules_install

La première commande va nous créer le fichier qui nous intéresse : bzImage. Il s'agit du kernel sous la forme d'un gros fichier compressé localisé dans le dossier arch/x86/boot.

L'ensemble des noyaux disponibles au lancement de linux se trouvent dans le dossier /boot. Nous allons donc y copier notre bébé. Dans la mesure où nous sommes en kernel monolithique, nous n'avons pas, et c'est là un des intérêt de la manoeuvre, besoin de générer d'initrd.

Nous avons donc trois fichiers à copier pour pouvoir démarrer : bzImage, .config et System.map. A noter que les deux derniers sont optionnels et généralement lorsque je teste, je m'en passe très bien :

Copie de bzImage (mandrake renomme ces fichiers en vmlinuz)
rootcp arch/x86/boot/bzImage /boot/vmlinuz-2.6.27-custom

Copie de System.map
rootcp System.map /boot/System.map-2.6.27-custom

Copie de .config
rootcp .config /boot/config-2.6.27-custom

Dernière étape, paramétrer GRUB pour prendre en compte notre nouveau kernel :

title 2.6.27-custom
kernel (hd0,0)/boot/vmlinuz-2.6.27-custom BOOT_IMAGE=2.6.27-custom root=/dev/sda1

Pensez à vous inspirer de la ligne du kernel courant pour ajouter la bonne option root ainsi que les options kernel que vous aviez avant.

Voilà, maintenant croisage de doigts et redémarrage. Si vous échouez sur un kernel panic, notez bien l'erreur, cherchez la sur le net et recommencez le paramétrage. Généralement il s'agit d'un problème de module disque ou système de fichier non chargé.

Conclusion

Comme nous le montre bootchart, là où le noyau mettait 15 secondes à se lancer, il est maintenant à moins de 5s. Ensuite le système est globalement plus réactif, c'est très sensible. C'est là que l'on prend bien conscience que la vielle technique a encore de beaux jours devant elle.