Compilation du kernel Linux

18 juin 2022 ― Pierre-Loup GOSSE

Résumé ― Le kernel Linux est un système d'exploitation open-source créé en 1991 par Linus Torvalds. Grandement utilisé dans différent domaine d'application, nous allons voir dans cette article comment configurer le kernel Linux, le compiler et l'intégrer dans vos projets. Nous émulerons son exécution avec QEMU.

Téléchargement des sources kernel

Les sources du kernel sont disponibles sur le site kernel.org. Ce site contient le dépôt git du kernel linux, ainsi que des logiciels open-source. La page git.kernel.org liste tous les dépôts git. On remarquera que plusieurs dépôts du kernel linux existent :

kernel/git/{nom}/linux.git

Chaque dépôt correspond à une version du kernel. La version qui nous intéresse est la version de Linus Torvalds, soit le dépôt linux accessible aussi sur github. Créeons un dossier de travail et téléchargeons les sources :

$ mkdir -p ~/kernel
$ cd !!$
$ git clone https://github.com/torvalds/linux.git

Nous allons nous brancher sur la dernière version stable du kernel, soit la 5.17 au moment de la rédaction :

$ cd linux
$ git checkout v5.17

Makefile

Le language majoritaire du kernel Linux est le C, malgré quelques sections en assembleur par nécessité ou gain de performance. Le code source du kernel étant très vaste, sa compilation est gérée par l'outil Make. Un fichier Makefile est présent à la racine des sources du kernel. Le Makefile est assez lourd mais les premières lignes peuvent nous intéresser, elles indiquent la version du kernel qui sera compilée :

$ head Makefile -n 6

# SPDX-License-Identifier: GPL-2.0
VERSION = 5
PATCHLEVEL = 17
SUBLEVEL = 0
EXTRAVERSION =
NAME = Superb Owl

Configuration du kernel

Le kernel doit être configuré pour sa compilation. En effet, la configuration permet d'activer / désactiver des fonctionnalités du kernel, comme le support réseau ou les systèmes de fichier supportés, ainsi que de définir des variables. Nous verrons que plusieurs fichiers de configuration existent, notamment une par architecture (arm, x86, etc.).

Fichier de configuration

Plusieurs fichiers de configuration existent en fonction des architectures. Vous pouvez retrouver ces configurations dans le dossier arch/{arch}/configs de chaque architecture supportée par le kernel Linux. Chacun des fichiers de configuration est suffixé d'un _defconfig.

Variables

Prenons comme exemple le fichier de configuration arch/x86/configs/x86_64_deconfig pour l'architecture x86-64 bits. Voici les premières lignes du fichier :

$ head arch/x86/configs/x86_64_defconfig

CONFIG_SYSVIPC=y
CONFIG_POSIX_MQUEUE=y
CONFIG_AUDIT=y
CONFIG_NO_HZ=y
CONFIG_HIGH_RES_TIMERS=y
CONFIG_PREEMPT_VOLUNTARY=y
CONFIG_BSD_PROCESS_ACCT=y
CONFIG_TASKSTATS=y
CONFIG_TASK_DELAY_ACCT=y
CONFIG_TASK_XACCT=y

Le fichier liste les variables de configuration préfixées par CONFIG_. Une variable peut définir :

  • Une valeur numérique
  • Une chaîne de caractère
  • Une valeur booléenne (y/n)

Une variable peut être non définie, c'est-à-dire non présente durant la compilation, la ligne commence alors par un # :

# CONFIG_SCHED_DEBUG is not set

Sélection d'une configuration

Parmis toutes les configurations existantes, il est nécessaire de sélectionner la configuration à utiliser pour la compilation du kernel. Pour ce faire, la configuration choisie est générée dans le fichier caché .config à la racine des sources du kernel Linux. Le Makefile inclut le fichier .config lors de la compilation. La sélection de la configuration doit se faire via la recette Make correspondant au fichier de configuration. Par exemple dans sélectionner la configuration x86_64_defconfig il faut exécuter la recette :

$ make x86_64_defconfig

#
# configuration written to .config
#

Le Makefile identifie le fichier correspondant dans les dossiers de configuration et charge ce dernier dans le fichier .config.

