Dans beaucoup de systèmes embarqués, il est important de réaliser certaines tâches au démarrage le plus rapidement possible et dans un ordre précis. Citons par exemple l’affichage d’un écran splashscreen, le montage d’une partition de données, l’initialisation des interfaces réseau, ou la mise à l’heure du système.
Or les systèmes d’initialisation modernes, comme systemd
, sont prévus pour fonctionner sur des serveurs ou des postes de travail, sur lesquels l’ordre d’exécution et la rapidité des tâches d’initialisation ne sont pas primordiaux.
Il est facile de résoudre ce problème avec un petit script, nommé early-init
, que je souhaite vous présenter ici.
Objectifs
L’idée de ce mini-projet (disponible ici) développé avec mon collègue Alexandre Grosset est de disposer d’un premier système d’init, inspiré de SysVinit, qui permet de lancer des scripts dans un ordre défini pour réaliser les tâches urgentes, puis d’appeler ensuite le système d’init normal pour terminer le boot et lancer le code applicatif. Dans des systèmes embarqués avec un code applicatif un peu complexe, l’utilisation de systemd
peut s’avérer indispensable pour gérer les dépendances et démarrer les services nécessaires (D-bus, etc.)
Le script early-init
lui même doit faire à peine cinquante lignes de code, mais il me permet de formaliser le mécanisme d’exécution de ces tâches à faire au tout début du boot.
Installation
Le principe est de demander au kernel de lancer /sbin/early-init
au lieu du /sbin/init
d’origine grâce à la directive init=
sur la ligne de paramètres du noyau. Une fois que notre script aura exécuté toutes les tâches désirées, il s’occupera de lancer le processus /sbin/init
normal. L’enchainement des opérations est résumé sur la figure suivante.
Pour ajouter l’option init=/sbin/early-init
sur la ligne de paramètre du noyau, je préfère la solution simple qui consiste à ajouter les lignes suivantes sous forme de fragment de configuration pour le kernel :
CONFIG_CMDLINE="init=/sbin/early-init"
CONFIG_CMDLINE_EXTEND=y
# CONFIG_CMDLINE_FROM_BOOTLOADER is not set
Comme cette solution ne fonctionne pas pour toutes les architectures, une autre possibilité consiste à modifier la variable du bootloader qui contient les arguments passés au noyau (bootargs
pour U-boot par exemple).
Une recette (à personnaliser) pour Yocto Project est présente dans un sous-répertoire du dépôt de early-init
, une recette pour Buildroot sera ajoutée prochainement.
Tâches
Les tâches que early-init
lance se trouvent dans le répertoire /etc/early-init.d/
. Les tâches sont appelées dans l’ordre alphanumérique, aussi est-il utile de les numéroter pour avoir un ordre précis d’exécution. Les tâches se déroulent les unes après les autres. Si on souhaite réaliser des traitements en parallèle, il faudra le gérer explicitement (par exemple avec des &
et des wait
si la tâche est un script shell, ou avec des threads si la tâche est un programme plus élaboré).
Une fois toutes les tâches exécutées, early-init
(contrairement aux systèmes d’initialisation classiques) passe complètement la main au processus init
original. Il ne reprend plus jamais le contrôle, notamment au moment de l’arrêt du système via un halt
ou un shutdown
(qui par ailleurs se produisent rarement dans le domaine de l’embarqué). Les tâches ne sont donc jamais rappelées une fois le boot terminé. Elles ne reçoivent pas d’argument start
ou stop
comme c’est le cas avec SysVinit et il n’est pas non plus nécessaire de préfixer les tâches par un S (ou un K) comme avec les mécanismes d’initialisation classiques.
Idées de tâches
Voici quelques idées de tâches que j’ai développées pour des projets :
- Une tâche qui n’est exécutée qu’au premier boot, juste après le flashage de l’image, en usine. Cette tâche peut remonter le rootfs en lecture-écriture pour enregistrer le numéro de série du produit, générer une clé SSH, stocker des certificats et des fichiers d’options pour les applicatifs, etc. Une fois ces opérations réalisées, ce script s’auto-efface avant de remonter le rootfs en lecture-seule.
- Montage d’une partition de données accessible en lecture-écriture avec vérification de la fiabilité de la partition, et dans le pire des cas reformatage de la partition si son système de fichiers est incohérent.
- Montage d’un overlay sur
/etc
(qui se trouve sur le rootfs en lecture seule), le contenu de l’overlay étant stocké sur la partition de données ci-dessus. Ceci permet de modifier dynamiquement la configuration du système (paramètres réseau par exemple). - Initialisation des interfaces réseau, lecture de l’horloge RTC, consultation éventuelle d’un serveur NTP…
- Vérification du fonctionnement des composants matériels nécessaires pour l’applicatif et déclenchement d’un mode dégradé en cas de défaut.
Conclusion
Ce petit projet est extrêmement simple mais il m’a déjà rendu service plusieurs fois en permettant d’avoir un boot fiable et prédictible avant l’initialisation des tâches applicatives complexes.