«« sommaire »»

IV.2 – Configuration des utilitaires système

Christophe BLAESS - juillet 2023

Dans la séquence précédente, nous avons configuré le réseau à notre convenance. Toutefois, un problème persiste avec le Raspberry Pi…

Gestion de l'heure système

Mon Raspberry Pi a démarré depuis quelques minutes, comme l'indique la commande «uptime» :

root@mybox:/tmp# uptime
12:39:23  up   0:04,  1 user,  load average: 0.00, 0.03, 0.01

Je consulte l'heure du système avec la commande «date» :

root@mybox:/tmp# date
Fri Mar  9 12:39:40 UTC 2018
root@mybox:/tmp# 

L'heure est totalement fausse ! Tout est erroné, même l'année…

Le Raspberry Pi n'a pas de composant RTC (Real Time Clock) pour maintenir l'heure lorsqu'il est éteint. Aussi au démarrage le système est-il à l'heure zéro d'Unix (aussi appelée l'«epoch») : le premier janvier 1970 à 00:00:00 UTC.

Outre le fait qu'un système évoluant en janvier 1970 paraît un peu ridicule à l'utilisateur, des problèmes peuvent se poser avec certains protocoles réseaux par exemple qui ne veulent pas accepter de certificats générés dans le lointain futur. Aussi, Yocto s'arrange-t-il pour que l'heure système soit initialisée avec une valeur réaliste : le vendredi 9 mars 2018 à 12:34:56 UTC. Si je comprends assez bien le choix de l'heure, en revanche je ne vois pas ce qui conduit à utiliser cette date. Elle est stockée «en dur» dans «poky/meta/conf/bitbake.conf». Yocto l'inscrit au moment de la génération de l'image dans le fichier /etc/timestamp sous forme «année, mois, jour, heure, minute, seconde» juxtaposés :

root@mybox:~# cat  /etc/timestamp 
20180309123456

L'inconvénient est qu'à chaque redémarrage le système reprend à la même heure, et qu'au fil du temps on s'éloigne de plus en plus de l'heure réelle. On préfèrerait une mise à l'heure automatique à partir du réseau. Pour cela il existe des serveurs NTP (Network Time Protocol) disponibles librement, il nous faut simplement un programme client ntpd pour les interroger.

root@mybox:~# ntpd
-sh: ntpd: command not found

Sur les systèmes embarqué, le client ntpd fait partie du package busybox, mais il n'a pas été intégré dans sa configuration. À nous de l'ajouter…

Configuration de Busybox

Busybox est un projet assez incontournable dans les systèmes restreints. Il est d'ailleurs surnommés «le couteau suisse de l'embarqué»…

Il s'agit d'un unique exécutable qui adapte son comportement en fonction du nom sous lequel il a été appelé. Cet exécutable implémente plus de 400 commandes Linux courantes (on choisit la liste des commandes à la configuration avant compilation), et des liens symboliques permettent de l'invoquer avec des noms différents. Lorsqu'on appelle busybox sans aucun argument il nous affiche la liste des commandes qu'il implémente :

root@mybox:~# busybox
BusyBox v1.35.0 () multi-call binary.
BusyBox is copyrighted by many authors between 1998-2015.
Licensed under GPLv2. See source distribution for detailed
copyright notices.

Usage: busybox [function [arguments]...]
   or: busybox --list
   or: function [arguments]...

        BusyBox is a multi-call binary that combines many common Unix
        utilities into a single executable.  Most people will create a
        link to busybox for each function they wish to use and BusyBox
        will act like whatever it was invoked as.