note
Le contenu du fichier .config n'est pas équivalent au fichier de configuration chargé. En effet, le contenu du .config définit plus de variables. C'est pourquoi la copie du fichier de configuration vers le fichier .config n'est pas conseillé.

Voici les premières lignes du fichier de configuration .config qui sera inclut par le Makefile :

$ head .config

# Automatically generated file; DO NOT EDIT.
# Linux/x86 5.17.0 Kernel Configuration
#
CONFIG_CC_VERSION_TEXT="gcc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0"
CONFIG_CC_IS_GCC=y
CONFIG_GCC_VERSION=90400
CONFIG_CLANG_VERSION=0
CONFIG_AS_IS_GNU=y
CONFIG_AS_VERSION=23400

Configuration par défaut

À partir de votre environnement bash, le Makefile est capable de déterminer le fichier de configuration à charger qui correspond à votre architecture avec la recette defconfig :

$ make defconfig

*** Default configuration is based on 'x86_64_defconfig'
#
# configuration written to .config
#

Dans le cas d'un ordinateur avec l'architecture x86_64, c'est le fichier arch/x86/configs/x86_64_defconfig qui sera chargé dans le fichier de configuration .config.

Variables d'environnement

Nous avons vu la composition d'un fichier de configuration et sa sélection pour la compilation d'une image kernel. Voyons maintenant comment les variables du fichier de configuration impacte la compilation.

Macro

À la compilation, le Makefile exporte les variables présentes dans le fichier de configuration .config avec l'option -D de gcc. Ainsi, toutes les variables définies dans le fichier de configuration seront accessibles dans le code C du kernel sous forme de macro.

Essayez le mécanisme avec un petit exemple. Créez un fichier test.c :

~/vrac/test.c

#include <stdio.h>

int main()
{
    printf("Value = %d\n", VALUE);
    return 0;
}

Puis compilez le code avec l'option -DVALUE=1 :

$ gcc -DVALUE=1 test.c -o test
$ ./test

Value = 1

Code source

La compilation du code source du kernel peut ainsi être altéré à partir des macros exportées. Prenons l'exemple de la variable CONFIG_SMP du fichier de configuration. Cette variable permet d'indiquer si le kernel doit supporter une architecture avec plusieurs CPU, le symmetric shared memory multiprocessor. Si définie, la macro CONFIG_SMP sera présente lors de la compilation et vérifiable via le préprocesseur de GCC :

#ifdef CONFIG_SMP
    /* Code compilé si SMP est pris en charge */
#else
    /* Code compilé si SMP n'est pas pris en charge */
#endif

Un exemple concret du SMP est l'affichage des informations CPU avec le fichier /proc/cpuinfo :

$ cat /proc/cpuinfo

processor   : 0
vendor_id   : AuthenticAMD
cpu family  : 23
model       : 113
model name  : AMD Ryzen 5 3600 6-Core Processor
stepping    : 0
microcode   : 0x8701021
cpu MHz     : 2800.000
cache size  : 512 KB
physical id : 0
siblings    : 12
core id     : 0
cpu cores   : 6
apicid      : 0
initial apicid  : 0
fpu     : yes
...

Son contenu diffère s'il y a un ou plusieurs CPU. Ce fichier est un pseudo-fichier, il appartient au pseudo-système de fichiers procfs monté dans /proc par le kernel Linux au démarrage. Lors sa lecture, via la commande cat par exemple, le kernel exécute la fonction liée à l'opération de lecture : afficher les informations du CPU.

Sans rentrer en détail sur le fonctionnement interne du procfs, les fonctions chargées d'afficher les informations CPU en x86 se trouvent dans le fichier /arch/x86/kernel/cpu/proc.c. En particulier la fonction show_cpuinfo_core, donnant des informations sur un cœur CPU :

~/kernel/linux/arch/x86/kernel/cpu/proc.c

