Logiciels Libres et Systèmes Embarqués


6.4. Système minimal

Nous allons voir dans cette section quel doit être le système minimal pour que le noyau Linux fonctionne. Tout d'abord, il faut un système de fichiers racine, celui-ci pouvant être stocké soit en mémoire vive (RAMDisk) soit sur un support physique (par exemple CompactFlash).

6.4.1. Le système de fichiers racine

Pour cette démonstration, je vais prendre comme exemple la création d'un système de fichier racine dans un RAMDisk :

bash# dd if=/dev/zero of=ramdisk bs=1k count=1024
bash# mke2fs -F -m 0 ramdisk
bash# mkdir /mnt/ramdisk
bash# mount -o loop ramdisk /mnt/ramdisk

La première commande crée un fichier creux[19] de 1 Mo, et la seconde le formate sans réserver d'espace pour le super utilisateur (l'option -m 0). Enfin, le système de fichier est attaché au point de montage /mnt/ramdisk, auquel on accéder maintenant comme un disque traditionnel. Si jamais le système hôte (où sont exécuté ces commandes) ne supporte ni le formatage, ni le montage d'un fichier n'étant pas un fichier spécial de périphérique, il existe une autre méthode expliquée dans le fichier Documentation/ramdisk.txt disponible dans l'arborescence des sources du noyau Linux.

6.4.2. Le programme "init"

Lorsque le noyau est complètement initialisé, il exécute la première commande qui fonctionne, parmi les suivantes :

  1. la commande passée avec le paramètre init=
  2. /sbin/init
  3. /etc/init
  4. /bin/init
  5. /bin/sh

Il faut savoir qu'init est le seul processus utilisateur lancé explicitement par le noyau, il est donc le père de tous les processus utilisateurs. Dans la plupart des cas, son rôle est de créer des processus en fonction du script /etc/inittab. Ce dernier précise quels sont les programmes à lancer au démarrage, ainsi que les consoles à ouvrir pour la connexion des utilisateurs. Dans cette démonstration, l'init est très simple :

#include <unistd.h>    // read(), write()
#include <fcntl.h>     // open()

#define BUF_SIZE 1024

int main(void)
{
        int ret = 0, size = 0, fd = 0;
        unsigned char buf[BUF_SIZE];

        ret = mount("proc", "/proc", "proc", 0, NULL);
        if( -1 == ret )
                goto end;

        fd = open("/proc/meminfo", O_RDONLY);
        if( -1 == fd )
                goto end;

        while( (size = read(fd, buf, BUF_SIZE)) > 0 ) {
                size = write(1, buf, size);
                if( BUF_SIZE != size )
                        break;
        }

 end:
        while(1);
}

Cela peut paraître étrange d'utiliser son propre programme à la place d'un vrai init, mais dans le cas où l'exécutable que l'on doit embarquer n'a besoin que d'une pile TCP/IP, d'une gestion du multi-process et d'une gestion des fichiers, ce système fait parfaitement l'affaire ! Il faut savoir que ce genre d'application est très courant dans l'embarqué, par exemple pour l'acquisition et le traitement des données, et que trop souvent les distributions existantes sont disproportionnées.

6.4.3. Mise en place

Revenons-en à ce système minimal. Si on copie l'exécutable à la place de /sbin/init, rien ne sera affiché. En effet, lorsqu'aucune console virtuelle n'est configurée, la sortie standard se fait sur /dev/console. Il faut donc créer ce fichier spécial :

bash# powerpc-linux-uclibc-gcc -static init.c -o init
bash# powerpc-linux-uclibc-strip -s init
bash# mkdir /mnt/ramdisk/sbin
bash# cp init /mnt/ramdisk/sbin
bash# mkdir /mnt/ramdisk/dev
bash# mknod /mnt/ramdisk/dev/console c 5 1
bash# mkdir /mnt/ramdisk/proc

Je reviendrai sur l'usage de l'option -static à la section 6.5.1.3. En ce qui concerne la commande strip, cette dernière permet d'enlever les symboles inutiles d'un binaire. Cela permet de réduire la taille d'un exécutable, et dans notre cas nous gagnons 20% ! Un dernier mot au sujet de l'affichage sur la console : il ne faut pas oublier de passer en paramètre au noyau quel pilote est utilisé (tty=). Par défaut, il utilise /dev/tty0 alors que nous voulons utiliser un terminal série (/dev/ttyS0).

Il ne reste plus qu'à charger le noyau Linux avec ce RAMDisk pour tester ce système. Il faut dans un premier temps compresser le fichier correspondant au système racine, puis le convertir en fichier utilisable par U-Boot. Enfin, il ne reste plus qu'à compiler Linux avec la commande make uImage :

bash# umount /mnt/ramdisk
bash# gzip -9 ramdisk
bash# $UBOOT/tools/mkimage -n 'Simple Ramdisk Image' \
      -A ppc -O linux -T ramdisk -C gzip             \
      -d  /ramdisk.gz ramdisk.uboot
bash# cd $LINUX/
bash# make uImage

Il ne reste alors qu'à charger les fichiers ramdisk.uboot et uImage avec U-Boot. Un dernier mot au sujet du RAMDisk utilisé comme système de fichier racine : il faut que l'option "Initial RAM disk (initrd) support" soit cochée dans le menu de configuration du noyau Linux. On peut alors observer le résultat sur le port série :

MemTotal:   31080 kB
MemFree:    30452 kB
MemShared:      0 kB
Buffers:        8 kB
Cached:        72 kB
SwapCached:     0 kB
Active:        20 kB
Inactive:      68 kB
HighTotal:      0 kB
HighFree:       0 kB
LowTotal:   31080 kB
LowFree:    30452 kB
SwapTotal:      0 kB
SwapFree:       0 kB

On voit donc qu'un système Linux minimal [20] utilise au total 2316 Ko.



[19] c'est à dire qui ne comporte que des zéros.

[20] le RAMDisk utilisé faisait 64 Ko.