Currently defined functions:
        [, [[, addgroup, adduser, ash, awk, base32, basename, blkid, bunzip2, bzcat, bzip2,
        cat, chattr, chgrp, chmod, chown, chroot, chvt, clear, cmp, cp, cpio, cut, date, dc,
        dd, deallocvt, delgroup, deluser, depmod, df, diff, dirname, dmesg, dnsdomainname,
        du, dumpkmap, dumpleases, echo, egrep, env, expr, false, fbset, fdisk, fgrep, find,
        flock, free, fsck, fstrim, fuser, getopt, getty, grep, groups, gunzip, gzip, head,
        hexdump, hostname, hwclock, id, ifconfig, ifdown, ifup, insmod, ip, kill, killall,
        klogd, less, ln, loadfont, loadkmap, logger, logname, logread, losetup, ls, lsmod,
        lzcat, md5sum, mesg, microcom, mkdir, mkfifo, mknod, mkswap, mktemp, modprobe, more,
        mount, mountpoint, mv, nc, netstat, nohup, nproc, nslookup, od, openvt, patch, pgrep,
        pidof, pivot_root, printf, ps, pwd, rdate, readlink, realpath, reboot, renice, reset,
        resize, rev, rfkill, rm, rmdir, rmmod, route, run-parts, sed, seq, setconsole,
        setsid, sh, sha1sum, sha256sum, shuf, sleep, sort, start-stop-daemon, stat, strings,
        stty, sulogin, swapoff, swapon, switch_root, sync, sysctl, syslogd, tail, tar, tee,
        telnet, test, tftp, time, top, touch, tr, true, ts, tty, udhcpc, udhcpd, umount,
        uname, uniq, unlink, unzip, uptime, users, usleep, vi, watch, wc, wget, which, who,
        whoami, xargs, xzcat, yes, zcat

Notre système est basé sur l'image «core-image-base» de Poky qui est assez complète, avec de nombreux packages, et le rôle de Busybox est relativement réduit. Si toutefois nous retournons sur l'image «core-image-minimal» que nous avons compilée dans la séquence I.3, nous voyons que Busybox est invoqué — par l'intermédiaire de liens symboliques — pour presque chaque appel de commande :

root@raspberrypi4:~# ls -l /bin/
lrwxrwxrwx    1 root     root            19 Dec 26 12:49 ash -> /bin/busybox.nosuid
lrwxrwxrwx    1 root     root            14 Dec 26 11:38 busybox -> busybox.nosuid
-rwxr-xr-x    1 root     root        476920 Dec 26 11:49 busybox.nosuid
-rwsr-xr-x    1 root     root         42440 Dec 26 11:49 busybox.suid
lrwxrwxrwx    1 root     root            19 Dec 26 12:49 cat -> /bin/busybox.nosuid
lrwxrwxrwx    1 root     root            19 Dec 26 12:49 chattr -> /bin/busybox.nosuid
lrwxrwxrwx    1 root     root            19 Dec 26 12:49 chgrp -> /bin/busybox.nosuid
lrwxrwxrwx    1 root     root            19 Dec 26 12:49 chmod -> /bin/busybox.nosuid
lrwxrwxrwx    1 root     root            19 Dec 26 12:49 chown -> /bin/busybox.nosuid
lrwxrwxrwx    1 root     root            19 Dec 26 12:49 cp -> /bin/busybox.nosuid
lrwxrwxrwx    1 root     root            19 Dec 26 12:49 cpio -> /bin/busybox.nosuid
lrwxrwxrwx    1 root     root            19 Dec 26 12:49 date -> /bin/busybox.nosuid
lrwxrwxrwx    1 root     root            19 Dec 26 12:49 dd -> /bin/busybox.nosuid
lrwxrwxrwx    1 root     root            19 Dec 26 12:49 df -> /bin/busybox.nosuid
lrwxrwxrwx    1 root     root            19 Dec 26 12:49 dmesg -> /bin/busybox.nosuid
lrwxrwxrwx    1 root     root            19 Dec 26 12:49 dnsdomainname -> /bin/busybox.nosuid
lrwxrwxrwx    1 root     root            19 Dec 26 12:49 dumpkmap -> /bin/busybox.nosuid
lrwxrwxrwx    1 root     root            19 Dec 26 12:49 echo -> /bin/busybox.nosuid
lrwxrwxrwx    1 root     root            19 Dec 26 12:49 egrep -> /bin/busybox.nosuid
lrwxrwxrwx    1 root     root            19 Dec 26 12:49 false -> /bin/busybox.nosuid
lrwxrwxrwx    1 root     root            19 Dec 26 12:49 fgrep -> /bin/busybox.nosuid
lrwxrwxrwx    1 root     root            19 Dec 26 12:49 getopt -> /bin/busybox.nosuid
lrwxrwxrwx    1 root     root            19 Dec 26 12:49 grep -> /bin/busybox.nosuid
lrwxrwxrwx    1 root     root            19 Dec 26 12:49 gunzip -> /bin/busybox.nosuid
lrwxrwxrwx    1 root     root            19 Dec 26 12:49 gzip -> /bin/busybox.nosuid
lrwxrwxrwx    1 root     root            19 Dec 26 12:49 hostname -> /bin/busybox.nosuid
lrwxrwxrwx    1 root     root            19 Dec 26 12:49 kill -> /bin/busybox.nosuid
lrwxrwxrwx    1 root     root            19 Dec 26 12:49 ln -> /bin/busybox.nosuid
lrwxrwxrwx    1 root     root            17 Dec 26 12:49 login -> /bin/busybox.suid
lrwxrwxrwx    1 root     root            19 Dec 26 12:49 ls -> /bin/busybox.nosuid
lrwxrwxrwx    1 root     root            19 Dec 26 12:49 mkdir -> /bin/busybox.nosuid

Le suffixe «.nosuid» signifie simplement que c'est une commande ne nécessitant pas de privilège particulier pour son exécution.

Busybox nous propose une implémentation du client ntpd qui n'avait pas été sélectionnée dans la configuration par défaut. Pour accéder à la configuration, nous pouvons exécuter la commande suivante dans notre répertoire de build :

[build-rpi]$ bitbake  -c menuconfig  busybox

Cette commande demande à bitbake d'exécuter l'étape menuconfig de la recette correspondant à busybox. Il s'agit d'un menu de configuration proposé par le Makefile de Busybox. En principe une nouvelle fenêtre s'ouvre (ou un nouvel onglet de terminal) avec le menu de configuration de la figure IV.2-1

Dans certaines circonstances (connexion à distance, etc.) l’écran de configuration peut refuser de s’afficher. Dans ce cas, on peut contourner le problème en remplissant dans le fichier conf/local.conf la variable OE_TERMINAL avec la valeur «screen».

Nous accédons au menu «Networking Utilities» pour activer l'utilitaire ntpd comme sur la figure IV.2-2.

Avant de quitter la configuration nous validons la sauvegarde de la configuration modifiée comme sur la figure IV.2-3.

La configuration de Busybox a été modifiée uniquement dans le sous-répertoire que Yocto Project utilise pour son build. Mais nous souhaitons pérenniser cette modification dans notre image. Nous devons appeler bitbake pour réaliser une étape particulière de la recette :

[build-rpi]$ bitbake  -c  diffconfig  busybox
  [...]
Config fragment has been dumped into:
 /home/cpb/Yocto-lab/builds-build-rpi/tmp/work/cortexa7t2hf-neon-vfpv4-poky-linux-gnueabi/busybox/1.35.0-r0/fragment.cfg

Cette étape a priori un peu obscure demande à bitbake de créer un fragment de fichier de configuration qui regroupe les modifications par rapport à la configuration précédente (celle par défaut pour nous). Le fragment se trouve dans le chemin indiqué en fin de commande.

Nous devons intégrer ce fragment dans une extension de recette pour Busybox dans notre layer. Pour nous aider l'outil recipetool que nous avons déjà rencontré lors de la séquence II.3 va prendre le relais ainsi :

[build-rpi]$ recipetool  appendsrcfile  -w  ../../layers/meta-my-layer/  busybox  tmp/work/cortexa7t2hf-neon-vfpv4-poky-linux-gnueabi/busybox/1.35.0-r0/fragment.cfg
NOTE: Starting bitbake server...
Loading cache: 100% |######################################################################| Time: 0:00:00
Loaded 2252 entries from dependency cache.
NOTE: Writing append file /home/cpb/Yocto-lab/meta-my-layer/recipes-core/busybox/busybox_%.bbappend
NOTE: Copying tmp/work/cortexa7t2hf-neon-vfpv4-poky-linux-gnueabi/busybox/1.35.0-r0/fragment.cfg to /home/cpb/Yocto-lab/layers/meta-my-layer/recipes-core/busybox/busybox/fragment.cfg
[build-rpi]$ 

On copie sur la ligne de commande de recipetool le chemin vers le fragment obtenu précédemment. Puis recipetool nous indique qu'il crée un fichier .bbappend et qu'il copie le fragment à l'endroit attendu par cette extension de recette.

Il nous reste à recompiler notre image. Nous effaçons la précédente compilation de Busybox d'abord pour éviter un warning de Yocto («do_compile is tainted from a forced run»).

[build-rpi]$ bitbake -c clean busybox
  [...]
[build-rpi]$ bitbake my-image

Après avoir recompilé et redémarré notre image, nous pouvons appeler ntpd implémenté par Busybox pour interroger le serveur de temps pool.ntp.org.

root@mybox:~# which  ntpd
/usr/sbin/ntpd

root@mybox:~# ls  -l  /usr/sbin/ntpd
lrwxrwxrwx 1 root root 19 Mar  9 12:34 /usr/sbin/ntpd -> /bin/busybox.nosuid

root@mybox:~# date
Fri Mar  9 12:36:40 UTC 2018

root@mybox:~# ntpd  -d  -n  -q  -p pool.ntp.org
ntpd: 'pool.ntp.org' is 193.52.136.2
ntpd: sending query to 193.52.136.2
ntpd: reply from 193.52.136.2: offset:+170459214.023987 delay:0.014274 status:0x24 strat:2 refid:0x0ecbee91 rootdela1
ntpd: sending query to 193.52.136.2
ntpd: reply from 193.52.136.2: offset:+170459214.024071 delay:0.014656 status:0x24 strat:2 refid:0x0ecbee91 rootdela3
ntpd: setting time to 2023-08-03 10:23:04.559876 (offset +170459214.024071s)

root@mybox:~# date
Thu Aug  3 10:23:14 UTC 2023

root@mybox:~#

Le système est maintenant à l'heure. Mais bien évidemment, au redémarrage…

root@mybox:~# reboot
 [...]
The system is going down for reboot NOW!
 [...]

Poky (Yocto Project Reference Distro) 4.0.11 mybox ttyS0

mybox login: root
Password: (linux)
root@mybox:~# date
Fri Mar  9 12:35:13 UTC 2018
root@mybox:~# 

Il faudrait que la mise à l'heure soit automatiquement réalisée au démarrage du système. C'est ce que nous allons écrire à présent.

@@@@@

Démarrage d'un service

Il existe sous Linux plusieurs mécanismes pour lancer une tâche au démarrage. Les plus connues sont «sysvinit» («System V Init») et «systemd». De nos jours la plupart des PC et des serveurs utilisent «systemd». Les systèmes embarqués quant à eux utilisent encore majoritairement «System V Init».

Yocto Project choisit par défaut «System V Init» mais nous offre la possibilité d'utiliser «systemd» si on le préfère en configurant une recette de distribution spécifique. Ceci est un peu compliqué pour le cadre de ce cours en ligne, aussi allons-nous conserver l'environnement «sysvinit».

Le processus «init» lancé automatiquement par le noyau au boot consulte le fichier /etc/inittab pour savoir comment agir. Ce fichier configure le runlevel du système (son niveau de fonctionnement) à la valeur 5. Puis lance successivement le script shell /etc/init.d/rcS suivi de tous les scripts se trouvant dans le répertoire /etc/rc5.d/ (le 5 correspondant au runlevel). Dans ce répertoire, on trouve :

root@mybox:~# ls  /etc/rc5.d/
S01networking   S18btuart        S22ofono
S02dbus-1       S20apmd          S64neard
S10dropbear     S20bluetooth     S99rmnologin.sh
S12rpcbind      S20syslog        S99stop-bootlogd
S15mountnfs.sh  S21avahi-daemon

Les scripts présents commencent tous par la lettre «S» comme Start pour indiquer qu'ils sont lancés au démarrage du système, suivie d'un numéro d'ordre de démarrage. En réalité tous ces fichiers sont des liens symboliques vers des scripts se trouvant de /etc/init.d :

root@mybox:~# ls  -l  /etc/rc5.d/
total 0
lrwxrwxrwx 1 root root 20 Mar  9 12:34 S01networking -> ../init.d/networking
lrwxrwxrwx 1 root root 16 Mar  9 12:34 S02dbus-1 -> ../init.d/dbus-1
lrwxrwxrwx 1 root root 18 Mar  9 12:34 S10dropbear -> ../init.d/dropbear
lrwxrwxrwx 1 root root 17 Mar  9 12:34 S12rpcbind -> ../init.d/rpcbind
lrwxrwxrwx 1 root root 21 Mar  9 12:34 S15mountnfs.sh -> ../init.d/mountnfs.sh
lrwxrwxrwx 1 root root 16 Mar  9 12:34 S18btuart -> ../init.d/btuart
lrwxrwxrwx 1 root root 14 Mar  9 12:34 S20apmd -> ../init.d/apmd
lrwxrwxrwx 1 root root 19 Mar  9 12:34 S20bluetooth -> ../init.d/bluetooth
lrwxrwxrwx 1 root root 16 Mar  9 12:34 S20syslog -> ../init.d/syslog
lrwxrwxrwx 1 root root 22 Mar  9 12:34 S21avahi-daemon -> ../init.d/avahi-daemon
lrwxrwxrwx 1 root root 15 Mar  9 12:34 S22ofono -> ../init.d/ofono
lrwxrwxrwx 1 root root 15 Mar  9 12:34 S64neard -> ../init.d/neard
lrwxrwxrwx 1 root root 22 Mar  9 12:34 S99rmnologin.sh -> ../init.d/rmnologin.sh
lrwxrwxrwx 1 root root 23 Mar  9 12:34 S99stop-bootlogd -> ../init.d/stop-bootlogd

Les liens symboliques sont créés au moment de la préparation du système de fichiers en prenant en compte les runlevels auxquels les services doivent démarrer et les dépendances entre ces services.

Nous pouvons créer un script qui lance automatiquement ntpd avec le nom du serveur NTP en argument. Pour être intégré correctement dans le système de démarrage, le script doit débuter avec un en-tête précis :

#! /bin/sh
### BEGIN INIT INFO
# Provides:             ntpd
# Required-Start:       $networking
# Required-Stop:        
# Default-Start:        2 3 4 5
# Default-Stop:         1
# Short-Description:    Network Time Protocol client Daemon
### END INIT INFO
#

Cet en-tête indique le nom du service fourni par ce script (lignes «Provides» et «Short-Description»), la liste des services dont il a besoin pour démarrer, ce qui permet de les ordonner. Nous demandons seulement la présence de «networking». Enfin on indique les runlevels dans lesquels le script doit être démarré et ceux où il doit être arrêté. Les listes «2 3 4 5» d'une part et «1» se retrouvent dans la plupart des packages. On consultera les scripts de /etc/init.d/ pour avoir plus d'exemples.

C'est la classe «update-rc.d» fournie par Poky qui crée les liens en fonction des dépendances. Notre recette devra donc en hériter.

Le code permettant de lancer le service proprement dit vient à la suite de cet en-tête. On peut l'écrire de différentes manières. La plus simple à mon avis est la suivante qui s'appuie sur la commande «start-stop-daemon» implémentée par Busybox :

do_start()
{
	start-stop-daemon --start --exec ntpd -- -p pool.ntp.org 
}

do_stop()
{
	start-stop-daemon --stop --name ntpd
}


case "$1" in
  start) do_start ;;
  stop)  do_stop  ;;
  restart) do_stop; do_start ;;
  *) echo "Usage: $0 {start|stop|restart}" >&2; exit 1 ;; 