static void show_cpuinfo_core(struct seq_file *m, struct cpuinfo_x86 *c, unsigned int cpu)
{
#ifdef CONFIG_SMP
    seq_printf(m, "physical id\t: %d\n", c->phys_proc_id);
    seq_printf(m, "siblings\t: %d\n",
            cpumask_weight(topology_core_cpumask(cpu)));
    seq_printf(m, "core id\t\t: %d\n", c->cpu_core_id);
    seq_printf(m, "cpu cores\t: %d\n", c->booted_cores);
    seq_printf(m, "apicid\t\t: %d\n", c->apicid);
    seq_printf(m, "initial apicid\t: %d\n", c->initial_apicid);
#endif
}

La macro CONFIG_SMP entre en jeu ici. Si le SMP est désactivé, cela signifie qu'il n'y a qu'un seul CPU, aucune information concernant les cœurs n'est donc disponible. La fonction devient alors non nécessaire pour le kernel sur ce type d'archiecture matériel, la fonction show_cpuinfo suffit pour une architecture avec un CPU.

note
La condition #ifdef ... #endif englobe uniquement le corps de la fonction, et non la fonction elle-même. En effet, englober la définition de la fonction (i.e. la faire disparaître à la compilation) implique aussi d'englober les multiples appels à la fonction pour éviter des erreurs de référencement. Par praticité, la définition reste et seul le corps (ou une partie) de la fonction est englobé.

Nous venons de voir l'intérêt et l'utilisation du fichier de configuration kernel. Certaines variables sont dépendantes entre elles. Par exemple la variable CONFIG_MAXSMP ne sera prise en compte à la compilation du kernel si et seulement si la variable CONFIG_SMP est activée. Cette gestion de dépendence rend la modification manuelle compliquée et propice aux erreurs.

Le Makefile du kernel propose une recette menuconfig permettant de modifier graphiquement les différentes variables du fichier de configuration.

$ make menuconfig

Capture d'écran du menuconfig

Baladez-vous dans la configuration du kernel. Le bouton Help (? pour le raccourci clavier) permet d'afficher l'aide pour chaque variable : sa valeur, un texte explicatif, ses dépendances, sa localisation dans menuconfig, etc.

Essayons de modifier notre variable SMP. Le menu de configuration étant un vrai labyrinthe, trouver manuellement la variable risque d'être compliqué. Le raccourci / vous permet de faire une recherche. Cherchons notre variable SMP :

Capture d'écran de la recherche

Pour modifier cette variable il est faut donc se rendre dans le sous-menu "Processor type and features". Deux manières pour s'y rendre :

  1. Fermer la recherche et se déplacer manuellement dans le sous-menu. Cette technique peut être vite contraignante lorsque le chemin d'accès est long.
  2. Appuyer sur le chiffre à gauche du sous-menu, ici 1. Le sous-menu apparaît et la variable est directement sélectionnée.

Désactivez la variable en appuyant sur n.

Capture d'écran SMP

Vous remarquerez que des variables ont disparu (et d'autres ont sûrement apparu ailleurs). Notamment Numascale NumaChip et ScaleMP vSMP qui avaient toutes les deux une dépendances avec CONFIG_SMP. Réactivez la variable en appuyant sur y puis sauvegardez cette configuration avec le bouton Save. Le fichier .config est alors sauvegardé.

Kconfig

Nous avons vu comment modifier la configuration kernel avec l'interface graphique menuconfig. Cet outil n'est que l'interprétation des fichiers de configuration kernel Kconfig. Le fichier Kconfig principal est présent à la racine des sources du kernel. Celui-ci inclus les différents Kconfig éparpillés dans le kernel :

$ cat Kconfig

mainmenu "Linux/$(ARCH) $(KERNELVERSION) Kernel Configuration"
source "scripts/Kconfig.include"
source "init/Kconfig"
source "kernel/Kconfig.freezer"
...

documentation
Voici la documentation kernel de la syntaxe Kconfig si vous souhaitez approfondir.

Retrouvons le menu Kconfig de notre variable SMP. L'interface menuconfig nous indique, dans l'aide, dans quel fichier Kconfig est définit la variable. Nous sommes sous l'architecture x86, par défaut le kernel sera compilé pour cette architecture (nous verrons comment modifier l'architecture par la suite), le fichier se trouve donc dans arch/x86/Kconfig ligne 400 :

