La Pandaboard au poteau de torture – 4 – Timers Xenomai

Publié par cpb
Juil 09 2012

La Pandaboard au poteau de torture - 4Dans les précédents articles nous avons observé les limites de performance des timers Linux et Linux-rt sur une Pandabaord. Cette fois nous allons comparer ces résultats avec ceux que nous obtenons sous Xenomai.

Aurons-nous une meilleure stabilité des tâches périodiques ?

Environnement d’expérimentation

Nous devons tout d’abord compiler un noyau Linux pour la Pandaboard après lui avoir appliqué le patch Xenomai. Ceci est relativement simple, je l’avais déjà décrit dans un article précédent.

Voici brièvement les étapes à suivre, nous considérons que la partition de boot de la Pandaboard est montée dans notre arborescence dans /media/Boot et sa partition racine dans /media/Root.

          Télécharger Xenomai
[~]$ git clone http://git.xenomai.org/xenomai-2.6.git xenomai
Cloning into 'xenomai'...
  [...]
          Télécharger et décompresser Linux
[~]$ wget http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.38.8.tar.bz2
  [...]
[~]$ tar xjf ~/Data/Repository/linux-2.6.38.8.tar.bz2

          Appliquer le patch Adeos sur Linux
[~]$ xenomai/scripts/prepare-kernel.sh --linux=linux-2.6.38.8/ --ipipe=xenomai/ksrc/arch/arm/patches/adeos-ipipe-2.6.38.8-arm-1.18-08.patch --arch=arm
patching file arch/arm/Kconfig
patching file arch/arm/boot/compressed/head.S
  [...]

          Configurer et compiler Linux et ses modules
[~]$ cd linux-2.6.38.8
[linux-2.6.38.8]$ wget https://www.blaess.fr/christophe/files/article-2012-07-09/config-linux-2.6.38.8-xenomai
  [...]
[linux-2.6.38.8]$ mv config-linux-2.6.38.8-xenomai .config
[linux-2.6.38.8]$ make ARCH=arm oldconfig
  [...]
[linux-2.6.38.8]$ make ARCH=arm CROSS_COMPILE=~/cross-panda/usr/bin/arm-linux- uImage
  [...]
Load Address: 80008000
Entry Point:  80008000
  Image arch/arm/boot/uImage is ready
[linux-2.6.38.8]$ make ARCH=arm CROSS_COMPILE=~/cross-panda/usr/bin/arm-linux- modules
  [...]
  IHEX2FW firmware/keyspan_pda/keyspan_pda.fw
  IHEX2FW firmware/keyspan_pda/xircom_pgs.fw
[linux-2.6.38.8]$ 

        Copier le noyau sur la partition de boot de la Pandaboard
[linux-2.6.38.8]$ cp arch/arm/boot/uImage /media/Boot/

        Copier les modules sur la partition racine de la Pandaboard
[linux-2.6.38.8]$ make ARCH=arm INSTALL_MOD_PATH=/media/Root  modules_install
  [...]
  INSTALL /media/Root/lib/firmware/keyspan_pda/keyspan_pda.fw
  INSTALL /media/Root/lib/firmware/keyspan_pda/xircom_pgs.fw
  DEPMOD  2.6.38.8-xenomai
[linux-2.6.38.8]$ 

        Compiler et installer les bibliothèques de Xenomai
[linux-2.6.38.8]$ cd ../xenomai
[xenomai]$ PATH=$PATH:~/cross-panda/usr/bin/
[xenomai]$ ./configure --prefix=/media/Root/usr/xenomai --host=arm-linux CFLAGS='-march=armv7-a' LDFLAGS='-march=armv7-a' --enable-smp
  [...]
config.status: linking ./include/asm-generic to src/include/asm-generic/xenomai
config.status: executing depfiles commands
config.status: executing libtool commands
[xenomai]$ make
  [...]
[xenomai]$ make install  (peut nécessiter les droits root)

Avant de démarrer notre Pandaboard sur Xenomai, il nous faut un outil de test des timers. Voici un programme, calqué sur celui que nous avons employé pour Linux et Linux-Rt, qui emploie l’API native de Xenomai.

fluctuations-timer-xenomai.c: 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>

#include <rtdk.h>
#include <native/task.h>
#include <native/timer.h>

static RTIME periode;
static int nb_mesures = 0; // Nombre de mesures entre deux affichages

