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