Linux embarqué avec Yocto (étape III.1)


Christophe BLAESS – Mai 2019

III.1 – Intégration de fichiers personnels dans une image

Le premier type de fichiers que nous allons ajouter au BSP que nous avons produit jusqu’à présent va être assez simple : il s’agira de scripts shell. L’avantage de ces fichiers est qu’ils ne nécessitent pas de compilation, il suffit de les copier au bon endroit, et avec les bonnes permissions, pour qu’ils soient utilisables sur la cible (bien sûr il faut qu’un shell adéquat soit disponible pour les interpréter mais ça fait partie de l’environnement de base fourni par Yocto).

Système de fichiers en lecture seule

Avant cela, nous allons commencer par sélectionner une feature d’image importante : la mise en lecture seule du système de fichiers. Dans les environnements embarqués, une préoccupation constante est en effet la corruption des données lors d’une coupure impromptue de l’alimentation électrique du système. Lorsqu’elle se produit, cette coupure provoque bien évidemment la perte des données qui étaient en cours d’enregistrement, mais peut engendrer une corruption du système de fichiers lui-même. Cela se traduit par une vérification longue au boot suivant, voire une impossibilité de monter le système de fichiers et un blocage du système.

Une manière simple de corriger ce défaut est de demander à Yocto de monter le système de fichiers contenu sur la partition principale en lecture-seulement. Bien sûr on pourra ajouter des partitions supplémentaires en lecture-écriture pour y stocker des données si besoin, en s’arrangeant pour qu’une détection d’incohérence ne soit pas bloquante, nous en reparlerons dans la quatrième partie. L’important est que la partition principale du système soit protégée.

J’édite donc mon fichier d’image pour ajouter la feature adéquate :

[build-qemu]$ nano  ../meta-my-own-layer/recipes-custom/images/my-own-image.bb
[...]
IMAGE_FEATURES += "read-only-rootfs"

Après re-compilation et démarrage, vérifions le comportement :

mybox login: root
Password: (linux)
root@mybox:~# pwd
/home/root
root@mybox:~# echo HELLO > my-file.txt
-sh: my-file.txt: Read-only file system

Pendant la phase de mise au point du système embarqué, il est possible de remonter temporairement la partition principale en lecture-écriture ainsi :

root@mybox:~# mount / -o remount,rw 
[188.917856] EXT4-fs (vda): re-mounted. Opts: (null)
root@mybox:~# echo HELLO > my-file.txt
root@mybox:~# ls
my-file.txt

Puis de revenir en lecture-seule :

root@mybox:~# mount / -o remount,ro
[260.995398] EXT4-fs (vda): re-mounted. Opts: (null)
root@mybox:~# rm my-file.txt
rm: cannot remove 'my-file.txt': Read-only file system

Néanmoins ces lignes de commandes sont un peu fastidieuses, aussi je vous propose de réaliser deux petits scripts, que j’appelle traditionnellement rw et ro pour réaliser ces tâches. Ceci nous donnera l’occasion d’écrire une recette pour les intégrer dans notre image.

Intégration de scripts shell

Commençons par écrire les deux scripts dans un répertoire dédié de notre layer.

[build-qemu]$ mkdir  -p  ../meta-my-own-layer/recipes-custom/my-scripts/files
[build-qemu]$ nano ../meta-my-own-layer/recipes-custom/my-scripts/files/rw
#! /bin/sh
mount / -o remount,rw
[build-qemu]$ nano ../meta-my-own-layer/recipes-custom/my-scripts/files/ro
#! /bin/sh
mount / -o remount,ro

Inutile de les rendre exécutables, nous ne les lancerons pas sur le système de développement, et leurs permissions sur la cible seront indiquées directement dans notre recette. Je crée le fichier de recette suivant :

[build-qemu]$ nano  ../meta-my-own-layer/recipes-custom/my-scripts/my-scripts.bb
SUMMARY = "Custom scripts for Yocto tutorial"
SECTION = "custom"

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

SRC_URI = "file://ro"
SRC_URI += "file://rw"