config SMP
    bool "Symmetric multi-processing support"
    help
        This enables support for systems with more than one CPU. If you have
        a system with only one CPU, say N. If you have a system with more
        than one CPU, say Y.
    ...

En cas d'ajout de fonctionnalité au kernel, il conviendra d'ajouter au fichier Kconfig adapté les variables pour rendre paramétrable notre fonctionnalité via l'interface menuconfig.

Compilation

Nous avons vu comment configurer le kernel et générer un fichier .config correspondant à nos besoins. Voyons maintenant comment compiler une image kernel.

Make

La commande make va se charger de lancer la recette all du Makefile, démarrant la compilation du kernel. Il est recommandé d'ajouter l'argument -j{nb_cpu} pour lancer la compilation sur plusieurs cœurs. Certains cœurs se trouveront bloqués (attente de lecture / écriture par exemple), pour un gain de temps nous pouvons doubler le nombre. Dans le cas d'une machine avec 12 cœurs on peut donc lancer -j24 et préfixer la commande avec time pour mesurer le temps de compilation totale :

$ time make -j24

...
BUILD   arch/x86/boot/bzImage
Kernel: arch/x86/boot/bzImage is ready  (#24)

real    2m6,600s
user    20m51,270s
sys     1m46,327s

Bravo, vous venez de compiler un kernel Linux prêt à l'utilisation ! La compilation n'a duré ici que 2 minutes (avec 12 cœurs à 4,2GHz). Le binaire finale produit se situe dans le chemin arch/{arch}/boot/bzImage{arch} dépend de l'architecture cible.

Architectures

Jusqu'ici la configuration et la compilation de notre kernel s'est fait pour la configuration x86_64 (si vous avez un ordinateur personnel, il y a de forte chance que ça soit votre situation).

note
x86 est la famille de jeu d'instruction CISC d'Intel. AMD proposa plus tard une extension 64 bits du jeu x86, nommée amd64. Intel, qui avait developpé son propre jeu 64 bits ia64, decida ― sous la popularité de l'extension amd64 ― de nommer le jeu d'instruction 64 bits x86 en x86_64. Les termes amd64 et x86_64 sont donc aujourd'hui équivalent.

Cependant, toutes les machines ne sont pas sous l'architecture x86_64. Comment faire pour produire un kernel Linux qui devra s'exécuter sur une machine en ARM 32 bits, comme une carte Raspberry Pi par exemple ? Évidemment, et heureusement, nous ne sommes pas obligé pour cela de compiler le kernel depuis une machine de cette architecture. Notre machine x86_64 et capable de compiler un kernel ARM 32 bits, il suffit de lui donner les ressources nécessaires. Dans le monde informatique, cette distinction d'architecture est définie par les termes suivants :

  • Hôte, pour référencer l'environnement de production du binaire.
  • Cible, pour référencer l'environnement d'exécution du binaire.

Dans notre situation, le binaire produit est l'image kernel.

Compilateur croisé

Compiler un binaire pour une cible ayant une architecture différente que l'hôte nécessite d'utiliser un compilateur croisé, "toolchain" en anglais. Par défaut, lorsque vous compilez vos programmes C pour votre machine, vous utilisez probablement sous linux le compilateur GCC. Ce dernier est conçu pour compiler vos programmes en un binaire exécutable correspondant à l'architecture de votre machine. Un compilateur croisé permet de fournir à votre machine hôte toutes les ressources nécessaires pour compiler un programme vers une autre architecture. Plusieurs compilateur croisées existent, dont Linaro pour ARM 32 et 64 bits. Pour compiler un programme avec un compilateur croisé il faut définir deux variables bash :

  • ARCH, l'architecture cible (par défaut celle de votre machine hôte).
  • CROSS_COMPILE, le préfixe des binaires du compilateur croisé installé sur la machine hôte (par exemple aarch64-linux-gnu- pour la toolchain ARM 64 bits).

Ces deux variables sont pris en compte par le Makefile du kernel. Ainsi, la recette make defconfig chargera la configuration kernel correspondant à l'architecture de la variable bash ARCH et le Makefile préfixera les binaires nécessaires à la compilation (gcc, ar, ld, etc.) par la variable bash CROSS_COMPILE.

Format d'image

Restons dans l'environnement simple où l'architecture cible équivaut à l'hôte, ici x86_64. La compilation nous a produit un fichier bzImage d'environ ~10MB:

$ du -h arch/x86/boot/bzImage

9,9M    bzImage

Le fichier bzImage est l'image kernel, c'est-à-dire le binaire du kernel exécutable :

$ file arch/x86/boot/bzImage

bzImage: Linux kernel x86 boot executable bzImage, version 5.17.0 (pierre-loup@alan) #25 SMP PREEMPT Sun May 22 17:52:02 CEST 2022, RO-rootFS, swap_dev 0x9, Normal VGA

C'est au bootloader que revient la tâche d'exécuter le binaire du kernel. On peut citer les biens connus bootloader GRUB ou U-Boot.

Le binaire du kernel brut, généré après la compilation du code sans compression, est nommée vmlinux. Le préfixe vm, pour Virtual Memory, signifie que le kernel supporte la mémoire virtuelle. Avec le temps, le binaire du kernel vmlinux est devenu de plus en plus lourd, au point qu'il n'est plus possible sur les versions récentes de le charger en mémoire. Le binaire du kernel est donc compressé avec la méthode gzip, formant le binaire vmlinuz (le z faisant référence à la compression gzip). Cette compression nécessite de décompresser le binaire du kernel une fois chargé en mémoire. C'est le binaire vmlinuz lui-même qui se charge de sa décompression. Ainsi, l'image kernel vmlinuz est en réalité la concaténation de trois fichiers compilés :

  • head.o, placé au début du fichier, décompresse le binaire du kernel chargé en mémoire avant de basculer dessus. En x86_64 le fichier source est head_64.S.
  • misc.o, contient des fonctions de bases utilisées par head.o, notamment celle de l'extraction du kernel en mémoire. En x86_64 le fichier source est misc.c.
  • piggy.o, contient le binaire vmlinux de l'image kernel.

Même compressé, le binaire vmlinuz est trop grand pour être chargé dans un espace mémoire contiguë. Le binaire zImage pallie ce problème en embarquant le binaire vmlinuz ainsi que des outils pour charger le binaire du kernel dans des espaces mémoires non contiguë.

Enfin, le binaire bzImage est la version "big" de zImage ― le b n'a pas de rapport avec la méthode de compression bzip2 ― permettant de charger des images kernel encore plus lourdes dans des espaces mémoires non contiguë.

note
Si vous regardez dans votre dossier /boot de votre machine, vous trouverez sûrement le binaire de votre kernel. Ce dernier sera nommé "vmlinuz" suffixé par la version du kernel. Avec la commande file vous remarquerez cependant que ce binaire est en réalité un "bzImage". La plupart des distributions renomment par convention le fichier bzImage en vmlinuz dans /boot.

Aujourd'hui le binaire kernel le plus utilisé est le bzImage. Le format zImage est quant à lui toujours utilisé pour des images kernel plus petites (notamment pour certaines Raspberry Pi).

approfondir
Voici un article expliquant en détail les étapes de compilation du kernel : du code au Makefile jusqu'au bzImage.

Émulation avec QEMU

Une manière rapide de tester son image kernel est de l'émuler avec QEMU sur votre machine. Essayons dans un premier temps d'exécuter avec QEMU notre image kernel (Ctrl+A X pour quitter QEMU) :

$ qemu-system-x86_64 -kernel linux/arch/x86_64/boot/bzImage -nographic -append "console=ttyS0"

[    0.000000] Linux version 5.17.0 (pierre-loup@alan) (gcc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0, GNU ld (GNU Binutils for Ubuntu) 2.34) #28 SMP PREEMPT Mon May 23 20:41:21 CEST 2022
[    0.000000] Command line: console=ttyS0
[    0.000000] x86/fpu: x87 FPU will use FXSAVE
[    0.000000] signal: max sigframe size: 1440
[    0.000000] BIOS-provided physical RAM map:
...
[    3.117482] Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)
[    3.117996] CPU: 0 PID: 1 Comm: swapper/0 Not tainted 5.17.0 #28
[    3.118327] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.13.0-1ubuntu1.1 04/01/2014
[    3.118810] Call Trace:
[    3.119658]  <TASK>
[    3.119970]  dump_stack_lvl+0x34/0x44
[    3.120257]  panic+0xef/0x2a6
[    3.120602]  mount_block_root+0x14e/0x1f5
[    3.120807]  mount_root+0x1c8/0x1ec
[    3.120943]  prepare_namespace+0x136/0x165
[    3.121087]  kernel_init_freeable+0x206/0x211
[    3.121244]  ? rest_init+0xc0/0xc0
[    3.121369]  kernel_init+0x11/0x110
[    3.121496]  ret_from_fork+0x22/0x30
[    3.121708]  </TASK>
[    3.122356] Kernel Offset: 0x17e00000 from 0xffffffff81000000 (relocation range: 0xffffffff80000000-0xffffffffbfffffff)
[    3.123020] ---[ end Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0) ]---

