Archives de la catégorie ‘Temps-réel’

Options de compilation pour Linux industriel (1)

Embarqué, Formations, Linux, Temps-réel | Publié par cpb
fév 19 2012

make menuconfig

J’ai remarqué une question récurrente, tant durant mes sessions de formation sur Linux industriel qu’au cours de prestations d’ingénierie concernant des systèmes temps-réel ou embarqués : « Quelles sont les options du noyau qui influent sur [un sujet donné] ? » Le sujet en question a généralement trait aux mécanismes d’ordonnancement, à la vitesse de boot, à la taille du code produit, etc.

Donner une réponse exhaustive est impossible mais au fil du temps, j’ai constitué une petite liste des options les plus importantes à vérifier lors de la compilation du noyau, pour optimiser certains aspects. Ces paramètres sont parfois complémentaires parfois antagonistes. Par exemple le souci d’économie énergétique sur un système embarqué autonome est aux antipodes des problématiques de temps de réponse aux interruptions en temps-réel.

Bien entendu, je ne traite pas ici des options permettant au kernel de fonctionner (reconnaître les périphériques présents, disposer des protocoles réseau, gérer les types de systèmes de fichiers nécessaires…) Je considère qu’elles sont indispensables pour que le noyau démarre. Ce qui m’intéresse ici, ce sont les options de compilation susceptibles d’améliorer le comportement dans un environnement industriel.

Je prends comme version de référence le noyau linux-3.2.tar.bz2 et je vais passer en revue les principaux menus de configuration que l’on peut observer avec  « make menuconfig » « make xconfig » « make gconfig » etc. Les paramètres décrits ci-dessous proviennent d’une configuration pour processeur x86, mais je décrirai également quelques options spécifiques à d’autres architectures.

 Menu General Setup

  • Cross-compiler tool prefix : lors d’une compilation croisée (pour un autre processeur), on indiquera ici le préfixe nécessaire pour trouver la cross-toolchain (que l’on peut générer de différentes manières). Par exemple /opt/cross-arm/usr/bin/arm-linux- indiquera que les appels au compilateur gcc devront se transformer en /opt/cross-arm/usr/bin/arm-linux-gcc, et ainsi de suite pour les autres outils de compilation (as, ld, ar, etc.). Il s’agit d’une option que l’on peut également surcharger en remplissant la variable d’environnement CROSS_COMPILE avant d’appeler make.
  • Kernel compression mode : pour optimiser la vitesse de boot on préférera une compression LZO, pour réduire au maximum la taille du noyau en mémoire flash (avant transfert dans la Ram) on choisira LZMA, pour avoir un équilibre entre ces deux paramètres j’utiliserais XZ. Sur certaines architectures, toutes les compressions ne sont pas disponibles.
  • Support for paging of anonymous memory (swap) : sur la plupart des systèmes industriels, l’utilisation de swap (partition ou fichier sur disque dans lesquels on transfère temporairement une partie de la mémoire Ram en cas de saturation) est proscrite. En effet le système de fichiers est généralement en mémoire flash qui ne supporte pas les écritures intensives. En outre cela diminue la prédictibilité des temps d’accès. Je désactive habituellement cette option afin de ne pas risque d’activer malencontreusement le swap ultérieurement.
  • System V IPC et Posix Message Queues : ces mécanismes de communication entre processus (files de messages, sémaphores, mémoire partagée) sont souvent employés dans les systèmes industriels, sauf si le code applicatif est constitué seulement de threads regroupé dans un unique processus. Généralement j’active ces deux options.
  • BSD Process accounting, Export task/process statistics, et Auditing Support : si ces options d’instrumentation peuvent être utiles pendant la phase de mise au point du code, elles n’ont plus lieu d’être activées sur un système en production. Je conseille de les désactiver (ce qui peut nécessiter d’intervenir dans les menus Security options et Virtualization que nous verrons dans d’autres articles).
  • Sous-menu RCU Subsystem : L’option Enable RCU priority boosting permet de limiter les inversions de priorité liées aux mécanismes de synchronisation Read-Copy-Update. Ceci peut être utile – bien que d’un effet limité – dans un système temps-réel à condition de mettre une valeur élevée (99 par exemple) pour Real-time priority to boost RCU readers to.
  • Kernel .config support et Enable access to .config through /proc/config.gz : ces deux options sont à mon avis très utiles. Elles permettent d’embarquer dans l’image du noyau compilé sa propre configuration (réalisée avec make menuconfig). La mise au point d’un système embarqué ou temps-réel peut nécessiter des dizaines de compilations et tests successifs en modifiant peu à peu les options décrites ici. Il n’est pas simple de garder une trace exacte de toutes les modifications apportées et la discipline nécessaire pour sauvegarder et documenter chaque fois le fichier .config vient souvent à manquer après des heures de frustrations et d’échecs successifs. Le fait que chaque noyau compilé puisse restituer sa propre configuration nous évite de gérer les versions et permet facilement de comparer les options utilisées.
  • Sous-menu Control Group support : la plupart des options de ce sous-menu peuvent être utiles pour gérer finement l’ordonnancement des tâches et la répartition des ressources (CPU, mémoire, entrées-sorties du sous-système Block, etc.). Ces paramètres peuvent servir à gérer des tâches en temps-partagé mais également en temps-réel (notamment Group CPU scheduler –> Group scheduling for SCHED_RR/FIFO).
  • J’ai évoqué l’option Automatic process group scheduling dans un article précédent. Elle active le regroupement automatique des processus rattachés au même terminal pour leur donner une part de CPU équivalente aux autres groupes en ordonnancement temps-partagé.
  • Sur de nombreux systèmes embarqués l’option Initial RAM filesystem and RAM disk sera nécessaire car elle permettra de démarrer avec un système de fichiers monté en mémoire Ram (quitte à accéder ensuite à des partitions différentes sur mémoire flash). On choisira en principe le même type de compression (LZMA, XZ, LZO…) que celui de l’option Kernel compression mode vue plus haut, en préférant un volume réduit ou une décompression plus rapide suivant les cas.
  • L’option Optimize for size sera activée sur les systèmes embarqués pour demander au compilateur de réduire la taille de l’image du noyau ; de même on désactivera Enable full-sized data structures for core qui se trouve un peu plus bas
  • Le sous-menu Configure standard kernel features (expert users) n’est parfois accessible que si l’option Embedded System (plus bas) est activée – ce qu’il faut faire. On peut désactiver toutes les options du sous-menu sur la plupart des systèmes embarqués. Le noyau deviendra totalement silencieux (pas de messages de trace avec printk(), pas d’avertissement avec WARN(), pas d’indication d’erreur interne avec BUG()) et ne nous fournira plus d’éléments de diagnostic. En revanche sa taille sera réduite et le boot sensiblement plus rapide.
  • J’active systématiquement Enable futex support pour bénéficier des mutex rapides (verrouillage sans passage par le noyau s’il n’y a pas de contention, pour plus de détails voir cet article précédent).
  • Toutes les options Profiling support, Kprobes, Optimize trace point call sites, et GCOV-based kernel profiling devront être désactivées sur le noyau final. Elles peuvent être trés utiles durant la mise au point du système, mais ne feraient que charger inutilement l’image cible.

