tp1_tr
Transcription
tp1_tr
TP TR ENSPS et MASTER 1 Travaux Pratiques Systèmes temps réel et embarqués ENSPS ISAV et Master TP1 - Ordonnancement et communication inter-processus (IPC) Environnement de travail – Un ordinateur dual-core à architecture x86 équipé du système Xenomai/Linux. – Un environnement de développement : man, make, gcc,modules-utils (insmod,lsmod,rmmod). Expérimentations – – – – – Choisir au démarrage, le système slackware 13. Se connecter sur le compte root avec le mot de passe donné. Utiliser la commande startx pour démarrer l’interface graphique. Ouvrir un terminal (icône en forme d’écran) et firefox. Récupérer les fichiers du TP1 2012 sur le site de l’équipe eavr, section Enseignement 3A et Master. – Dans le terminal ou la console, décompresser les fichiers de l’archive : tar xvf tp1_tr.tgz. – Finalement, descendre dans le répertoire ainsi crée (tp1 tr). – L’éditeur de texte se lance en tapant kwrite nom de fichier ou kate nom de fichier. Pour les puristes, vi, vim/gvim et emacs sont aussi disponibles. – La compilation des programmes (et modules noyaux) est réalisée en tapant la commande make. – Documentation de l’API Xenomai et Posix (Modules/Native Xenomai API ou Posix skin) : disponible dans le répertoire /usr/xenomai/share/doc/ ou plus simplement en ligne avec un signet (bookmark) pré-enregistré dans firefox. Sinon, vous pouvez toujours rechercher dans le manuel (man pthread_create, par exemple) ou en ligne des informations sur les fonctions utilisées. 1 Mesure de la performance d’un système temps réel : la gigue Un système temps réel doit être temporellement déterministe. La gigue (jitter en anglais) est la fluctuation de ce temps de réaction. Idéalement, celle-ci est nulle. Dans la pratique, un système temps réel performant présente une gigue dont la valeur est faible (<100 us) et surtout bornée (pour le déterminisme). On se propose de mesurer la gigue sur la période d’une tâche définie périodique sous Linux (temps réel mou) et Xenomai (temps réel dur). 1.1 Gigue sous Xenomai 1. Dans le répertoire gigue_xenomai, étudier le code du fichier periodic.c avec la documentation POSIX skin/Clocks et Thread management. 2. Compiler avec make et exécuter le programme avec ./periodic. Quelle est la résolution de l’horloge MONOTONIC dans Xenomai ? TP TR ENSPS et MASTER 2 On notera que le temps système est renvoyé sous la forme de 2 champs dans une structure timespec : tv_sec pour la partie en secondes et tv_nsec< 109 pour la partie en nanosecondes du temps courant. 3. Constater que la tache (thread) périodique n’a pas le temps de s’exécuter. Pourquoi, sachant que le fin du main implique la fermeture du processus ? 4. Décommenter alors la ligne \\pthread_join(). Rechercher l’utilité de cette fonction. Pourquoi l’exécution du programme ne se finit alors jamais ? Utiliser la combinaison de touche ctrl-c (envoie du signal SIG INT à la tâche) pour demander au système l’INTerruption/fermeture du programme. 5. Commenter la ligne mlockall(...); dans le main. Recompiler avec make et exécuter. Est-il cohérent que Xenomai contrairement à Linux refuse l’exécution sans l’instruction mlockall ? 6. On veut mesurer précisément la borne inférieure (avance maximale > 0) et la borne supérieure (retard maximal > 0) de la gigue : l’écart entre la période de la tâche et la période demandée. Utiliser les 2 variables max_advance et max_delay pour stocker l’écart maximal rencontré au cours de l’exécution. On affichera à chaque période de l’exécution la valeur actuelle de ces 2 variables. 7. Tester et stresser le système pour observer l’évolution de la gigue : ouvrir Firefox, passer de l’interface graphqiue (ctl-alt-F7) à la console (ctr-alt-F2), ... Conclure sur la gigue de Xenomai. 1.2 Gigue sous Linux 1. Dans le répertoire gigue_linux, ouvrir le fichier periodic.c. Constater qu’il est très semblable au fichier précédent. Exécuter et noter la résolution de l’horloge MONOTONIC sous Linux. 2. Ajouter dans le code le calcul de la gigue. Exécuter et stresser le système d’exploitation. 3. Entre le noyau Linux utilisé (temps réel mou et faible latence) et le Noyau Xenomai (temps réel dur) lequel est le plus déterministe temporellement ? 2 IPC (Inter Processus Communication) : Synchronisation entre tâches L’exécution parallèle de plusieurs tâches (en temps partagé, ou simultané si plusieurs CPUs) pose des problèmes de synchronisation pour l’accès concurrent à un périphérique ou un emplacement mémoire. 2.1 Analyse d’un problème d’accès concurrent 1. Dans le répertoire synchro_frigo1, examiner, compiler et exécuter le programme race_pthread. Quel problème survient avec le partage de la variable commune refrigerator ? Est-ce dû à la présence de 2 tâches ? Commenter la création d’une des deux tâches pour vérification. 2. L’erreur survient de manière sporadique. On cherche l’enchaı̂nement spécifique de commutation de tâches à la source du problème par l’ajout d’affichages. Ouvrir race_pthread_display. 3. Exécuter. Interpréter la séquence d’exécutions responsable du problème d’accès concurrent. Faire valider par un encadrant. TP TR ENSPS et MASTER 2.2 3 Mutex On souhaite utiliser un objet de synchronisation mutex pour résoudre le problème d’accès concurrent. On pourra lire la description de l’objet MUTEX et des fonctions pour le manipuler dans la documentation Xenomai POSIX Skin/Mutex services ou dans le cours. 1. Dans le fichier race_pthread, un objet pthread_mutex d’identifiant mx est déjà défini en variable globale. A vous de l’utiliser pour résoudre le problème d’accès avec : (a) l’initialisation du mutex dans le main. On peut utiliser NULL pour les attributs par défaut. (b) l’utilisation des fonctions pthread_mutex_lock et pthred_mutex_unlock dans les 2 tâches pour définir des sections critiques : sections autour du code critique et dans l’ensemble desquels, un seul processus peut s’exécuter à la fois. 2. Tester et essayer différents emplacements pour l’acquisition et libération du mutex. Ajouter un fprintf(,"...") dans le corps des tâches pour confirmer leur bonne exécution et non leur blocage (deadlock) suite à un mauvais usage du mutex. 2.2.1 Sémaphore Le sémaphore est utile lorsqu’il faut un compteur pour synchroniser l’accès à une ressource présente en N exemplaires. Son compteur est alors initialisé à N. Une tâche, qui acquiert le sémaphore, décrémente son compteur. Si il vaut 0, la tâche qui veut acquérir le sémaphore est bloquée (elle dort) tant qu’une autre tâche ne libère pas le sémaphore et donc incrémente le compteur. Le sémaphore garantit que son compteur n’est manipulé que par une tâche à la fois et n’est ainsi pas sujet aux problèmes d’accès concurrents. Notre famille compte désormais 3 fils. Le père de famille a donc investi dans 3 réfrigérateurs pour stocker le coca ! 1. Dans le répertoire synchro_frigo2, noter la création de 3 tâches ”fils” identiques. Que renvoie la fonction pthread_self() ? Pourquoi l’avoir utilisée ici dans la fonction d’affichage ? 2. Exécuter le programme. On peut rediriger l’affichage sur l’erreur standard (stderr, de descripteur 2) vers un fichier log : ./race_pthread 2>log.txt. Constater la présence d’incohérences dues à des accès concurrents. 3. Décommenter les 4 lignes définissant les sections critiques avec un mutex. Lister les variables partagées par plusieurs tâches. Sont elles correctement protégées par le mutex ? 4. La section critique définie pour les tâches ”fils” ont 2 désavantages : – l’affichage ”Papa, il n’y a plus de coca !” est inutilement protégé par le mutex ; – limitation du parallélisme : un fils bloque tout autre fils tant qu’il n’a pas trouvé lui-même un coca en testant successivement les 3 réfrigérateurs. Proposer et tester une meilleur définition de la section critique du code de la tâche ”fils”. On note que les fils testent les réfrigérateurs en continu alors qu’ils sont désespérément vides. On parle d’une attente active car elle consomme du temps CPU. On souhaite utiliser un sémaphore pour rendre cette attente passive : la tache est mise en sommeil tant qu’aucun coca n’est disponible. 1. Ajouter un objet sémaphore RT_SEM et initialiser son compteur à une valeur adéquate. Utiliser les 2 fonctions basiques sem_wait et sem_post pour transformer les attentes actives en attentes passives par l’ajout de ces fonctions. Faire valider par un encadrant. 3 Ordonnancement On s’intéresse maintenant au problème d’ordonnancement de tâches avec le respect de contraintes temps réel. 4 TP TR ENSPS et MASTER On utilisera dans cette section, l’API native de Xenomai décrite dans la documentation sous l’onglet : Module/Native Xenomai API. Celle-ci propose des fonctionnalités supplémentaires et une syntaxe plus simple. L’ordonnanceur par défaut de Xenomai est un ordonnanceur à priorités fixes : la tâche en exécution est toujours celle qui est prête ET de priorité la plus élevée. Dans le cas où 2 tâches ont la même priorité, la politique utilisée est SCHED FIFO : la plus ancienne en attente s’exécute et l’autre attend la fin de son exécution. On notera en particulier que : – les taches sont des objets RT_TASK créées et démarrées avec rt_task_spawn(), où on indique la priorité de la tâche ainsi que ses options. Note : On travaille dans le cas monoprocesseur. Toutes les tâches sont créées sur le même coeur. – rt_timer_read retourne le temps système en ns dans une variable de type RTIME (qui n’est autre qu’un unsigned long long). 3.1 Ordonnancement RM L’ordonnancement RM permet d’ordonnancer des tâches périodiques en leur assignant une priorité fixe et en fournissant un critère suffisant de réussite de l’ordonnancement de ces tâches (critère de Lyu et Layland). L’ordonnancement est considéré réussi si une tâche finie son exécution avant le début de sa prochaine période. On se propose ici de retrouver les résultats des 2 exemples traités en cours. 3.1.1 Cas 1 Tâches i Tâche A Tâche B Ai (ms) 0 0 Ci (ms) 25 30 Ti (ms) 50 95 avec – Ai , la date d’activation de la tâche ; – Ci , la durée CPU nécessaire à l’exécution complète de la tâche ; – Ti , la période si la tâche est périodique. 1. Dans le répertoire ordonnancement_RM, étudier dans le code source l’usage de la fonction rt_task_set_periodic pour obtenir le même instant d’activation des tâches A et B ? 2. Commenter la création de la tache B. Exécuter. Noter le temps d’exécution nécessaire à la tâche A pour incrémenter MAX_COUNT_A fois la variable. 3. Définir alors le nombre adéquat d’incréments MAX_COUNT_A et MAX_COUNT_B pour le temps d’exécution (Ci ), ainsi que les priorités et les périodes des tâches A et B. Exécuter avec les 2 tâches actives. 4. Observer et compléter le chronogramme 1. Est ce le résultat attendu ? Remarque : L’affichage par le noyau Linux est réalisé à la fin pour éviter de perturber de l’ordonnancement Xenomai par des migrations entre Xenomai et Linux des tâches. 3.1.2 Cas 2 Tâches i Tâche A Tâche B Ai (ms) 0 0 Ci (ms) 25 30 Ti (ms) 50 75 1. Modifier la période de la tâche B en conséquence. Exécuter et compléter le chronogramme 2. 5 TP TR ENSPS et MASTER 0 1 1 Priorité A 0 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 50 100 150 200 250 50 100 150 200 250 B 0 s Figure 1 – Chronogramme 1 0 1 1 Priorité A 0 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 50 100 150 200 250 50 100 150 200 250 B 0 s Figure 2 – Chronogramme 2 2. Est-ce le résultat attendu ? Comment Xenomai gère (ou ne gère pas) le fait qu’une tâche ne soit pas finie alors qu’une nouvelle période a déjà commencé ? 3. D’après la documentation de la fonction rt_task_wait_period (paramètre et code de retour -ETIMEDOUT), dans quel cas Xenomai génère un signal d’avertissement ? 3.2 Ordonnancement RM et inversion de priorité L’ordonnancement RM fait 2 hypothèses sur les tâches : periodicité et indépendance des tâches. Etudions les conséquences sur l’ordonnancement de tâches qui sont dépendantes via un mutex ou un sémaphore. 3.3 Partage d’un sémaphore binaire Soit des tâches A et C, dont la première instruction est d’acquérir le sémaphore sem et la dernière instruction de le libérer. On considère alors le cas d’ordonnancement suivant : Tâches i Tâche A Tâche B Tâche C Ai (ms) 2 3 0 Ci (ms) 2 8 15 Ti (ms) 10 32 60 1. Descendre dans le répertoire semaphore_RM, modifier le fichier semaphore.c pour obtenir l’ordonnancement attendu, soit : – initialiser le compteur du sémaphore à 1 dans le main ; – choisir les valeurs adéquates de MAX_COUNT_x avec x={A,B,C} pour obtenir les bons temps d’exécutions Ci (on pensera à les vérifier) ; – modifier les paramètres des fonctions rt_task_set_periodic pour obtenir les dates de première activation Ai requises. 2. Exécuter. D’après les résultats obtenues, compléter le chronogramme 3 et montrer que la tâche A saute une période. 6 TP TR ENSPS et MASTER 0 1 0 Priorité A 1 1 0 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 Tentative d’acquisition du semaphore binaire 5 10 15 20 25 5 10 15 20 25 5 10 15 20 25 B 0 B C 111 000 000 111 000 111 0 B Acquistion du semaphore binaire Figure 3 – Chronogramme 3 : Inversion de priorité 3.4 Héritage de priorité : Solution à l’inversion de priorité Le cas précédent est un cas typique d’inversion de priorité où la tâche intermédiaire B est exécutée avant la tache prioritaire A car cette dernière bloque sur un mutex. Le mutex bénéficie d’un protocole d’héritage de priorité sous Xenomai (ce qui n’est pas le cas du sémaphore) : si une tâche C possède un mutex et qu’une tâche plus prioritaire A tente d’acquérir ce même Mutex, alors la priorité de C est augmentée au niveau de celle de A afin de libérer au plus vite le mutex (la ressource protègée). 1. Modifier le fichier semaphore.c pour remplacer le sémaphore par un mutex (RT_SEM devient RT_MUTEX) et remplacer les fonctions d’acquisition/libération du sémaphore par celles du mutex. 2. Tester et tracer le nouveau chronogramme obtenu. Le saut de période a-t-il disparu ? 3. La fonction rt_task_inquire permet de récupérer un structure RT_TASK_INFO contenant la priorité de base (champ entier bprio) et la priorité courante (champ entier cprio) de la tâche. Utiliser cette fonction pour afficher la priorité de base et courante de la tâche C juste avant qu’elle ne libère le sémaphore. 4. Tester et contrôler que l’héritage n’a lieu que si A bloque sur le même mutex. 7 TP TR ENSPS et MASTER 0 1 0 Priorité A 1 1 0 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 Tentative d’acquisition du mutex 5 10 15 20 25 5 10 15 20 25 5 10 15 20 25 B 0 B C 111 000 000 111 000 111 0 B Acquistion du mutex Figure 4 – Chronogramme 4 : héritage de priorité