Par défaut, la console système du kernel s'initialise sur la sortie vidéo. Pour y accéder depuis la sortie standard de notre terminal, on indique à QEMU qu'il n'y a pas de sortie vidéo avec l'option -nographic. Cette option va aussi rediriger le port série sur la console système du kernel. Ainsi, nous devons indiquer au kernel d'initialiser la console système sur ce port série en lui fournissant un argument à travers sa commandline avec l'option -append. L'argument "console=ttyS0" indique au kernel d'initialiser la console sur le port série ttyS0 (le premier port série).

La première ligne de la trace nous donne des informations sur l'image kernel exécutée. La seconde, les arguments passées via la commandline, ici l'argument sur la console. La trace se termine par un Kernel Panic : le kernel a une erreur système qui l'empêche de continuer son exécution. Et pour cause, l'erreur indique Unable to mount root fs on unknown-block que nous pouvons traduire par "Je n'ai pas réussi à monter le système de fichiers".

Utilisation d'un système de fichiers

Jusque là nous avons uniquement parlé du kernel et de sa compilation. Pour rappel, l'objectif du kernel est de créer une couche d'abstraction du matériel physique permettant l'exécution d'applicatifs. Ainsi, un kernel seul n'a pas vraiment d'intêret. Et la preuve en est : il plante ! Au démarrage, le kernel rentre dans sa phase d'initialisation. Il va chercher à préparer cette couche d'abstraction puis essayer de passer la main aux applicatifs. Et pour cela il faut être capable d'accèder à ces applicatifs, de les charger en mémoire et de les exécuter. De manière général, ces applicatifs sont stockés - sous forme de fichier - dans un système de fichiers.