void fonction_periodique (void * unused)
{
    RTIME precedent = 0; 
    RTIME heure = 0;
    RTIME duree;

    int mesure = 0;
    long long int min = -1;
    long long int max = 0;
    long long int max_max = 0;
    double sum = 0.0;

    periode = periode * 1000; // en ns
    rt_task_set_periodic(NULL, TM_NOW, periode);
    while(1) {
        rt_task_wait_period(NULL);
        heure = rt_timer_read();
        if (mesure != 0) { 
            duree = heure - precedent;
            duree = duree / 1000; // en microsecondes
            if ((min == -1) || (duree < min))
                min = duree;
            if (duree > max) {
                max = duree;
                if (max_max < max)
                    max_max = max;
            }
            sum += duree;
        }
        precedent = heure;
        mesure ++;
        if (mesure >= nb_mesures) {
            rt_fprintf(stdout, "[%ld] Min.= %lld, Moy.= %.1lf, Max.= %lld,  Max.Max.= %lldn",
                (long int)(heure/1000000000), min, sum / nb_mesures, max, max_max);
            min = -1;
            max = 0;
            sum = 0.0;
            mesure = 0;
            rt_print_flush_buffers();
        }
    }
}

int main (int argc, char * argv[])
{
    int cpu = 0;
    RT_TASK task;
    int err;

    mlockall(MCL_CURRENT|MCL_FUTURE);
    rt_print_auto_init(1);

    if ((argc < 2)
     || (sscanf(argv[1], "%lld", & periode) != 1)
     || ((argc == 3) && (sscanf(argv[2], "%d", & cpu) != 1))) {
        fprintf(stderr, "usage: %s periode_us [cpu]n", argv[0]);
        exit(EXIT_FAILURE);
    }

    if ((periode <= 0) || (periode > 2000000)) {
        fprintf(stderr, "%s: La periode doit etre dans [1, 2000000]n", argv[0]);
        exit(EXIT_FAILURE);
    }

    if ((cpu < 0) || (cpu >= sysconf(_SC_NPROCESSORS_ONLN))) {
        fprintf(stderr, "%s: Le CPU doit etre dans [0, %ld]n", argv[0], sysconf(_SC_NPROCESSORS_ONLN) - 1);
        exit(EXIT_FAILURE);
    }

    setvbuf(stdout, NULL, _IONBF, 0);

    nb_mesures = 5000000 / periode;
    if (nb_mesures == 0) {
        fprintf(stderr, "periode trop longuen");
        exit(EXIT_FAILURE);
    }

    if ((err = rt_task_spawn(& task, "Fluctuation-RT-TIMER", 0, 99, T_JOINABLE | T_CPU(cpu),
                             fonction_periodique, NULL)) != 0) {
        fprintf(stderr, "rt_task_spawn: %sn", strerror(-err));
        exit(EXIT_FAILURE);
    }

    rt_task_join(& task);
    return EXIT_SUCCESS;
}

On passe au programme deux arguments : la période du timer (en microsecondes) et le numéro du CPU sur lequel il doit s’exécuter. Pour compiler ce code, il nous faut un Makefile qui gère les options de compilation de Xenomai.

Makefile :
.PHONY: all
all: fluctuations-timer-xenomai

XENOCONFIG?=/usr/xenomai/bin/xeno-config

fluctuations-timer-xenomai:fluctuations-timer-xenomai.c
	$(shell $(XENOCONFIG) --cc) $(shell $(XENOCONFIG) --skin=native --cflags) $(shell $(XENOCONFIG) --skin=native --ldflags) -L $(shell  $(XENOCONFIG) --skin=native --libdir) -o fluctuations-timer-xenomai fluctuations-timer-xenomai.c -lnative -lxenomai

.PHONY: clean
clean::
	rm -f  *.o  .*.o  .*.o.* *.ko  .*.ko  *.mod.* .*.mod.* .*.cmd *~
	rm -f Module.symvers Module.markers modules.order 
	rm -rf .tmp_versions
	rm -f fluctuations-timer-xenomai

Les paramètres de compilation seront fournis par le script xeno-config qui se trouve dans le répertoire d’installation de Xenomai (sur la partition Root de la carte SD).

$ make XENOCONFIG=/media/Root/usr/xenomai/bin/xeno-config
arm-linux-gcc -I/media/Root/usr/xenomai/include -D_GNU_SOURCE -D_REENTRANT -D__XENO__ -lnative -L/media/Root/usr/xenomai/lib -lxenomai -lpthread -lrt -L /media/Root/usr/xenomai/lib -o fluctuations-timer-xenomai fluctuations-timer-xenomai.c -lnative -lxenomai
$ cp fluctuations-timer-xenomai /media/Root/usr/local/bin/
$