Les options qui n’ont pas été mentionnées seront activées ou non en fonction de l’utilisation de votre système. Si l’appel-système signalfd() par exemple n’est jamais employé, l’option Enable signalfd() system call n’a pas vraiment d’intérêt. Néanmoins on ne gagnera pas grand-chose à la désactiver.

 

Voici un premier tableau récapitulatif des options à activer (Y) ou désactiver (N) en fonction des optimisations recherchées. Les options non mentionnées ici n’ont pas d’influence directe sur les aspects embarqués (taille mémoire et vitesse de boot) ou les performances temps-réel.

Option Optimisation
pour
embarqué
Optimisation
pour
temps-réel
Kernel compression mode LZMA / LZO
Support for paging of anonymous memory N N
BSD Process accounting N N
Open by fhandle syscalls N
Export statistics through netlink N
Auditing support N N
RCU Subsystem -> Enable tracing for RCU N N
RCU Subsystem -> Enable RCU priority boosting Y
Kernel .config support Y
Enable access to .config through /proc/config.gz Y
Control Group Support -> Example debug cgroup… N N
Control Group Support -> Freezer cgroup subsystem Y
Control Group Support -> Device controller… Y
Control Group Support -> Cpuset support Y
Control Group Support -> Group CPU -> …SCHED_RR/FIFO Y
Control Group Support -> Block IO controller Y Y
Control Group Support -> Enable Block IO controller debugging N N
Namespace support N
Automatic process group scheduling Y
Initial RAM filesystem and RAM disk Y
Support initial RAM disk cmopressed using LZMA Y
Support initial RAM disk cmopressed using XZ Y
Support initial RAM disk cmopressed using LZO Y
Optimize for size Y
Configure standard kernel features -> Enable 16-bit UID… N
Configure standard kernel features -> Sysctl syscall support N
Configure standard kernel features -> Enable support for printk N
Configure standard kernel features -> BUG() support N
Configure standard kernel features -> Enable Elf core dumps N
Enable full-sized data structures for core N
Enable futex support Y
Enable AIO support Y
Embedded system Y
Kernel performance… -> Kernel performance counters N
Kernel performance… -> Debug; use vmalloc to… N N
Enable VM event counters for /proc/vmstat N N
Profiling support N N
Kprobes N N
Optimize trace point call sites N N
GCOV-based kernel profiling -> Enable gcov-based … N N

Nous examinerons les autres menus dans les prochains articles…

Tous les commentaires, rermarques, corrections, sont les bienvenus…

Linux 3.2 – CFS CPU Bandwidth

Linux, Temps-réel | Publié par cpb
jan 07 2012

(English translation here)

Linus Torvalds a publié le noyau Linux 3.2 il y a deux jours. Ce dernier contient comme d’habitude de nombreux ajouts. L’un d’eux a attiré mon attention et j’ai souhaité observer son fonctionnement, il s’agit du contrôleur de consommation CPU pour l’ordonnanceur CFS.

Il existait déjà de nombreux moyens de régler le pourcentage de CPU dont pouvait disposer une tâche par rapport aux autres pour l’ordonnancement temps-partagé (entre autres avec l’appel-système setpriority(), la commande en ligne nice, ou les paramètres du systèmes de fichiers /sys/fs/cgroup/cpu). On pouvait ainsi attribuer facilement 25% du temps processeur à une tâche et 75% à une autre par exemple. Mais jusqu’alors, si la seconde se terminait, la première disposait alors de 100% du temps CPU.

Autrement dit, la « bande passante CPU » d’une tâche seule active était toujours de 100%. Il est à présent possible de modifier ceci afin de lui attribuer une portion plus réduite du temps processeur, même lorsqu’elle est seule.

Quel peut-être l’intérêt de réduire la consommation CPU instantanée d’un processus ? Pour les systèmes courants, il faut bien l’avouer, cet intérêt est réduit. Mais il en va autrement pour certains gros serveurs où l’on facture le temps processeur consommé mais également la puissance CPU mise à disposition instantanément. Dans ces environnements il est très intéressant de limiter la puissance CPU dont disposera un processus quelle que soit la charge globale du système.

Un autre intérêt de cette limitation est de réguler la disponibilité du CPU en évitant les pointes de puissance et les brusques ralentissements. On peut imaginer encore un environnement hôte sur lequel on est amené à faire fonctionner simultanément plusieurs – disons 4 – émulateurs (de type Qemu par exemple). En limitant à 25% la puissance CPU accordée à chacun d’entre-eux, le comportement d’un système virtuel ne dépendra pas de la présence ou non d’un autre émulateur sur l’hôte.

Mise en oeuvre

Il existe une nouvelle option de compilation que l’on trouve dans le menu de configuration du kernel dans l’arborescence suivante : « General Setup » – « Control Group support » – « Group CPU scheduler » – « CPU bandwith provisioning for FAIR_GROUP_SCHED« . Il est nécessaire d’activer toutes les options de ce chemin.

Voici un fichier de configuration générique Linux 3.2 pour PC (provenant d’une distribution Ubuntu 11.10) avec l’option « CPU bandwidth provisioning » activée.