do_install() {
install -d ${D}${sbindir}
install -m 0755 ${WORKDIR}/ro ${D}${sbindir}
install -m 0755 ${WORKDIR}/rw ${D}${sbindir}
}

FILES_${PN} += "${sbindir}/ro"
FILES_${PN} += "${sbindir}/rw

Quelques explications sur la syntaxe de notre première recette complète :

  • Les variables SUMMARY et SECTION sont purement informatives et permettent de ranger la recette et la classer lorsqu’elle est présentée dans une liste comme le site Open Embedded Layer Index.
  • Yocto accorde beaucoup d’importance aux licences des packages intégrés dans les images qu’il produit. Son orientation industrielle nécessite une attention particulière pour s’assurer de la compatibilité du code métier (le plus souvent sous licence propriétaire) avec les outils et bibliothèques (généralement libres) employés. Il est donc nécessaire de déclarer dans la variable LICENSE le type de licence, et dans LIC_FILES_CHKSUM le nom du fichier la décrivant ainsi qu’une somme de contrôle de ce dernier pour s’assurer que la version est bien identique. Le projet Yocto lui-même est distribuée sous licence MIT (libre et sans copyleft), aussi de nombreuses recettes emploient-elles la même licence. C’est le choix que j’ai fait ici. Pour éviter de fournir une version du fichier de licence, j’ai fait référence à celui fourni dans le sous-dossier meta/files/common-licenses/ de Poky (qui contient près de deux cents variantes des licences les plus courantes). La checksum est obtenue avec la commande md5sum ../poky/meta/files/common-licenses/MIT.
  • La variable SRC_URI contient une liste des fichiers sources utilisés par la recette ainsi que leur origine. Ici les fichiers sont livrés directement avec la recette aussi sont-ils préfixés file:// mais d’autres descripteurs de provenance sont disponibles (ftp://, git://, svn://, etc.) nous en reparlerons. Nous pouvons remarquer l’usage de l’opérateur += pour ajouter un élément dans la liste.
  • Lorsqu’il traite une recette, Yocto suit un certain nombre d’étapes (fetch pour télécharger les sources, unpack pour les décompresser, patch pour les modifier éventuellement, compile pour produire le code exécutable, install pour les placer dans l’arborescence, etc.). Ces étapes sont représentées par des fonctions do_fetch(), do_compile(), etc. qui ont des implémentations par défaut mais que nous pouvons surcharger. C’est ce que je fais ici en implémentant la fonction do_install(). Cette dernière fait appel à l’utilitaire install (une sorte de cp plus complet) pour placer les scripts aux endroits voulus. On notera que l’on prend soin de créer les répertoires (même standards) que l’on utilise avec l’option -d de install.
  • La variable WORKDIR est automatiquement initialisée avec l’emplacement où Yocto travaille pour produire la recette. C’est l’endroit où il a placé les fichiers sources obtenus dans l’étape do_fetch(). Notre travail consiste à aller les installer dans le répertoire désiré de l’arborescence de la cible. Le point de départ de cette arborescence est toujours représenté par la variable D. Et la variable sbindir contient l’emplacement recommandé pour les utilitaires d’administration (répertoire /usr/sbin généralement).
  • Yocto veut s’assurer que chaque fichier placé dans l’arborescence finale de la cible peut être identifié et attribué à un package. Pour cela la recette remplit sa variable FILES avec la liste des fichiers dont elle est responsable. On utilise l’opérateur _${PN} (Package Name) pour remplir la variable correspondant à ce package.

Nous avons utilisé la variable sbindir, il existe tout une liste de variables prédéfinies avec des chemins standards. Voici ci-dessous les plus courantes, pour en savoir plus, se reporter au début du fichier poky/meta/conf/bitbake.conf.

VariableValeur par défaut
bindir/usr/bin
datadir/usr/share
docdir/usr/share/doc
includedir/usr/include
infodir/usr/share/info
libdir/usr/lib
libexecdir/usr/libexec
localedir/usr/lib/locale
localstatedir/var
sbindir/usr/sbin
servicedir/srv/
sysconfdir/etc
systemd_unitdir/lib/systemd
systemd_system_unitdir/lib/systemd/system
systemd_user_unitdir/usr/lib/systemd/user

