Pour le début, nous savons tous que notre système d'exploitation est composé de fichiers sur une/des partition(s) de disque dur (ou autre) + un bootloader dans le MBR.
Je préconise fortement la lecture préalable de cet article, qui explique tout ça : structure d'un périphérique bloc
ETAPE 0 : Le bootloader (ici GRUB 0.97)
Je commence donc au BIOS qui exécute les 446 premiers octets du MBR, qui contient un programme (une copie de /boot/grub/stage1), qui cherche soit directement /boot/grub/stage2, soit stage1_5 le cas échéant, qui va chercher stage2.
stage2 étant trouvé, il est exécuté, lit /boot/grub/menu.lst (sur la partition pointée par stage1), et affiche votre menu GRUB.Quand vous choisissez une entrée de menu, les commandes sous celle-ci sont exécutées, c'est à dire par exemple :
root (hd0,0) : on spécifie la partition à « monter » (lecture seule)
kernel /boot/vmlinuz26 root=/dev/sda1 ro : on charge en mémoire ce fichier, le noyau. On lui transmet également des arguments. (tout ce qui est après vmlinuz26)
initrd /boot/kernel26.img : on charge en mémoire pour le noyau une image de disque, l'initrd. (aussi appelé initramfs)
boot : quand on utilise GRUB en mode commande manuel, il faut taper "boot" pour dire que tout est prêt. Il exécute alors le kernel.
ETAPE 1 : Le noyau (et son initrd, ou pas)
Notre bootloader quel qu'il soit, a exécuté le noyau.
Selon qu'il y ait un initrd ou pas, je vais donner les deux scénarios, le début étant le même :
Le bootloader a copié en RAM le noyau et l'initrd, deux fichiers, et exécuté le noyau.
Au début, le fichier du noyau copié en RAM contient un programme de décompression et une archive. Il décompresse donc linux dans la suite de la RAM libre, puis l'exécute.
Linux est alors vraiment lancé, charge les modules compilés en interne, et vous affiche notamment un logo, si le mode vidéo le permet.
- Il trouve alors son initrd. (si le support de l'initrd est compilé dans le noyau)
Il le décompresse en RAM, le monte comme racine (dans le répertoire « virtuel » « / »), et exécute le programme /sbin/init. Dans l'initrd d'archlinux, c'est un script sh qui va charger les modules et exécuter les hooks définis dans /etc/mkinitcpio.conf. Bien sûr, l'initrd contient sh et la plupart des commandes de base, dans une busybox.
Le but des modules et hooks est bien entendu de rendre la partition racine visible ! Pour un simple disque dur, le(s) bon(s) modules suffisent, pour une racine en NFS il y a un script…
Après avoir chargé modules et lancé hooks, le script /sbin/init va monter la partition racine indiquée au noyau par le bootloader (et disponible alors dans /proc/cmdline) : root=/dev/sda1 dans mon exemple. Et lancer en chroot dans le point de montage de cette partition, son /sbin/init. (le vrai, cette fois !)
-
Le noyau charge tous ses modules compilés en interne, et l'un d'entre eux trouve un disque dur (ou autre périphérique bloc, dont il faut le support compilé), sur lequel il trouve des partitions (il faut leur support compilé).
Il essaie ensuite de monter la partition racine qui lui est donnée par le bootloader (par root=/dev/sda1 dans mon exemple). Il faut bien sûr le support du système de fichiers de la partition en question dans le noyau.
Puis, il exécute /sbin/init.
Si la partition n'est pas trouvable ou pas montable, ou si init est introuvable, on a un kernel panic. Que voulez-vous qu'il fasse d'autre, le pauvre petit noyau, quand il a un gros pépin ?
ETAPE 2 : init
Le programme /sbin/init de la partition racine a été lancé.
C'est le premier processus (PID 1) lancé par votre noyau. (tapez ps ax, vous verrez)
Ce programme ressemble à tous les autres, sauf qu'il sert à lancer les autres.
Init fonctionne par runlevels.
Ce sont des ensembles de programmes (processus) :
- Au runlevel S, sont lancés le script rc.sysinit, et un prompt de login root.
- Au runlevel 1, sont lancés les scripts rc.sysinit, et rc.single qui peut faire plusieurs choses puis passer en runlevel S.
- Au runlevel 3, sont lancés rc.sysinit, rc.multi (daemons), et 6 agetty dans les consoles 1 à 6 du noyau.
- Au runlevel 5, sont lancés rc.sysinit, rc.multi (daemons), 6 agetty dans les consoles 1 à 6 du noyau, et la commande définie par la ligne x:5:…
Init a son fichier de configuration : /etc/inittab. Ce fichier lui dit quoi lancer, il le lit dans l'ordre.
- Pour un login manager :
x:5:respawn:/usr/bin/slim >& /dev/null - Pour lancer startx pour un utilisateur donné :
x:5:once:/bin/su utilisateur -l -c "/bin/bash --login -c startx &>/dev/null"
Si on met respawn au lieu de once, quand on fermera la session, elle se relancera aussitôt.
La première ligne non commentée qu'il rencontre est (dans le fichier d'origine) :
id:3:initdefault:
Ceci correspond au runlevel auquel init doit s'arrêter (ici le 3), si aucun n'a été spécifié sur la ligne de commande du noyau en y ajoutant le numéro du runlevel, exemple : root=/dev/sda1 ro 1
pour s'arrêter au runlevel 1. (init voit ça dans /proc/cmdline)
rc::sysinit:/etc/rc.sysinit
Une ligne qui indique ce qui doit être fait aux runlevel 1 et S, lancer rc.single (qui n'agit en runlevel S que si on arrive d'un runlevel plus élevé, pour tout fermer proprement) :
rs:S1:wait:/etc/rc.single
rm:2345:wait:/etc/rc.multi
Note : les "wait" servent à préciser qu'il faut attendre que le processus se termine avant de procéder à la suite.
Une ligne pour ce qui doit être fait en runlevel 6 (reboot). Tapez init 6, ça revient à taper reboot.rh:06:wait:/etc/rc.shutdown
Une ligne qui spécifie le runlevel S, où on a un login root (et c'est tout) :
su:S:wait:/sbin/sulogin -p
c1:2345:respawn:/sbin/agetty -8 38400 tty1 linux
c2:2345:respawn:/sbin/agetty -8 38400 tty2 linux
…
Une ligne qui spécifie que l'appui de Ctrl Alt Suppr lance le reboot (par shutdown, qui en fait appelle le runlevel 6) :
ca::ctrlaltdel:/sbin/shutdown -t3 -r now
Vous savez tout sur votre boot ! Il n'y a plus qu'à aller regarder les éléments un par un, maintenant.
Un petit tour des initscripts…
Les initscripts sont les scripts qui servent à faire plein de choses au boot, et dont j'ai parlé plus haut.
-
C'est le script lancé par init au départ.
Il affiche un peu de texte
:: Arch Linux
etc…
Il monte les arborescences du kernel dans les dossiers système /dev, /sys et /proc
Il met l'heure kernel à l'heure locale du fuseau du boot précédent pour que la suite ne trouve pas bizarre l'heure qu'il est
Il lance udev, qui va créer les nodes des périphériques dans /dev quand détectés :
:: Starting UDev Daemon
Il demande à udev de détecter tous les périphériques et de peupler /dev :
:: Triggering UDev uevents
Pendant ce temps, il charge les modules de contrôle ACPI : bouton power, batterie, alimentation, processeur…
:: Loading standard ACPI modules
Il attend que tous les périphériques soient détectés :
:: Waiting for UDev uevents to be processed
Il crée lo, la boucle locale réseau :
:: Bringing up loopback interface
Il assemble les LVM, RAID et partitions chiffrées si nécessaire
Il remonte la partition racine en lecture seule (généralement c'est déjà bon) :
:: Mounting Root Read-only
Il vérifie les partitions pour lesquelles <pass> est à 1 dans /etc/fstab :
:: Checking Filesystems
Il monte toutes les partitions (systèmes de fichiers en fait) spécifiés dans /etc/fstab :
:: Mounting Local Filesystems
Il active (rend utilisable) le swap spécifié dans /etc/fstab :
:: Activating Swap
Il met le kernel à l'heure locale en allant regarder le fuseau de rc.conf, TIMEZONE :
:: Configuring System Clock
Il réinjecte dans le générateur de nombres aléatoires un bout qui a été gardé du boot précédent :
:: Initializing Random Seed
Il vide /tmp et autres fichiers temporaires dans /var :
:: Removing Leftover Files
Il applique le nom d'hôte, variable du noyau (qu'on voit dans le shell [moi@mamachine ~]$) :
:: Setting Hostname: mamachine
Il met à jour l'arbre de dépendances des modules de tous les noyaux installés :
:: Updating Module Dependencies
Il exporte la variable LANG, langue à utiliser pour les applications (définie dans rc.conf) :
:: Setting Locale: fr_FR.UTF-8
Si c'est une locale UTF, il passe les consoles du noyau en UTF-8 :
:: Setting Consoles to UTF-8 mode
Il charge la disposition de clavier en console (KEYMAP dans rc.conf) :
:: Loading Keyboard Map: fr-pc
Ainsi que la CONSOLEFONT si définie dans rc.conf :
:: Loading Console Font: lat9w-16
À la fin, le log du boot est conservé dans /var/log/dmesg.log
-
Il est lancé juste après rc.sysinit dans un boot habituel. (runlevel 3 ou 5)
Il charge les variables de /etc/sysctl.conf, qui servent à définir des options du noyau, touches SysRQ, IP forwarding…
Il lance tous les dæmons définis dans rc.conf dans DAEMONS
Il lance /etc/rc.local, qui contient ce que vous voulez lancer
Un boot de base, c'est donc ces deux scripts, puis 6 agetty dans les 6 premières consoles du noyau.
Ce script fait ce qui est nécessaire avant d'éteindre le système.
Il arrête tous les daemons lancés (ils avaient chacun un fichier correspondant dans /var/run/daemons)
Il arrête tous les processus qui tournent toujours, avec une demande de fermeture, attente de 5s, puis un kill brut :
:: Sending SIGTERM To Processes
:: Sending SIGKILL To Processes
Il conserve un petit bout de la sortie aléatoire du noyau pour le boot suivant :
:: Saving Random Seed
Il écrit l'heure noyau dans l'horloge de la carte mère :
:: Saving System Clock
Il désactive le swap et démonte les systèmes de fichiers
:: Deactivating Swap
:: Unmounting Filesystems
Il remonte la paritition racine en lecture seule :
:: Remounting Root Filesystem Read-only
Il éteint la machine matériellement : le noyau synchronise le cache des disques, range leurs têtes des disques et les éteint, envoie le signal ACPI de coupure d'alim et ne fait plus rien.
Généralement, le PC s'éteint, les très vieux il faudra appuyer sur le bouton de façade.
C'est la fin de ces explications, qui devraient suffire à vous motiver à aller lire les initscripts, avec un bon aperçu de départ !
Bonus intéressant : pour intercaler des choses à lancer à divers moments du boot, on peut inclure des hooks (scripts définissant des fonctions) dans le dossier /etc/rc.d/functions.d/
Un exemple, qui me permet d'avoir un shell, si j'ai mis shell dans les arguments du noyau, dès que /dev/sdxx existent, mais avant que mes partitions soient remontées en écriture, pour faire des vérifs.Je l'ai mis dans /etc/rc.d/functions.d/shell :
supply_shell() {
[ -n "$(/bin/grep -o shell /proc/cmdline)" ] && bash
}
add_hook sysinit_udevsettled supply_shell
Have a nice day !
Article publié sous licence GFDL.
Commentaires