Accueil du site > Linux > Créer une distribution Linux pour l’embarqué

Créer une distribution Linux pour l’embarqué

dimanche 24 décembre 2006, par Pierre-Luc Bacon

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

  Du début  
  Mise en place  
  Configuration de busybox  
  Configuration d'uClibc  
  Compilation des programmes avec la chaîne de cross-compiling  
  Compilation du noyau  
  Appliquer le patch de compression LZMA  
  Placer le tout sur disquette  
  Pour aller plus loin  

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 :

  1. compression de l’asborescence et du noyau avec lzma
  2. utilisation d’uClibc
  3. utilisation de Busybox
  4. 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 :

$wc helloworld -c

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 :

svn co svn ://uclibc.org/trunk/buildroot

et décompressez l’archive au besoin (dans /tmp par exemple) :

tar xjf buildroot-20061115.tar.bz2

Une fois dans le répertoire créé, lancez :

$ make menuconfig

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.

PNG - 39.6 ko
Écran principal

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).

PNG - 46.9 ko
Target Architecture

« 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.

PNG - 45.4 ko
Target Architecture Variant

« Build options » ne devrait pas être de premier intérêt. La configuration par défaut conviendra dans la plupart des cas.

PNG - 46.6 ko
Build options

Portez attention cependant à la section « Toolchain Options ».

PNG - 69.9 ko
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.

PNG - 62.3 ko
Kernel Headers

« 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.

PNG - 52.3 ko
Package Selection for the target

« 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 :

The project is curently frozen. I originally intended this to be used like genext2fs for generating Minix type images for init ramdisk for booting Linux. Using Minix was considered a good idea as it was a small file system. However, with Linux 2.6, creating a cpio image is easier and makes up for a even smaller file system.

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.

PNG - 55.4 ko
Target Options

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 :

$make

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 :

su -c "cp lzma /bin"

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 :

$make

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.

PNG - 36 ko
Busybox : Build Options

Une fois la configuration terminée, copiez le fichier .config du répertoire courant (build_ARCH/busybox) vers

package/busybox/busybox.config
. Si cette étape est oubliée, les modifications ne seronts pas appliquées lors de la prochaine compilation.

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

make menuconfig
dans
toolchain_build_ARCH/uClibc/
. Le fichier .config généré devra par la suite être copié dans
toolchain/uClibc/uClibc.config
. Notez que la configuration de base établie lors de la première compilation devrait convenir dans la plupart des cas. Un fichier de configuration est donné en exemple au bas de cette page.

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

build_ARCH/staging_dir/bin/
où se retrouvent toutes les commandes relatives à gcc.
PNG - 56.6 ko
Chaîne de compilation produite

Il sera dès lors possible de compiler toute application d’une telle manière :

i486-linux-uclibc-gcc codesource.c -o programme
Cependant, puisque n’est pas spécifié l’option -static, le programme résultant sera lié dynamiquement aux librairies d’uClibc. De ce fait, vous ne devez pas vous attendre à pouvoir lancer l’exécutable sur votre système :

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

export PATH=$PATH :/tmp/buildroot/build_ARCH/staging_dir/bin/

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
  • 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
  • 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

$make bzImage
à la racine du répertoire contenant les sources du noyau.

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 :

$patch -p1 < /chemin/vers/le/patch/lzma-2.6-kernelimage.patch
et
$patch -p1 < /chemin/vers/le/patch/lzma-2.6-initrd.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 :

/sbin/mkfs.msdos disk.img
Par la suite :
$syslinux disk.img $mount disk.img /mnt/floppy -o loop
L’image ainsi créée sera accessible sur le point de montage /mnt/floppy (à ajouter si nécessaire).

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 :

$cp /tmp/linux-2.6.14.4/arch/i386/boot/bzImage /mnt/floppy/linux
Idem pour le rootfs :
$cp /tmp/buildroot/rootfs.i486.ext2.lz /mnt/floppy/root.img
Ajoutez maintenant cette ligne à syslinux.cfg dans /mnt/floppy :
APPEND load_ramdisk=1 initrd=root.img root=/dev/ram0 rw

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

umount /mnt/floppy
vous pouvez le copier par
dd if=disk.img of=/dev/fd0
Le rootfs compressé sera placé sur la deuxième disquette de la même manière
dd if=rootfs.i486.ext2.lz of=/dev/fd0

Même si les disquettes utilisées sont neuves [4], il vaudra mieux lancer

fdformat /dev/fd0
avant de copier quelque fichier : on élimine ainsi une possibilité lors du débuggage.

À 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

Linux on a Floppy v2-pre2

Buildroot - Usage and documentation

Portfolio

Écran principal Target Architecture Target Architecture Variant Build options Toolchain Options Kernel Headers Package Selection for the target Target Options Busybox : Build Options Chaîne de compilation produite

Documents joints

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 !

[5] Voir COTS

Enregistrer au format PDF
Marquer cet article: Delicious Technorati

Répondre à cet articleRépondre à l'auteur:Pierre-Luc BaconRecommander à un ami

2 Messages de forum