Avant de recompiler l’image, il convient d’inclure notre package dans le fichier d’image :

[build-qemu]$ nano  ../meta-my-own-layer/recipes-custom/images/my-own-image.bb
[...]
IMAGE_INSTALL_append = " my-scripts"

Après compilation et démarrage de la cible, nous vérifions que nos scripts sont bien accessibles :

mybox login: root
Password: (linux)
root@mybox:~# ls
root@mybox:~# echo HELLO > my-file.txt
-s
h: my-file.txt: Read-only file system
root@mybox:~# rw
[ 119.571107] EXT4-fs (vda): re-mounted. Opts: (null)
root@mybox:~# echo HELLO > my-file.txt
root@mybox:~# ro
[ 124.120540] EXT4-fs (vda): re-mounted. Opts: (null)
root@mybox:~# rm my-file.txt
rm: cannot remove 'my-file.txt': Read-only file system
root@mybox:~#

Scripts et modules Python

Nous pouvons facilement ajouter des scripts Python sur notre cible, un interpréteur Python 3 étant déjà présent dans notre image, mais le nombre de modules installés par défaut est assez limité. Il est donc possible d’ajouter la ligne suivante dans notre fichier my-own-image.bb pour enrichir l’installation.

IMAGE_INSTALL_append = " python-modules"

Après recompilation, nous pouvons observer qu’un interpréteur Python 2.7 est aussi présent.

root@mybox:~# python -V
Python 2.7.15
root@mybox:~# python3 -V
Python 3.7.2
root@mybox:~# 

Je rajoute donc le petit script de test suivant :

#! /usr/bin/python
#
# https://www.logilin.fr/files/yocto-lab/my-hello.py
#
# Fichier script d'exemple du cours en ligne "Linux embarque avec Yocto" (https://www.logilin.fr/linux-embarque-avec-yocto/)
#
# Christophe BLAESS 2019.
#
# Licence GPL.
#
from future import print_function
import socket
import sys
print("Python", sys.version[0:3], "says 'Hello' from", socket.gethostname())

Ainsi que la recette python-hello.bb ci-dessous, dans le répertoire meta-my-own-layer/recipes-custom/python-hello.

SUMMARY = "Python Hello World for Yocto tutotial"
SECTION = "custom"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
SRC_URI  = "file://my-hello.py"
do_install() {
    install -d ${D}${bindir}
    install -m 0755 ${WORKDIR}/my-hello.py ${D}${bindir}
}
FILES_${PN} += "${bindir}/my-hello.py"
RDEPENDS_python-hello += "python-modules"

On peut noter la présence de la dernière ligne, qui renseigne la variable RDEPENDS pour cette recette. Cette variable, abréviation de Runtime Dependencies contient la liste des packages nécessaires pour le fonctionnement de la recette sur la cible. Il faut la distinguer de la variable DEPENDS qui liste les packages nécessaires pour compiler une recette. Le package python-module doit être installé sur al cible pour deux raisons :

  • disposer du module socket qui nous fournit la fonction gethostname()
  • avoir un interpréteur /usr/bin/python (lien vers python2.7) afin de disposer d’un script avec une ligne shebang indépendante de la version de l’interpréteur.

En ajoutant dans notre image la ligne

IMAGE_INSTALL_append = " python-hello"

et en recompilant notre système, nous pouvons observer après démarrage :

Poky (Yocto Project Reference Distro) 2.7 mybox ttyAMA0
mybox login: root
Password: (linux)
root@mybox:~# my-hello.py 
Python 2.7 says 'Hello' from mybox
root@mybox:~# 

Conclusion

Nous avons vu dans cette séquence comment ajouter dans une image des fichiers fournis par une recette. Dans le cas d’un script, il faut simplement penser à lui ajouter les permissions d’exécution (ce qui se fait facilement avec la commande install) et éventuellement l’interpréteur nécessaire. Dans la séquence suivante, nous écrirons une recette nécessitant une compilation de code source.