Après compilation et redémarrage sur le nouveau kernel, nous observons la présence d’un nouveau fichier  /proc/sys/kernel/sched_cfs_bandwidth_slice_us qui indique la durée des tranches de temps employées lorsqu’une tâche bénéficie d’une puissance CPU reposant sur plusieurs processeurs. Par défaut cette valeur est de 5 millisecondes.

# cat /proc/sys/kernel/sched_cfs_bandwidth_slice_us
5000
#

Pour accéder au paramétrage de la puissance CPU accordée à un groupe ou à une tâche nous devons passer par le système de fichiers cgroup :

# mount none /sys/fs/cgroup -t tmpfs
# mkdir /sys/fs/cgroup/cpu
# mount none /sys/fs/cgroup/cpu/ -t cgroup -o cpu
# ls /sys/fs/cgroup/cpu/
cgroup.clone_children  cgroup.procs       cpu.cfs_quota_us  cpu.rt_runtime_us  cpu.stat           release_agent
cgroup.event_control   cpu.cfs_period_us  cpu.rt_period_us  cpu.shares         notify_on_release  tasks
#

Essais

Tâche unique

Pour verifier la puissance CPU accordée à une tâche nous allons créer un petit programme qui boucle indéfiniment, et affiche toutes les secondes sur sa sortie standard le nombre de boucles qu’il a pu réaliser durant la seconde écoulée.

consomme-cpu.c:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>

int main(void)
{
    long long int compteur;
    time_t debut = time(NULL);
    // Attendre un debut de seconde
    while (time(NULL) == debut)
        ;
    while (1) {
        compteur = 0;
        time (& debut);
        while (time(NULL) == debut)
            compteur ++;
        fprintf(stdout, "[%u]%lld\n", getpid(), compteur);
    }
    return 0;
}

Lançons ce processus dans un terminal et laissons-le tourner.

$ ./consomme-cpu 
          [3040]8046202
          [3040]8051003
          [3040]8038645
          [3040]8049329
          [3040]8378210
          [3040]8419106
          [3040]8416285
          [3040]8418075
          [3040]8415878
          [3040]8419727
          [3040]8416073
          [3040]8417343
          [3040]8414809
          ...

Il réalise environ huit millions de boucles par seconde. Dans une autre console, créons un groupe de contrôle spécifique et inscrivons notre processus.

# cd /sys/fs/cgroup/cpu/
# mkdir groupe-1
# echo 3040 > groupe-1/tasks
#

Les paramètres de contrôle de la bande passante sont par défaut les suivants.

# cat groupe-1/cpu.cfs_period_us
100000
# cat groupe-1/cpu.cfs_quota_us
-1
#

La période de régulation de la bande passante est donc de 100 millisecondes, et le quota CPU accordé à la tâche vaut -1, une valeur négative indiquant qu’aucune contrainte n’est appliquée au processus.

Nous allons modifier cette valeur pour lui accorder un quota de 25 millisecondes par périodes de 100 millisecondes.

# echo 25000 > groupe-1/cpu.cfs_quota_us
#

Le comportement du processus varie alors :

          [3040]8416132
          [3040]8417509
          [3040]4302933
          [3040]1766917
          [3040]1763016
          [3040]1798414
          [3040]1740740
          ...

Le nombre de boucles descend alors à 18 millions environ. Ré-augmentons le quota à 50%.

# echo 50000 > groupe-1/cpu.cfs_quota_us
#

Nous voyons alors le nombre de boucles par secondes augmenter à nouveau.

          [3040]1672509
          [3040]2776452
          [3040]3662061
          [3040]3777860
          [3040]3694039
          [3040]3768352
          [3040]3745732
          [3040]3822385
          ...

Restaurons-la valeur 100000 microsecondes et notre processus reprend son comportement initial.

# echo 100000 > groupe-1/cpu.cfs_quota_us
#
          [3040]3708664
          [3040]3779417
          [3040]5944852
          [3040]8051275
          [3040]8049984
          [3040]8050405
          ...

Groupe de tâches

Observons à présent le comportement lorsque nous lançons plusieurs tâches en parallèle que nous inscrivons dans le même groupe de contrôle.

          $ ./consomme-cpu & ./consomme-cpu
          [6105]8051374
          [6106]8050150
          [6106]8047698
          [6105]8046993
          [6106]8046275
          [6105]8046629
          [6106]8050943
          [6105]8044749
          [6106]8049421
          [6105]8047196
          ...

Nos tâches ont été placées sur deux processeurs (ou cœurs) distincts et arrivent ainsi à réaliser chacune 8 millions de boucles par seconde. Toutefois nous pouvons les limiter et leur attribuer un quota de temps-processeur plus faible. Par exemple 100% CPU en tout (pour les deux).

# echo 6105 > groupe-1/tasks
# echo 6106 > groupe-1/tasks
# echo 100000 > groupe-1/cpu.cfs_quota_us
#

Chacune arrive à réaliser environ 4 millions de boucles par seconde.

          [6105]8055582
          [6106]8023743
          [6105]7876233
          [6106]7598323
          [6106]3678658
          [6105]3910164
          [6105]3800836
          [6106]3753870
          [6105]3776468
          [6106]3659704
          ...

Ou encore une valeur de 150% CPU (en s’appuyant sur deux processeurs bien sûr).

# echo 150000 > groupe-1/cpu.cfs_quota_us
#
          [6105]3667414
          [6106]3831150
          [6106]3839798
          [6105]3714979
          [6105]5004119
          [6106]5851544
          [6106]5801272
          [6105]6028022
          [6105]5810675
          [6106]5749970
          [6106]5784018
          [6105]5769202
          ...

Conclusion

Les valeurs observées ci-dessus ne sont pas parfaitement stables et exactes, eu égard à l’ordonnancement temps-partagé qui prend en compte l’ensemble des tâches prêtes à s’exécuter et le comportement de chacune d’entre-elles vis-à-vis de la consommation de temps processeur.

Le contrôle de la puissance CPU disponible pour chaque groupe de tâches est à mon avis principalement intéressant pour les serveurs d’applications et les containers de machines virtuelles. Dans mon cas particulier, je pense l’employer lorsque je fais tourner des programmes de tests sur plusieurs instances de Qemu afin d’isoler chacune de la puissance CPU globalement disponible sur le serveur hôte.

<p style= »text-align: justify; »>

Xenomai 2.6.0 sur Ubuntu 11.10

