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.

I. Prise en main de l'environnement de développement

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 :
  1. 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)
  2. Préparation de l'image binaire pour le déploiement.(sur xuri)
  3. Connection à la cible si disponible (il faudra vérifier la disponibilité de la cible).
  4. Chargement et lancement. (mode slave ou master pour la cible)
  5. Récupération de la sortie dans un fichier texte
  6. 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)

II L'ordonnancement RMS du système RTEMS

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

II.1 Mapping RMS -> RTEMS 

  1. Vous pouvez consulter une documentation sur le fonctionnement du gestionnaire RMS de RTEMS en cliquant ici.
  2. 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).







    1. Initialisation du descripteur de RMS pour la tâche courante

    2. Implantation du comportement périodique via une boucle exécutant la séquence d'action suivante :
      1. le traitement à répéter (la fonction de la tâche)
      2. 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 *****/


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

  4. 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 !),

  5. La documentation générale sur les fonctions de tasking de RTEMS est accessible en appuyant ici.

II.2 Exercice de base RMS (application directe du motif)

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 :

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.

II.3 Production et déploiement pour spif/xuri.

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 :

  1. Récupérer les fichiers contenus dans exo1.tar ,
  2. 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)
  3. 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

III. Configuration d'un lot de tâches et traitement d'erreur.

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.

    IV. Gestion de l'inversion de priorité

    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 :

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