Le kernel va donc essayer d'accèder à un système de fichiers qu'il appelle le rootfs pour "système de fichiers racine". Notre trace nous indique donc que le kernel n'a pas été en mesure de monter, c'est-à-dire de rendre accessible le système de fichiers depuis son périphérique matériel (disque dur, SSD, etc.) dans le kernel, ce qui provoque un kernel panic.

Le sujet de cette article n'étant porté sur les systèmes de fichiers, qui seront traités plus en détail dans d'autres articles, nous allons directement générer un petit système de fichiers pour tester notre kernel. Sous Debian, le binaire mkinitramfs permet de générer un petit système de fichiers :

$ mkinitramfs -o ramdisk.img

note
Un initramfs est un système de fichiers chargé en RAM par le kernel dont l'objectif est de réaliser des opérations avant de monter le rootfs final. Par exemple, déchiffrer le disque où se trouve le rootfs. Dans notre utilisation, nous nous servons de l'initramfs comme un simple petit système de fichiers en RAM pour tester le démarrage de notre kernel.

Démarrage du kernel

Exécutons notre kernel avec notre système de fichiers généré :

$ qemu-system-x86_64 -kernel linux/arch/x86_64/boot/bzImage -nographic -append "console=ttyS0" -initrd ramdisk.img -m 512 --enable-kvm

...
[    1.191744] Run /init as init process
Loading, please wait...
...
No root device specified. Boot arguments must include a root= parameter.


BusyBox v1.30.1 (Ubuntu 1:1.30.1-4ubuntu6.4) built-in shell (ash)
Enter 'help' for a list of built-in commands.