Linux, Temps-réel | Publié par cpb
déc 28 2011

J’ai voulu comparer certains résultats obtenus avec Xenomai sur une carte Pandaboard avec ceux d’un PC classique. J’ai choisi un poste de travail (type bureautique) fonctionnant avec une distribution Ubuntu récente, afin de disposer d’une configuration la plus courante possible.

Installation de Xenomai 2.6.0 sur Ubuntu 11.10

La méthode d’installation est assez simple, mais je la décris ici afin de regrouper les options de compilation et les fichiers de configuration du noyau.

Préparation des sources de Linux

Les sources de Xenomai 2.6.0 et de Linux 2.6.38.8 sont préalablement téléchargées dans mon répertoire personnel (~cpb/)

[~]$ sudo -i
[sudo] password for cpb:
[~]# cd /usr/src/
[src]# tar -xjf ~cpb/xenomai-2.6.0.tar.bz2
[src]# tar -xjf ~cpb/linux-2.6.38.8.tar.bz2
[src]# mv linux-2.6.38.8 linux-2.6.38.8-xenomai
[src]# xenomai-2.6.0/scripts/prepare-kernel.sh --linux=/usr/src/linux-2.6.38.8-xenomai --adeos=xenomai-2.6.0/ksrc/arch/x86/patches/adeos-ipipe-2.6.38.8-x86-2.10-01.patch --arch=i686
patching file arch/x86/Kconfig
patching file arch/x86/include/asm/apic.h
patching file arch/x86/include/asm/apicdef.h
[...]
patching file mm/mmu_context.c
patching file mm/mprotect.c
patching file mm/vmalloc.c
[src]#

Nous disposons donc des répertoires

  • /usr/src/xenomai-2.6.0 : contenant entre autres les sources des bibliothèques de Xenomai (que nous compilerons en second lieu);
  • /usr/src/linux-2.6.38.8-xenomai : les sources du noyau Linux modifié par le patch Adeos/Xenomai.

Compilation du noyau

Je pars de la configuration du noyau Linux 2.6.38 telle qu’elle était fournie avec Ubuntu, puis je la modifierai pour ajuster certains paramètres à Xenomai.

[src]# cd /usr/src/linux-2.6.38.8-xenomai/
[linux-2.6.38.8-xenomai]# cp /boot/config-2.6.38-11-generic ./.config
[linux-2.6.38.8-xenomai]# make menuconfig

Dans le menu de configuration, il faut réaliser quelques modifications.

General setup -->
    <*> Kernel .config support
    [*]  Enable access to .config through /proc/config.gz

Ceci est juste un élément de confort afin de retrouver facilement le fichier .config du noyau (dans /proc/config.gz). Je l’utilise systématiquement, sauf dans le cas d’un système embarqué avec de fortes contraintes de taille mémoire.

Processor type and features -->
    Processor family (Core 2/newer Xeon) -->

Configurez le type de processeur en fonction de votre machine (et pas de la mienne…). Vous pouvez trouver cette information dans /proc/cpuinfo.

Processor type and features -->
    [ ] Enable -fstack-protector buffer overflow detection
Power managment and ACPI options -->
    ACPI Support -->
        < > Processor
Power managment and ACPI options -->
    < > APM
Power managment and ACPI options -->
    CPU frequency scaling -->
        [ ] CPU frequency scaling
Power managment and ACPI options -->
    [ ] Cpuidle Driver for Intel Processors
Processor type and features -->
    Preemption Model  -->
        (X) Preemptible Kernel (Low-Latency Desktop)

Le fichier .config étant préparé, nous pouvons démarrer la compilation en utilisant la méthode Debian / Ubuntu.

[linux-2.6.38.8-xenomai]# make-kpkg --append-to-version -xenomai --initrd buildpackage
exec make kpkg_version=12.036+nmu1 -f /usr/share/kernel-package/ruleset/minimal.mk debian APPEND_TO_VERSION=-xenomai  INITRD=YES
====== making target debian/stamp/conf/minimal_debian [new prereqs: ]======
[...]
test ! -f scripts/package/builddeb.kpkg-dist ||	mv -f scripts/package/builddeb.kpkg-dist scripts/package/builddeb
test ! -f scripts/package/Makefile.kpkg-dist ||	mv -f scripts/package/Makefile.kpkg-dist scripts/package/Makefile
echo done >  debian/stamp/build/buildpackage
[linux-2.6.38.8-xenomai]#

Ceci crée dans le répertoire /usr/src les packages .deb correspondant au nouveau kernel. Installons-les.

[linux-2.6.38.8-xenomai]# cd /usr/src
[src]# dpkg -i *xenomai*deb
Sélection du paquet linux-doc-2.6.38.8-xenomai précédemment désélectionné.
(Lecture de la base de données... 306802 fichiers et répertoires déjà installés.)
[...]
Paramétrage de linux-source-2.6.38.8-xenomai (2.6.38.8-xenomai-10.00.Custom) ...
dpkg : avertissement : option obsolète « --print-installation-architecture », veuillez utiliser « --print-architecture » à la place.
Examining /etc/kernel/src_postinst.d.
[src]#

Et redémarrons sur le nouveau noyau.

[src]# /sbin/reboot

Au moment du redémarrage, il est nécessaire de choisir dans le menu de Grub « Previous Linux versions » puis « Ubuntu, avec Linux 2.6.38.8-xenomai« . Une fois le système démarré, nous pouvons vérifier que tout va bien :

