Le boot d'ArchLinux

Classé dans : archlinux | 3 commentaire(s)

09
05 | 10

Vu le nombre de personnes pour lesquelles leur distro est un élément "indispensable comme « base »", et qui croient que faire un GNU/linux de zéro est hors de leur portée… Je me suis dit qu'il serait de bon ton d'expliquer comment ça fonctionne.

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.

Ensuite, deux cas possibles.

  • Avec un initrd, comme avec le noyau ARCH officiel :
      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 !)

  • Sans initrd (noyau perso, par exemple) :
      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:…
    On peut revenir au runlevel 3 ou 1 du runlevel 5, par exemple. Tous les processus du runlevel 5 et 3 et leurs « fils » qu'ils ont lancé sont killés, et rc.single, spécifique au runlevel 1, est lancé.

    Init a son fichier de configuration : /etc/inittab. Ce fichier lui dit quoi lancer, il le lit dans l'ordre.

      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)

      Puis une ligne qui spécifie que rc.sysinit sera exécuté dans tous les cas dès qu'init sera lancé :
      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

      Une ligne qui indisque que rc.multi doit être lancé aux runelevels 2, 3, 4 et 5 :
      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

      Des lignes pour lancer des prompts de login dans les consoles 1 à 6 du noyau aux runlevels 2, 3, 4 et 5, il y en a 6 d'origine :
      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

      Ensuite, on peut définir ce qui sera lancé en runlevel 5, par exemple une commande qui lance l'environnement graphique !

      • 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.

    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.

  • /etc/rc.sysinit
      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

  • /etc/rc.multi
      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.

  • /etc/rc.shutdown

      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

    Le 10 mai 2010 erdnaxeli a dit :

    Très bon article. J'y ai appris plein de choses qu'on apprend pas à l'école et qui me permettront de briller en société (et aussi de mieux comprendre pourquoi mon système bug). Merci !

    Le 17 juillet 2010 Neewok a dit :

    Excellent article, super instructif, merci :)

    Je découvre linux avec arch et gentoo, et j'apprend énormement.

    Pourtant il y a quelques mois si on m'avait parlé de patch de noyau, j'aurais fui en courant !

    C'est une belle sensation de se sentir en mesure de comprendre comment fonctionne son système (et la portée de linux). C'est pour ça que j'apprécie beaucoup ce genre d'articles.

    Le 13 décembre 2011 babi a dit :

    Merci pour cet article très bien présenté; un peu trop compliqué pour moi. Je repasserai :)

    Fil des commentaires de cet article

    Ecrire un commentaire




    Quelle est la troisième lettre du mot yglhs ? :