Ordonnancement temps réel sur
système embarqué
(Utilisation de RTEMS sur la plate-forme Spif.)
Bertrand Dupouy -- Gérard Mouret -- Thomas Robert
SOMMAIRE
Ce TP illustre le processus de développement d'une application temps réel embarquée.
Nous allons écrire et tester des programmes en utilisant le système
RTEMS
embarqué sur une plate-forme Spif développée au département INFRES de l'ENST.
La première étape de ce TP qui se déroule sur plusieurs séances consiste à réaliser sous forme de scripts et
makefile la coordination du processus de production et déploiement du code
pour le système embarqué Spif.
La structure de l'arhitecture matérielle est la suivante. Nous avons une
passerelle unique, xuri.enst.fr, qui permet de contrôler les cartes via de
multiples ports "série" et de communiquer avec elles via un protocole
ethernet simplifié (tftp). Cette machine a été
configurée pour permettre un déploiement interactif.
Le but est d'automatiser le processus qui doit comprendre les phases suivantes :
- Compilation du code pour MPC860 et édition de liens entre RTEMS
et l'application. (sur xuri) cf consigne sur la page de description de la plateforme xuri)
- Préparation de l'image binaire pour le déploiement.(sur xuri)
- Connection à la cible si disponible (il faudra vérifier la disponibilité de la cible).
- Chargement et lancement. (mode slave ou master pour la cible)
- Récupération de la sortie dans un fichier texte
- Réinitialisation de la plateforme pour libérer la carte
La description de la plateforme permettant le déploiement et son mode d'emploi sont disponibles ici.
Nous allons réaliser deux implémentations de la partie chargement et
lancement pour vous permettre de comprendre la fonction des couches
moniteur-bootloader.
La première étape consiste à rendre automatique la procédure de
connexion sur les comptes tprtems. Pour cela nous allons utiliser une
authentification par des clès (publiques/privées) stockées dans des fichiers. Pour plus de
détails sur cette procédure, vous pouvez vous référer à la documentation
de ssh et ssh-keygen.
Pour des questions d'organisation, il vous sera attribué un numéro de
carte, cela correspond à la carte à laquelle vous pourrez vous
connecter. Un compte mirroir existe pour chaque carte et contient un
fichier de configuration prédéfini qui permet de se connecter à
la-dîte carte.
Pour la carte spif1 le compte est tprtems11.. et tprtems12 pour spif2
et ainsi de suite. Les mots de passes sont identiques aux login...
Automatiser la connexion et le chargement sur Xuri du code
source
Le principe est de récupérer la partie privée d'une paire de clés appelée xuriXX et
xuriXX.pub en fonction du compte (XX=11 pour tprtems11, 12 pour tprtems12
...).
Nous vous demandons de copier la clé xuriXX dans le répertoire .ssh/authorized_keys/ localisé
sur votre comptre personnel (des machine de la salle de tp).
La commande pour se connecter sans taper de mot de passe est (depuis
votre répertoire par exemple tp-eter-1) :
ssh -i xuriXX tprtemsXX@xuri avec XX allant de 11 à 14.
Sur Xuri le compilateur a été installé pour compiler directement les
applications destinées à spif. Le chemin vers ce compilateur sont
définis dans un fichier placé à la racine de chaque compte appelé
rtems.env. (Nous vous conseillons de le rappatrier sur votre machine)
Nous vous demandons d'écrire un script/makefile qui copie directement votre code
sur Xuri, le compile, tente une connexion avec la carte correspondante
(et si cela fonctionne, charge et démarre l'application). La page suivante
donne quelques informations sur les commandes que vous pourrez
utiliser pour piloter PPCBOOT.
Le contrôle distant de la carte dans sa phase de boot se fera via Kermit dont la documentation est en ligne. (google kermit console...)
Mode cible en mode Esclave
Dans ce contexte, c'est la passerelle qui pilote la carte via l'envoi
et la reception de message sur le port série.
Pour cela, nous vous encourageons à utiliser l'outil kermit permettant d'établir une communication réseau avec la carte. Ce mode esclave peut être utilisé soit de manière interactive (vous tapez les commandes, soit en mode automatique par un script kermit à définir dans le fichier kermitconfX
Vous utiliserez donc les
fonctions de KERMIT permettant d'envoyer des commandes via la ligne
série en modifiant le fichier kermitconf fourni par défaut.
(ATTENTION pour une commande kermit une ligne se termine le
code 13 : \13 au lieu de \n
Vous pouvez tester votre procédure de démarrage sur le code suivant
destiné les exemple destinés à rtems placé dans l'arborescence du
répertoire où est installé le compilateur.
CORRECTION ici
Mode cible en Maître
Dans ce cas de figure, c'est la carte qui vient chercher le fichier
sur Xuri. Cela suppose que la carte sait par défaut quel fichier
télécharger. Regardez le script exectobin pour constater que le
binaire est écrit dans un fichier qui possède un nom constant.
Dans ce cas de figure la séquence de commandes à éxecuter pour charger
et lancer un code peut être mémorisée dans la rom de la carte (cf doc
de PPCBOOT).
Trouvez la variable en rom qui peut vous servir à stocker les lignes
de scripts permettant le chargement et le lancement du programme
(lancement par : go 100000)
FAIRE VALIDER AVANT DE SAUVEGARDER SUR LA ROM (vous devez être capable
d'expliquer pourquoi vous choississez cette variable)
RMS est un modèle de tâche théorique qu'il faut implanter sur un
système d'exploitation concret, en l'occurrence RTEMS. A cet effet, on
utilise un design pattern
pour implanter ce modèle tâches dans un OS fournissant des tâches
préemptives selon priorités fixes et FIFO au sein de ces priorités.
Vous trouverez ci-dessous, la description en langage naturel et sous
forme forme de code C de ce design pattern. On appelle ce processus de
"traduction" un mapping du modèle de tâche A vers le modèle de tâche B.
-
Vous pouvez consulter une documentation
sur le fonctionnement du gestionnaire RMS
de RTEMS en
cliquant ici.
- Nous donnons ci-dessous le design pattern permettant
d'implémenter une tâche RMS sour RTEMS à partir de l'API RTEMS (en
utilisant les procédures rtems_rate_monotonic_create et rtems_rate_monotonic_period) :
Voici ce qu'il se passe lors des appels successifs à rtems_rate_monotonic_period en supposant que la tâche courante est la seule à s'exécuter :
Appel 1 à t0
(comportement spécifique d'initialisation) : le service d'attente est
initialisé de sorte à réveiller la tâche appelant la fonction au
prochain instant correspond à une date de la forme t0 +k*T, k entier.
Appel 2 : Suite à l'exécution de la tâche dans sa première période si
la tâche ne consomme pas son WCET mais C1 unités de temps ,
alors la tâche est bloquée pendant T - C1,
Appel 3 : Si on consomme C2, la tâche est bloquée pendant T - C2,
Appel 4 : Si on réalise l'appel au service d'attente de la prochaine
activation à une date supérieure à t0+ 4T, alors l'appel demade un
réveil pour une activation dépassée et la fonction retourne une erreur
(code TIMEOUT).
|
|

|
- Initialisation du descripteur de RMS pour la tâche courante
- Implantation du comportement périodique via une boucle exécutant la séquence d'action suivante :
- le traitement à répéter (la fonction de la tâche)
- Passage dans l'état bloqué jusqu'à l'activation suivant
|
#define T 10 ... rtems_rate_monotonic_create (nom, &valeur); init=rtems_rate_monotonic_period(valeur, T);
while (1) { Etat = rtems_rate_monotonic_period(valeur, T); if (Etat == RTEMS_TIMEOUT) break ; /***** Ici se trouve le code a executer : on va consommer successivement C1, C2, C3 unites de temps pendant la periode *****/ } /***** Echéance dépassée *****/ |
- La fonction de mise en attente rtems_rate_monotonic_period(valeur, T) possède une procédure de signalement du cas de défaillance temporelle suivant:
Si la tâche courante n'a pas terminé l'exécution du traitement
périodique au sein de la période en cours, alors la fonction retourne
un code d'erreur spécifique.
En interne, cette détection se fait à travers un timer devant expirer à
la date T. Cependant, si la tâche termine son traitement avant
l'échéance, alors ce timer est automatiquement désactivé par
RTEMS.
Ainsi, l'expiration du timer indique que la tâche n'a pas réussi
à respecter son echéance. Cette information sert à déterminer le code
de retour de la fonction
- Le détail des services utilisés dans le pattern se trouve aux pages suivantes (avec exemples en code C) :
cliquez ici.
et
ici.
(c'est la doc. pour compléter task.c !),
-
La documentation générale sur les fonctions de tasking de RTEMS est accessible en appuyant
ici.
On va voir, sur un exemple relativement simple, comment implanter un lot
de tâches périodiques sur RTEMS.
On donne un canevas de cette application dans les fichiers init.c
et task.c. Vous trouverez ces fichiers ainsi que d'autres fichiers
annexes dans
exo1.tar .
Nous vous conseillons dans un premier temps de vous limiter à un lot à trois
tâches (avec par exemple les paramètres suivants : T1 = 500, T2 = 800,
T3 = 1000 avec WCET1 = 50, WCET2 = 100, WCET3 = 200.
Dans init.c, on trouve successivement :
- Promotion de la tâche main à une priorité importante pour empêcher
sa préemption par les tâches qu'elle crée lors de l'initialisation du
lot de tâche RMS-RTEMS :
rtems_task_set_priority(RTEMS_SELF, 10, &the_priority);
-
création des tâches périodiques
dans un intervalle de priorités inférieures à celle de main
status = rtems_task_create(...)
-
Question :
Sachant que plus une tâche est prioritaire, plus son indice de priroité
est faible sous RTEMS, dîtes en quoi l'attribution de priorités faite
dans le code est correcte.
Il faudra compléter init.c puis task.c. Pour ce
dernier fichier, on utilisera les procédures de gestion RMS introduites précédement.
On rappelle que la doc. sur la gestion RMS se trouve
ici.
Schéma de rappel de l'environnement de développement croisé :
Ici, la machine xuri.enst.fr joue le rôle du host du schéma
ci-dessus, la plate-forme spif est la machine cible (target).
A Faire :
Nous allons maintenant utiliser cette machine xuri.enst.fr
pour exécuter l'exemple de base sur la machine cible, la carte Spif.
C'est sur xuri que l'on va :
-
Récupérer les fichiers contenus dans exo1.tar ,
-
Compléter les fichiers sources init.c et task.c
pour mettre en oeuvre le design pattern implémentant les tâches
périodiques RMS. Vous pourrez instancier les structures Periods[] et
Task_computation[] pour des lots de tâches de 1 à 9 tâches (cf
déclaration des tableaux)
-
Utiliser les outils de développement croisé pour créer l'exécutable,
le télécharger puis l'exécuter sur la cible,
Pour mener à bien ces étapes, vous êtes invités à utiliser le corrigé
des scripts de compilation et déploiement de la première partie du
TP et ce mode d'emploi.
Pour information nous vous donnons l'affichage du résumé de l'exécution de ce lot de tâche :
PPCBoot 2.0.0.spif (Feb 10 2003 - 21:59:56) Spif3
CPU: XPC860PZPnnD3 at 50 MHz: 16 kB I-Cache 8 kB D-Cache FEC present
Board: TQM860LDDBA3-P50.203
DRAM: 64 MB
FLASH: 8 MB
In: serial
Out: serial
Err: serial
Net: SCC ETHERNET
=> tftpboot
SCC ETHERNET configured
ARP broadcast 1
TFTP from server 137.194.160.128; our IP address is 137.194.160.89
Filename 'spif1.bin'.
Load address: 0x100000
Loading: ##############################
done
Bytes transferred = 150720 (24cc0 hex)
=> go 100000
## Starting application at 0x00100000 ...
/*******************************/
main-- starting
CPU Usage by thread
ID
NAME TICKS
PERCENT
0x151060481
IDLE
0 0.00
0x167837697 UI1 103 0.00
main-- ticks_per_second : 10000
operation count for ten ticks : 5988/n main-- initial current priority : 50
main-- actual current priority:10
main-- creating : Task_num = 1, Task_name = (0x)54413120
main-- Task nb. 1 created, status = 0, priority = 20, id = (0x)a010002
main-- creating : Task_num = 2, Task_name = (0x)54413220
main-- Task nb. 2 created, status = 0, priority = 21, id = (0x)a010003
main-- creating : Task_num = 3, Task_name = (0x)54413320
main-- Task nb. 3 created, status = 0, priority = 22, id = (0x)a010004
main-- start time(s) : 0
Task 1-- : starting
Task 2-- : starting
Task 3-- : starting
main-- current priority : 10, set to 50
main-- current priority : 50
Task 1-- : end with charge value 4
Task 2-- : end with charge value 4
Task 3-- : end with charge value 4
main-- end time(s) : 23
CPU Usage by thread
ID
NAME TICKS
PERCENT
0x151060481
IDLE
0 0.00
0x167837697 UI1 136598 57.24
0x167837698 TA1 24079 10.09
0x167837699 TA2 30264 12.68
0x167837700 TA3 47787 20.02
main-- Periods done for task 1 : 471
main-- Periods done for task 2 : 295
main-- Periods done for task 3 : 236
Period information by period
0x42010001 ****
471 0
0/1/0.99 0/0/0.00
0x42010002 ****
295 0
0/1/0.99 0/0/0.00
0x42010003 ****
236 0
0/1/0.99 0/0/0.00
main-- exiting |
Un corrigé de cet exercice se trouve ici : exo1-corr.tar
Reprendre l'exercice précédent et le modifier de façon à créer une
situations où le lot de tâche déclaré n'est ordonnançable pour vérifier
que vous capturez bien les dépassement d'échéances :
Rappel : n(2**(1/n)-1) = 0,78 pour n = 3.
On va utiliser les sémaphores RTEMS
compatibles POSIX pour traiter le problème de l'inversion de priorités grâce à l'héritage de priorités RTEMS.
Nous vous proposons d'étudier le scénario d'ordonnancement suivant :
Nous considérons trois tâches TA1, TA2 et TA3 telles que deux d'entre elles accèdent à une section critique protégée par un sémarphore appelé SM1.
Pour faciliter l'observation du phénomène d'inversion, la section
critique couvre l'intégralité du traitement périodique. Les paramètres
des tâches sont les suivants.
Tâche
|
TA1
|
TA2
|
TA3
|
Période
|
300
|
400
|
1200
|
Coût
|
50
|
100
|
400
|
Détaillez le scénario menant à l'inversion de priorités (pour
quelles tâches ? A quel moment peut s'en apercevoir (tenir compte des
services founis par RTEMS) ?
(Indice: l'inversion de priorité concerne les tâches TA1 et TA3)
Question 1:
Utilisez la correction du premier exercice comme point de départ.
Intégrez le sémaphore permettant de protéger les sections critiques dans TA1 et TA3.
Remarques :
- Le comportement des tâches peut être spécialisé en testant
l'indice Task_num au sein de la boucle représentant le corps de la
tâche RMS-RTEMS
- Rappel la documentation sur les sémaphores se trouve au chapitre 9 du "User Guide Rtems 4.7" ici.
-
Attention :
- vérifier la présence de SEMAPHORE dans la liste des MANAGERS spécifiés dans le Makefile.
- Vous
pouvez initialiser vos sémaphore par les options suivantes dans cet
exercice (rappel : pas de gestion des priorité pour le sémaphore) :
RTEMS_DEFAULT_ATTRIBUTES, RTEMS_NO_PRIORITY,
Question 2:
Pour éviter ce dysfonctionnement, il faut configurer le sémaphore pour
qu'il supporte une forme d'héritage de priorité. On modifie alors
l'attribut du sémaphore SM1 pour le mettre à RTEMS_INHERIT_PRIORITY. Quel scénario d'exécution doit être observé ?
Faire cette modification
Remarques :
Question 3 (Optionelle):
Utilisez une variable de compilation (e.g.) DEBUG associée
à des "printf" masqués par des
directives #ifdef pour générer une trace
"approximative" de l'exécution identifiant l'ordre de prise et de
relachement des verrous par les différentes tâches (On ne cherchera pas
à compenser le
surcout temporel).