Enfin, pour lancer le programme en boucle, j’ai réutilisé le script mis au point précédemment, en remplaçant le nom du fichier de résultat

resultat-fluctuations-timer-linux-${COMPTEUR}.txt

en

resultat-fluctuations-timer-xenomai-${COMPTEUR}.txt

et l’invocation

chrt -f 99 /usr/local/bin/fluctuations-timer 100

par l’appel du programme ci-dessus ainsi

/usr/local/bin/fluctuations-timer-xenomai 100 0

Nous copions donc le script modifié dans le système de fichiers sur la carte SD.

$ wget https://www.blaess.fr/christophe/files/article-2012-07-09/lance-test-fluctuations-timer-xenomai.sh
[...]
$ cp lance-test-fluctuations-timer-xenomai.sh /media/Root/usr/local/bin/
$ umount /media/*oot
$

Lancement du test

J’ai tout d’abord fait tourner le test sur le cœur 1, en modifiant dans le script le second argument sur la ligne invoquant le programme fluctuations-timer-xenomai. Le système était soumis à une très forte charge en appels-système, en processus, et en interruptions externes. Nous avons vu dans l’article précédent que les traitements des interruptions étaient, par défaut, réalisés sur le cœur 0. Aussi notre programme s’exécutant sur le cœur 1 est légèrement préservé de leurs effets.

Le premier test s’est déroulé pendant une quinzaine d’heures (puis j’ai dû interrompre l’expérience car il me fallait débrancher l’alimentation). J’ai alors réalisé un graphique avec le même script que les expériences sous Linux et Linux-rt

Fluctuations d'un timer Xenomai - Jour 1 (première version)

Quelle surprise ! Alors qu’avec Linux vanilla et Linux-rt les fluctuations s’étendaient jusqu’à 6 millisecondes, les résultats sous Xenomai montrent des timers dont la pire variation ne dépasse pas 128 microsecondes au lieu de 100 microsecondes, soient 28 microsecondes de jitter. J’ai dû modifier le script de création du graphique en zoomant 20 fois l’axe des ordonnées.

Fluctuations d'un timer Xenomai - Jour 1 (seconde version)

On peut remarquer sur ce graphique des périodes nettes où les valeurs fluctuent dans des intervalles différents. Je suppose qu’il s’agit des moments où j’ajoutais des ping flood supplémentaires depuis d’autres machines.

Encouragé par ces premiers résultats, j’ai relancé le test pendant deux jours, en le plaçant cette fois sur le cœur zéro, là où les interruptions sont traitées. Les variations sont légèrement plus importantes puisque l’on atteint un maximum de 151 microsecondes au lieu de 100 microsecondes. Voici les graphiques correspondants.

Fluctuations d'un timer Xenomai - Jour 2

Fluctuations d'un timer Xenomai - Jour 3

Conclusion

Ce test met en relief les performances de Xenomai, comparées à celles de Linux et Linux-rt. Les tâches temps réel de Xenomai s’exécutent avec une fiabilité beaucoup plus grande en ce qui concerne l’instant de déclenchement. Nous voyons que la fluctuation maximale obtenue sur une Pandaboard avec un timer Xenomai est de 51 microsecondes (j’ai prolongé encore l’expérience depuis, et obtenu la même limite maximale). Ceci est cohérent avec les résultats fournis par l’outil latency livré avec Xenomai, qui nous indique ici une latence maximale de 51.579 microsecondes sur un test à haute charge de 6 heures.

[Panda]# export LD_LIBRARY_PATH=/usr/xenomai/lib
[Panda]# /usr/xenomai/bin/latency -p 100 -T 21700
== Sampling period: 100 us
== Test mode: periodic user-mode task
== All results in microseconds
warming up...
[...]
RTD|      3.507|      7.674|     27.321|       0|     0|      2.908|     51.579
RTD|      3.517|      7.724|     30.472|       0|     0|      2.908|     51.579
---|-----------|-----------|-----------|--------|------|-------------------------
RTS|      2.908|      7.797|     51.579|       0|     0|    06:00:00/06:00:00
[Panda]#

Pour en savoir plus:

Une réponse

  1. Corneliu Zuzu dit :

    Christopher,

    Thank you very much for your well-written, comprehensive articles! I am a complete beginner in what concerns both hardware and real-time and currently experimenting with my Raspberry Pi 2. And now I have finally understood what ‘latency’ does after wrapping my head around it yesterday and today. Your articles make my task so much easier and pleasurable, so, again, thank you!

    Corneliu.

URL de trackback pour cette page