X. Méthodologie de développement▲
Les deux premiers paragraphes s'adressent aux personnes n'ayant pas développé de système RT (ceux qui les trouvent simplistes, je leur rappelle que ce document est destiné aux débutants).
X-A. Prototypage de l'architecture dynamique▲
L'architecture dynamique comprend les tâches, les structures qu'elles utilisent pour communiquer (files de messages, variables partagées...) et la manière dont elles sont activées (périodiquement, sémaphore...).
L'étape du prototypage valide cet ensemble sans s'embarrasser de l'aspect fonctionnel du système puisque l'application est réduite à une coquille vide ne contenant que des appels Xenomai. Dans le cycle en V, elle s'effectue en phase de conception.
On débute par l'identification des interfaces du logiciel : monde extérieur (réseau...), matériel (carte série...), autres applications Linux, etc.
Ensuite, on identifie les tâches et on les caractérise, par exemple :
- asynchrone (sur quel événement ? interruption, action utilisateur) ou synchrone (sur quel événement ? libération d'un sémaphore...) ;
- si synchrone, la fréquence d'activation ;
- la priorité et sa justification ;
- les variables partagées avec les autres tâches ;
- le mode d'exécution attendu : primary ou secondary (dépend de la contrainte temporelle et des interfaces) ;
- le point d'entrée dans l'architecture statique.
Par la suite, on s'attaque aux variables partagées, ou plus exactement à leur protection. Elles peuvent être protégées par les priorités (surtout si l'architecture est très synchrone, c'est-à-dire que l'on maîtrise totalement l'ordre d'activation des tâches) ou utiliser les mécanismes de Xenomai (mutexes, files de messages...).
Exemple : soient trois tâches :
- Periodic, priorité = 30, périodique ;
- Compute, priorité = 20, synchrone sur Periodic ;
- Display, priorité = 10, synchrone sur Periodic.
Compute et Display partagent une variable appelée buffer.
L'ordre d'activation est alors le suivant :
La variable buffer est alors protégée par conception grâce aux priorités.
Attention: la protection des variables par les priorités n'est valable que dans les systèmes monoCPU.
La tenue des contraintes temporelles est également vérifiée de manière préliminaire à cette étape. Des benchmarks sont réalisés pour les traitements « lourds », en tenant compte des hypothèses les plus défavorables.
La validation du prototype ne doit pas s'arrêter au cas nominal, mais doit également prendre en compte les cas d'erreur. C'est d'ailleurs eux qui compliquent l'application puisqu'il faut terminer proprement l'application alors que tous les objets RT n'ont pas été créés, que des tâches sont bloquées derrière des sémaphores qui ne seront jamais débloqués, etc.
X-B. Pratiques▲
X-B-1. Les mauvaises...▲
Rappelez-vous, rt_task_create prend en paramètre la taille de la pile de la tâche. Contrairement aux tâches Linux, elle n'est pas extensible dynamiquement, l'utilisation de la récursivité est donc à éviter.
X-B-2. ... et les bonnes▲
Initialisez les variables locales lors de la déclaration, cela dépend des compilateurs et des options, mais elles peuvent alors avoir une valeur indéterminée (dépend du contenu de la pile) et surtout totalement différente d'une exécution sur l'autre (non déterminisme).
Notez symboliquement les priorités (#define) et regroupez ces définitions (dans un même fichier par exemple), cela permet de comprendre plus facilement le logiciel sans forcément avoir la documentation à côté.
Pour les files de messages, il est toujours intéressant de savoir si on a perdu des messages et combien lors du stockage (cela peut aider à la mise au point en indiquant un dysfonctionnement ou une sous-évaluation du volume des messages). Si l'écriture dans une file échoue, incrémentez un compteur de messages et affichez-le en fin d'exécution.
X-C. Aide à la mise au point▲
X-C-1. Ajout de traces▲
Bien que très pratique, le printf n'est pas toujours accessible (intérieur du noyau) ou impacte les performances temporelles (changement de mode).
Dans les applications hors du noyau, on peut utiliser le même mécanisme que VxWorks : une tâche dédiée à l'affichage des traces. Les tâches appellent la fonction log_write(38) qui écrit un message dans une file. Cette file est scrutée par la tâche log_task qui a la priorité la plus faible et qui ensuite effectue un printf.
Cette solution présente plusieurs avantages :
- on peut insérer des informations en plus du message brut comme le nom de la tâche et la date ;
- à la manière de printk, on peut associer un niveau de criticité du message que l'on filtrera lors de l'affichage au fur et à mesure du développement (syslog).
Parfois, l'ajout de traces est nécessaire à la validation. Dans la version de production, ces traces doivent seulement être inhibées, les performances temporelles sont ainsi identiques.
X-C-2. Détection des basculements « sauvages » en secondary mode▲
Certains basculements sont prévisibles (printf, write, read...), d'autres beaucoup moins évidents à trouver (code tiers ou générés automatiquement par exemple).
La suspicion d'un passage en secondary mode peut être envisagée lorsque les performances temporelles attendues sont dégradées ou si le comportement du logiciel est inattendu.
La recherche de la fonction fautive se fait en deux temps :
- l'activation de l'émission du signal SIGXCPU lors du basculement ;
- la recherche de la fonction incriminée.
Un exemple se trouve dans le répertoire d'installation de Xenomai : xenomai-<version>/examples/native/sigxcpu.c (test réalisé avec Xenomai 2.3.3 sur Pentium III).
[root@localhost native]# ./sigxcpu
./sigxcpu
(
warn_upon_switch+0x1f)[0x80488e7]
[0xffffe420]
/lib/i686/libpthread.so.0
[0xb7f51540]
/lib/i686/libc.so.6
(
__clone+0x5e)[0xb7ee355e]
./sigxcpu
(
warn_upon_switch+0x1f)[0x80488e7]
[0xffffe420]
/usr/xenomai/lib/libnative.so.0
[0xb7f4a0c4]
/lib/i686/libpthread.so.0
[0xb7f51540]
/lib/i686/libc.so.6
(
__clone+0x5e)[0xb7ee355e]
Switched to secondary mode
./sigxcpu
(
warn_upon_switch+0x1f)[0x80488e7]
[0xffffe420]
/lib/i686/libc.so.6
[0xb7e77b05]
/lib/i686/libc.so.6
(
_IO_do_write+0x1f)[0xb7e77ddf]
/lib/i686/libc.so.6
(
_IO_file_overflow+0x128)[0xb7e78708]
/lib/i686/libc.so.6
(
__overflow+0x53)[0xb7e7aeb3]
/lib/i686/libc.so.6
(
puts+0x140)[0xb7e6f8c0]
./sigxcpu
(
task_body+0x42)[0x80488c6]
...
On bascule en secondary mode dans la fonction task_body, lors de l'appel de la fonction puts (appelée par printf).
X-C-3. Tests de bon fonctionnement bas niveau▲
Il est intéressant dès le début du développement, de prévoir des applications légères de bas niveau afin de vérifier les interfaces matérielles ou encore le câblage. Elles pourront être lancées lorsque rien ne va plus ou encore pour valider le bon fonctionnement du matériel après un remontage.
X-C-4. Utilisation du débogueur gdb▲
Comme nous l'avons déjà dit un peu plus haut, les tâches Xenomai s'exécutent en mode utilisateur. On peut donc tout naturellement utiliser gdb, au prix de la perte des performances temps réel bien entendu.
Le programme doit être compilé avec l'option -g de gcc.