Bien que la capacité des unités de stockage à base de mémoire flash devienne de plus en plus importante, cette contrainte d’occupation n’est pas la seule pour une application embarquée. Certes le mini-itx, ou mieux, le nano-itx, est à portée de main... mais à quel prix ? Confectionner une distribution adaptée s’avère de premier ordre pour tout autre architecture que le x86 des systèmes "standards".
Sommaire
La démarche qui suit est celle qui a été mise en place lors d’un projet de distribution Linux sur disquette pour ordinateur x86. Ainsi, vous ne verrez pas d’instructions détaillées pour certains processeurs conçus pour l’embarqué. Cependant, si le contenu de cet article constitue pour vous une première approche à ce genre de projet, vous saurez rapidement utiliser les outils proposés pour arriver assez facilement au même résultat pour un autre système.
Les outils
Au minimum :
- un ordinateur sous Linux
- une connection à internet à un moment
Pour cet article :
- le code source du kernel (version au choix)
- les patchs lzma pour le kernel et le rootfs (voir au bas de cette page)
- buildroot
Du début
Du moment où l’on appuie sur l’interrupteur qui met en marche l’ordinateur à celui où les programmes conçus entrent en exécution, les éléments mis en jeu pourraient, très grossièrement, être résumés à : bootstrapping (amorçage) du bios, bootloader de second niveau, chargement du noyau, initrd, rootfs... Pour notre application, nous devrons ainsi fournir : un chargeur de système, en l’occurence syslinux, le noyau compilé compressé, et une arborescence (rootfs) compressée. À la fin de ce processus de chargement, le root file system sera en mémoire, et le système utilisable.
La préoccupation première du projet proposé dans cette article sera celle de l’occupation mémoire de tout le système. À cette effet, les stratégie suivantes seronts adoptées :
- compression de l’asborescence et du noyau avec lzma
- utilisation d’uClibc
- utilisation de Busybox
- instructions d’optimisation du compilateur (-O3)
Glibc et uClibc
La librairie Glibc est certes évoluée, mais peut devenir beaucoup trop lourde pour Linux embarqué. Nous choisirons alors plutôt de compiler tous nos programmes à l’aide d’uClibc. Ce projet étant utilisé pour uClinux - linux adapté pour les processeurs dépourvus de MMU - il est tout indiqué pour ce genre d’application. À titre comparatif, prenons le programme suivant :
Pour compter le nombre d’octets :
La compilation avec Glibc produira un exécutable lié dynamiquement de 11075 octets (11049 avec -O3) alors qu’avec uClibc, on en comptera plutôt 4353. Les résultats en compilation statique sont les suivants :
On obtient donc un binaire de quelques 483K octets avec la Glibc et environ 26K pour uClibc. L’option d’optimisation -O3 de gcc avec Glibc s’avère totalement inutile pour cet exemple puisqu’on on obtiendra un exécutable de 482515 octets : différence de 4 octets par rapport à l’autre. Les paramètres d’optimisation de gcc avec la glibc produiront des résultats variables selon le code compilé. Mentionnons finalement que cet exemple simpliste n’est le meilleur pour faire voir les différences apportées à la taille des exécutables compilés avec -O3. Cependant, il apparaît clair qu’uClibc est à préféré pour notre usage.
Buildroot
Puisque la mise en place d’une chaîne de compilation croisée est une opération fastidieuse [1], nous nous en remettrons à Buildroot. Cet outil est basé sur un ensemble de Makefile qui permettent le téléchargement automatique des sources, la compilation, et la mise en place du rootfs dans le format du système de fichier retenu. Également, plusieurs architectures peuvent être choisies pour le projet développé, et la mise en place de la chaîne compilation basée sur uClibc sera prise en charge par Buildroot.
Busybox
Busybox est outil conçu par les mêmes développeurs d’uClibc et Buildroot. Ce projet permet la mise en place d’un ensemble de programmes communs à tout système Unix (coreutils) avec une occupation disque minimale. Busybox est en fait un binaire qui comprend à lui seul l’ensemble de ces commandes. Il suffit par la suite de mettre en place des liens durs ou symboliques vers Busybox pour toutes les commandes supportées. Bien que les utilitaires de Busybox comprennent généralement moins d’options que dans leur version originale, ils suffiront dans la plupart des cas à une application embarquée. Par son architecture modulaire, il est possible de choisir les programmes compilés dans l’exécutable lors de sa configuration. Busybox ne s’en tient pas non plus aux programmes les plus élémentaires : un serveur http est disponible par exemple.
Mise en place
Téléchargez buildroot à cette adresse : http://buildroot.uclibc.org/download.html
ou par svn :
et décompressez l’archive au besoin (dans /tmp par exemple) :
Une fois dans le répertoire créé, lancez :
Cette commande, identique à celle utilisée lors de la compilation du kernel, fera apparaître un menu ncurses dans lequel nous allons personnaliser la configuration du système.
L’option « Target Architecture », l’architecture générale, fait apparaître l’ensemble des architectures supportées. Nous choisirons i386 (appuyer sur la barre d’espacement pour sélectionner).
« Target Architecture Variant » sera défini à 486 puisque cette distribution sera destinée à un ordinateur de cette génération. Choisissez celle qui convient à l’ordinateur cible.
« Build options » ne devrait pas être de premier intérêt. La configuration par défaut conviendra dans la plupart des cas.
Portez attention cependant à la section « Toolchain Options ».
Dans « Kernel Headers », il faudra choisir les entêtes qui correspondent au kernel utilisé, ou à défaut, ceux qui s’en approchent le plus.
« Package Selection for the target » fait apparaître une liste d’applications communément utilisées sur tout système GNU/Linux. Cependant, busybox suffira à nos besoins. Sélectionnez donc que cette option.
« Target Options » fait voir les options relatives à l’image disque générée pour le rootfs. Nous choisirons ext2. Il faut mentionner que ce choix n’est pas optimal en ce qui a trait à l’occupation mémoire. Cependant, il se justifie dans ce contexte par sa souplesse de mise en place et l’existence d’un outil de créations d’images formattées dans l’espace d’utilisateur (userspace) : en l’occurence, genext2fs. Minixfs pourrait aussi être utilisé pour obtenir un système de fichiers compact, mais l’outil mfstool, l’équivalent de genext2fs pour Minixfs, est incomplet et son développement interrompu. Après avoir contacté l’auteur, Alejandro Liu, voici ce qu’il répondait au sujet de l’état du projet :
Liu recommande plutôt d’utiliser la fonctionalité initramfs de la branche 2.6 du kernel pour arriver à un résultat similaire. Le fichier situé en Documentation/filesystems/ramfs-rootfs-initramfs.txt donne plus de précisions sur ce point. La différence principale entre rootfs et initramfs réside dans le fait que le premier est un fichier formatté, dont le support aura dû être compilé dans le kernel préalablement, et séparé du noyau, alors que l’autre n’est qu’une archive cpio compressée avec gzip liée au noyau lors de la compilation. Aussi, faut-il spécifier qu’il s’agit d’une archive cpio et non tar qui est utilisée pour des raisons détaillées dans ce fichier d’aide. Pour cet article, nous nous en tiendrons à rootfs.
Vous avez peut-être aussi remarqué sur la capture d’écran qu’on peut déjà choisir le mode de compression lzma. Cependant, cette apparition au menu relève d’un ajout effectué manuellement donc nous verrons comment plus loin. Pour l’instant, choisissez la compression gzip.
Avant d’aller plus loin dans la configuration du système, commençons par télécharger le code source et procédons à sa compilation. Quittez donc le menu et enregistrez la nouvelle configuration. Ensuite, exécutez :
et occupez-vous ailleurs car ce processus devrait être assez long. Une fois terminé, vous vous retrouverez devant une arborescence du répertoire buildroot/ quelque peu changée. En effet, les répertoires build_arch et toolchain_build_arch seront ajoutés. De plus, le fichier rootfs.i486.ext2.gz aura été généré : c’est l’image du rootfs dans le système de fichiers choisi.
Lzma
Comme indiqué précédemment, nous souhaitons avoir le rootfs compressé. LZMA est un algorithme de compression du programme 7-zip qui permet d’obtenir un rendement supérieur à gzip ou bzip2. En appliquant le patch approprié sur le noyau, il est possible de l’utiliser pour vmlinuz. Certes il existe aussi UPX qui puisse s’appliquer sur le noyau mais son adoption ne semble pas généralisée et son efficacité inférieure.
Téléchargez l’archive compressée http://www.7-zip.org/sdk.html Dans un répertoire quelconque (par exemple /tmp) :
Si la compilation se complète sans erreurs, vous pourrez ensuite faire :
Pour ajoutez l’option lzma au menu comme mentioné plus-haut, déplacez-vous dans le répertoire target/ext2 et ajoutez au fichier Config.in :
Maintenant, cette option devrait apparaître au menu. Pour spécifier l’action à exécuter si cette compression a été choisi, ajoutez au fichier ext2root.mk :
Prenez note ici qu’utilisant l’utilitaire Makefile, il est nécessaire de respecter l’indentation pour ces directives.
Vous pourrez maintenant relancer make près avoir choisi l’option lzma ajoutée au menu :
Il peut arriver parfois que modifications effectuées dans la configuration ne soient pas prises en compte. Dans ce cas, il faudra supprimer manuellement les fichiers rootfs.i486.ext2 ou rootfs.i486.ext2.lz.
Configuration de busybox
Après avoir effectué une première compilation, un répertoire nommé build_ARCH/busybox/ a été créé, où ARCH correspond évidemment à l’architecture choisie précédemment dans le menu de configuration de buildroot. De la même manière que nous avons configuré buildroot, Busybox offre également un utiltaire de configuration avec ncurses. Lancez make menuconfig pour faire apparaître le menu. Il n’y pas d’instructions spéciale à appliquer pour cette configuration si ce n’est que de choisir si vous souhaitez que Busybox soit compilé statiquement ou dynamiquement dans Busybox Settings -→ Build Options.
Une fois la configuration terminée, copiez le fichier .config du répertoire courant (build_ARCH/busybox) vers
Configuration d'uClibc
Mêmes si quelques paramètres généraux ont été passés lors de la configuration de Buildroot pour la mise en place de la chaîne de compilation, uClibc n’a pas encore été configuré. Sa configuration devra se faire par les menus de l’interface obtenue par
Compilation des programmes avec la chaîne de cross-compiling
La chaîne de compilation croisée produite par Buildroot est située maintenant en
Il sera dès lors possible de compiler toute application d’une telle manière :
Pour s’assurer si un programme a bel et bien été compilé avec les librairies d’uClibc, ldd sera utilisé :
Ce message est alors bon signe. Cependant, si la compilation a été effectuée avec la glibc, vous verrez par exemple :
Il est à considérer d’ajouter à la variable d’environnement PATH le chemin vers le répertoire contenant les outils de compilation de la chaîne pour faciliter l’écriture dans le terminal. Vous pouvez par exemple ajouter ceci à votre .bashrc
où ARCH correspond toujours à l’architecture choisie pour le projet. Il suffira maintenant d’appeler i486-linux-gcc sans avoir à spécifier le chemin pour le rejoindre.
Pour tester le rootfs produit, vous pouvez procéder ainsi :
À présent, notre programme d’exemple qui ne pouvait être exécuté le sera. N’oubliez pas de quitter l’environnement en chroot par et de démonter l’image :
Compilation du noyau
Téléchargez d’abord les sources du noyau dans un répertoire quelconque (/tmp par exemple) et procédez au make menuconfig habituel. Pour le noyau 2.6.14.4, voici les quelques options importantes.
- Code maturity level options -→
- Prompt for development and/or incomplete code/drivers
- General setup -→
- Kernel .config support
- Enable access to .config through /proc/config.gz Cette option aura pour effet de rendre disponible la configuration du kernel en /proc/config.gz. Ceci peut s’avérer particulière pratique si vous perdez [2] votre configuration et n’avez que le noyau résultant sur un système sur disquette, cdrom, en flash, etc.
- Configure standard kernel features (for small systems) -→
- Load all symbols for debugging/kksymoops
- Enable support for printk
- BUG() support
- Enable full-sized data structures for core
- Enable futex support
- Enable eventpoll support
- Optimize for size
- Use full shmem filesystem
- Sysctl support
- Kernel .config support
- Loadable module support -→
- Automatic kernel module loading
- Processor type and features -→
- Subarchitecture Type (PC-compatible) -→ À définir à PC-compatible le cas échéant.
- Processor family (486) -→ À définir selon la famille du processeur utilisée. Ici, compilé pour i486. Si le système utilisé est de la famille x86, alors vous pouvez choisir
- Generic x86 support
- Math emulation est à cocher si le processeur du système cible ne possède pas de coprocesseur mathématique.
- High Memory Support (off) -→ sera probablement à défini à off
- Power management options (ACPI, APM) -→ Pour un ordinateur « standard », il vaudra mieux ne pas négliger ce point au risque d’endommager l’équipement en raison de la surchauffe.
- Bus options (PCI, PCMCIA, EISA, MCA, ISA) -→ À configurer si applicable.
- Executable file formats -→
- Kernel support for ELF binaries
- Kernel compression format (Compress kernel image using lzma (EXPERIMENTAL)) -→
- Compress kernel image using lzma (EXPERIMENTAL) Vous verrez apparaître cette option de compression seulement après avoir appliqué le patch à l’aide de la procédure détaillée dans cet article.
- Networking -→ À configurer si applicable.
- Device Drivers -→
À configurer selon vos besoins. Garder le nécessaire.
- Block devices -→
- Normal floppy disk support Puisque nous embarquerons le système sur disquette dans cet article.
- Loopback device support
- RAM disk support
- Lzma compressed ramdisk support (EXPERIMENTAL) Vous verrez apparaître cette option de compression seulement après avoir appliqué le patch à l’aide de la procédure détaillée dans cet article.
- Initial RAM disk (initrd) support
- Block devices -→
- File systems -→
Vous devez ici obligatoirement compiler le support pour le système de fichiers du rootfs généré avec buildroot. Dans notre cas :
- Second extended fs support
- Pseudo filesystems -→
- /proc file system support
Une fois la configuration sauvegardée, vous pouvez exécuter
Appliquer le patch de compression LZMA
Récupérez les fichiers lzma-2.6-initrd.patch et lzma-2.6-kernelimage.patch attachés au bas de cette page. Ces patchs ont été corrigés pour la version 2.6.14.4. du kernel par l’auteur de cet article, mais ont été diffusés par Christian Leber. Étant dans à la racine du répertoire contenant les sources, pour appliquer le patch :
Placer le tout sur disquette
Ayant maintenant un noyau compilé et un rootfs minimal, nous avons tous les éléments nécessaires à la mise en place du système sur disquette. Commençons par créer une image dos qui servira à Syslinux :
Sur une seule
Si le noyau compilé est assez compact pour partager l’espace de la disquette avec le rootfs, alors vous pouvez l’ajouter à l’image :
Sur deux disquettes
Si votre noyau ou le rootfs occupent trop d’espace et ne peuvent être placés sur la même disquette, il est possible de les séparer sur deux supports mémoire différents. Ainsi, le noyau sera chargé jusqu’à ce que le rootfs ait à être monté. La première disquette sera créée de la même manière qu’effectué plus haut, à la seule différence qu’on devra donner une configuration différente :
Notez que cette configuration contient des éléments optionels qui permettent d’afficher des menus au démarrage à partir de touches F1 et F2 et de faire apparaître le texte contenu dans les fichiers f1.txt et message.txt respectivement. Si vous ne voulez de cette fonctionalité, il suffira de supprimer quelques lignes pour obtenir :
Il importe surtout de remarquer la différence, par rapport à la configuration « monodisquette », au paramètre root donné à APPEND : on spécifie ainsi que le rootfs est situé en /dev/fd0 (donc sur une autre disquette) plutôt qu’en /dev/ram0. Reférez-vous à la documentation de syslinux pour plus de détails sur sa configuration [3] .
Après avoir démonté le premier disque de /mnt/floppy
Même si les disquettes utilisées sont neuves [4], il vaudra mieux lancer
À présent, vous devriez pouvoir démarrer sur la première disquette. Le noyau une fois chargé affichera à l’écran une indication pour insérer la seconde.
Pour aller plus loin
Cet article délaisse certains aspects auxquels vous aurez à faire face dans un projet plus complexe. Par exemple, il devient rapidement avantageux d’ajouter au menu de Buildroot un programme à compiler ne faisant pas partie de l’ensemble déjà proposé. Pour se faire, il faudra fournir des fichiers Makefile en respectant l’arborescence adoptée par le projet. Aussi, il est évident que ext2 ne serait pas adapté pour un système vraiment embarqué placé en flash : gumstix, WRT54G, nslu2, soekris par exemple. Il faudrait alors opter pour cramfs, jffs2, squashfs, etc. Dans tous les cas, on devra tenir en compte des caractéristiques du matériel utilisé : c’est pourquoi il est difficile de procéder à l’élaboration d’instructions génériques.
Portez attention aux produits à votre portée [5] dans les commerces (routeurs, petits serveurs domestiques, etc.), car vous y trouverez peut-être ce qu’il vous faut pour entâmer votre prochain projet de robotique !
Voir en ligne
Documents joints
-
Fichiers de configuration (BZip - 13.3 ko)Buildoot, Busybox, uClibc, 2.6.14.4
-
Les patchs pour lzma et le kernel 2.6 (BZip - 11 ko)rootfs et noyau compressés avec lzma
Notes
[1] Voir Cuisiner sa chaîne de compilation croisée
[2] Ceci s’est produit alors que le disque dur de mon ordinateur portable a cessé de fonctionner sans prévenir : plusieurs jours de travail et de recherche étaient alors à l’eau.
[3] Voir http://syslinux.zytor.com/
[4] Si on peut encore les trouver neuves !





















Répondre à l'auteur: