Archives de la catégorie ‘Microprocesseur’

Parallelizing Compilations

Linux, Microprocesseur | Publié par cpb
jan 14 2012

(Version originale en français)

I very frequently compile Linux kernels, often during training sessions or engineering services (mainly in the field of embedded systems or drivers development), sometimes while writing articles or books.

Compilation time varies greatly depending on the amount of code (drivers, filesystems, protocols, etc.) and on the CPU power of the host machine. On a mid-range PC, compiling a kernel adjusted for an embedded system (with very few drivers) lasts about three minutes. On an entry level machine (or a little old one), compiling a generic kernel for PC (with hundreds of drivers as modules) can last an hour.

To take advantage of the parallelism offered by the current processors (multiprocessor, multicore or hyper-threading), the make command allows us to run multiple jobs. So

$ make -j 4

guarantees there is always four compilation jobs active.

I have long reiterated that « if you have N processors (or cores, or virtual CPUs) available, you will save time by starting 2N compilation jobs in parallel« . The idea is that for every proccessor we have a job that performs the compilation (consuming CPU time) while another job is saving the results of the previous compilation and loading the source file of the next task. But wait… is this true?

Test script

I wrote the following script which downloads and unpack the required kernel sources, then makes several compilations using a variable number of jobs. For example, if we start

$ ./test-make-j.sh 3 5 8

It performs three complete compilations: one with three tasks in parallel, one with the five jobs and the last with eight jobs. The results are recorded in a text file. The script follows.

test-make-j.sh 
#! /bin/sh

KERNEL_VERSION="linux-3.2"
KERNEL_URL_PATH="www.kernel.org/pub/linux/kernel/v3.0/"
RESULT_FILE="compilation-timing.txt"

if [ "$#" -eq 0 ]
then
  echo "usage: $@ jobs_number..." >& 2
  exit 0
fi

if [ ! -d "${KERNEL_VERSION}" ]
then
  if [ ! -f "${KERNEL_VERSION}.tar.bz2" ]
  then
    wget "${KERNEL_URL_PATH}/${KERNEL_VERSION}.tar.bz2"
    if [ $? -ne 0 ] || [ ! -f "${KERNEL_VERSION}.tar.bz2" ]
    then
      echo "unable to obtain ${KERNEL_VERSION} archive" >&2
      exit 1
    fi
  fi
  tar xjf "${KERNEL_VERSION}.tar.bz2"
  if [ $? -ne 0 ]
  then
    echo "Error while uncompressing kernel archive" >&2
    exit 1
  fi
fi

cd "${KERNEL_VERSION}"

echo "# Timings of ${KERNEL_VERSION} compilations" >> "${RESULT_FILE}"
nb_cpu=$(grep "^processor" /proc/cpuinfo | wc -l)

echo "# Processors: ${nb_cpu}" >> "${RESULT_FILE}"
affinity=$(taskset -p $$ | sed -e 's/^.*://') >> "${RESULT_FILE}"

echo "# Affinity mask: ${affinity}" >> "${RESULT_FILE}"
for nb in "$@"
do
  echo "# Compiling with $nb simultaneous jobs" >> "${RESULT_FILE}"
  make mrproper
  make i386_defconfig
  sync
  sleep 10 # Let's all calm down
  start=$(date "+%s")
  make -j $nb
  sync
  end=$(date "+%s")
  # This script will fail during february 2038 ;-)
  echo "$nb     $((end - start))" >> "${RESULT_FILE}"
done

Results

Here are the results of a run on an Intel Q6600 Quad-Core (file: Intel-Q6600-1.txt)

# Timings of linux-3.2 compilations
# Processors: 4
# Affinity mask:  f
# Compiling with 1 simultaneous jobs
1     675
# Compiling with 2 simultaneous jobs
2     346
# Compiling with 3 simultaneous jobs
3     241
# Compiling with 4 simultaneous jobs
4     197
# Compiling with 5 simultaneous jobs
5     198
# Compiling with 6 simultaneous jobs
6     194
# Compiling with 7 simultaneous jobs
7     195
# Compiling with 8 simultaneous jobs
8     196
# Compiling with 9 simultaneous jobs
9     197
# Compiling with 10 simultaneous jobs
10     198
# Compiling with 11 simultaneous jobs
11     198
# Compiling with 12 simultaneous jobs
12     198
# Compiling with 13 simultaneous jobs
13     200
# Compiling with 14 simultaneous jobs
14     201
# Compiling with 15 simultaneous jobs
15     201
# Compiling with 16 simultaneous jobs
16     200

Let’s see them graphically with this little Gnuplot command line. On the horizontal axis, lies the number of concurrent jobs and on the vertical axis is the compilation time (in seconds).

$ echo "set terminal png size 640,480 ; set output './Intel-Q6600-1.png'; plot 'Intel-Q6600-1.txt' with linespoints" | gnuplot

 

Parallel Compilations on 4 CPU

Parallel Compilations on 4 CPU

Apparently, the best results are achieved (with some fluctuations) with make-j 4. Let’s try to confirm this. Before running again the script, we limit it to two processors with the following command which binds on processors 2 and 3 all the jobs launched from the running shell.

$ taskset -pc 2-3 $$

Here are the results (file: Intel-QL6600-2.txt).

# Timings of linux-3.2 compilations
# Processors: 4
# Affinity mask:  c
# Compiling with 1 simultaneous jobs
1     684
# Compiling with 2 simultaneous jobs
2     360
# Compiling with 3 simultaneous jobs
3     362
# Compiling with 4 simultaneous jobs
4     366
# Compiling with 8 simultaneous jobs
8     370
# Compiling with 16 simultaneous jobs
16     376
# Compiling with 32 simultaneous jobs
32     377
# Compiling with 64 simultaneous jobs
64     378
Parallel Compilations on 2 CPU

Parallel Compilations on 2 CPU

This time, it is clear that the minimum time is achieved with make-j 2. If we repeat the experiment on a single CPU, we obtain the following values (file: Intel-Q6600-3.txt).

# Timings of linux-3.2 compilations
# Processors: 4
# Affinity mask:  8
# Compiling with 1 simultaneous jobs
1     683
# Compiling with 2 simultaneous jobs
2     698
# Compiling with 3 simultaneous jobs
3     708
# Compiling with 4 simultaneous jobs
4     709
# Compiling with 5 simultaneous jobs
5     719
# Compiling with 6 simultaneous jobs
6     719
# Compiling with 7 simultaneous jobs
7     720
# Compiling with 8 simultaneous jobs
8     724

Represented on the graph below:

Parallel Compilations on a single CPU

Parallel Compilations on a single CPU

We can group these three curves on a single graph to better see their scales (I have not extended the curve of the compilation on a single CPU, but we can imagine that it continues with a slight increase).

Parallel Compilations on Q6600

Parallel Compilations on Q6600

To be sure, we can repeat the experiment on a different processor with two cores (AMD QL66). The results are as follows (file: AMD-QL66-1.txt).

# Timings of linux-3.2 compilations
# Processors: 2
# Affinity mask:  3
# Compiling with 1 simultaneous jobs
1     1113
# Compiling with 2 simultaneous jobs
2     844
# Compiling with 3 simultaneous jobs
3     875
# Compiling with 4 simultaneous jobs
4     863
# Compiling with 5 simultaneous jobs
5     840
# Compiling with 6 simultaneous jobs
6     844
# Compiling with 7 simultaneous jobs
7     844
# Compiling with 8 simultaneous jobs
8     851
Parallel Compilations on two CPU

Parallel Compilations on two CPU

Let’s try one last experiment on the same machine (two CPU), by disabling two elements:

  • prefetching of the next blocks of the disk (which can improve localized readings) with echo 0 > /sys/block/sda/read_ahead_kb
  • delayed (about 30 seconds) block writes (avoiding repetitive access to the disk in case of subsequent modification of the same block) with mount / -o sync,remount.

 

This time the results are very different (file: AMD-QL66-2.txt). The times are much longer than before because for each write to disk, the process waits for data to be transmitted to the device to continue his work.

 Timings of linux-3.2 compilations
# Processors: 2
# Affinity mask:  3
# Compiling with 1 simultaneous jobs
1     3487
# Compiling with 2 simultaneous jobs
2     2562
# Compiling with 3 simultaneous jobs
3     2198
# Compiling with 4 simultaneous jobs
4     1963
# Compiling with 5 simultaneous jobs
5     1779
# Compiling with 6 simultaneous jobs
6     1646
# Compiling with 7 simultaneous jobs
7     1636
# Compiling with 8 simultaneous jobs
8     1602
# Compiling with 9 simultaneous jobs
9     1738
# Compiling with 10 simultaneous jobs
10     1577
Parallel Compilations on 2 CPU without disk optimizations

Parallel Compilations on 2 CPU without disk optimizations

Here, the curve is closer to that than I imagined at first. Placing more jobs per CPU can take advantage of the wait times due to the disk access to progress in another compilation. Group the two curves in order to see the respective durations.

Parallel Compilations on QL66

Parallel Compilations on QL66

Conclusion

We see that with the quality of the I/O scheduler of Linux, and the optimized management of block devices, the best compilation time are obtained as soon as we launch one job per processor.

So I will modify my recommendation in the future as « If you have N processors available, compile your kernel with make -j N to get the best execution time. »

PS: If you have the opportunity to run this script on different architectures (8 processors, 16 processors, etc.). I am very interested in your results.

Expérimentation sur la préemptibilité du noyau Linux

Embarqué, Linux, Microprocesseur, Temps-réel | Publié par cpb
nov 04 2011

Cet article est extrait de la version préparatoire de mon livre « Solutions temps-réel sous Linux » (parution envisagée au début 2012).

J’ai eu envie de mettre en évidence la différence de comportement entre un noyau préemptible (avec l’option CONFIG_PREEMPT activée durant sa compilation) et un noyau non-préemptible classique. Toutefois, cette mise en évidence n’est pas très simple, car elle concerne précisément des cas rares et difficiles à  reproduire.

Nous allons nous intéresser à une interruption déclenchée par le port de communication série RS-232. Nous allons envoyer un caractère sur une liaison série à destination d’un système Linux sur lequel fonctionnera un processus temps-réel qui renverra le même caractère dans l’autre sens sur la même liaison série.

Mise en œuvre

L’expérience met en œuvre une carte à processeur Arm (Pandaboard, à gauche sur la photo ci-dessous) fonctionnant avec un noyau Linux 3.0. Ce choix s’explique par le fait que la Pandaboard dispose d’un véritable port série RS-232 possédant sa propre interruption – et non d’une émulation basée sur un contrôleur USB comme la plupart des PC actuels. Nous essaierons de mesurer précisément le temps de réponse de notre processus temps-réel et voir s’il varie sous l’influence d’un processus de moindre priorité.

Pour effectuer cette mesure, j’utiliserai une carte de développement (STK500, à droite sur la photo) pour micro-contrôleurs Atmel, plus particulièrement ici un ATmega32 (inséré dans le support de programmation rouge sur la partie gauche de la carte). Ce dernier fonctionne sans système d’exploitation et nous permet un contrôle direct et sans perturbation de la liaison série. Le micro-contrôleur enverra le caractère et mesurera le temps écoulé jusqu’à la réception du caractère renvoyé.

 

Pandaboard et STK500

Voici le programme qui est compilé puis chargé dans le micro-contrôleur (à l’aide des utilitaires avr-gcc et avr-dude qui sont librement disponible sous Linux). Mentionnons pour le lecteur non habitué à la programmation de micro-contrôleur que les constantes UDR, UCSRA, UCSRB, UCSRC, UBRRH et UBRRL représentent des accès directs aux registres du micro-contrôleur.

exemple-mesure-atmega.c :
// Declarer la frequence du micro-controleur (1MHz)
#define F_CPU 1000000
// Inclusion d'entetes specifiques au micro-controleur
#include <avr/io.h>
#include <util/delay.h>

void envoyer_octet (unsigned char octet)
{
    // Attendre buffer emission libre
    while ((UCSRA & (1<<UDRE)) == 0)
        ;
    // Ecriture sur le port Usart Data Register
    UDR = octet;
}

int main(void)
{
    unsigned char uc;
    unsigned long int nb_boucles;

    // Parametrage du port de communication
    // 12 -> 9600 bits/sec
    UBRRH = (unsigned char) (0);
    UBRRL = (unsigned char) (12);
    // Clock x 2
    UCSRA = (1 << U2X);
    // TX et RX actives
    UCSRB = (1 << RXEN) | (1 << TXEN);
    // 8 bits, 1 stop, pas de parite
    UCSRC = (1 << URSEL) | (1 << UCSZ1) | (1 << UCSZ0);

    while (1) {
        nb_boucles = 0;

        envoyer_octet(0xFF);
        // Attendre 1 octet recu
        while ((UCSRA & (1 << RXC)) == 0)
            nb_boucles ++;
        // Lire l'octet
        uc = UDR;

        envoyer_octet((nb_boucles >> 24) & 0xFF);
        _delay_ms(2);
        envoyer_octet((nb_boucles >> 16) & 0xFF);
        _delay_ms(2);
        envoyer_octet((nb_boucles >> 8 ) & 0xFF);
        _delay_ms(2);
        envoyer_octet((nb_boucles >> 0 ) & 0xFF);
        _delay_ms(500);
    }
    return 0;
}

On peut voir que ce programme envoie un octet (0xFF) sur le port série et boucle – en incrémentant une variable – tant qu’il n’a pas reçu de réponse. La durée de la boucle importe peu. Chaque itération dure environ 10 micro-secondes, et on pourrait la calibrer précisément, mais ceci ne présente pas de véritable intérêt. Ce n’est pas le nombre absolu de boucles effectuées qui nous concerne, mais plutôt les variations de ce nombre au cours des essais successifs. Une fois la réponse obtenue, nous envoyons sur le même port série le nombre de boucles effectuées. Le petit sommeil de 2 ms entre les octets sert à garantir la réception par le correspondant car il n’y a pas de contrôle de flux (CTS/RTS, DTS/DSR, etc.) sur la carte à micro-contrôleur employée ici.

Le programme qui fonctionne sur la Pandaboard sous Linux est le suivant :

exemple-reponse-irq-serie.c  :
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <unistd.h>

volatile int quitter = 0;

void handler_sigint(int unused)
{
    quitter = 1;
}

int main(int argc, char * argv[])
{
    int fd;
    unsigned char  octet;
    int nb_cycles;
    struct termios parametres;
    struct termios original;

    if (argc != 2) {
        fprintf(stderr, "usage: %s port_serie\n",argv[0]);
        exit(EXIT_FAILURE);
    }

    signal(SIGINT, handler_sigint);

    if ((fd = open(argv[1], O_RDWR | | O_NONBLOCK))<0){
        perror(argv[1]);
        exit(EXIT_FAILURE);
    }

    tcgetattr(fd, & original);
    tcgetattr(fd, & parametres);
    cfmakeraw(& parametres);
    cfsetispeed(& parametres, B9600);
    cfsetospeed(& parametres, B9600);
    parametres.c_iflag |= IGNPAR;
    parametres.c_cflag ^= (CSIZE | PARENB | CSTOPB);
    parametres.c_cflag |= CS8 | CLOCAL;
    if (tcsetattr(fd, TCSANOW, & parametres) != 0) {
        perror("tcsetattr");
        exit(EXIT_FAILURE);
    }
    fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) ^ O_NONBLOCK);

    while (! quitter) {
        if (read(fd, & octet, 1) < 0) {
            perror(argv[1]);
            exit(EXIT_FAILURE);
        }
        if (write(fd, & octet, 1) < 0) {
            perror(argv[1]);
            exit(EXIT_FAILURE);
        }
        nb_cycles = 0;
        read(fd, & octet, 1);
        nb_cycles |= octet;
        nb_cycles <<= 8;
        read(fd, & octet, 1);
        nb_cycles |= octet;
        nb_cycles <<= 8;
        read(fd, & octet, 1);
        nb_cycles |= octet;
        nb_cycles <<= 8;
        read(fd, & octet, 1);
        nb_cycles |= octet;
        fprintf(stdout, "%d\n", nb_cycles);
    }
    tcsetattr(fd, TCSANOW, & original);
    close(fd);
    fprintf(stderr, "Bye !\n");
    return EXIT_SUCCESS;
}

Le programme attend donc un caractère, le renvoie en écho, récupère les quatre octets représentant son temps de réponse et les affiche sur sa sortie standard. Voici un exemple d’exécution sur un noyau non-préemptible.