(initramfs)

L'option -m 512 permet d'attribuer de la mémoire vive à notre machine virtuelle, sans quoi un kernel panic serait survenu (testez !). L'option --enable-kvm quant à elle permet d'activer la virtualisation avec KVM, un hyperviseur intégré dans le noyau Linux, permettant l'accélération de l'émulation. Vous remarquerez que le kernel ne tombe plus en erreur. Une fois le système de fichiers monté, le kernel passe la main aux applicatifs en exécutant un binaire, et pas n'importe lequel : le processus init, le père de tout les processus. C'est lui qui en charge de l'exécution des processus suivants, ses enfants.

[    1.191744] Run /init as init process
Loading, please wait...

Dans cette rootfs, le processus init n'est rien d'autre qu'un script shell destiné à rendre accessible le rootfs final afin de pivoter l'exécution du kernel. Cependant, aucun rootfs final n'a été renseigné ou n'est accessible. Ainsi, le processus init tombe en échec (No root device specified) et donne accès à un petit shell nous permettant d'intéragir avec notre kernel.

Essayez de taper quelques commandes shell, comme cat /init pour afficher le contenu du processus init, ou bien cat /proc/cpuinfo pour afficher les informations CPU fournies par le kernel. Quittez le shell avec la commande exit une première fois : le processus init reprend son exécution et retombe en erreur redonnant la main sur le shell. Quittez une seconde fois : le processus init fini son exécution, le kernel se retrouve alors sans processus à exécuter provoquant le kernel panic Attempted to kill init!.

Bibliographie

Linux, https://fr.wikipedia.org/wiki/Noyau_Linux
Kernel.org, https://kernel.org/
Git.kernel.org, https://git.kernel.org/
Dépôt linux, https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/?h=v5.18-rc7
Github, https://github.com/torvalds/linux
Make, https://fr.wikipedia.org/wiki/Make
L'option -D de gcc, https://gcc.gnu.org/onlinedocs/gcc/Preprocessor-Options.html
CONFIG_SMP, https://cateee.net/lkddb/web-lkddb/SMP.html
Symmetric shared memory multiprocessor, https://fr.wikipedia.org/wiki/Symmetric_multiprocessing
Procfs, https://en.wikipedia.org/wiki/Procfs
/arch/x86/kernel/cpu/proc.c, https://elixir.bootlin.com/linux/latest/source/arch/x86/kernel/cpu/proc.c
CONFIG_MAXSMP, https://cateee.net/lkddb/web-lkddb/MAXSMP.html
Documentation kernel, https://www.kernel.org/doc/html/latest/kbuild/kconfig-language.html
Compilateur croisé, https://fr.wikipedia.org/wiki/Cha%C3%AEne_de_compilation
Linaro, https://www.linaro.org/downloads/
Bootloader, https://en.wikipedia.org/wiki/Bootloader
GRUB, https://fr.wikipedia.org/wiki/GNU_GRUB
U-Boot, https://fr.wikipedia.org/wiki/Das_U-Boot
Mémoire virtuelle, https://en.wikipedia.org/wiki/Virtual_memory
Head_64.S, https://elixir.bootlin.com/linux/latest/source/arch/x86/boot/compressed/head_64.S
Misc.c, https://elixir.bootlin.com/linux/latest/source/arch/x86/boot/compressed/misc.c
Espace mémoire contiguë, https://www.educative.io/edpresso/contiguous-memory
Article, https://0xax.gitbooks.io/linux-insides/content/Misc/linux-misc-2.html
QEMU, https://fr.wikipedia.org/wiki/QEMU
Commandline, https://www.kernel.org/doc/html/v4.14/admin-guide/kernel-parameters.html
Kernel Panic, https://fr.wikipedia.org/wiki/Panique_du_noyau
Système de fichiers, https://fr.wikipedia.org/wiki/Syst%C3%A8me_de_fichiers
Mkinitramfs, https://manpages.debian.org/stretch/initramfs-tools-core/mkinitramfs.8.en.html
Initramfs, https://fr.wikipedia.org/wiki/Initrd
KVM, https://fr.wikipedia.org/wiki/Kernel-based_Virtual_Machine