esac

exit 0

Si notre script est appelé avec «start» en argument, il démarre le client NTP en enregistrant le numéro de processus (PID, Process IDentifier) dans un fichier. Par défaut start-stop-daemon le stocke dans /var/run/<nom-du-service>.pid. Si on appelle le script avec l'argument «stop», il arrête le client NTP en lui envoyant un signal (grâce au PID enregistré au démarrage).

Préparons un répertoire dans notre layer pour accueillir la recette et le script :

[build-rpi]$ mkdir  -p  ../../layers/meta-my-layer/recipes-custom/ntpd-start/files/ 

Dans le sous-répertoire files/ de ma recette, j'écris le script complet décrit ci-dessus.

[build-rpi]$ nano  ../../layers/meta-my-layer/recipes-custom/ntpd-start/files/ntpd 
   [...]

Puis je rédige la recette suivante qui hérite de la classe update-rc.d et copie le script dans le répertoire /etc/init.d/ de la cible en indiquant son nom dans la variable INITSCRIPT_NAME :

[build-rpi]$ nano  ../../layers/meta-my-layer/recipes-custom/ntpd-start/ntpd-start_1.0.bb

SUMMARY = "Script to start ntpd client service"
SECTION = "custom"

LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302" 

SRC_URI  = "file://ntpd"