[~]# uname -a
Linux Logilin-A131 2.6.38.8-xenomai #1 SMP PREEMPT Wed Dec 28 11:05:33 CET 2011 i686 i686 i386 GNU/Linux
[~]# dmesg | grep I-pipe
[    0.000000] I-pipe 2.10-01: pipeline enabled.
[    2.378745] I-pipe: Domain Xenomai registered.
[~]# dmesg | grep Xenomai
[    2.378745] I-pipe: Domain Xenomai registered.
[    2.378882] Xenomai: hal/i386 started.
[    2.378923] Xenomai: scheduling class idle registered.
[    2.378930] Xenomai: scheduling class rt registered.
[    2.380254] Xenomai: real-time nucleus v2.6.0 (Movin' On) loaded.
[    2.380257] Xenomai: debug mode enabled.
[    2.380508] Xenomai: starting native API services.
[    2.380510] Xenomai: starting POSIX services.
[    2.380543] Xenomai: starting RTDM services.
[~]# cat /proc/ipipe/version
2.10-01
[~]# cat /proc/xenomai/version
2.6.0
[~]#

Compilation des bibliothèques de Xenomai

Les options de configuration sont assez sensibles pour le bon fonctionnement des applications. Celles utilisées ci-dessous correspondent à la compilation du noyau avec le fichier de configuration indiqué plus haut. Il peut s’avérer nécessaire de jouer sur les options --enable-smp --enable-x86-tsc et --enable-x86-sep pour ajuster le comportement.

L’option --enable-x86-sep sert sur les processeurs où la bibliothèque C peut utiliser le mécanisme SYSENTER/SYSEXIT pour invoquer les appels-système plutôt que le principe des trappes INT 0x80. La bibliothèque NPTL sera nécessaire pour fournir les points d’entrée dans le noyau.

[~]# cd /usr/src/xenomai-2.6.0/
[xenomai-2.6.0]# ./configure --enable-smp --enable-x86-sep --enable-x86-tsc --enable-debug
checking build system type... i686-pc-linux-gnu
checking host system type... i686-pc-linux-gnu
checking for a BSD-compatible install... /usr/bin/install -c
[...]
config.status: executing depfiles commands
config.status: executing libtool commands
[xenomai-2.6.0]# make install
Making install in src
make[1]: entrant dans le répertoire « /usr/src/xenomai-2.6.0/src »
[...]
make[2]: quittant le répertoire « /usr/src/xenomai-2.6.0 »
make[1]: quittant le répertoire « /usr/src/xenomai-2.6.0 »
[xenomai-2.6.0]#

Tests

Pour tester le bon fonctionnement de Xenomai, quelques outils de mesure sont livrés avec le système.

[xenomai-2.6.0]# cd /usr/xenomai/bin/
[bin]# export LD_LIBRARY_PATH=/usr/xenomai/lib/
[bin]# ./cyclictest 
0.06 0.26 0.25 1/387 25025          

T: 0 (25019) P:99 I: 1000000 C:      26 Min:       4 Act:      15 Avg:      15 Max:      35
  (Contrôle-C)
[bin]# ./latency
== Sampling period: 100 us
== Test mode: periodic user-mode task
== All results in microseconds
warming up...
RTT|  00:00:01  (periodic user-mode task, 100 us period, priority 99)
RTH|----lat min|----lat avg|----lat max|-overrun|---msw|---lat best|--lat worst
RTD|     -1.449|      2.415|     19.886|       0|     0|     -1.449|     19.886
RTD|     -1.470|      2.412|     19.979|       0|     0|     -1.470|     19.979
RTD|     -1.456|      2.384|     15.410|       0|     0|     -1.470|     19.979
RTD|     -1.668|      2.401|     20.585|       0|     0|     -1.668|     20.585
RTD|     -1.445|      2.364|     18.106|       0|     0|     -1.668|     20.585
RTD|     -1.671|      2.377|     18.016|       0|     0|     -1.671|     20.585
RTD|     -1.475|      2.384|     19.541|       0|     0|     -1.671|     20.585
RTD|     -1.470|      2.356|     17.344|       0|     0|     -1.671|     20.585
RTD|     -1.483|      2.418|     20.039|       0|     0|     -1.671|     20.585
RTD|     -1.469|      2.374|     14.998|       0|     0|     -1.671|     20.585
  (Contrôle-C)
---|-----------|-----------|-----------|--------|------|-------------------------
RTS|     -1.671|      2.388|     20.585|       0|     0|    00:00:10/00:00:10
[bin]#

Naturellement, pour valider une configuration temps-réel, il faudra exécuter ces tests pendant des durées très longues, avec une charge système importante et une activité intense des périphériques.

Conclusion

Je ne conseille pas d’installer Xenomai sur un poste de travail bureautique, ceci ne présente pas d’intérêt et peut même diminuer (très légèrement) les performances moyennes. L’intérêt d’un environnement temps-réel strict comme Xenomai apparaît sur des systèmes industriels ou scientifiques ayant des contraintes temporelles fortes dans leurs interactions avec des périphériques externes. Pour ce type d’applications (employant généralement des cartes embarquées spécifiques ou des PC industriels durcis), on emploie souvent des installations Linux personnalisées plutôt que des distributions grand public. Toutefois, je trouve très intéressant de pouvoir disposer de Xenomai et de tous son environnement de fonctionnement directement sur le poste du développeur, permettant ainsi des tests rapides des applications sans nécessiter un transfert systématique sur la machine cible.

Xenomai sur Pandaboard

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

L’installation de la dernière version de Xenomai (2.6.0) sur une carte Pandaboard ne devrait en principe pas présenter de difficultés. En principe. Mais en pratique je crois être tombé dans tous les pièges possibles avant d’arriver à faire fonctionner correctement mon système. Je vous fais grâce de mes mésaventures, et voici donc un petit résumé des opérations à réaliser pour pouvoir disposer de temps-réel strict sur la carte Pandaboard.

Chaîne de compilation

J’ai habituellement un regard assez dubitatif sur les personnes qui accusent le compilateur de ne pas fournir le code attendu. Il a quand même fallu que je finisse par me poser la question lorsque le noyau modifié et les bibliothèques Xenomai étaient incompatibles et que j’étais obligé de modifier manuellement des Makefile pour forcer la compilation. En fait les options de compilation de Xenomai (notamment FASTSYNCH) dépendent du type de processeur cible. Et ce dernier est déterminé en interrogeant le compilateur… Alors que dans le noyau Linux les options dépendent de la configuration choisie manuellement lors du make menuconfig.
Mon tort avait été d’utiliser une chaîne de compilation Arm générique, que j’avais préparée pour une carte IGEPv2 comme je l’avais décrit dans cet article.

Pour que la compilation des bibliothèques et applications Xenomai se passe bien, il faut utiliser une toolchain adaptée au processeur de la Pandaboard. Ce dernier est basé sur un coeur Cortex A9. Voici la préparation de la toolchain avec la dernière version de Buildroot.

[~]$ cd Projets/Panda/
[Panda]$ wget http://buildroot.uclibc.org/downloads/buildroot-2011.08.tar.bz2
--2011-11-27 06:52:17--  http://buildroot.uclibc.org/downloads/buildroot-2011.08.tar.bz2
Résolution de buildroot.uclibc.org... 140.211.167.224
Connexion vers buildroot.uclibc.org|140.211.167.224|:80... connecté.
requête HTTP transmise, en attente de la réponse... 200 OK
Longueur: 1838467 (1,8M) [application/x-bzip2]
Sauvegarde en : «buildroot-2011.08.tar.bz2.1»

100%[=======================================================================>] 1 838 467   64,7K/s   ds 39s     

2011-11-27 06:52:57 (45,9 KB/s) - «buildroot-2011.08.tar.bz2.1» sauvegardé [1838467/1838467]

[Panda]$ tar xjf buildroot-2011.08.tar.bz2 
[Panda]$ cd buildroot-2011.08/
[buildroot-2011.08]$ wget http://www.blaess.fr/christophe/files/article-2011-11-27/config-buildroot-2011.08-Pandaboard
--2011-11-27 06:53:48--  http://www.blaess.fr/christophe/files/article-2011-11-27/config-buildroot-2011.08-Pandaboard
Résolution de www.blaess.fr... 217.16.3.18
Connexion vers www.blaess.fr|217.16.3.18|:80... connecté.
requête HTTP transmise, en attente de la réponse... 200 OK
Longueur: 18679 (18K) [text/plain]
Sauvegarde en : «config-buildroot-2011.08-Pandaboard»

100%[=======================================================================>] 18 679      31,1K/s   ds 0,6s    

2011-11-27 06:53:49 (31,1 KB/s) - «config-buildroot-2011.08-Pandaboard» sauvegardé [18679/18679]

[buildroot-2011.08]$ mv config-buildroot-2011.08-Pandaboard .config
[buildroot-2011.08]$ make menuconfig

Éditez l’option « Host dir » du menu « Build options » pour choisir le chemin absolu d’installation de la toolchain. Par défaut le chemin est /home/cpb/cross-panda ce qui correspond à l’installation dans mon répertoire personnel. Pensez donc à l’adapter à votre environnement. Puis lancez la compilation avec

[buildroot-2011.08]$ make

La durée de cette étape varie très nettement suivant la puissance du processeur, l’activité du système et la vitesse du réseau. Disons qu’elle se mesure en dizaines de minutes. Elle nous fournira seulement la chaîne de compilation, je n’ai rien sélectionné d’autre dans la configuration de Buildroot (mais rien ne vous en empêche). La compilation se terminera ainsi :

[...]
for dir in ; \
	do \
		for lang in $(cd $dir; ls .|grep -v man); \
		do \
			grep -qx $lang /home/cpb/Projets/Panda/buildroot-2011.08/output/build/locales.nopurge || rm -rf $dir/$lang; \
		done; \
	done
rm -f /home/cpb/Projets/Panda/buildroot-2011.08/output/build/.fakeroot*
[buildroot-2011.08]$

Compilation du noyau Linux

Pour savoir quel noyau Linux utiliser, il nous faut d’abord télécharger la version désirée de Xenomai et regarder quels patches sont disponibles.

[~]$ cd ~/Projets/Panda/
[Panda]$ wget http://download.gna.org/xenomai/stable/xenomai-2.6.0.tar.bz2
--2011-11-27 07:23:30--  http://download.gna.org/xenomai/stable/xenomai-2.6.0.tar.bz2
Résolution de download.gna.org... 78.40.125.79
Connexion vers download.gna.org|78.40.125.79|:80... connecté.
requête HTTP transmise, en attente de la réponse... 200 OK
Longueur: 21068430 (20M) [application/x-bzip2]
Sauvegarde en : «xenomai-2.6.0.tar.bz2»

100%[=========================================================================>] 21 068 430  54,5K/s   ds 3m 45s  

2011-11-27 07:27:16 (91,5 KB/s) - «xenomai-2.6.0.tar.bz2» sauvegardé [21068430/21068430]

[Panda]$ tar xjf xenomai-2.6.0.tar.bz2 
[Panda]$ ls xenomai-2.6.0/ksrc/arch/arm/patches/
adeos-ipipe-2.6.35.9-arm-1.18-03.patch  adeos-ipipe-2.6.37.6-arm-1.18-03.patch  adeos-ipipe-2.6.38.8-arm-1.18-04.patch  mxc  README
[Panda]$

La version 2.6.0 de Xenomai propose donc un patch pour le noyau Linux 2.6.38.8. Nous allons donc télécharger ce dernier.

[Panda]$ wget http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.38.8.tar.bz2
--2011-11-27 07:34:33--  http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.38.8.tar.bz2
Résolution de www.kernel.org... 149.20.4.69
Connexion vers www.kernel.org|149.20.4.69|:80... connecté.
requête HTTP transmise, en attente de la réponse... 200 OK
Longueur: 74811146 (71M) [application/x-bzip2]
Sauvegarde en : «linux-2.6.38.8.tar.bz2»

100%[=========================================================================>] 74 811 146  1,41M/s   ds 88s     

2011-11-27 07:36:01 (831 KB/s) - «linux-2.6.38.8.tar.bz2» sauvegardé [74811146/74811146]

[Panda]$ tar xjf linux-2.6.38.8.tar.bz2
[Panda]$

Ce noyau pose un souci pour la Pandaboard en ce qui concerne l’initialisation du limitateur de courant. La conséquence est que la carte risque de s’éteindre automatiquement dès que l’on exécute une opération qui réclame une augmentation de la consommation électrique (communication sur port série, etc.) Heureusement il existe un patch pour la série des Linux 2.6.38 permettant de corriger ce problème. J’ai passé quelques heures à modifier la configuration du noyau avant de le découvrir… Appliquons ce patch.

[Panda]$ cd linux-2.6.38.8/
[linux-2.6.38.8]$ wget http://www.blaess.fr/christophe/files/article-2011-11-27/pandaboard-current-2.6.38.patch
--2011-11-27 07:46:56--  http://www.blaess.fr/christophe/files/article-2011-11-27/pandaboard-current-2.6.38.patch
Résolution de www.blaess.fr... 217.16.3.18
Connexion vers www.blaess.fr|217.16.3.18|:80... connecté.
requête HTTP transmise, en attente de la réponse... 200 OK
Longueur: 1745 (1,7K) [text/plain]
Sauvegarde en : «pandaboard-current-2.6.38.patch»

100%[==========================================================================>] 1 745       --.-K/s   ds 0s      

2011-11-27 07:46:56 (32,2 MB/s) - «pandaboard-current-2.6.38.patch» sauvegardé [1745/1745]

[linux-2.6.38.8]$ patch -p1 < pandaboard-current-2.6.38.patch 
patching file arch/arm/mach-omap2/board-omap4panda.c
[linux-2.6.38.8]$ cd ..
[Panda]$

Avant de compiler le noyau, il convient de le modifier pour y incorporer les ajouts de Xenomai (notamment ipipe, le pipeline d’interruption bas-niveau).

[Panda]$ cd xenomai-2.6.0/
[xenomai-2.6.0]$ scripts/prepare-kernel.sh --linux=../linux-2.6.38.8 --adeos=ksrc/arch/arm/patches/adeos-ipipe-2.6.38.8-arm-1.18-04.patch --arch=arm
patching file arch/arm/Kconfig
patching file arch/arm/boot/compressed/head.S
patching file arch/arm/common/gic.c
patching file arch/arm/common/it8152.c
patching file arch/arm/common/timer-sp.c
[...]
patching file mm/mmu_context.c
patching file mm/mprotect.c
patching file mm/vmalloc.c
[xenomai-2.6.0]$ cd ..
[Panda]$

Puis compilons enfin notre noyau Linux modifié. Pour cela j’ai préparé rapidement un fichier de configuration qui pourrait être nettement amélioré. J’en profite également pour renommer le répertoire afin de pouvoir éventuellement disposer d’une version 2.6.38.8 sans Xenomai pour comparaison.

[Panda]$ mv linux-2.6.38.8 linux-2.6.38.8-xenomai
[Panda]$ cd linux-2.6.38.8-xenomai/
[linux-2.6.38.8-xenomai]$ wget http://www.blaess.fr/christophe/files/article-2011-11-27/config-linux-2.6.38.8-xenomai
--2011-11-27 08:00:43--  http://www.blaess.fr/christophe/files/article-2011-11-27/config-linux-2.6.38.8-xenomai
Résolution de www.blaess.fr... 217.16.3.18
Connexion vers www.blaess.fr|217.16.3.18|:80... connecté.
requête HTTP transmise, en attente de la réponse... 200 OK
Longueur: 66509 (65K) [text/plain]
Sauvegarde en : «config-linux-2.6.38.8-xenomai»

100%[==========================================================================>] 66 509       260K/s   ds 0,2s    

2011-11-27 08:00:43 (260 KB/s) - «config-linux-2.6.38.8-xenomai» sauvegardé [66509/66509]

[linux-2.6.38.8-xenomai]$ mv config-linux-2.6.38.8-xenomai .config
[linux-2.6.38.8-xenomai]$ make ARCH=arm menuconfig

En vous promenant dans le menu de configuration, vous apercevrez un nouveau sous-menu « Realtime Subsystem » correspondant à la configuration de Xenomai notamment de ses « skins » qui permettent de reproduire l’interface de programmation de différents systèmes temps-réel (RTAI, VxWorks, pSos+, etc.). Nous pouvons à présent lancer la compilation du noyau.

[linux-2.6.38.8-xenomai]$ make ARCH=arm CROSS_COMPILE=~/cross-panda/usr/bin/arm-linux- uImage modules
  CHK     include/linux/version.h
  UPD     include/linux/version.h
  CHK     include/generated/utsrelease.h
  UPD     include/generated/utsrelease.h
[...]
  CC      fs/nls/nls_iso8859-15.mod.o
  LD [M]  fs/nls/nls_iso8859-15.ko
  CC      fs/nls/nls_utf8.mod.o
  LD [M]  fs/nls/nls_utf8.ko
  CC      net/bluetooth/l2cap.mod.o
  LD [M]  net/bluetooth/l2cap.ko
  IHEX    firmware/kaweth/new_code.bin
  IHEX    firmware/kaweth/trigger_code.bin
  IHEX    firmware/kaweth/new_code_fix.bin
  IHEX    firmware/kaweth/trigger_code_fix.bin
[linux-2.6.38.8-xenomai]$

Installation du noyau modifie

Pour l’installation, j’insère la carte micro-SD de la Pandaboard dans un connecteur sur mon poste de travail, où le système de fichiers principal se trouve monté en /media/root et la partition de démarrage (contenant le bootloader) sur /media/boot. Sur ma carte se trouve un système de fichiers minimal avec une configuration de Busybox et Dropbear comme décrit dans cet article et celui-ci.

Attention, si vous avez déjà installé une toolchain en suivant mes premiers articles sur la Pandaboard, il faut absolument installer les nouvelles bibliothèques sinon vous ne pourrez pas utiliser celles de Xenomai (et resterez bloqués comme moi pendant plusieurs heures à jouer à tort sur sa configuration). Il faut donc les copier ainsi :

[linux-2.6.38.8-xenomai]$ rm -rf /media/root/lib/*
[linux-2.6.38.8-xenomai]$ cp -Rdp ~/cross-panda/usr/arm-linux/sysroot/lib /media/root/
[linux-2.6.38.8-xenomai]$

On peut installer le noyau et ses modules ainsi :

[linux-2.6.38.8-xenomai]$ cp arch/arm/boot/uImage /media/boot/
[linux-2.6.38.8-xenomai]$ make ARCH=arm INSTALL_MOD_PATH=/media/root/ modules_install
  INSTALL drivers/hid/hid-gaff.ko
  INSTALL drivers/hid/hid-samsung.ko
  INSTALL drivers/hid/hid-sjoy.ko
  [...]
  INSTALL /media/root//lib/firmware/kaweth/trigger_code.bin
  INSTALL /media/root//lib/firmware/kaweth/new_code_fix.bin
  INSTALL /media/root//lib/firmware/kaweth/trigger_code_fix.bin
  DEPMOD  2.6.38.8-xenomai-cpb
[linux-2.6.38.8-xenomai]$ umount /media/*oot
[linux-2.6.38.8-xenomai]$

Après avoir démarré la Pandaboard, et s’être connecté par SSHs, nous observons deux nouvelles entrées dans /proc.

[Panda]# ls /proc/ipipe/
Linux    Xenomai  version
[Pandaboard]# ls /proc/xenomai/
acct          apc           heap          irq           lock          sched         stat          timer         version
affinity      faults        interfaces    latency       registry      schedclasses  timebases     timerstat
[Pandaboard]#

La première correspond à Adeos (renommé ipipe pour interrupt pipeline) et la seconde au domaine de Xenomai, dans lequel seront ordonnancées les futures tâches temps-réel.

Bibliothèques de Xenomai

Les bibliothèques de Xenomai doivent être compilées et installées pour que les tâches de l’espace utilisateur puissent accéder aux fonctionnalités temps-réel. Certaines options de configuration ont changé depuis les dernières versions de Xenomai (notamment l’option --enable-smp dont je cherchais en vain la présence dans la configuration du kernel).

Pour compiler ces bibliothèques nous utiliserons les manipulations suivantes. Attention, dans la ligne « ./configure« , précisez le chemin dans lequel les bibliothèques et exécutables de Xenomai seront installés sur votre poste de développement (en modifiant donc le chemin qui est prévu pour mon répertoire).

[Panda]$ cd xenomai-2.6.0/
[xenomai-2.6.0]$ PATH=$PATH:~/cross-panda/usr/bin/
[xenomai-2.6.0]$ ./configure --prefix=/home/cpb/Projets/Panda/xenomai --host=arm-linux CFLAGS='-march=armv7-a' LDFLAGS='-march=armv7-a' --enable-smp
configure: WARNING: if you wanted to set the --build type, don't use --host.
    If a cross compiler is detected then cross compile mode will be used
checking build system type... i686-pc-linux-gnu
checking host system type... arm-unknown-linux-gnu
checking for a BSD-compatible install... /usr/bin/install -c
checking for arm-linux-gcc... arm-linux-gcc
checking whether the C compiler works... yes
[...]
config.status: executing depfiles commands
config.status: executing libtool commands
[xenomai-2.6.0]$ make 
Making all in src
make[1]: entrant dans le répertoire « /home/cpb/Projets/Panda/xenomai-2.6.0/src »
Making all in include
[...]
make[1]: entrant dans le répertoire « /home/cpb/Projets/Panda/xenomai-2.6.0 »
make[1]: Rien à faire pour « all-am ».
make[1]: quittant le répertoire « /home/cpb/Projets/Panda/xenomai-2.6.0 »
[xenomai-2.6.0]$ make install
[...]
make[2]: Rien à faire pour « install-data-am ».
make[2]: quittant le répertoire « /home/cpb/Projets/Panda/xenomai-2.6.0 »
make[1]: quittant le répertoire « /home/cpb/Projets/Panda/xenomai-2.6.0 »
[xenomai-2.6.0]$ cd ..
[Panda]$

Transférons les exécutables et les bibliothèques en utilisant scp (s’il n’est pas installé sur votre Pandaboard, faites une copie directe avec la carte micro-SD).

[Panda]$ cd xenomai
[xenomai]$ ls
bin  include  lib  sbin  share
[xenomai]$ scp -r bin/ lib/ sbin/ root@192.168.3.152:/usr/
root@192.168.3.152's password:
xeno-test-run-wrapper                                                100%  181     0.2KB/s   00:00
xeno-test-run                                                        100%   17KB  17.3KB/s   00:00
insn_read                                                            100%   14KB  14.1KB/s   00:00
[...]
analogy_config                                                       100% 9300     9.1KB/s   00:00
rtcanconfig                                                          100% 8493     8.3KB/s   00:00
rtps                                                                 100%   22KB  22.2KB/s   00:00
[xenomai]$

Test de Xenomai

Après s’être connecté sur Xenomai, nous pouvons lancer les tests se trouvant installés dans le répertoire /usr/bin. Il y en a plusieurs que je vous laisse découvrir, voyons juste le résultat d’un outil que j’aime bien : cyclictest qui mesure la variabilité des timers. Il en existe une version utilisant l’API système Linux classique (que l’on trouve dans le projet Linux-rt) et un portage utilisant l’API de Xenomai. Voyons un exemple où nous déclenchons un timer toutes les 100 micro-secondes et observons le jitter.

[Panda]# cd /usr/bin
[Panda]# ./cyclictest -i 100
0.47 0.17 0.09 1/50 11834          

T: 0 (11809) P:99 I:     100 C: 2372138 Min:       0 Act:       4 Avg:       0 Max:      17

En outre, l’utilitaire latency donne des résultats comparables.

[Panda]# ./latency -p 100
== Sampling period: 100 us
== Test mode: periodic user-mode task
== All results in microseconds
warming up...
RTT| 00:00:01 (periodic user-mode task, 100 us period, priority 99)
RTH|----lat min|----lat avg|----lat max|-overrun|---msw|---lat best|--lat worst
RTD| 0.652| 0.837| 9.404| 0| 0| 0.652| 9.404
RTD| 0.498| 0.857| 8.105| 0| 0| 0.498| 9.404
[...]
RTT| 04:28:07 (periodic user-mode task, 100 us period, priority 99)
RTH|----lat min|----lat avg|----lat max|-overrun|---msw|---lat best|--lat worst
RTD| 0.460| 0.869| 9.210| 0| 0| -0.116| 17.025
RTD| 0.478| 0.869| 8.599| 0| 0| -0.116| 17.025
RTD| 0.511| 0.869| 8.744| 0| 0| -0.116| 17.025
RTD| 0.494| 0.873| 9.376| 0| 0| -0.116| 17.025
RTD| 0.499| 0.871| 9.029| 0| 0| -0.116| 17.025
RTD| 0.523| 0.869| 8.698| 0| 0| -0.116| 17.025

Au bout de quatre heure et demi de travail, la variation maximale observée est de 17 micro-secondes, ce qui est excellent pour une Pandaboard. A titre d’exemple, avec un noyau Linux 3.0 standard, j’ai observé un retard maximal de plus de 500 micro-secondes (avec un timer cadencé à la milli-seconde), et un décalage maximal de 58 micro-secondes avec un noyau 3.0 auquel le patch Linux-rt a été appliqué.

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