Linux Embarqué

Transcription

Linux Embarqué
Linux Embarqué
Gilles Chanteperdrix
Imprimé le 4 juin 2002
Résumé
Ce document est la propriété d’IDEALX S.A.S.1 . Il est placé sous licence de documentation
libre GNU (GNU Free Documentation License, voir http ://www.gnu.org/copyleft/fdl.html
ou http ://www.idealx.org/fr/licences/gfdl.html pour une traduction en français non officielle).
Ce document est le support d’une formation à l’utilisation de Linux dans un contexte embarqué.
La réalisation concrète d’un système embarqué basé sur Linux en est le fil rouge.
1 http
://www.IDEALX.com
Table des matières
Introduction
3
1 Démarrage d’un système Linux par le réseau
1.1 Dynamique du démarrage par le réseau . . . . .
1.2 Etherboot . . . . . . . . . . . . . . . . . . . . . .
1.3 Configuration du serveur DHCP . . . . . . . . .
1.4 Configuration du serveur TFTP . . . . . . . . . .
1.5 Préparation du noyau . . . . . . . . . . . . . . .
1.5.1 Paramètres de compilation du noyau . . .
1.5.2 Options choisies . . . . . . . . . . . . . .
1.5.3 Préparation pour démarrage par le réseau
1.6 Configuration du serveur NFS . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
4
4
5
5
6
7
7
8
9
10
2 Mise en place de l’environnement d’exécution
2.1 Structure d’un répertoire racine type . . . . . . . .
2.2 Découpage du répertoire racine de la cible . . . . .
2.3 Contenu du répertoire partagé sur le système hôte
2.4 Bibliothèques partagées . . . . . . . . . . . . . . .
2.5 Busybox . . . . . . . . . . . . . . . . . . . . . . . .
2.6 Démarrage de la cible . . . . . . . . . . . . . . . .
2.7 Scripts de démarrage . . . . . . . . . . . . . . . . .
2.7.1 Démarrage de devfsd . . . . . . . . . . . . .
2.7.2 Montage des systèmes de fichiers . . . . . .
2.7.3 Configuration du réseau . . . . . . . . . . .
2.7.4 Démarrage des journaux système . . . . . .
2.7.5 Démarrage du watchdog . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
11
11
11
12
12
13
13
14
15
16
16
17
17
3 Développement d’applications embarquées avec GNU/Linux
3.1 Développement d’applications natives . . . . . . . . . . . . . . .
3.1.1 Le compilateur GCC . . . . . . . . . . . . . . . . . . . . .
3.1.2 Liaison statique vs Liaison dynamique . . . . . . . . . . .
3.2 Adaptation d’une application native . . . . . . . . . . . . . . . .
3.3 Portage d’une application écrite pour un OS propriétaire . . . . .
3.3.1 Temps-réel ? . . . . . . . . . . . . . . . . . . . . . . . . . .
3.3.2 Portage de premier ordre . . . . . . . . . . . . . . . . . .
3.3.3 Xenomai . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.4 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
18
18
18
19
20
21
21
22
22
22
4 Finalisation du système
4.1 Partitionnement du disque flash . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.2 Copie des fichiers sur le disque flash . . . . . . . . . . . . . . . . . . . . . . . . . .
4.2.1 Système de fichiers racine . . . . . . . . . . . . . . . . . . . . . . . . . . . .
24
24
24
24
1
4.2.2
Système de fichiers d’amorçage . . . . . . . . . . . . . . . . . . . . . . . . .
2
25
Introduction
L’utilisation d’un système d’exploitation généraliste pour un système embarqué pourrait paraı̂tre saugrenue, mais elle a l’énorme avantage de permettre d’éviter les longues itérations d’un
processus de développement croisé. En outre, la flexibilité et la modularité de Linux en font un bon
candidat pour l’embarquement dans des systèmes de taille (modérément) réduite. Enfin, les sources
de nombreux composants logiciels écrits pour Linux sont disponibles gratuitement, ce qui permet
de concentrer réellement l’effort de développement sur la partie spécifique d’une application.
Afin de tirer le plus grand parti des avantages de Linux, un processus de développement raisonnable pourrait comprendre les étapes suivantes :
– développement de l’application sur machine hôte,
– test sur cible avec système de fichiers distribué,
– test sur cible avec mémoire de masse embarquée.
Le démarrage d’une cible avec un système de fichiers distribué nous semble être un atout
déterminant pour faciliter la mise au point d’un système embarqué basé sur Linux. Cependant il
nécessite un minimum de préparation du système hôte que nous exposons donc dans le premier
chapitre de ce document. Dans le deuxième chapitre, nous créons un système de fichier minimal
permettant au système de démarrer complètement. Dans le troisième chapitre, nous exposons les
problèmes rencontrés et les outils nécessaires au développement d’applications embarquées sous
Linux. Enfin, dans le quatrième chapitre, nous finalisons le système pour qu’il puisse fonctionner
en utilisant sa mémoire de masse embarquée.
3
Chapitre 1
Démarrage d’un système Linux
par le réseau
Avant de plonger dans les règles de configuration des serveurs, il est nécessaire de comprendre,
au moins superficiellement, comment démarre une machine sous Linux dans le cas particulier d’un
démarrage par le réseau, c’est donc ce que nous ferons dans une première section.
Ensuite, nous configurerons les serveurs de la machine hôte. Les explications quant aux noms
des répertoires utilisés pour cette configuration seront données dans le chapitre 2 page 11.
1.1
Dynamique du démarrage par le réseau
Lors du démarrage d’un système Linux par le réseau, un certain nombre de protocoles sont
utilisés dont nous allons donner une brève description avant de détailler le processus du démarrage :
DHCP permet la configuration de l’interface réseau d’une machine cible à partir de paramètres
réseaux fournis par une machine hôte.
BOOTP est une extension du protocole DHCP qui permet à une machine de charger un noyau
par le réseau, la machine cible reçoit par le réseau l’emplacement de téléchargement de son
noyau en plus des paramètres réseau.
TFTP est un protocole de transfert de fichiers par le réseau qui est utilisé par la cible pour
télécharger son noyau.
NFS est un système de fichiers distribué.
Le processus de démarrage comprend les étapes suivantes :
4
Sur la cible
Le système démarre sur une disquette contenant le programme Etherboot, le charge et
l’exécute.
Le programme Etherboot configure son interface réseau, puis émet une requête BOOTP.
Sur l’hôte
Le serveur DHCP fournit à la machine cible
ses paramètres réseau et l’emplacement de son
noyau.
Le serveur TFTP fournit le noyau du système
cible.
Le programme Etherboot télécharge le noyau
en utilisant le protocole TFTP.
Le programme Etherboot passe le contrôle au
noyau (les paramètres de configuration de l’interface réseau sont alors perdus).
Le noyau émet une requête DHCP pour obtenir ses paramètres réseau
Le noyau monte son répertoire racine en utilisant le système de fichiers NFS.
Le serveur DHCP fournit ses paramètres réseau à la machine cible.
Le serveur NFS accorde l’accès au répertoire
partagé à la machine cible, à qui il sert alors
de répertoire racine.
Le démarrage de la cible se poursuit en utilisant les fichiers qui sont sur le répertoire partagé.
Dans la suite de ce chapitre, nous allons configurer les serveurs de la machine hôte dans l’ordre
dans lequel ils sont utilisés au cours du processus que nous venons de décrire. Cet ordre n’est pas
impératif, mais il permet, après chaque étape, de vérifier qu’elle a été correctement effectuée.
1.2
Etherboot
Etherboot est le programme qui effectue le téléchargement en mémoire vive du noyau, prépare
son exécution puis l’exécute. Afin qu’une machine puisse démarrer en utilisant ce programme, il
doit être stocké sur le secteur d’amorce d’une disquette, d’un disque IDE, d’un CDROM, ou même
sur la ROM d’une carte réseau.
Etherboot peut-être téléchargé (voir [1]), puis compilé. Mais il est aussi possible de télécharger
une image de disquette prête à l’emploi (cf [2]). Il suffit alors de choisir la version d’Etherboot, le
type de carte réseau, le format de sortie (normalement, disquette de démarrage).
Après avoir téléchargé l’image, la disquette peut être créée avec la commande :
dd if=eb-(version)-(carte réseau).lzdsk of=/dev/fd0
Pour tester la validité de la disquette Etherboot ainsi générée, elle peut être utilisée comme
disquette d’amorce du système cible. Si tout se passe bien, le type de carte réseau du système cible
doit être reconnu, son adresse MAC doit s’afficher, puis le système doit échouer quand il essaie de
contacter le serveur DHCP.
1.3
Configuration du serveur DHCP
Le paquet Red-Hat correspondant au serveur DHCP doit d’abord être installé sur la machine
hôte, c’est le paquet dchp. Le serveur DHCP contenu dans ce paquet a l’avantage d’être aussi
serveur pour le protocole BOOTP.
5
Nous allons configurer le serveur DHCP en utilisant l’adresse MAC de la carte réseau de la
cible. L’adresse MAC de la carte se présente sous la forme de 6 entiers de 2 caractères en notation
hexadécimale. Elle est écrite sur la carte elle-même ou s’affiche à l’écran lors du démarrage avec
la disquette Etherboot.
Le fichier de configuration du serveur DHCP est le fichier /etc/dhcpd.conf dont voici un
exemple :
option broadcast-address 192.168.0.255;
subnet 192.168.0.0 netmask 255.255.255.0
{
use-host-decl-names on;
host vnt-alf {
hardware ethernet 00:e0:98:33:e0:86;
fixed-address 192.168.0.117;
filename "/tftpboot/192.168.0.117/zImage";
}
}
Lorsque la machine dont l’adresse MAC est 00:e0:98:33:e0:86 effectuera une requête BOOTP,
le serveur lui fournira l’adresse IP 192.168.0.117, le nom vnt-alf comme nom d’hôte, et lui transmettra /tftpboot/192.168.0.117/zImage comme nom de noyau à télécharger pour démarrer.
Après avoir édité ce fichier de configuration, il faut démarrer le serveur, en tapant :
# /etc/init.d/dhcpd start
Afin de tester le serveur DHCP, redémarrer la cible, dont la requête BOOTP doit maintenant
être satisfaite, mais qui ne doit pas arriver à charger son noyau. Le fichier de journal de la machine
hôte (/var/log/messages), doit contenir les lignes :
DHCPDISCOVER from 00:e0:98:33:e0:86 via eth0
DHCPOFFER on 192.168.0.117 to 00:e0:98:33:e0:86 via eth0
Il est à noter que l’exemple proposé ici est simple, car il suppose qu’un seul serveur DHCP
est présent sur le réseau, ce qui est facilement réalisable (en reliant l’hôte et la cible par un câble
croisé, par exemple). Toutefois, il est aussi possible d’avoir plusieurs serveurs DHCP sur un même
réseau, mais la configuration est un peu plus compliquée. La solution la plus radicale consiste à
utiliser les “vendor encapsulated options”, la documentation d’Etherboot (voir [1]) est complète à
ce sujet.
1.4
Configuration du serveur TFTP
Le paquet Red-Hat qui doit être installé sur la machine hôte est le paquet tftp-server. Le
démon tftpd est un démon très simple lancé par le gestionnaire de services réseau inetd ou
xinetd. Le démon TFTP ne nécessite pas l’édition de fichiers de configuration, en revanche inetd
ou xinetd doivent être configurés pour lancer tftpd.
6
Si le serveur inetd est utilisé, c’est le fichier /etc/inetd.conf qui doit être édité. Il doit contenir
la ligne :
tftp
dgram
udp
wait
root
/usr/sbin/tcpd
in.tftpd /tftpboot
Si le serveur xinetd est utilisé, c’est le fichier /etc/xinetd.d/tftp qui doit être édité :
#/etc/xinitd.d/tftp
service tftp
{
protocol
socket_type
wait
user
log_on_success
log_on_failure
server
server_args
disable
}
= udp
= dgram
= yes
= root
+= USERID
+= USERID
= /usr/sbin/in.tftpd
= /tftpboot
= no
Les champs qu’il est important de modifier sont le champ disable qui doit être positionné à
no et le champ server_args qui doit contenir exclusivement /tftpboot.
Après avoir modifié la configuration, il faut redémarrer le gestionnaire de services internet ; en
tapant :
– /etc/init.d/inetd restart pour redémarrer inetd,
– ou /etc/init.d/xinetd restart pour redémarrer xinetd.
Afin de tester que ce service fonctionne bien, redémarrer la cible. Le processus de démarrage
par Etherboot devrait aller plus loin qu’au précédent essai, mais si le fichier correspondant au
paramètre filename de la configuration de dhcpd n’existe pas, le processus Etherboot échoue.
1.5
Préparation du noyau
La prochaine étape du processus de démarrage par Etherboot est le chargement du noyau. Nous
n’embarquons pas un noyau standard car certaines options du noyau doivent être activées pour
permettre l’utilisation d’un système de fichiers distribué comme répertoire racine. Cette section
fournit donc une bonne occasion pour détailler un peu les options de compilation qui peuvent
présenter un certain intérêt pour des systèmes embarqués.
1.5.1
Paramètres de compilation du noyau
Pour lancer l’interface de configuration de la compilation du noyau, taper :
# cd /usr/src/linux
# make xconfig
Une fenêtre apparaı̂t affichant plusieurs sections contenant chacune un certain nombre d’options
de compilation. Les sections suivantes nous ont semblé contenir des options intéressantes :
7
Loadable module support Certaines fonctionnalités du noyau peuvent être compilées sous forme de modules. Ces modules peuvent alors être chargés, utilisés et déchargés à loisir pendant
le fonctionnement du noyau. Alors que cette fonctionnalité est intéressante pour les noyaux
généralistes fournis par des distributions ou pour ne pas payer en permanence le coût en
occupation mémoire de fonctionnalités occasionnelles sur une station de travail, elle l’est
beaucoup moins sur un système embarqué. L’activation de l’option “Enable module support”
nécessite l’installation d’outils pour charger et décharger les modules.
Processor type and features Cette section contient des options qui permettent d’adapter le
noyau à sa plate-forme d’exécution. L’option “Processor family”, notamment, permet de
choisir exactement le processeur présent sur la plate-forme d’exécution.
Memory Technology Device Cette section donne accès à toute une série de pilotes pour périphériques flash.
Networking Options C’est dans cette section que se trouvent les options qui permettent au
noyau de déterminer automatiquement sa configuration réseau au moment du démarrage.
Watchdog cards Cette section se trouve dans la section “Character devices”, et permet d’activer
la gestion de systèmes de surveillance matériel ou logiciel.
File systems Cette section permet de sélectionner les systèmes de fichiers qui seront reconnus
par le noyau généré. Parmi ces systèmes de fichiers notons :
– le système de fichiers EXT2, standard et classique sous Linux, que nous utiliserons sur la
partition de démarrage de la cible ;
– le système de fichiers NFS (dans la sous-section “Network File systems”) qui permet de
partager un répertoire entre plusieurs machines par le réseau ;
– le système de fichiers TMPFS, est un système de fichiers stocké en mémoire vive, idéal
pour le stockage des fichiers temporaires ;
– le système de fichiers JFFS2, journalisé, compressé et adapté à une utilisation optimale
des périphériques flash.
1.5.2
Options choisies
Nous sélectionnons ici tous les paramètres que nous utiliserons par la suite. Si la présence de
certaines options devrait déjà paraı̂tre évidente, d’autres, en revanche, ne le deviendront que plus
tard, notamment lorsque nous aurons vu comment et pourquoi le système de fichier racine est
découpé, section 2.2 page 11.
Afin de démarrer en utilisant un système de fichiers distribué, les options à activer sont :
– dans la section “Networking Options”, “IP : kernel level autoconfiguration” et “IP : BOOTP
support”
– dans la section “Filesystems”, sous-section “Network File systems” , “NFS file system support”
et “Root file system on NFS”
Pour éviter d’avoir à créer des fichiers spéciaux pour les périphériques, dans la section “File
systems”, activer les options
– “/dev file system support”
– et “Automatically mount at boot”.
Pour gérer les systèmes de fichiers sur disque compact flash, dans la section “Memory Technology
Devices (MTD)”, activer
– “Memory Technology Devices (MTD) support”,
– “Caching block device access to MTD devices”,
– “Readonly block device access to MTD devices”.
Dans la sous-section “Self-contained MTD device drivers”, activer “MTD emulation using block
8
device”. Enfin, dans la section “File systems”, activer l’option “Journalling Flash File System v2
(JFFS2) support”.
Pour utiliser la mémoire vive comme moyen de stockage des fichiers temporaires, dans la section
“File systems” activer l’option “Virtual memory file system support (former shm fs)”.
Pour un système embarqué, il peut être utile d’avoir système de surveillance (watchdog) qui
redémarre le système s’il est bloqué. Pour pouvoir sélectionner un watchdog, activer “Watchdog
Timer Support”, puis sélectionner le watchdog matériel si vous en avez un, ou le watchdog logiciel
(moins fiable) si aucun watchdog matériel ne convient.
Après avoir fini la sélection d’option, valider en sélectionnant “Save and Exit”. Les options
sélectionnées sont sauvées dans le fichier /usr/src/linux/.config.
Pour lancer la compilation, taper :
make dep; make clean; make bzImage
L’image générée est alors le fichier /usr/src/linux/arch/i386/bzImage, avant de démarrer le
système en utilisant cette image, il convient de la préparer pour être démarrée par le réseau.
Pour compiler, puis installer les modules, taper :
make modules; make INSTALL_MOD_PATH=/tftpboot/192.168.0.117/flash modules_install
Il est à noter que la valeur de la variable INSTALL_MOD_PATH peut être stockée dans le fichier
/usr/src/linux/Makefile.
1.5.3
Préparation pour démarrage par le réseau
Le programme permettant de préparer un noyau pour son démarrage par le réseau est mknbi,
il doit être installé sur le serveur. Le paquet Red-Hat pour mknbi peut être téléchargé à la même
adresse qu’Etherboot ([1]).
L’invocation de mknbi est suffisamment pénible et fréquente pour qu’il soit avantageux d’en
faire un script :
#!/bin/sh
IMAGE="/usr/src/linux/arch/i386/boot/bzImage"
NBI_IMAGE="/tftpboot/192.168.0.117/zImage"
NFS_ROOT="192.168.0.61:/tftpboot/192.168.0.117/flash"
OTHER_PARAM="ro blkmtd_device=/dev/hda2"
mknbi-linux --rootdir="$NFS_ROOT" --output="$NBI_IMAGE" --ip=bootp \
--append="$OTHER_PARAM" "$IMAGE"
L’option --rootdir permet de fournir au noyau l’emplacement du répertoire racine sur un
système de fichiers distribué. Cet emplacement est donné sous la forme :
<adresse IP du serveur>:<chemin du répertoire sur le serveur>
9
L’option --output permet de spécifier le nom du noyau au format nécessaire à Etherboot.
L’option --ip permet de spécifier la méthode de configuration automatique du réseau (on ne
peut pas compter sur les scripts de démarrage pour configurer le réseau, nécessaire bien plus tôt
lors du démarrage).
L’option --append permet d’ajouter des paramètres au noyau qui ne sont pas connus directement par mknbi. Ici les paramètres passés sont ro, qui permet de demander le montage du
répertoire racine en lecture seul, et blkmtd_device=/dev/hda2, qui permet de spécifier quelle
partition IDE (la deuxième partition du premier disque IDE, en l’occurrence) sera utilisée comme
disque flash à travers la couche d’émulation.
Pour une description plus détaillée des options de mknbi-linux, se reporter à sa page de manuel.
Après exécution de ce script, pour vérifier que l’image générée est correcte, redémarrer la cible.
La cible doit arriver à charger le noyau par tftp, commencer le démarrage, puis s’arrêter au moment
de monter le répertoire racine par le réseau.
1.6
Configuration du serveur NFS
Pour installer le serveur NFS, installer le paquet Red-Hat nfs-utils. Le fichier de configuration
du serveur NFS est /etc/exports. Pour que la cible puisse monter son répertoire racine, ce fichier
doit contenir la ligne :
/tftpboot/192.168.0.117 192.168.0.117(rw,no_root_squash)
Les options rw et no_root_squash permettent à toute personne qui utilise ce répertoire à travers
le réseau d’effectuer n’importe quelle opération.
Après la modification du fichier /etc/exports, il faut démarrer le serveur NFS en tapant :
# /etc/init.d/nfs start
Pour tester le fonctionnement du serveur NFS, redémarrer la cible. Le montage du répertoire
racine doit fonctionner, mais lorsque le noyau essaie d’exécuter le fichier /sbin/init, il doit
échouer.
À ce point, la configuration des serveurs sur la machine hôte est terminée. Le système cible
n’a plus besoin que de quelques fichiers pour être capable de démarrer. C’est ce que nous nous
proposons de faire dans le chapitre suivant.
10
Chapitre 2
Mise en place de l’environnement
d’exécution
Le but de ce chapitre est de mettre en place, en plus du noyau que nous avons compilé dans la
première partie, un ensemble d’éléments supplémentaires afin de permettre le démarrage complet
de la cible, et de simplifier l’ajout d’autres éléments. Ce sera pour nous aussi l’occasion de toucher
du doigt les problèmes liés à l’adaptation d’applications Linux à un environnement embarqué que
nous verrons plus en détails dans le chapitre 3 page 18.
2.1
Structure d’un répertoire racine type
Voici un minimum raisonnable de répertoires que doit contenir le répertoire racine d’un système
Linux :
– /boot : fichiers utilisés pour la création d’un secteur d’amorce ;
– /dev : fichiers spéciaux d’accès aux périphériques ;
– /proc : répertoire de base nécessaire au système de fichiers proc, permettant le contrôle et
l’observation dynamique du noyau ;
– /etc : fichiers de configuration et scripts de démarrage ;
– /sbin, /bin : fichiers exécutables, utilitaires systèmes ;
– /lib : bibliothèques partagées nécessaires à l’exécution des programmes ;
– /mnt : points de montage pour d’autres systèmes de fichiers ou d’autres périphériques ;
– /usr : utilitaires et applications supplémentaires ;
– /tmp : fichiers temporaires ;
– /var : journaux systèmes, fichiers systèmes temporaires.
2.2
Découpage du répertoire racine de la cible
Les fichiers du répertoire racine de la cible suivront le découpage suivant :
– les fichiers spéciaux, qui se résument aux répertoires /proc et /dev, puisque nous avons choisi
l’option d’utiliser devfs lors de la configuration du noyau, ne seront que des points de montage
sur le système cible ;
– les fichiers destinés à être accessibles en lecture seule au cours du fonctionnement nominal
(exécutables, bibliothèques, scripts de démarrage, fichiers de configuration) seront stockés sur
11
le répertoire partagé de l’hôte pendant le développement, puis sur le disque flash du système
cible pour son fonctionnement autonome ;
– les fichiers temporaires (stockés dans les répertoires /tmp et /var) seront stockés sur un
système de fichiers TMPFS, afin d’éviter des cycles d’écriture fréquents sur la mémoire flash,
il est à noter que l’on devra sauver périodiquement les journaux systèmes sur le disque flash
si l’on veut en disposer pour déboguer des crash du système ;
– le répertoire /boot (en fait seulement l’image du noyau) ne peut être stocké dans un système
de fichiers JFFS2, il nécessitera donc la création d’une partition supplémentaire (utilisant le
système de fichiers EXT2) sur le disque flash.
2.3
Contenu du répertoire partagé sur le système hôte
Sur le système hôte, le répertoire partagé (/tftpboot/192.168.0.117) contiendra donc :
– le noyau Linux préparé pour le démarrage par Etherboot : zImage ;
– un répertoire flash, contenant l’image du système de fichier racine de la cible ;
– un répertoire boot, correspondant au répertoire du même nom sur le système cible.
Afin que les répertoires /tmp et /var du système cible soient stockés en mémoire vive, ces
répertoires seront des liens symboliques qui ne deviendront valides qu’après l’exécution d’un script
de démarrage qui montera un système de fichier TMPFS.
Dans le répertoire /tftpboot/192.168.0.117, nous lançons donc les commandes suivantes :
#
#
#
#
#
mkdir flash
cd flash
mkdir bin boot dev etc proc lib usr usr/bin usr/lib mnt mnt/tmpfs
ln -s mnt/tmpfs/tmp .
ln -s mnt/tmpfs/var .
Dans la suite de ce chapitre, nous allons peupler le répertoire flash pour permettre le démarrage
complet du système.
2.4
Bibliothèques partagées
La bibliothèque standard du langage C (libc) fournie par une distribution de Linux à usage
général peut sembler un peu imposante. Suivant l’encombrement visé, elle sera embarquée telle
quelle, ou elle sera remplacée par une libc réduite (donc à laquelle il manque potentiellement
certaines fonctionnalités) comme µclibc (voir [4]) ou newlib (voir [5]).
Ici nous conservons la libc fournie par la distribution de Linux de l’hôte, ce qui a pour principal avantage de pouvoir éviter la problématique de la compilation croisée, c’est-à-dire que la
compilation d’une application pour la cible ne nécessite aucune astuce particulière et qu’une application compilée sur l’hôte pour la cible peut être exécutée sur l’hôte. D’autre part, nous pouvons
reprendre toutes les applications déjà compilées de l’hôte pour les embarquer sur la cible.
La libc GNU comprend plusieurs composants sous forme de bibliothèques partagées. Comme
cette bibliothèque est fournie sous forme de bibliothèque partagée, nous devons aussi installer le
chargeur de bibliothèques partagées. Au final, nous embarquons donc les trois fichiers :
/lib/ld-linux.so.2
12
/lib/libc.so.6
/etc/ld.so.conf
Lors de son fonctionnement, la bibliothèque de chargement de bibliothèques partagées utilise le
fichier /etc/ld.so.cache. Ce fichier indexe l’ensemble des bibliothèques partagées stockées dans
les répertoires dont le nom figure dans le fichier /etc/ld.so.conf. Il doit donc être régénéré après
l’ajout ou la suppression de bibliothèques partagées dans l’un de ces répertoires, en tapant :
ldconfig -r /tftpboot/192.168.0.117/flash
2.5
Busybox
Busybox est un programme dont l’utilisation simplifie le peuplement du répertoire racine du
système cible. Il regroupe, à lui seul, les fonctionnalités de beaucoup d’utilitaires GNU, et d’outils
système Linux en un seul exécutable de taille réduite.
Busybox existe sous forme de paquet pour les distributions Red-Hat version 7.2 et Red-Hat
version 7.3. Toutefois, pour une version 7.1 ou pour pouvoir adapter Busybox finement à ses
besoins, il vaut mieux télécharger (voir [3]) ses sources et le compiler.
La configuration de Busybox se trouve dans le fichier Config.h (situé dans le répertoire de
ses sources) , il suffit d’éditer ce fichier pour garder seulement les composants nécessaires. Par
exemple, pour l’utilisation d’un watchdog, décommenter la ligne de Config.h :
//#define BB_WATCHDOG
Ensuite, pour compiler, taper :
# make
Enfin, pour installer Busybox dans le répertoire partagé :
# make PREFIX=/tftpboot/192.168.0.117/flash install
Il est à noter, que la méthode que nous venons d’utiliser pour compiler Busybox le lie à la même
libc que celle du système hôte. Tous les noms de fichiers donnés dans la suite de ce chapitre sont
relatifs au répertoire racine du système cible, i.e. /tftpboot/192.168.0.117/flash.
2.6
Démarrage de la cible
À la fin de la phase d’initialisation du noyau, celui-ci exécute le programme /sbin/init. Busybox contient une version simplifiée du fichier /sbin/init System V, dont le fichier de configuration
est /etc/inittab. La version de /sbin/init de Busybox a un comportement par défaut si ce fichier est absent.
Le fichier /etc/inittab par défaut est le suivant :
13
::sysinit:/etc/init.d/rcS
::askfirst:/bin/sh
::ctrlaltdel:/sbin/reboot
::shutdown:/sbin/swapoff -a
::shutdown:/bin/umount -a -r
::restart:/sbin/init
tty2::askfirst:/bin/sh
tty3::askfirst:/bin/sh
tty4::askfirst:/bin/sh
Une ligne de configuration prend la forme :
<console>::<moment>:<action>
Elle décrit une action qui doit être exécutée à un moment précis. La console permet au système
d’utiliser les consoles virtuelles de Linux, si aucune console n’est spécifié, la première console
(/dev/tty1) est utilisée. Les différents valeurs qui nous intéressent pour le champ <moment> sont :
sysinit permet d’indiquer le nom d’un programme qui sera exécuté à l’initialisation du système,
init attend la fin de son exécution avant d’exécuter quoi que ce soit d’autre, c’est l’emplacement idéal pour lancer les scripts de démarrage ;
respawn permet d’indiquer le nom d’un programme qui sera exécuté après la fin de l’initialisation
puis automatiquement relancé chaque fois qu’il se termine ;
askfirst fonctionne comme respawn à la différence prêt qu’il demande à l’utilisateur d’appuyer
sur entrée avant de provoquer l’action associée.
Le fichier inittab que nous allons utiliser est à peu près le même :
::sysinit:/etc/init.d/rcS start
::askfirst:/bin/sh
::ctrlaltdel:/sbin/reboot
::shutdown:/etc/init.d/rcS stop
::restart:/sbin/init
2.7
Scripts de démarrage
Comme nous venons de le voir, par défaut, le fichier /sbin/init fourni avec Busybox exécute
le script /etc/init.d/rcS. Ici, deux approches peuvent être utilisées pour lancer les démons et
initialiser les différents services dont nous avons besoin :
– lancer tous les démons dans /etc/init.d/rcS,
– créer un script de démarrage par démon à lancer.
Si la première solution a l’avantage d’être simple, la deuxième permet de créer une notion de paquet pour notre système embarqué, et s’adapte plus facilement à l’ajout de nouveaux programmes.
Comme pour une distribution classique, nous allons créer un script par démon ou service qui sera
lancé avec l’option start ou stop suivant que l’on veut le démarrer ou l’arrêter.
En résumé, le fichier /etc/init.d/rcS contient :
#! /bin/sh
for file in /etc/rc$1.d/*
14
do
$file "$1"
done
À chaque démon est associé un script de démarrage /etc/init.d/daemon construit en suivant
le modèle :
#! /bin/sh
DAEMON="/sbin/daemon"
PARAMS=""
case "$1" in
start)
$DAEMON $PARAMS
;;
stop)
kill ‘pidof $DAEMON‘
;;
*)
echo unknown parameter: $1
;;
esac
D’autre part, les répertoires /etc/rcstart.d et /etc/rcstop.d contiennent chacun des liens
symboliques vers les scripts contenus dans /etc/init.d. L’intérêt d’utiliser des liens symboliques
est de permettre de contrôler l’ordre d’exécution des scripts au moment du démarrage en préfixant
leur nom par un numéro d’ordre, mais d’éviter tout désagrément avec ce numéro d’ordre si l’on
veut éditer un script de démarrage ou démarrer ou arrêter un service manuellement. Dans la suite
de cette section les scripts apparaissent dans l’ordre où ils doivent être lancés sur le système cible
(et normalement, l’ordre inverse de celui dans lequel ils doivent être arrêtés).
2.7.1
Démarrage de devfsd
Puisque nous avons choisi d’utiliser devfs, nous devons démarrer le démon devfsd. Le rôle de
devfsd est d’établir les liens entre les fichiers spéciaux du répertoire /dev dans leur ancienne
dénomination (c’est-à-dire dans leur version antérieure à l’apparition de devfsd), et leur nouvelle
dénomination. Cela pourrait sembler inutile, mais la plupart des programmes utilisent les anciens
noms de fichiers spéciaux.
D’autre part, devfsd doit être le premier programme lancé et le dernier programme arrêté afin
d’avoir une chance que les autres programmes puissent être lancés. Le script de lancement de
devfsd commence comme suit :
#! /bin/sh
DAEMON="/sbin/devfsd"
PARAMS="/dev"
...
Pour que devfsd fonctionne correctement, il faut aussi embarquer son fichier de configuration
(/etc/devfsd.conf ou /etc/devfs/devfsd.conf).
15
2.7.2
Montage des systèmes de fichiers
Les systèmes de fichiers qui sont montés au démarrage et leurs points de montage sont stockés
dans le fichier /etc/fstab. Un contenu minimal pour ce fichier et pour le système qui nous
intéresse pourrait être :
proc
tmpfs
/dev/hda1
/dev/hda2
/dev/fd0
/proc
/mnt/tmpfs
/boot
/
/floppy
proc
tmpfs
ext2
jffs2
auto
defaults
size=10m
defaults,noauto
defaults,rw,noauto
defaults,noauto
0
0
0
0
0
0
0
0
0
0
Ce fichier fstab demande le montage d’un système de fichiers TMPFS au démarrage, les entrées
associées aux partitions /dev/hda1 et /dev/hda2 ont été mises en prévision, mais ne peuvent pas
fonctionner tant que le disque flash n’a pas été partitionné et que le répertoire racine est un
répertoire NFS.
Le script de démarrage correspondant est le suivant :
#! /bin/sh
case "$1" in
start)
mount -a
cd /mnt/tmpfs
for dir in \
tmp \
var \
var/log \
var/run \
; do
mkdir $dir
done
;;
stop)
umount -a -r
;;
*)
echo unknown parameter: $1
;;
esac
Ce script crée aussi les sous répertoires du système de fichier TMPFS afin que les liens symboliques /tmp et /var deviennent valides.
2.7.3
Configuration du réseau
Le script de configuration du réseau ne présente aucune particularité, il n’est utile que si la
cible a une autre interface réseau que l’interface configurée par DHCP.
#! /bin/sh
16
IFACE=eth1
IPADDR=192.168.0.117
NETMASK=255.255.255.0
GATEWAY=""
case "$1" in
start)
ifconfig lo 127.0.0.1 up
ifconfig "$IFACE" "$IPADDR" netmask "$NETMASK" up
test -n "$GATEWAY" && route add default gw "$GATEWAY"
;;
stop)
ifconfig "$IFACE" down
ifconfig lo down
;;
*)
echo unknown parameter: $1
;;
esac
2.7.4
Démarrage des journaux système
Les programmes chargés de gérer respectivement le journal système et le journal du noyau sont
/sbin/syslogd et /sbin/klogd. Leurs scripts de démarrage n’ont aucune particularité.
2.7.5
Démarrage du watchdog
Le démon associé au watchdog est le fichier /usr/sbin/watchdog, lancé sans paramètre. Son
script de démarrage est tout à fait habituel.
Après l’écriture de tous ces scripts de démarrage, le système devrait pouvoir enfin démarrer
normalement. Après le démarrage du système, celui-ci demande à l’utilisateur d’appuyer sur la
touche entrée pour activer la console. L’appui sur la touche entrée provoque le démarrage d’un
interpréteur de commandes. L’utilisateur peut alors taper des commandes comme ls ou ps, qui
doivent fonctionner.
À ce point, la cible démarre complètement. Celà devrait nous permettre de tester l’ajout de
nouvelles applications, ou de tester une application spécifique que nous développons. Le travail à
réaliser pour “embarquer” un application dépend des contraintes de cette opération (temps imparti,
performance, taille visée), c’est pourquoi, dans le chapitre suivant, nous essayons de conserver une
approche dans les grandes lignes.
17
Chapitre 3
Développement d’applications
embarquées avec GNU/Linux
Comme nous l’avons mentionné dans l’introduction du présent document, un des intérêts d’utiliser un système d’exploitation généraliste sur une plate-forme embarquée est la compatibilité entre
l’hôte et la cible qui permet d’éviter les problèmes du développement croisé.
Si l’on essaie d’être plus précis, le développement d’applications embarquées comprend trois
problématiques :
– le développement d’applications natives,
– l’adaptation d’applications natives à un environnement embarqué,
– le portage d’une application écrite pour un OS propriétaire.
Ces trois problématiques constituent les sections du présent chapitre.
3.1
Développement d’applications natives
Dans cette section, nous allons décrire l’utilisation des outils de développement standard sous
l’angle des développements embarqués.
3.1.1
Le compilateur GCC
GCC est en même temps le compilateur C et le frontal de toute la suite de compilation. C’està-dire qu’il pilote le pré-processeur, le compilateur, l’assembleur et l’éditeur de liens. Lors de la
compilation d’un programme pour un environnement embarqué, deux types de contraintes peuvent
être rencontrées.
Contrainte mémoire
Il s’agit ici de générer des fichiers exécutables qui occupent peu de place sur la mémoire de
masse de la plate-forme embarquée. Les moyens d’action sont :
Certaines optimisations du code peuvent permettre de générer un code occupant moins d’espace.
18
L’option de GCC qui permet d’optimiser un code pour la taille est l’option -Os. Malheureusement,
il est rare de gagner beaucoup de place par cette méthode.
L’option -g permet d’ajouter aux objets générés des symboles de debug. Cette option peut être
supprimée au cours de la compilation pour générer des exécutables occupant moins de place. La
commande strip permet aussi de supprimer ces symboles d’exécutables et de bibliothèques après
leur compilation.
Contrainte de performance
Il s’agit ici de générer une application qui respecte une certaine contrainte de performance.
Comme pour les contraintes de taille, l’on peut agir sur les options d’optimisation de code
(l’option -O3 de gcc permet de générer du code très optimisé), mais comme pour les contraintes
de taille, l’effet de ces options sont faibles, sauf dans des cas très particuliers.
L’autre option est d’analyser les performances du programme à l’aide de gprof. Afin que gprof
fonctionne, le programme (et tous ses composants) à analyser doit être compilé avec l’option -pg.
À l’exécution d’un programme compilé avec l’option -pg, celui-ci enregistre les temps d’exécution
de chacun de ses blocs de base dans un fichier, que gprof est ensuite capable d’analyser pour
déterminer dans quelle partie du programme le temps CPU est consommé.
3.1.2
Liaison statique vs Liaison dynamique
Le développement d’application embarquées nécessite de minimiser à la fois la mémoire de
masse occupée par les programmes et les bibliothèques embarqués.
Il existe deux types de bibliothèques, qui apportent deux solutions distinctes au problème de
la contrainte mémoire, solutions qui sont applicables dans des cas distincts.
Bibliothèques statiques
Les bibliothèques statiques sont un regroupement de modules (au sens ”unité de compilation”
du langage C) sous forme d’une archive. Lors de l’édition de liens entre cette bibliothèque et le
reste d’une application, les modules qui sont nécessaires au fonctionnement de l’application et
seulement ceux-ci sont inclus dans le binaire résultant.
Lorsqu’une même bibliothèque statique est liée à plusieurs exécutables, certaines parties de la
bibliothèques sont donc incluses dans plusieurs exécutables.
Bibliothèques partagées
Les bibliothèques partagées sont aussi un regroupement de modules, mais contrairement au cas
des bibliothèques statiques, leur contenu n’est jamais inclus dans une application qui les utilise,
les applications générées sont donc de taille inférieure à celles générées avec une édition de liens
statique, mais l’on embarque alors la bibliothèque toute entière.
19
Quelle solution ?
Lorsqu’une bibliothèque est relativement imposante et utilisé par peu d’applications, il vaut
probablement mieux procéder à une édition de liens statique.
L’outil libopt (voir [7]) permet de combiner les avantages des deux types d’édition de liens
en régénérant des bibliothèques partagées ne contenant que ceux de leurs composants qui sont
effectivement utilisés par un ensemble de fichiers exécutables. Malheureusement, l’utilisation de
cet outil induit un coût de développement qui n’est pas forcément rentable.
3.2
Adaptation d’une application native
La problématique est ici d’intégrer dans un système embarqué une application qui n’a pas
été écrite pour. Le problème se réduit en fait à déterminer avec le compromis temps/précision
satisfaisant les dépendances de l’application à embarquer. Suivant l’occupation visée, l’on peut :
– se contenter des dépendances par paquet, i.e. embarquer tous les fichiers (bibliothèques partagées, fichiers exécutables, fichiers de configuration) du paquet (au sens des fichiers .rpm ou
.deb) contenant l’application à embarquer et de tous les paquets dont ce paquet dépend ;
– se contenter des dépendances par fichier, i.e. analyser quels sont exactement les bibliothèques
partagées et les fichiers de configuration dont dépend le fichier exécutable que l’on veut
embarquer ;
– modifier les options de compilation offertes par le compilateur ou l’auteur de l’application à
embarquer afin d’en diminuer la taille ou les dépendances ;
– en dernier ressort, modifier les sources de l’application.
Pour déterminer le contenu d’un paquet d’une distribution et ses dépendances envers d’autres
paquets, utilisez votre gestionnaire de paquets (dpkg pour les distributions Debian et rpm pour les
distributions Red Hat).
L’outil qui permet de déterminer de quelles bibliothèques partagées dépend un fichier exécutable
est ldd. Par exemple :
# ldd /bin/ls
libc.so.6 => /lib/libc.so.6 (0x40019000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
Tous les fichiers qui utilisent des bibliothèques partagées sont liés à la bibliothèque partagée
/lib/ld-linux.so.2, c’est elle qui permet de charger les autres bibliothèques partagées. Mais un
programme peut aussi, grâce aux appels système dlopen, dlsym et dlclose, charger et utiliser
une bibliothèque partagée, sans que celle-ci apparaisse pour autant dans la liste fournie par ldd.
Par exemple :
# ldd /usr/X11R6/bin/XF86_SVGA
libz.so.1 => /usr/lib/libz.so.1 (0x40019000)
libm.so.6 => /lib/libm.so.6 (0x40029000)
libdl.so.2 => /lib/libdl.so.2 (0x40046000)
libc.so.6 => /lib/libc.so.6 (0x4004a000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
Ici, le programme /usr/X11R6/bin/XF86_SVGA utilise les appels système dlopen, dlsym ou
dlclose, car il est lié au fichier libdl.so.2. Pour obtenir une photographie des fichiers chargés
20
dynamiquement à un moment donné par ce programme, il est possible d’utiliser le répertoire /proc.
Par exemple, si le processus 19437 est une instance du programme /usr/X11R6/bin/XF86\_SVGA :
# cat /proc/19437/maps
08048000-082fa000 r-xp 00000000
082fa000-0833a000 rw-p 002b1000
0833a000-08761000 rwxp 00000000
40000000-40012000 r-xp 00000000
40012000-40014000 rw-p 00011000
40019000-40025000 r-xp 00000000
40025000-40028000 rw-p 0000b000
40028000-40029000 rw-p 00000000
40029000-40045000 r-xp 00000000
40045000-40046000 rw-p 0001b000
40046000-40048000 r-xp 00000000
40048000-4004a000 rw-p 00001000
4004a000-4011f000 r-xp 00000000
4011f000-40123000 rw-p 000d4000
40123000-40127000 rw-p 00000000
40127000-4012f000 r-xp 00000000
4012f000-40130000 rw-p 00007000
40130000-401ab000 r-xp 00000000
401ab000-401b0000 rw-p 0007a000
401b0000-40220000 r-xp 00000000
40220000-4022f000 rw-p 0006f000
4022f000-40230000 rw-p 00000000
40230000-40430000 rw-s f4400000
40430000-40440000 rw-s 000a0000
40440000-40840000 rw-s f5000000
40840000-409c1000 rw-p 00000000
bffd9000-c0000000 rwxp fffda000
3.3
03:03
03:03
00:00
03:03
03:03
03:03
03:03
00:00
03:03
03:03
03:03
03:03
03:03
03:03
00:00
03:03
03:03
03:03
03:03
03:03
03:03
00:00
03:03
03:03
03:03
00:00
00:00
209343
209343
0
163204
163204
241582
241582
0
163618
163618
163523
163523
163208
163208
0
164078
164078
483328
483328
483321
483321
0
454556
454556
454556
0
0
/usr/X11R6/bin/XF86_SVGA
/usr/X11R6/bin/XF86_SVGA
/lib/ld-2.1.3.so
/lib/ld-2.1.3.so
/usr/lib/libz.so.1.1.3
/usr/lib/libz.so.1.1.3
/lib/libm-2.1.3.so
/lib/libm-2.1.3.so
/lib/libdl-2.1.3.so
/lib/libdl-2.1.3.so
/lib/libc-2.1.3.so
/lib/libc-2.1.3.so
/lib/libnss_files-2.1.3.so
/lib/libnss_files-2.1.3.so
/usr/X11R6/lib/modules/pex5.so
/usr/X11R6/lib/modules/pex5.so
/usr/X11R6/lib/modules/xie.so
/usr/X11R6/lib/modules/xie.so
/dev/mem
/dev/mem
/dev/mem
Portage d’une application écrite pour un OS propriétaire
Le problème du portage dans le cas général est un problème vaste, dont nous ne prétendons pas
aborder tous les aspects dans la présente section. Nous aimerions cependant présenter quelques
questions que doit se poser qui essaie d’effectuer le portage sous Linux d’une applications écrite
pour un OS propriétaire. Puis nous présenterons un outil qui facilite cette migration.
3.3.1
Temps-réel ?
Certains systèmes d’exploitation dédiés aux applications embarquées sont dits temps-réel ou
déterministes, i.e. adaptés à l’écriture d’applications temps-réel. Linux ne fait pas partie de cette
classe de systèmes d’exploitation, notamment à cause des latences d’interruption qu’il induit.
Mais lors d’un portage, il se peut que le déterminisme en lui-même ne soit pas l’objectif. Dans ce
premier cas, l’on peut effectuer le portage en accédant à l’ensemble des fonctions de l’API POSIX
like de Linux, sachant qu’il est tout de même possible de réduire les latences d’interruption de
Linux à l’aide d’un patch du noyau.
21
Si le comportement déterministe est une absolue nécessité, il est possible d’utiliser RTAI (voir
[8]). RTAI est un ordonnanceur temps-réel dont Linux est la tâche de fond. L’utilisation de RTAI
induit une séparation claire entre les éléments temps-réel (qui utilisent l’API de RTAI) et les
éléments qui ne le sont pas (qui utilisent l’API de Linux). Cette architecture a l’avantage de
permettre l’utilisation sans modification des applications Linux dans un système temps-réel, mais
l’inconvénient que les applications temps-réel, exécutées en mode noyau, ne peuvent bénéficier de
la richesse et de la facilité de debug d’un environnement POSIX like.
3.3.2
Portage de premier ordre
Pour un portage de premier ordre, l’architecture de l’application à porter sera conservée, en
faisant correspondre une tâche de l’application d’origine à un processus ou un thread POSIX sous
Linux, en sacrifiant le déterminisme et potentiellement le comportement de l’OS d’origine.
Dans le cas où l’on choisit d’utiliser les processi Linux, les appels système de communication
entre tâches de l’OS d’origine doivent être remplacés par les appels système de communication
inter-processi de Linux. Le choix est alors très large, l’on peut utiliser au choix, les “pipe”, les
“socket” ou les appels système System V.
Dans le cas où l’on choisit d’utiliser les threads POSIX (plus légers que les processi en termes
de temps de création et de vitesse de changement de contexte) les appels système disponibles et
conseillés sont ceux de l’API des thread POSIX.
3.3.3
Xenomai
Que l’on veuille faire un portage du premier ordre en utilisant les thread POSIX, ou utiliser
l’API temps-réel de RTAI, le problème reste de remplacer tous les appels système de l’OS d’origine,
par des appels système ressemblant mais qui n’ont pas forcément le même comportement.
Xenomai est un outil qui permet de se libérer de ce problème. Xenomai est basé sur un nanokernel qui implémente le comportement d’un noyau temps-réel générique en utilisant au choix RTAI,
les threads POSIX ou un simulateur permettant un débogage graphique. Ce nanokernel permet
de reconstruire une version émulée des appels système d’OS temps-réel à partir des services de
base du nanokernel. Xenomai est fourni avec les émulateurs de certains OS temps-réel propriétaires populaires, sachant que l’écriture d’un nouvel émulateur ou API temps-réel est relativement
simple.
Pour plus de détails sur Xenomai, consulter [9].
3.4
Conclusion
Nous avons essayé, dans ce chapitre, de donner des pistes quant aux méthodes à suivre et aux
outils à utiliser pour mener efficacement à bien le développement d’une application embarquée. Il
est difficile d’être plus précis sans entrer dans les détails de telle ou telle application.
À ce point, nous supposons que vous avez pu utiliser le système de fichiers partagé mis en place
lors des deux premiers chapitres pour développer une application embarquée, comprenant éventuellement un module temps-réel. Nous supposons que vous avez testé, optimisé cette application
ou son occupation disque.
22
Dans le chapitre suivant, nous allons enfin créer les systèmes de fichiers sur la mémoire de masse
du système cible, afin de pouvoir le tester en vraie grandeur.
23
Chapitre 4
Finalisation du système
4.1
Partitionnement du disque flash
Nous allons utiliser le système de fichiers JFFS2 pour le répertoire racine. D’autre part, comme
le système de fichiers JFFS2 est compressé, nous ne pouvons pas stocker le noyau sur le système
de fichiers racine. Nous allons donc créer deux partitions sur le disque flash : une petite partition
utilisant le système de fichiers EXT2 pour stocker le noyau et une partition utilisant le système
de fichiers JFFS2 pour stocker le répertoire racine.
Sur la cible, utiliser cfdisk ou fdisk pour partitionner le disque compact flash.
4.2
4.2.1
Copie des fichiers sur le disque flash
Système de fichiers racine
Un système de fichiers JFFS2 est créé à partir d’un répertoire en copiant le contenu de ce
répertoire dans le système de fichiers. Malheureusement, le répertoire ne peut être le répertoire
racine de la cible, car il comprend, en plus des fichiers qui seront présents sur le disque compact
flash, tous les fichiers qui sont sous des points de montage, comme /proc et /dev. Nous devons
donc créer l’image du système de fichiers JFFS2 sur l’hôte, puis le copier sur le disque compact
flash.
La création, sur l’hôte, de l’image du système de fichiers JFFS2, s’effectue à l’aide du programme
mkfs.jffs2. Une version exécutable de mkfs.jffs2 peut être téléchargée à l’adresse [6].
# cd /tftpboot/192.168.0.117
# mkfs.jffs2 -o flash.img -r flash
# mv flash.img flash
Sur la cible, cette image doit être copiée sur la deuxième partition du disque compact flash, en
tapant :
# dd if=/flash.img of=/dev/mtdblock/0
Le processus peut prendre un peu de temps. Ensuite, pour monter l’image JFFS2, taper :
24
# mount -t jffs2 /dev/mtdblock/0 /mnt/flash
Là encore, le processus peut prendre plusieurs minutes. En outre, un certain nombre de messages
d’erreur est affiché, car l’image JFFS2 que nous avons faite n’a pas la même taille que le disque.
4.2.2
Système de fichiers d’amorçage
Le répertoire /tftpboot/192.168.0.117/boot de la machine hôte doit contenir les fichiers
nécessaires à un démarrage avec LILO, pour cela, taper :
# cd /tftpboot/192.168.0.117/boot
# cp /boot/boot.b .
# cp /usr/src/linux/arch/i386/boot/bzImage .
Sur la machine cible, ce répertoire doit être monté, en tapant :
# mount -t nfs 192.168.0.61:/tftpboot/192.168.0.117/boot /boot
Sur la machine cible, pour formater, monter la partition /boot et copier les fichiers, taper :
# mke2fs /dev/hda1
# mount -t ext2 /dev/hda1 /mnt/flash/boot
# cp /boot/* /mnt/flash/boot/
Enfin, pour créer le secteur de démarrage, taper :
# lilo -r /mnt/flash
Pour tester, redémarrer la cible sans disquette Etherboot. Le système embarqué fonctionne.
25
Bibliographie
[1] http://freshmeat.net/projects/etherboot
[2] http://rom-o-matic.net
[3] http://freshmeat.net/projects/busybox
[4] http://freshmeat.net/projects/uclibc
[5] http://sources.redhat.com/newlib
[6] http://sources.redhat.com/jffs2
[7] http://sourceforge.net/projects/libraryopt/
[8] http://www.aero.polimi.it/~rtai/
[9] http://www.xenomai.org/
[10] http://freshmeat.net/projects/e2fsprogs/
26