inherit update-rc.d

INITSCRIPT_NAME = "ntpd"

do_install() {
  install -d ${D}${sysconfdir}
  install -d ${D}${sysconfdir}/init.d
  install -m 755 ${WORKDIR}/ntpd ${D}${sysconfdir}/init.d/ntpd
}

Après inscription de ntpd-start dans la recette d'image, recompilation et installation de l'image, le service est lancé dès le démarrage et le système est à l'heure :

Poky (Yocto Project Reference Distro) 4.0.11 mybox ttyS0

mybox login: root
Password: (linux)

root@mybox:~# date
Thu Aug  3 11:49:06 UTC 2023

root@mybox:~# ls  /var/run/
agetty.reload  ifstate      lock         resolvconf    syslogd.pid
avahi-daemon   initctl      mount        rpcbind.lock  udev
dbus           klogd.pid    ntpd.pid     rpcbind.sock  utmp
dropbear.pid   ld.so.cache  resolv.conf  runlevel      wpa_supplicant
root@mybox:~# cat  /var/run/ntpd.pid
437
root@mybox:~# ps
  [...]
437 root      2688 S    ntpd -p pool.ntp.org
  [...]
root@mybox:~# ls -l /etc/rc5.d/
total 0
  [...]
lrwxrwxrwx 1 root root 19 Mar  9  2018 S20bluetooth -> ../init.d/bluetooth
lrwxrwxrwx 1 root root 14 Mar  9  2018 S20ntpd -> ../init.d/ntpd
lrwxrwxrwx 1 root root 16 Mar  9  2018 S20syslog -> ../init.d/syslog
  [...]

Conclusion

Cette séquence nous a permis de voir comment modifier la configuration de Busybox et lancer un service au démarrage par l'intermédiaire d'un script de lancement.

Dans la séquence suivante, nous allons continuer l'adaptation de notre image pour une cible personnalisée, en s'intéressant cette fois aux interactions avec le matériel.

Ce document est placé sous licence Creative Common CC-by-nc. Vous pouvez copier son contenu et le réemployer à votre gré pour une utilisation non-commerciale. Vous devez en outre mentionner sa provenance.

Le nom Yocto Project est une marque déposée par la Linux Foundation. Le présent document n'est en aucune façon approuvé par Yocto Project ou la Linux Foundation.

«« sommaire »»