[Panda]# ./exemple-reponse-irq-serie /dev/ttyO2
290
287
289
287
[...]
287
287
286
286
(Contrôle-C)
Bye !
[Panda]#

Appel-système long

Pour perturber le fonctionnement du programme, j’ai réalisé un petit script shell qui charge (avec insmod) et décharge (avec rmmod) toutes les cinq secondes ce petit module du kernel :

module-delay.c  :
#include <linux/module.h>

static int __init module_delay_init (void)
{
    unsigned long fin = jiffies + HZ/2; // 500 ms
    while(time_before(jiffies, fin))
        ;
    return 0;
}

static void __exit module_delay_exit (void)
{}

module_init(module_delay_init);
module_exit(module_delay_exit);
MODULE_LICENSE("GPL");

Ce module effectue une boucle active de 500 millisecondes dès son chargement dans le kernel. Ceci nous permet de disposer d’un appel-système suffisamment long pour perturber le temps de réponse à une interruption sur un système non-préemptible. Une remarque : j’ai également monté le thread kernel kworker qui est utilisé pour gérer une partie de l’interruption série à la priorité Fifo 50.

Résultats sur système non-préemptible

[NB: Pour étudier les résultats, après les avoir redirigés dans un fichier, j'utilise un ensemble de petits outils développés précédement dans le livre]

[Panda]# uname -a
Linux (Pandaboard) 3.0.0-cpb #4 SMP Mon Oct 31 19:52:43 CET 2011 armv7l GNU/Linux
[Panda]# taskset -p 1  $$
pid 547's current affinity mask: 3
pid 547's new affinity mask: 1
[Panda]#  chrt -f 40 . ./exemple-reponse-irq-serie /dev/ttyO2 > resultats-non-preempt.txt
(Contrôle-C après quelques minutes)
Bye!
[Panda]#

Pendant le déroulement du programme, mon script qui charge et décharge le module perturbateur toutes les cinq secondes s’exécute sur le même processeur, avec une priorité temps-réel 10.

L’analyse du résultat donne :

$ ../chapitre-04/calculer-statistiques < resultats-non-preempt.txt
Nb mesures = 1156
Minimum = 283
Maximum = 46181
Moyenne = 4737
Ecart-type = 13397
$ ../chapitre-04/calculer-histogramme 100 0 50000  < resultat-nonpreempt.txt > histo-nonpreempt.txt

[NB: L'histogramme calculé ici est ensuite injecté dans un petit script Gnuplot, présenté dans un chapitre précédent, pour produire les figures ci-dessous]

Les résultats statistiques paraissent assez catastrophiques : une variabilité entre 283 et 46181 itérations de boucles, et un écart-type largement plus grand que la moyenne des valeurs ! Graphiquement les résultats sont plus compréhensibles, comme nous le voyons sur la figure suivante.

Réponse aux interruptions sur noyau non-préemptible

Notre système fait un grand écart entre un temps de réponse faible (290 itérations par boucles, environ 3 millisecondes) et une réponse très retardée par l’appel-système perturbateur (45000 itérations par boucle, à peu près 500 ms).

Résultat sur système préemptible

Après avoir redémarré la carte Pandaboard sur un noyau Linux compilé avec l’option CONFIG_PREEMPT, nous obtenons les résultats suivants.

[Panda] # uname -a 
Linux Pandaboard 3.0.0-rc7-cpb #1 SMP PREEMPT Thu Sep 29 14:49:25 CEST 2011 armv7l GNU/Linux
[Panda]# taskset -p 1 $$
pid 579's current affinity mask: 3
pid 579's new affinity mask: 1
[Panda]#  chrt -f 40 . ./exemple-reponse-irq-serie /dev/ttyO2 > resultats-preempt.txt
(Contrôle-C après quelques minutes)
Bye!
[Panda]#

Après avoir rappatrié le fichier sur le PC de développement, nous analysons les résultats.

$ ../chapitre-04/calculer-statistiques < resultats-preempt.txt
Nb mesures = 1307
Minimum = 284
Maximum = 309
Moyenne = 287
Ecart-type = 2
$

Voilà qui est beaucoup mieux ! Le résultat est visible sur la figure suivante avec la même échelle que précédemment.
Réponse aux interruptions sur noyau préemptible (1)

Un zoom au début de l’axe des abscisses est représenté sur la figure ci-dessous. Cette fois aucune préemption du processus de haute priorité, la réponse à l’interruption est toujours comprise entre 284 et 309 boucles de la carte à micro-contrôleur. La réponse n’est pas réellement plus rapide, mais elle est plus fiable et prévisible.
Réponse aux interruptions sur noyau préemptible (2)

Conclusion

La préemptibilité optionnelle du noyau, disponible depuis sa version 2.6, est un élément important pour améliorer la fiabilité des systèmes temps-réel dans leur réponse aux événements externes. Elle permet de garantir qu’un processus de haute priorité en attente d’un événement externe (matérialisé par cette interruption) sera réveillé avec rapidité et surtout fiabilité, même si une autre tâche – de priorité moindre – est en train d’exécuter un appel-système. Ceci évite le problème classique de l’inversion de priorité.

Signes de vie d’un système embarqué

Embarqué, Linux, Microprocesseur | Publié par cpb
juil 15 2011

Suite à une question posée par Chriss en commentaire d’un précédent article, j’ai eu envie de revenir rapidement sur un élément essentiel pour la mise au point d’un système embarqué : le signe de vie. Il s’agit simplement de faire effectuer par le système une tâche régulière, avec un effet facilement observable par l’utilisateur, afin de s’assurer du bon fonctionnement global du dispositif. Ce signe de vie peut prendre diverses formes : signal électrique visible à l’oscilloscope sur une broche de test de la carte, trame vide émise régulièrement sur un port réseau, compteur incrémenté périodiquement dans une zone de mémoire partagée, etc. Le signe de vie le plus simple à mettre en oeuvre sur un système embarqué est le clignotement d’une LED.

Premier exemple sur carte Pandaboard

La carte Pandaboard, qui a déjà fait l’objet de plusieurs articles, dispose de trois LEDs. Les deux premières se trouvent à côté du support pour mémoire MicroSD, la troisième est placée à côté du connecteur d’alimentation. Cette dernière n’est pas contrôlable par l’utilisateur, elle indique une surtension d’alimentation. Nous allons donc nous intéresser uniquement aux LEDs 1 et 2.

LEDs sur carte Pandaboard

La première méthode pour accéder à ces LEDs, et en conserver un contrôle complet, va consister à les piloter en programmant les broches GPIO (General Purpose Input Output) du processeur auxquelles les LEDs sont reliées. Nous démarrons la carte en utilisant un kernel Linux 3.0-rc7 compilé (avec ce fichier .config) sans support particulier pour les LEDs. Nous avons simplement activé

  • le support GPIO (dans le menu « Device Drivers »),
  • l’interface « /sys/class/gpio/ » (dans le menu « GPIO Support » activé précédemment),
  • et le driver pour Texas Instruments TWL 4030 et ultérieurs.

Connectons-nous sur la carte Pandaboard, et examinons le contenu du répertoire /sys/class/gpio/.

[~]$ telnet 192.168.3.152
Trying 192.168.3.152...
Connected to 192.168.3.152.
Escape character is '^]'.
(none) login: root
Password:
BusyBox v1.18.4 (2011-06-22 12:27:46 CEST) built-in shell (ash)
Enter 'help' for a list of built-in commands.
(panda)[root]$ cd /sys/class/gpio/
(panda)[gpio]$ ls
export       gpio62       gpiochip128  gpiochip32   gpiochip96
gpio1        gpiochip0    gpiochip160  gpiochip64   unexport
(panda)[gpio]$

Nous voyons que dans ce répertoire le noyau nous propose l’accès à certaines broches GPIO (1 et 62). Toutefois ce ne sont pas celles qui correspondent aux LEDs. En consultant les spécifications de notre carte (pages 48-49), nous voyons que les LEDs 1 et 2 sont connectées aux GPIO 7 et 8. Demandons au noyau d’exporter (c’est-à-dire de nous rendre accessible) la broche GPIO 8. Nous remarquons alors que le contenu du répertoire /sys/class/gpio/ est modifié.

(panda)[gpio]$ echo 8 > export 
(panda)[gpio]$ ls
export       gpio62       gpiochip0    gpiochip160  gpiochip64   unexport
gpio1        gpio8        gpiochip128  gpiochip32   gpiochip96
(panda)[gpio]$

L’entrée gpio8 a fait son apparition, il s’agit d’un sous-répertoire contenant des fichiers virtuels, dont les contenus représentent des paramètres internes du noyau. Voyons donc la direction par défaut de la broche GPIO 8.

(panda)[gpio]$ ls gpio8/
active_low  edge        subsystem   value
direction   power       uevent
(panda)[gpio]$ cat gpio8/direction 
in
(panda)[gpio]$

Le processeur initialise par défaut ses broches GPIO en entrée, pour des raisons de sécurité. Elles présentent alors un état de haute impédance, et les manipulations externes (mises à la masse ou au +5V, court-circuit, etc.) n’ont pas d’influence. Toutefois, pour piloter la LED 2, il faut basculer la broche en sortie.

(panda)[gpio]$ echo out > gpio8/direction 
(panda)[gpio]$

Nous pouvons alors allumer et étendre la LED 2 ainsi :

(panda)[gpio]$ echo 1 > gpio8/value
(panda)[gpio]$ echo 0 > gpio8/value
(panda)[gpio]$

Il devient alors facile de programmer un petit signe de vie en utilisant un script shell simple :

/usr/bin/heartbeat.sh
#! /bin/sh

echo 7 > /sys/class/gpio/export
echo 8 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio7/direction
echo out > /sys/class/gpio/gpio8/direction

while true
do
        echo 1 > /sys/class/gpio/gpio7/value
        usleep 150000
        echo 0 > /sys/class/gpio/gpio7/value
        usleep 100000
        echo 1 > /sys/class/gpio/gpio8/value
        usleep 150000
        echo 0 > /sys/class/gpio/gpio8/value
        usleep 100000
        usleep 500000
done

Lancé en arrière-plan dans le script de démarrage /etc/init.d/rcS ainsi

  /usr/bin/heartbeat.sh &

ce petit script nous permet de contrôler visuellement que le système est opérationnel, et que l’ordonnancement des processus fonctionne normalement (sans qu’une tâche temps-réel ne consomme tout le CPU disponible par exemple).

Deuxième exemple, sur carte IGEP

Sur la carte IGEP v2, que nous avons également abordée précédement, il existe deux LEDs bicolores. La couleur verte de l’une d’entre elles est programmable par l’I2C. Les trois autres couleurs sont accessibles via les GPIO 26, 27, et 28. À noter : lorsque les couleurs rouge et verte de la même LED sont allumées simultanément, la couleur visible est jaune orangée. On peut donc obtenir un signe de vie visuellement plus riche en jouant sur les changements de couleur.

Voici un petit programme C qui assure un scintillement permanent des LEDs et remplit un rôle similaire au premier script ci-dessus.

sdv-igepv2.c
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void)
{
	int i;
	int compteur = 0;
	int fd[3];
	char buffer[80];

	fd[0] = open("/sys/class/gpio/gpio26/value", O_WRONLY);
	fd[1] = open("/sys/class/gpio/gpio27/value", O_WRONLY);
	fd[2] = open("/sys/class/gpio/gpio28/value", O_WRONLY);
	if ((fd[0] < 0) || (fd[1] < 0) || (fd[2] < 0)) {
		perror("open");
		exit(EXIT_FAILURE);
	}
	while(1) {
		for (i = 0; i < 3; i ++) { 			sprintf(buffer, "%d", (compteur >> i) & 1);
			write(fd[i], buffer, 2);
		}
		compteur ++;
		compteur %= 8;
		usleep(100000);
	}
}

Troisième exemple, avec les triggers du noyau Linux

Le kernel Linux nous offre une interface pour accéder aux LEDs de notre système, et propose même des triggers, c’est-à-dire des comportements prédéfinis pour leur clignotement. Pour que le noyau puisse accéder aux LEDs et nous offrir un moyen de les contrôler, il est nécessaires de sélectionner les options suivantes lors de sa compilation (cliquez sur l’aperçu ci-dessous). Dans le sous-menu « LED Support » du menu « Device Drivers ».

make ARCH=arm menuconfig

Vous pouvez utiliser directement ce fichier de configuration .config pour Linux 3.0-rc7 sur Pandaboard.

Dans cette configuration, nous ajoutons le support pour les LEDs pilotées par un GPIO, ainsi que la configuration spécifique à la plateforme. Cette configuration se trouve dans le fichier arch/arm/mach-omap2/board-omap4panda.c des sources du kernel.

[...]
static struct gpio_led gpio_leds[] = {
        {
                .name                   = "pandaboard::status1",
                .default_trigger        = "heartbeat",
                .gpio                   = 7,
        },
        {
                .name                   = "pandaboard::status2",
                .default_trigger        = "mmc0",
                .gpio                   = 8,
        },
};

static struct gpio_led_platform_data gpio_led_info = {
        .leds           = gpio_leds,
        .num_leds       = ARRAY_SIZE(gpio_leds),
};
[...]

On y retrouve bien les numéros de GPIO que nous avons utilisés plus tôt. Après compilation du noyau et reboot de la carte, nous trouvons une nouvelle interface dans /sys.

(panda)[root]$ cd /sys/class/
(panda)[class]$ ls
backlight     display       graphics      i2c-dev       leds          misc          net           rtc           scsi_host     ubi           vtconsole
bdi           firmware      hwmon         input         mdio_bus      mmc_host      power_supply  scsi_device   spi_master    usb_device
block         gpio          i2c-adapter   lcd           mem           mtd           regulator     scsi_disk     tty           vc
(panda)[class]$

Allons voir le contenu de cette classe LEDs.

(panda)[class]$ cd leds/
(panda)[leds]$ ls
pandaboard::status1  pandaboard::status2
(panda)[leds]$ ls pandaboard\:\:status1/
brightness      device          max_brightness  power           subsystem       trigger         uevent
(panda)[leds]$

Nous y trouvons deux sous-répertoires, un pour chaque LED, avec les noms observés plus haut dans le code source du noyau. Le pseudo-fichier trigger indique l’évenement qui déclenche le changement d’état de la LED.

(panda)[leds]$ cat pandaboard\:\:status1/trigger
none nand-disk mmc0 mmc1 timer [heartbeat] gpio default-on
(panda)[leds]$

Par défaut il s’agit du trigger heartbeat, qui simule un battement de coeur, dont la fréquence augmente en fonction de la charge du système, mais on peut en choisir d’autres :

  • none nous donne l’accès direct à la LED depuis l’entrée brightness du sous-répertoire,
  • nand-disk fait scintiller la LED lors de l’accès à la mémoire Flash interne (inutilisée sur mon système),
  • mmc0 et mmc1 signalent les accès aux partitions de la carte MicroSD, rappelant les LEDs d’accès aux disques durs,
  • timer allume et éteint la LED périodiquement : elle reste allumée pendant la durée en millisecondes indiquée dans delay_on puis s’éteint pendant delay_off millisecondes,
  • gpio a un comportement proche de celui de none, l’entrée brightness permettant d’allumer ou d’éteindre à volonté la LED.
  • default-on: allumer la LED dès le boot.

Voici par exemple la configuration pour une LED clignotant à 10 Hz, et dont la durée d’allumage est de 10 ms à chaque cycle.

(panda)[leds]$ echo timer > pandaboard\:\:status2/trigger
(panda)[leds]$ echo 10 > pandaboard\:\:status2/delay_on
(panda)[leds]$ echo 90 > pandaboard\:\:status2/delay_off
(panda)[leds]$

Conclusion

Nous voyons que les cartes embarquées sur lesquelles on fait couramment fonctionner Linux nous permettent aisément de disposer d’un petit signe de vie en employant des LEDs. Bien sûr ces LEDs peuvent servir à d’autres utilisations (indication de l’état d’un serveur, de la disponibilité des ressources, etc.). L’avantage d’un signe de vie programmé dans l’espace utilisateur sur celui intégré dans le kernel, est de nous indiquer réellement si le système est utilisable pour les applications, et pas seulement le bon fonctionnement de l’espace noyau. J’en emploie également sur les systèmes embarquant des applications temps-réel, afin de vérifier facilement si CPU est surchargé ou s’il reste du temps processeur pour les tâches temps-partagé.

Création et installation d’une toolchain native pour processeur Arm

Embarqué, Linux, Microprocesseur | Publié par cpb
juil 01 2011

Certains projets posent des problèmes difficiles à surmonter lorsqu’il s’agit de les cross-compiler pour une plate-forme différente du poste de compilation. Citons par exemple Apache ou PHP que nous avons laborieusement réussi à cross-compiler dans les quatrième et cinquième articles de notre série « Construire son système personnel sur Pandaboard« . Il peut être utile de disposer sur la cible embarquée d’une chaîne de compilation native c’est-à-dire capable de produire du code exécutable pour le processeur sur lequel la compilation a eu lieu.

Nous allons mettre ceci en oeuvre sur une carte à processeur Arm à l’aide de Buildroot.

Je vous propose de procéder en deux étapes : génération de la chaîne de compilation croisée classique, puis une fois celle-ci prête, construction de la chaîne de compilation native. Comme toujours, il faudra intervenir un peu manuellement pour réussir toute la compilation…

Tout d’abord téléchargeons et décompressons les sources de Buildroot :

[~]$ mkdir Projets/Arm/
[~]$ cd Projets/Arm/
[Arm]$ wget http://buildroot.uclibc.org/downloads/buildroot-2011.05.tar.bz2
--2011-06-30 23:22:45--  http://buildroot.uclibc.org/downloads/buildroot-2011.05.tar.bz2
[...]
2011-06-30 23:22:50 (522 KB/s) - «buildroot-2011.05.tar.bz2» sauvegardé [1834679/1834679]
[Arm]$ tar -xjf buildroot-2011.05.tar.bz2
[Arm]$ cd buildroot-2011.05
[buildroot-2011.05]$

Je vous propose de télécharger ce fichier de configuration qui va générer la chaîne de compilation croisée et installer dans les répertoires destinés à la cible les fichiers d’entête de la bibliothèque C (option Development files in target filesystem du menu Build Options ci-dessous).

[buildroot-2011.05]$ cp ~/config-buildroot-2011-05-pass-1 ./.config
[buildroot-2011.05]$ make menuconfig
[...]

Vérifiez en particulier les chemins mentionnés dans le menu « Build Options », pour qu’il soient en accord avec votre système. Le répertoire « Host Dir » doit être un chemin absolu (il contiendra donc probablement votre nom d’utilisateur plutôt que le mien !). Quittez le menu et lancez la compilation :

[buildroot-2011.05]$ make
[...]

Pendant la compilation, make nous pose une dizaines de questions (probablement de nouvelles options qui ne sont pas enregistrées dans le fichier .config), auxquelles nous pouvons répondre en utilisant le choix par défaut, c’est-à-dire en pressant simplement Entrée.

[...]
		grep -qx $lang /home/cpb/Projets/Arm/buildroot-2011.05/output/build/locales.nopurge || rm -rf $dir/$lang; \
	done; \
done
rm -f /home/cpb/Projets/Arm/buildroot-2011.05/output/build/.fakeroot*
[buildroot-2011.05]$

La première étape est terminée nous avons une chaîne de compilation croisée, contenant GCC, accessible ainsi :

[buildroot-2011.05]$ ~/Projets/Arm/Target/usr/bin/arm-linux-gcc -v
Utilisation des specs internes.
Target: arm-unknown-linux-uclibcgnueabi
Configuré avec: /home/cpb/Projets/Arm/buildroot-2011.05/output/toolchain/gcc-4.3.5/configure --prefix=/home/cpb/Projets/Arm/Target/usr --build=i686-pc-linux-gnu --host=i686-pc-linux-gnu --target=arm-unknown-linux-uclibcgnueabi --enable-languages=c,c++ --with-sysroot=/home/cpb/Projets/Arm/Target/usr/arm-unknown-linux-uclibcgnueabi/sysroot --with-build-time-tools=/home/cpb/Projets/Arm/Target/usr/arm-unknown-linux-uclibcgnueabi/bin --disable-__cxa_atexit --enable-target-optspace --disable-libgomp --with-gnu-ld --disable-libssp --disable-multilib --disable-tls --enable-shared --with-gmp=/home/cpb/Projets/Arm/Target/usr --with-mpfr=/home/cpb/Projets/Arm/Target/usr --enable-threads --disable-decimal-float --with-abi=aapcs-linux --with-pkgversion='Buildroot 2011.05' --with-bugurl=http://bugs.buildroot.net/
Modèle de thread: posix
gcc version 4.3.5 (Buildroot 2011.05)
[buildroot-2011.05]$

Passons à la seconde phase, la compilation d’un environnement de développement natif pour notre système embarqué en utilisant ce fichier de configuration.

[buildroot-2011.05]$ cp ../config-buildroot-2011-05-pass-2 ./.config
[buildroot-2011.05]$ make menuconfig

Vous pourrez remarquer que nous avons inséré plusieurs outils de développement comme Bison, Flex, Make, Gcc, et même l’interpréteur Microperl, qui comme son nom l’indique implémente un sous-ensemble (assez conséquent) du langage Perl.

[buildroot-2011.05]$ make 
[...]
-DDEPENDS_ON_LIBINTL=1 -DEXEEXT=\"\" -Os -pipe -Os -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -c csharpcomp.c -o csharpcomp.o >/dev/null 2>&1
make[5] : on quitte le répertoire « /home/cpb/Projets/Arm/buildroot-2011.05/output/build/gettext-0.16.1/gettext-tools/gnulib-lib »
make[4]: *** [all] Erreur 2
make[4] : on quitte le répertoire « /home/cpb/Projets/Arm/buildroot-2011.05/output/build/gettext-0.16.1/gettext-tools/gnulib-lib »
make[3]: *** [all-recursive] Erreur 1
make[3] : on quitte le répertoire « /home/cpb/Projets/Arm/buildroot-2011.05/output/build/gettext-0.16.1/gettext-tools »
make[2]: *** [all] Erreur 2
make[2] : on quitte le répertoire « /home/cpb/Projets/Arm/buildroot-2011.05/output/build/gettext-0.16.1/gettext-tools »
make[1]: *** [all-recursive] Erreur 1
make[1] : on quitte le répertoire « /home/cpb/Projets/Arm/buildroot-2011.05/output/build/gettext-0.16.1 »
make: *** [/home/cpb/Projets/Arm/buildroot-2011.05/output/build/gettext-0.16.1/gettext-runtime/src/gettext] Erreur 2
[buildroot-2011.05]$

Comment ? Une erreur de compilation ? Quelle surprise !

La cause apparait un peu plus haut dans les traces :

 /home/cpb/Projets/Arm/Target/usr/bin/arm-unknown-linux-uclibcgnueabi-gcc -DHAVE_CONFIG_H -DEXEEXT=\"\" -DEXEEXT=\"\" -I. -I.. -I../intl -I../intl -I../intl -I.. -I.. -DDEPENDS_ON_LIBICONV=1 -DDEPENDS_ON_LIBINTL=1 -DEXEEXT=\"\" -Os -pipe -Os -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -c copy-file.c  -fPIC -DPIC -o .libs/copy-file.o
copy-file.c:35:25: error: sys/utime.h: No such file or directory
make[5]: *** [copy-file.lo] Erreur 1

Un fichier utime.h ne se trouve pas dans le bon répertoire. Classique. Cherchons-le.

[buildroot-2011.05]$ find . -name 'utime.h'
./output/toolchain/uClibc-0.9.31/include/utime.h
./output/toolchain/uClibc_dev/usr/include/linux/utime.h
./output/toolchain/linux/include/linux/utime.h
./output/toolchain/linux-2.6.38.7/include/linux/utime.h
[buildroot-2011.05]$

Nous ne prendrons pas les deux dernières instances, elles sont réservées à la compilation de modules du noyau, mais plutôt la première. A présent nous voyons que le compilateur cherche ce fichier dans un sous-répertoire sys/ or il n’y en a pas dans les répertoires de recherches des fichiers d’entête lors de la compilation de copy-file.c.
Nous allons devoir faire une petite opération manuelle avant de relancer la compilation.

[buildroot-2011.05]$ mkdir -p output/build/gettext-0.16.1/gettext-tools/gnulib-lib/sys
[buildroot-2011.05]$ cp output/toolchain/uClibc-0.9.31/include/utime.h output/build/gettext-0.16.1/gettext-tools/gnulib-lib/sys/
[buildroot-2011.05]$ make

A vrai dire, la commande make s’est à nouveau interrompue en erreur sur mon système. Toutefois, soupçonnant une erreur due à la compilation en parallèle (8 jobs), j’ai relancé simplement make qui a fonctionné.

[...]
do \
		grep -qx $lang /home/cpb/Projets/Arm/buildroot-2011.05/output/build/locales.nopurge || rm -rf $dir/$lang; \
	done; \
done
rm -f /home/cpb/Projets/Arm/buildroot-2011.05/output/build/.fakeroot*
[buildroot-2011.05]$ ls output/target/usr/bin/
addr2line  as     c++  c++filt  elfedit  g++  gccbug  gprof  ld      ldd   microperl  objcopy  perl    readelf  strings  yacc
ar         bison  cc   cpp      flex     gcc  gcov    iconv  ld.bfd  make  nm         objdump  ranlib  size     strip
[buildroot-2011.05]$ file output/target/usr/bin/gcc
output/target/usr/bin/gcc: ELF 32-bit LSB executable, ARM, version 1 (SYSV), dynamically linked (uses shared libs), stripped
[buildroot-2011.05]$

Pour tester notre toolchain native, nous allons l’installer sur un système Pandaboard, contenant simplement un noyau Linux et une Busybox intrégrant une connexion à distante par telnet. Après avoir inséré la carte micro-SD sur mon PC, je vais copier le contenu du répertoire ~/Projets/Arm/buildroot-2011.05/output/target/usr/ sur la cible :

[buildroot-2011.05]$ cp -R output/target/usr/* /media/root/usr/
[buildroot-2011.05]$ umount /media/root/
[buildroot-2011.05]$

Après avoir inséré la micro-SD dans la carte Pandaboard, laissons-la booter, puis connectons-nous dessus via le réseau.

[buildroot-2011.05]$ telnet 192.168.3.152
Trying 192.168.3.152...
Connected to 192.168.3.152.
(none) login: root
Password:
BusyBox v1.18.4 (2011-06-22 12:27:46 CEST) built-in shell (ash)
Enter 'help' for a list of built-in commands.
[PANDABOARD]$  vi /root/hello.c

A l’aide du petit éditeur vi intégré dans Busybox, nous saissons un petit programme de test

#include <stdio.h>

int main(void)
{
        fprintf(stdout, "Hello from the native Panda Compiler !\n");
        return 0;
}

Que nous compilons ainsi :

[PANDABOARD]$ gcc /root/hello.c -o /root/hello
[PANDABOARD]$ /root/hello
Hello from the native Panda Compiler !
[PANDABOARD]$

Voilà ! notre compilateur natif est prêt. Il manque encore quelques utilitaires pour se lancer dans une compilation de plus grande envergure (le noyau Linux par exemple), nous les verrons la semaine prochaine.

PS : Cet article est plus court que d’habitude, mais cette période est très chargée pour moi : plusieurs développements importants arrivant à leurs termes, un déménagement, et quelques nouveaux projets traitant de robotique et de micro-contrôleurs (que je développerai prochainement)…

Construire son système personnel sur une carte Pandaboard (2)

Embarqué, Linux, Microprocesseur | Publié par cpb
mai 13 2011

PandaboardNous avons réussi dans l’article précédent à compiler les bootloaders X-loader et U-boot, ainsi qu’un noyau Linux pour la carte Pandaboard. Le démarrage du système se déroulait très bien, le chargeur X-loader s’initialisant d’abord, puis passant le contrôle à U-boot. Ce dernier plaçait l’image du noyau Linux en mémoire et lui transmettait l’exécution.

Nous avons observé que le noyau commençait par s’auto-décompresser en mémoire, puis détectait les éléments matériels du système, initialisait tous ses composants logiciels, montait la seconde partition de la carte flash à la racine du système de fichiers, et cherchait – en vain – à démarrer le premier processus du système, le processus init.

Nous devons donc lui fournir ce fameux programme init.  Il en existe plusieurs versions :

  • l’init dit « System V » adopte un comportement classique sous Unix, en examinant le contenu du fichier /etc/inittab, puis en démarrant des processus en fonction du contenu de ce fichier ;
  • l’init du projet Upstart plus récent permet de paralléliser plus de traitements, et d’offrir ainsi une interface visuelle à l’utilisateur alors même que tous les services ne sont pas encore actifs (ce fonctionnement est aussi proposé par le projet Initng mais de manière moins complète) ;
  • le projet systemd, jeune et actif, a pour but de regrouper les opérations d’init et de la plupart des démons usuels.

 

Pour les systèmes embarqués, on fait souvent appel à une version simplifiée de l’init System V, proposée par le projet Busybox. Nous allons l’utiliser pour mettre en place les outils minimum sur un système embarqué.

Système de fichiers

Tout d’abord il nous faut préparer le système de fichiers. Même dans un environnement minimal, Linux nécessite une arborescence de fichiers. Ceux-ci peuvent se trouver sur un disque dur (dans le cas d’un poste de travail, d’un serveur…), sur une carte flash (smartphone, systèmes embarqués…) ou même sur un ramdisk, disque virtuel en mémoire RAM, initialisé au moment du boot à partir d’une image se trouvant généralement dans une mémoire flash.

Dans notre configuration, le système de fichiers résidera sur la seconde partition de la carte SD sur laquelle notre Pandaboard démarre. Nous avions précédemment initialisé cette seconde partition au format Ext3, classique sous Linux. Nous lui avions donné le nom root (pour root filesystem). En insérant la carte flash sur notre ordinateur hôte de développement, la partition se trouve automatiquement montée dans /media/root.

Commençons à créer l’arborescence usuelle.

[~]# cd /media/root/
[root]# ls
lost+found
[root]#

Le répertoire lost+found est automatiquement créé lorsqu’on formatte une partition en Ext3.

[root]# mkdir bin dev etc home lib mnt proc root sbin sys tmp usr var 
[root]# mkdir usr/bin usr/sbin etc/init.d
[root]#

Nous rajouterons d’autres répertoires ultérieurement, ceux-ci nous permettront déjà d’avoir un accès minimal sur notre système. Nous pourrions à ce point créer les fichiers spéciaux de /dev représentant les périphériques du système. Sur un système embarqué avec de fortes contraintes d’occupation mémoire, c’est ce que je préconiserais. Ici je préfère tirer profit de la création automatique des fichiers spéciaux grâce à l’utilitaire mdev contenu dans Busybox.

Busybox

Busybox est un outil bien connu des développeurs pour Linux embarqué. Il s’agit d’un projet qui regroupe efficacement en un seul fichier exécutable plus de 300 commandes Unix classiques. Sur cet exécutable (nommé busybox), on crée des liens symboliques ou physiques portant les noms des commandes implémentées (par exemple ls, sh, cp, etc.) Lorsque l’exécutable démarre, il regarde sous quel nom il a été invoqué – en consultant l’argument argv[0] de sa fonction main() – et adopte le comportement correspondant. Outre la réduction de taille liée à la mise en commun du code utilisé par toutes les applets de busybox, il est possible de gagner beaucoup de place car les implémentations des commandes standards sont faites avec le souci de réduction de taille. De plus lors de la compilation de Busybox, on peut choisir les applets et les options que l’on juge utiles pour notre projet.

Busybox implémente une version simple de processus init, capable de lire un fichier /etc/inittab et d’en tirer parti. En l’absence d’un fichier /etc/inittab le processus init de Busybox cherche à exécuter automatiquement un script nommé /etc/init.d/rcS.

Compilons Busybox (dans le fichier de configuration proposé ci-dessous, j’ai activé la plupart des applets disponibles).

[~]$ cd ~/Projets/Panda/
[Panda]$ wget http://www.busybox.net/downloads/busybox-1.18.4.tar.bz2
[...]
2011-05-08 18:09:13 (122 KB/s) - «busybox-1.18.4.tar.bz2» sauvegardé [2130598/2130598]
[Panda]$ tar xjf busybox-1.18.4.tar.bz2 
[Panda]$ cd busybox-1.18.4
[busybox-1.18.4]$
[busybox-1.18.4]$ cp ~/config-busybox-1.18.4 ./.config
[busybox-1.18.4]$ make menuconfig
[busybox-1.18.4]$ make CROSS_COMPILE=/cross-arm-linux/usr/bin/arm-linux-
[...]
[busybox-1.18.4]$ ls -l busybox
-rwxrwxr-x 1 cpb cpb 892444  8 mai   19:26 busybox
[busybox-1.18.4]$

Note : la variable CROSS_COMPILE contient le préfixe à ajouter devant les outils standards (gcc, as, ar, etc.) pour accéder aux outils de la toolchain correspondant au processeur Arm. Si vous ne disposez pas de chaîne de compilation croisée, vous pouvez télécharger cette archive à décompresser directement à la racine de votre système de fichiers. Un article à venir dans deux semaines vous proposera plusieurs méthodes pour générer vous-mêmes votre propre toolchain.

Voici une liste des commandes intégrées dans l’exécutable busybox que nous avons compilé.

[, [[, acpid, add-shell, addgroup, adduser, adjtimex, arp, arping, ash, awk, base64, basename, bbconfig, beep, blkid, blockdev, bootchartd, brctl, bunzip2, bzcat, bzip2, cal, cat, catv, chat, chattr, chgrp, chmod, chown, chpasswd, chpst, chroot, chrt, chvt, cksum, clear, cmp, comm, cp, cpio, crond, crontab, cryptpw, cttyhack, cut, date, dc, dd, deallocvt, delgroup, deluser, depmod, devmem, df, dhcprelay, diff, dirname, dmesg, dnsd, dnsdomainname, dos2unix, du, dumpkmap, dumpleases, echo, ed, egrep, eject, env, envdir, envuidgid, ether-wake, expand, expr, fakeidentd, false, fbset, fbsplash, fdflush, fdformat, fdisk, fgconsole, fgrep, find, findfs, flock, fold, free, freeramdisk, fsck, fsck.minix, fsync, ftpd, ftpget, ftpput, fuser, getopt, getty, grep, gunzip, gzip, halt, hd, hdparm, head, hexdump, hostid, hostname, httpd, hush, hwclock, id, ifconfig, ifdown, ifenslave, ifplugd, ifup, inetd, init, insmod, install, ionice, iostat, ip, ipaddr, ipcalc, ipcrm, ipcs, iplink, iproute, iprule, iptunnel, kbd_mode, kill, killall, killall5, klogd, length, less, linux32, linux64, linuxrc, ln, loadfont, loadkmap, logger, login, logname, logread, losetup, ls, lsattr, lsmod, lspci, lsusb, lzcat, lzma, lzop, lzopcat, makedevs, man, md5sum, mdev, mesg, microcom, mkdir, mkdosfs, mke2fs, mkfifo, mkfs.ext2, mkfs.minix, mkfs.vfat, mknod, mkpasswd, mkswap, mktemp, modinfo, modprobe, more, mount, mountpoint, mpstat, mt, mv, nameif, nbd-client, nc, netstat, nice, nmeter, nohup, nslookup, ntpd, od, openvt, passwd, patch, pgrep, pidof, ping, ping6, pipe_progress, pivot_root, pkill, pmap, poweroff, powertop, printenv, printf, ps, pscan, pwd, raidautorun, rdate, rdev, readahead, readlink, readprofile, realpath, reboot, remove-shell, renice, reset, resize, rev, rm, rmdir, rmmod, route, rpm, rpm2cpio, rtcwake, run-parts, runlevel, runsv, runsvdir, rx, script, scriptreplay, sed, seq, setarch, setconsole, setfont, setkeycodes, setlogcons, setsid, setuidgid, sh, sha1sum, sha256sum, sha512sum, showkey, slattach, sleep, smemcap, softlimit, sort, split, start-stop-daemon, stat, strings, stty, su, sulogin, sum, sv, svlogd, swapoff, swapon, switch_root, sync, sysctl, syslogd, tac, tail, tar, tcpsvd, tee, telnet, telnetd, test, tftp, tftpd, time, timeout, top, touch, tr, traceroute, traceroute6, true, tty, ttysize, tunctl, udhcpc, udhcpd, udpsvd, umount, uname, unexpand, uniq, unix2dos, unlzma, unlzop, unxz, unzip, uptime, usleep, uudecode, uuencode, vconfig, vi, vlock, volname, wall, watch, watchdog, wc, wget, which, whoami, xargs, xz, xzcat, yes, zcat, zcip

Nous pouvons observer que la couverture des applications est très large. Certaines nous serons indispensables dès le démarrage.

  • init : lors du boot, le kernel cherche à démarrer un processus /bin/init (il recherche également dans /sbin et /etc). Il nous faut donc créer un lien symbolique sur busybox qui porte ce nom. Ce processus init va consulter le fichier /etc/inittab et agir en conséquence. En l'absence de ce fichier, il commencera par lancer le script /etc/init.d/rcS.
  • sh : le script /etc/init.d/rcS est un script shell. Il commence par la ligne shebang "! /bin/sh". Il faut donc qu'un lien de ce nom existe et pointe vers busybox également.
  • mount : le script de démarrage doit commencer par monter le pseudo-système de fichier proc. Pour cela, la commande mount doit être disponible comme lien vers busybox.

Ensuite, l'exécutable busybox va pouvoir créer les liens sur lui-même avec tous les noms mentionnés plus haut. Installons les quelques fichiers indispensables.

[busybox-1.18.4]$ su
Password:
[busybox-1.18.4]# cp  busybox  /media/root/bin/
[busybox-1.18.4]# cd  /media/root/bin/
[bin]# ln busybox init
[bin]# ln busybox sh
[bin]# ln busybox mount
[bin]#

Puis copions le fichier rcS (à télécharger ici) sans oublier de le rendre exécutable ! (le contenu du script est décrit plus bas)

[bin]# cd /media/root/etc/init.d
[init.d]# cp  ~/rcS  .
[init.d]# chmod  +x  rcS
[init.d]#

Il nous faut également installer les bibliothèques dynamiques nécessaires pour le fonctionnement de Busybox. Nous allons procéder de manière un peu excessive en copiant toutes les bibliothèques fournies par la chaîne de compilation. Dans le cas d'un système plus restreint, nous sélectionnerions précisément les bibliothèques indispensables, avec l'aide de la commande arm-linux-ldd se trouvant dans notre toolchain.

[lib]# cp -Rdp /cross-arm-linux/usr/arm-linux/sysroot/lib/*  .
[lib]#

Le fichier rcS contient les lignes suivantes.

#! /bin/sh

mount none  /proc  -t proc
mount none  /sys   -t sysfs
mount /     -o rw,remount

busybox --install

mdev -s
echo /sbin/mdev > /proc/sys/kernel/hotplug

La première ligne indique quel est l'interpréteur à utiliser pour traiter ce script. Le fichier /bin/sh existe, nous l'avons créé sous forme de lien. Ensuite on monte les deux pseudo-systèmes de fichier /proc et /sys, puis on remonte le système de fichier original en lecture et écriture (pendant le boot, le noyau le monte en lecture seule).

Nous pouvons alors demander à Busybox de créer les liens sur son propre fichier exécutable avec les noms de tous les utilitaires qu'il sait représenter.

L'utilitaire mdev que nous démarrons ensuite a pour rôle de créer les fichiers spéciaux de /dev représentant tous les périphériques détectés par le noyau. C'est une version simplifiée du programme udevd (User Device Dameon) que l'on retrouve sur certaines distributions. Nous l'enregistrons également comme utilitaire à invoquer lors d'un hotplug, la détection d'un branchement à chaud de périphérique (par exemple une clé USB). Le script de démarrage proposé ici est vraiment minimal, nous l'étendrons dans le prochain article.

Nous pouvons démonter notre mémoire flash et l'insérer sur la Pandaboard.

[init.d]# cd /media
[media]# umount boot root
[media]#

Démarrons minicom sur notre système hôte, relié au port série de la carte Pandaboard. Et mettons cette dernière sous tension. Nous voyons sur l'écran de minicom les lignes suivantes.

Texas Instruments X-Loader 1.5.0 (May  3 2011 - 07:49:31)
[...]
U-Boot 2011.03-00398-ga621b16-dirty (May 03 2011 - 08:42:19)
[...]
## Booting kernel from Legacy Image at 82000000 ...
   Image Name:   Linux-2.6.39-rc5-cpb
[...]
Starting kernel ...
Uncompressing Linux... done, booting the kernel.
[    0.000000] Initializing cgroup subsys cpu
[    0.000000] Linux version 2.6.39-rc5-cpb (cpb@tr-a-0) (gcc version 4.3.5 (Buildroot 2010.08) ) #1 SMP Tue May 3 11:14:47 1
[    0.000000] CPU: ARMv7 Processor [411fc092] revision 2 (ARMv7), cr=10c5387f
[...]
[    3.794860] smsc95xx v1.0.4
[    3.922637] smsc95xx 1-1.1:1.0: eth0: register 'smsc95xx' at usb-ehci-omap.0-1.1, smsc95xx USB 2.0 Ethernet, b6:f0:b5:2e:9

Please press Enter to activate this console.

Pressons la touche "Entrée", afin d'obtenir le prompt du shell de Busybox

/ # ls
bin         home        lost+found  root        tmp
dev         lib         mnt         sbin        usr
etc         linuxrc     proc        sys         var
/ # uname -a
Linux (none) 2.6.39-rc5-cpb #1 SMP Tue May 3 11:14:47 CEST 2011 armv7l GNU/Linux
/ # cat /proc/cpuinfo
Processor       : ARMv7 Processor rev 2 (v7l)
processor       : 0
BogoMIPS        : 2007.19

processor       : 1
BogoMIPS        : 1965.18

Features        : swp half thumb fastmult vfp edsp thumbee neon vfpv3
CPU implementer : 0x41
CPU architecture: 7
CPU variant     : 0x1
CPU part        : 0xc09
CPU revision    : 2

Hardware        : OMAP4 Panda board
Revision        : 0020
Serial          : 0000000000000000
/ #

Nous voyons bien que nous sommes connectés sur une carte Pandaboard ; les deux coeurs du processeur OMAP 4 sont visibles. Observons les liens que Busybox a créé sur lui-même.

/ # ls /bin/
[              dumpkmap       kbd_mode       pmap           tail
[[             dumpleases     kill           powertop       tar
add-shell      echo           killall        printenv       tcpsvd
addgroup       ed             killall5       printf         tee
adduser        egrep          length         ps             telnet
arping         eject          less           pscan          test
ash            env            linux32        pwd            tftp
awk            envdir         linux64        readahead      tftpd
base64         envuidgid      ln             readlink       time
basename       ether-wake     logger         realpath       timeout
bbconfig       expand         login          remove-shell   top
beep           expr           logname        renice         touch
bunzip2        false          ls             reset          tr
busybox        fdflush        lsattr         resize         traceroute
bzcat          fdformat       lspci          rev            traceroute6
bzip2          fgconsole      lsusb          rm             true
cal            fgrep          lzcat          rmdir          tty
cat            find           lzma           rpm            ttysize
catv           flock          lzop           rpm2cpio       udpsvd
chat           fold           lzopcat        rtcwake        umount
chattr         free           md5sum         run-parts      uname
chgrp          fsync          mesg           runsv          unexpand
chmod          ftpget         microcom       runsvdir       uniq
chown          ftpput         mkdir          rx             unix2dos
chpst          fuser          mkfifo         script         unlzma
chrt           getopt         mknod          scriptreplay   unlzop
chvt           grep           mkpasswd       sed            unxz
cksum          gunzip         mktemp         seq            unzip
clear          gzip           more           setarch        uptime
cmp            hd             mount          setkeycodes    usleep
comm           head           mountpoint     setsid         uudecode
cp             hexdump        mpstat         setuidgid      uuencode
cpio           hostid         mt             sh             vi
crontab        hostname       mv             sha1sum        vlock
cryptpw        hush           nc             sha256sum      volname
cttyhack       id             netstat        sha512sum      wall
cut            ifplugd        nice           showkey        watch
date           init           nmeter         sleep          wc
dc             install        nohup          smemcap        wget
dd             ionice         nslookup       softlimit      which
deallocvt      iostat         od             sort           whoami
delgroup       ip             openvt         split          xargs
deluser        ipaddr         passwd         stat           xz
df             ipcalc         patch          strings        xzcat
diff           ipcrm          pgrep          stty           yes
dirname        ipcs           pidof          su             zcat
dmesg          iplink         ping           sum
dnsdomainname  iproute        ping6          sv
dos2unix       iprule         pipe_progress  sync
du             iptunnel       pkill          tac
/ # ls /sbin/
acpid              fsck.minix         man                route
adjtimex           ftpd               mdev               runlevel
arp                getty              mkdosfs            setconsole
blkid              halt               mke2fs             setfont
blockdev           hdparm             mkfs.ext2          setlogcons
bootchartd         httpd              mkfs.minix         slattach
brctl              hwclock            mkfs.vfat          start-stop-daemon
chpasswd           ifconfig           mkswap             sulogin
chroot             ifdown             modinfo            svlogd
crond              ifenslave          modprobe           swapoff
depmod             ifup               nameif             swapon
devmem             inetd              nbd-client         switch_root
dhcprelay          init               ntpd               sysctl
dnsd               insmod             pivot_root         syslogd
fakeidentd         klogd              poweroff           telnetd
fbset              loadfont           raidautorun        tunctl
fbsplash           loadkmap           rdate              udhcpc
fdisk              logread            rdev               udhcpd
findfs             losetup            readprofile        vconfig
freeramdisk        lsmod              reboot             watchdog
fsck               makedevs           rmmod              zcip
/ #

Nous pouvons également voir les fichiers spéciaux qui ont été créé par mdev.

/ # ls /dev/
console             ptyvf               ttydd
cpu_dma_latency     ptyw0               ttyde
full                ptyw1               ttydf
i2c-1               ptyw2               ttye0
i2c-2               ptyw3               ttye1
i2c-3               ptyw4               ttye2
i2c-4               ptyw5               ttye3
kmem                ptyw6               ttye4
kmsg                ptyw7               ttye5
loop0               ptyw8               ttye6
loop1               ptyw9               ttye7
loop2               ptywa               ttye8
loop3               ptywb               ttye9
loop4               ptywc               ttyea
loop5               ptywd               ttyeb
loop6               ptywe               ttyec
loop7               ptywf               ttyed
mem                 ptyx0               ttyee
mice                ptyx1               ttyef
mmcblk0             ptyx2               ttyp0
mmcblk0p1           ptyx3               ttyp1
mmcblk0p2           ptyx4               ttyp2
network_latency     ptyx5               ttyp3
network_throughput  ptyx6               ttyp4
null                ptyx7               ttyp5
psaux               ptyx8               ttyp6
ptmx                ptyx9               ttyp7
ptya0               ptyxa               ttyp8
ptya1               ptyxb               ttyp9
ptya2               ptyxc               ttypa
[...]
ptyu7               ttyc5               ttyzf
ptyu8               ttyc6               ubi_ctrl
ptyu9               ttyc7               urandom
ptyua               ttyc8               usbdev1.1
ptyub               ttyc9               usbdev1.2
ptyuc               ttyca               usbdev1.3
ptyud               ttycb               usbdev2.1
ptyue               ttycc               usbmon0
ptyuf               ttycd               usbmon1
ptyv0               ttyce               usbmon2
ptyv1               ttycf               vcs
ptyv2               ttyd0               vcs1
ptyv3               ttyd1               vcs2
ptyv4               ttyd2               vcs3
ptyv5               ttyd3               vcs4
ptyv6               ttyd4               vcsa
ptyv7               ttyd5               vcsa1
ptyv8               ttyd6               vcsa2
ptyv9               ttyd7               vcsa3
ptyva               ttyd8               vcsa4
ptyvb               ttyd9               watchdog
ptyvc               ttyda               zero
ptyvd               ttydb
ptyve               ttydc
/ # /sbin/halt 
swapoff: /etc/fstab: No such file or directory
The system is going down NOW!
Sent SIGTERM to all processes
Sent SIGKILL to[  973.315673] System halted.

Notez bien que Busybox a créé les liens et les fichiers spéciaux ci-dessus directement sur la carte flash. Si nous la ré-insérons dans l'hôte de développement, nous observerons que tous ces fichiers existent à présent dans les répertoires mentionnés.

Conclusion

Nous avons installé une base minimale pour notre système Linux, et obtenu un accès par un shell sur le port série. Dans le prochain article, nous permettrons une connexion réseau sur notre carte, en utilisant les protocoles telnet et ssh, avant d'offrir une interface via un navigateur web par protocole http.

Capture Minicom sur Pandaboard