introduction à la programmation temps réel
Transcription
introduction à la programmation temps réel
I NTRODUCTION À LA P ROGRAMMATION T EMPS R ÉEL P OLYCOPIÉ DU COURS PTR Prof. Yann Thoma HEIG-VD 2010 Chapitre 1 Introduction à la programmation temps réel 1.1 Qu’est-ce que la programmation temps réel ? U temps réel est un système informatique devant répondre à des stimuli de l’environnement en tenant compte de contraintes de temps. Le comportement correct d’un tel système ne dépend donc pas uniquement des résultats fournis, mais également de la date à laquelle ces résultats sont communiqués. Un calcul correct mais tardif sera considéré comme faux, si les contraintes temporelles du système sont strictes. Par opposition aux systèmes temps réel, un système informatique standard est dit transformationnel. Une application de bureautique standard tournant sur un PC est un système transformationnel. Les données y sont traitées, sans que le temps nécessaire à ce traitement ne soit crucial. Un système temps réel typique sera décomposé en tâches, chaque tâche étant responsable de la gestion d’une partie du système. La figure 1.1 illustre l’interaction qu’a un tel système avec son environnement, dans le cas d’une tâche de contrôle. Nous pouvons noter que certains systèmes n’auront pas à proprement parler de système de gestion des sorties vers l’environnement. Les sorties pourront effectivement être un fichier restant interne au système informatique. N SYSTÈME Figure 1.1 – Système temps réel pour une tâche de contrôle Système temps réel Système de contrôle Gestion des entrées Gestion des sorties Environnement La définition donnée par Abrial-Bourgne définit bien ce qu’est un système temps réel : Un système fonctionne en temps réel s’il est capable d’absorber toutes les informations d’entrée sans qu’elles soient trop vieilles pour l’intérêt qu’elles représentent, et par ailleurs, de réagir à celles-ci suffisamment vite pour que cette réaction ait un sens. [6] c Programmation Temps Réel Yann Thoma // HEIG-VD 1.2 CHAPITRE 1. INTRODUCTION À LA PROGRAMMATION TEMPS RÉEL Et l’adage suivant devra être gardé en mémoire : Un résultat juste mais hors délai est un résultat faux. Le fait qu’un résultat soit faux de par son arrivée tardive peut être extrêmement critique et même dangereux. Un exemple typique est un pilote automatique dans un avion. Un tel système pourrait être décomposé en une tâche responsable de récupérer les valeurs de différents capteurs (altitude, vitesse, ...), une tâche responsable de calculer la puissance à fournir aux réacteurs ainsi que les angles des empennages, et une dernière tâche responsable des actuateurs (arrivée de carburant, empennages). Si une mesure des capteurs est effectuée régulièrement, que les données sont transmises à la tâche de calcul, qui ensuite transmet ses résultats à la dernière tâche, il serait judicieux que ce processus ne prenne pas un temps trop important. En effet, à 300km/h lors d’un atterissage, une second d’inattention pourrait être fatale. Dans ce cas, les tâches doivent avoir des délais stricts, et ces délais doivent absolument être respectés pour que le fonctionnement global soit considéré comme correct, un fonctionnement anormal pouvant avoir des conséquences désastreuses. Cet exemple peut montrer qu’il faut que le système effectue son traitement le plus rapidement possible. Mais attention à ne pas tomber dans le piège : Un système temps réel n’est pas un système qui effectue du calcul de manière rapide. En effet, les systèmes que nous traiterons doivent fournir les résultats dans un temps bien spécifique, mais celui-ci peut être long. La rapidité n’est pas un critère, par contre la fiabilité, liée au respect de contraintes temporelles, en est un. Les délais de systèmes temps réel peuvent effectivement être très lâches, mais extrêmement sensibles à des dépassements de délai. Le tableau 1.1 montre des exemples d’ordres de grandeur de temps, dans les systèmes informatiques. Durant ce cours de programmation temps réel, nous allons étudier des algorithmes permettant d’assurer la bonne marche d’un système temps réel, et notamment voir pourquoi aller vite ne suffit pas. Nous verrons également qu’il est très délicat d’être sûr qu’un système a été correctement conçu, et qu’il est possible qu’un tel système fonctionne pendant longtemps avant qu’une faute ne puisse survenir. Afin de garder à l’esprit ce type de mauvaise surprise, le tableau 1.2 liste quelques lois tirées de [4] particulièrement adaptées au contexte du temps réel. N’oubliez pas de vous y référer lorsqu’un système qui a fonctionné pendant plusieurs minutes, heures, ou jours, vient subitement de décider de crasher. 1.2 Propriétés attendues Les systèmes temps réel sont des systèmes devant respecter les contraintes de temps imposées. A priori, le code de chacune des tâches composant un tel système est connu. Il peut donc être analysé en termes de temps d’exécution, ressources nécessaires, et dépendance envers d’autres tâches. Les propriétés suivantes devraient être respectées : Gestion du temps. Les résultats doivent être corrects, non seulement en termes de valeur, mais également termes de temps d’arrivée. Le système doit garantir que les tâches se terminent dans les délais impartis, en respectant les contraintes de temps imposées. Conception pour stress. Un système temps réel ne doit pas succomber lorsqu’il est soumis à une charge importante. Des scénarios mettant en avant des charges limites doivent être anticipés, et testés. Predictibilité. A chaque décision liée à l’ordonnançement des tâches, le système doit être capable de prédire les conséquences de son choix. Il doit pouvoir dire si les tâches sont ordonnançables, et si tel est le cas proposer un ordonnançement. S’il n’est pas possible de gérer l’ensemble de tâches, des actions coordonnées doivent être prises afin de ne pas compromettre le fonctionnement du système. c Programmation Temps Réel Yann Thoma // HEIG-VD 1.2. PROPRIÉTÉS ATTENDUES Temps Exemple nanosecond - O(ns) Temps d’accès à une RAM (5-80ns) Durée entre deux ticks d’horloge d’un processeur Pentium (1GHz) Fréquence de 1GHz (109 Hz) microseconde - O(µs) Traitement dans un noyau de système d’exploitation (changement de contexte, interruption matérielle) Systèmes utilisant des radars (navigation, détection de mouvement, etc.) Transmission sur des bus de terrain, transmission radio Fréquence de 1 MHz (106 Hz) milliseconde - O(ms) Temps d’accès à un disque dur SCSI ou IDE (5-20 ms) La durée d’échantillonnage du son, protocoles de télécommunication Fréquence de 1 KHz (103 Hz) seconde (s) - O(s) Systèmes de visualisation humain (temps durant lequel l’oeil peut "intégrer" 25 images au plus) Applications multimédia Temps de réponse des applications informatiques (accès DB, compilation, etc.) Fréquence de 1 Hz L’heure (h) - O(h) Applications de surveillance de réactions chimiques, surveillance de données météorologiques Le mois, l’année - O(m, a) Systèmes de navigation de sonde spatiale Tableau 1.1 – Ordres de grandeur de temps Tolérance aux fautes. Un système de contrôle critique (p. ex. dans un avion) doit être tolérant aux fautes logicielles et matérielles. Les composants utilisés doivent donc être conçu pour être tolérants aux fautes. Maintenance. Comme tout système informatique, l’architecture d’un système temps réel doit être pensé de façon modulaire, afin que des modications du système puissent être réalisées facilement. 1.2.1 Prédictibilité Alors qu’un système informatique standard ne fait que traiter des données, le système temps réel doit garantir que les contraintes de temps seront respectées. Lors de l’attribution du processeur à telle ou telle tâche, le système doit obligatoirement connaître le temps d’exécution de chacune des tâches. Ceci nécessite de connaître plusieurs éléments : • Le code écrit par le programmeur • La structure du noyau du système d’exploitation • La structure du processeur et de ses périphériques c Programmation Temps Réel Yann Thoma // HEIG-VD 1.3 1.4 CHAPITRE 1. INTRODUCTION À LA PROGRAMMATION TEMPS RÉEL Murphy’s General Law If something can go wrong, it will go wrong. Murphy’s Constant Damage to an object is proportional to its value. Naeser’s Law One can make something bomb-proof, not jinx-proof. Troutman Postulates 1. Any software bug will tend to maximize the damage. 2. The worst software bug will be discovered six months after the field test. Green’s Law If a system is designed to be tolerant to a set of faults, there will always exist an idiot so skilled to cause a nontolerated fault. Corollary Dummies are always more skilled than measures taken to keep them from harm. Johnson’s First Law If a system stops working, it will do it at the worst possible time. Sodd’s Second Law Sooner or later, the worst possible combination of circumstances will happen. Corollary A system must always be designed to resist the worst possible combination of circumstances. Tableau 1.2 – Lois de Murphy relatives aux systèmes temps-réel. c Programmation Temps Réel Yann Thoma // HEIG-VD 1.2. PROPRIÉTÉS ATTENDUES Programmation Le code d’une tâche devant être exécutée dans un contexte temps réel doit être particulièrement bien conçu. Nous notons ici trois lois qu’un développeur devrait respecter : Absence de structures de données dynamiques. Les langages de haut niveau permettent d’allouer et de désallouer dynamiquement de la mémoire lors de l’exécution. Toutefois ces opérations sont très lourdes en termes de temps processeur, et ne peuvent aisément être bornées en temps. Dès lors il est très délicat d’évaluer le pire temps d’exécution d’une tâche exécutant une allocation mémoire. Si des allocations dynamiques sont nécessaires, elles devraient être faites au lancement du système, mais pas durant son exécution courante. Absence de récursion. Les appels récursifs devraient être évités, car, si la profondeur de récursion n’est pas connu a priori, le temps ainsi que les ressources nécessaires ne pourront être bornés. Boucles bornées en temps. Le nombre d’itérations des boucles devrait être borné, afin de pouvoir estimer leut temps d’exécution. Si tel n’est pas le cas, il est impossible d’estimer le temps d’exécution d’une tâche. Appels système En analysant le code écrit par le programmeur, il semble être possible d’estimer le pire temps d’exécution. Un des problèmes vient des différents appels système, l’autre type de problème étant lié à la structure matérielle du processeur. Les appels systèmes font appel à des librairies, et permettent par exemple à une tâche d’allouer de la mémoire, d’écrire/lire dans des fichiers, afficher des informations sur un écran, communiquer via un port Ethernet ou un port série, ou se synchroniser avec d’autres tâches. Ces différents appels système, vus par le programmeur, restent des appels à des fonctions de haut niveau. Le noyau du système d’exploitation se charge du traitement des requêtes, et ce traitement dépend grandement du système d’exploitation cible. Toutes les fonctions appelées devraient être analysées afin de mieux définir le temps d’exécution d’une tâche. Gestion de la mémoire Le système d’exploitation est responsable de la bonne gestion de la mémoire (paginée, segmentée). Le concept de mémoire virtuelle, que l’on trouve utilisé dans tous les systèmes d’exploitation standards, permet de mieux protéger les processus, et d’offrir aux processus l’impression qu’ils disposent d’une quantité de mémoire plus importante que la quantité de mémoire physique réellement présente. Ce concept puissant a toutefois un désavantage en termes de temps d’exécution. Les accès mémoires y sont plus lents, et surtout une faute de page nécessite un temps de traitement très important. Une faute de page pouvant intervenir n’importe quand, il est dès lors impossible de prédire le temps d’exécution d’une tâche si la mémoire virtuelle est activée. Un système temps réel doit donc éviter d’utiliser le concept de pagination tel qu’il existe dans les OS standards. Verrous/Sémaphores Nous verrons plus loin le concept de verrous et de sémaphores. Les verrous servent à protéger une ressource critique afin d’éviter que deux tâches n’y accèdent en même temps. A titre d’exemple, un fichier est une ressource critique pour laquelle il n’est pas possible de voir deux tâches y écrire des données en même temps sous peine de corruption de fichier. L’utilisation de verrous dans un cadre temps réel pose le problème de l’inversion de priorité, que nous verrons également plus loin. Ce problème peut être résolu par différents algorithmes (héritage de priorité, priorité plafonnée, c Programmation Temps Réel Yann Thoma // HEIG-VD 1.5 1.6 CHAPITRE 1. INTRODUCTION À LA PROGRAMMATION TEMPS RÉEL allocation de ressource basée sur une pile) qui doivent être implémentés dans le noyau. Les fonctions d’accès aux verrous et aux sémaphores doivent donc être adaptées pour pouvoir donner des garanties sur le temps d’exécution des tâches. Interruptions Les interruptions sont générées par les périphériques d’un système informatique. Un port de communication peut ainsi per exemple signaler que des données sont prêtes, et qu’une application peut donc les récupérer pour les traiter. Les interruptions sont généralement traitées de manière prioritaire sur les autres tâches. Le problème liée au temps réel vient du fait qu’il est en général impossible de savoir quand ses interruptions peuvent survenir, ni quel sera leur fréquence. Afin de gérer tout de même les périphériques, trois alternatives peuvent être proposées : 1. Toutes les interruptions peuvent être masquées, pour être sûr qu’aucune d’elle ne viendra interférer avec le déroulement des tâches. L’interfaçage des périphériques est ainsi laissé au soin des tâches, qui doivent aller régulièrement vérifier l’état des périphériques et agir en conséquence. L’observation des périphériques se fait donc par scrutation, ce qui oblige le processeur à être oisif lorsqu’il est en attente d’une entrée. De la puissance de calcul est alors perdue à chaque fois qu’une tâche doit attendre qu’il se passe quelque chose sur un périphérique. 2. Toutes les interruptions sont masquées, à l’exception du timer. Une routine du noyau est alors appelée régulièrement et vérifie l’état des différents périphériques. Si un périphérique voit son état modifié, la routine averti alors la tâche attendant les données y relatives. Cette approche permet de gérer les périphériques avec une tâche périodique bornée, qui peut sans autre être prise en compte lors des choix d’ordonnançement. Du temps est tout de même perdu par la scrutation active des périphériques par cette tâche. 3. Une troisième alternative propose d’autoriser les interruptions, mais de ne placer que du code minimal dans les routines appelées. La routine du driver 1 appelée par une interruption n’y fait que noter le fait qu’une interruption a été levée, et active la tâche qui était en attente sur des données particulières. Ici une tâche ayant besoin d’attendre des données venant d’un port de communication sera mise en attente passive et permet de ne pas gaspiller du temps processeur en exécutant une boucle de scrutation. Le temps de gestion de l’interruption par la routine du driver est très faible, et peut souvent être négligé. DMA Le concept de DMA (Direct Memory Access) est un mécanisme permettant au processeur de déléguer à un sous-système le soin d’effectuer un grand transfert de mémoire. Ceci permet au processeur de continuer à exécuter des instructions pendant que le transfert s’effectue. Bien que permettant d’accélérer le traitement global, ce mécanisme peut momentanément ralentir le cours d’exécution car le processeur doit partager le bus de données avec le contrôleur DMA. Si la tâche exécutée par le processeur nécessite d’accéder à la mémoire, un arbitrage doit se faire. Plusieurs solutions existent, mais sortent du cadre de ce cours. Nous pouvons toutefois noter que ce problème ne doit pas être négligé lors de l’estimation du temps d’exécution d’une tâche. 1. Portion de code chargée dans le noyau, et responsable de la bonne gestion d’un périphérique. c Programmation Temps Réel Yann Thoma // HEIG-VD 1.3. SYSTÈME D’EXPLOITATION Mémoire cache Pour terminer, les mémoires caches (ou antémémoires) ont été introduites dans les systèmes informatiques afin d’accélérer les accès mémoire. Une mémoire cache est insérée entre le processeur et la mémoire principale et contient un sous-ensemble des données présentes en mémoire principale. Etant plus petite et plus proche du processeur, son accès est nettement plus rapide, et le principe de localité temporelle et spatiale permet de voir le temps d’exécution des applications fondre. L’accès à une donnée présente en cache est donc très rapide. Les études montrent que 80% des accès mémoires sont trouvés en cache, et que seulement 20% nécessitent un accès à la mémoire principale. Le problème de la prédictibilité est qu’il est impossible de savoir exactement quels seront les accès réussi et lesquels seront des échecs. Il est donc possible de borner le temps d’exécution d’une tâche lorsqu’il n’y a pas de cache, ou que son accès a été désactivé. Par contre, si une cache est utilisée, une borne pourrait être donnée en considérant que tous les accès sont des échecs, ce qui donnerait une estimation totalement faussée. Il s’agit du seul moyen d’assurer une predictibilité exacte, mais l’erreur d’estimation est énorme. Il est donc possible d’être un peu plus flexible en usant de statistiques concernant la probabilité de réussite/échec des accès mémoires. Comme nous venons de le voir, les facteurs d’impredictibilité sont nombreux, et peuvent aisément conduire à l’observation d’un système fonctionnant parfaitement le 99% du temps et se retrouvant soudainement dans un état non gérable. Ces facteurs doivent donc être finement analysés lors de la mise au point d’une application temps réel. 1.3 Système d’exploitation Un système temps réel peut être mis en place via un système d’exploitation ou non. Sans système d’exploitation, il est possible de disposer d’un exécutif (ou moniteur) temps réel, qui est une sorte de micro-noyau. Cet exécutif est responsable de l’ordonnançement des tâches (ordre dans lequel elles s’exécutent), les tâches s’occupant de leur traitement propre. Cette solution peut être adaptée à des systèmes embarqués dont la fonctionnalité est très spécifique et peu soumise à modification. Dans des systèmes plus importants, il est agréable de disposer des facilités proposées par un système d’exploitation, tels qu’une interface utilisateur ou la gestion de fichiers, par exemple. Un système d’exploitation doit par contre être spécifiquement conçu pour le temps réel. Il est en effet impossible de faire fonctionner une application temps réel strict sur un système d’exploitation standard. Ceci est notamment en partie dû aux facteurs d’imprédictibilité cités précédemment. Il existe plusieurs systèmes d’exploitation temps réel, propriétaires ou open source. Dans le cadre de ce cours, nous utiliserons Xenomai [1], qui est un noyau temps réel qu’il est possible d’adjoindre à un Linux standard. Les applications temps réel peuvent alors être gérées correctement par Xenomai, tandis que les applications standards son exécutées dans le contexte Linux. 1.4 Temps réel strict vs. souple En fonction de la nature des contraintes temporelles appliquées à un système temps réel, celui-ci peut être de deux types : Temps réel strict ou dur (hard). Il s’agit d’un système où l’ensemble des contraintes temporelles doit absolument être respecté. Pour ce faire il faut pouvoir définir les conditions de fonctionnement du système, c’est-à-dire connaître parfaitement l’environnement du système. Il faut également être capable de garantir la fiabilité du système avant son exécution. Tous les scénarios possibles d’exécution doivent donc être étudiés et le bon fonctionnement du système doit être c Programmation Temps Réel Yann Thoma // HEIG-VD 1.7 1.8 CHAPITRE 1. INTRODUCTION À LA PROGRAMMATION TEMPS RÉEL garanti pour chacun de ces scénarios. L’ensemble du système doit évidemment être suffisamment connu pour pouvoir fournir de telles garanties. A titre d’exemple, le contrôle d’un avion par un pilote automatique est un système temps réel strict. Il existe également des systèmes appelés temps réel ferme (firm), où la pertinence du résultat est nulle passée l’échéance, mais dont le non respect de l’échéance ne compromet pas le fonctionnement du système ou l’intégrité des personnes. Une application bancaire responsable de calculer des risques en fonction des paramètres courants du marché en est un exemple. Temps réel souple ou mou (soft). Les systèmes temps réel souple ont des contraintes de temps moins exigeantes que les précédents. Une faute temporelle n’y est pas catastrophique pour le fonctionnement. Un tel système pourra donc accepter un certain nombre de fautes temporelles, tout en pouvant continuer son exécution. A titre d’exemple, les applications de type multimédia telles que la téléphonie ou la vidéo sont des applications temps réel souple, car la perte de certaines informations, liée à un débit trop faible, n’est pas dangereux ou catastrophique. Les systèmes de ce type peuvent ensuite être comparés en fonction de la qualité de service qu’ils offrent, en termes de probabilité d’erreur. La figure 1.2 résume la valeur d’un calcul en fonction de son instant d’arrivée. Dans le cas d’une échéance stricte ou firme, passé l’échéance, le calcul n’a plus aucune valeur. La différence entre ces deux cas vient du fait qu’une échéance stricte manquée met en péril le système, alors qu’une échéance firme manquée implique uniquement que le résultat du calcul n’est plus pertinent. Dans le cas d’une échéance molle, la pertinence du résultat perd de sa valeur après l’échéance, mais ce d’une manière plus douce. Valeur Valeur échéance Valeur échéance Temps (a) Echéance stricte échéance Temps (b) Echéance ferme Figure 1.2 – Valeur du calcul en fonction du temps c Programmation Temps Réel Yann Thoma // HEIG-VD Temps (c) Echéance molle Chapitre 2 Introduction aux tâches 2.1 Qu’est-ce qu’une tâche ? E séquentielle, un programme est décomposé en sous-programmes (procédures ou fonctions). Chaque sous-programme correspond à une suite d’instructions, et l’exécution du programme voit ces instructions être exécutées les unes à la suite des autres. Il est en général possible de réaliser une application en une seule tâche, mais la scrutation de périphériques risque d’y être délicate et coûteuse en temps processeur. Un système temps réel est toujours décomposé en tâches, chacune ayant des fonctionnalités, des temps d’exécution et des échéances différentes. Il serait parfois possible de les combiner en une seule tâche, mais cet exercice n’est pas souhaité, notamment à cause du contrôle plus délicat sur les parties d’exécution que ceci impliquerait. Considérons un simple système composé de deux entrées (un port série et un clavier), d’une partie de traitement, et d’une sortie. Dans un contexte temps réel, le port série doit être scanné régulièrement afin d’éviter qu’un octet ne soit perdu. Si la tâche de traitement est conséquente, il faudrait, en milieu de traitement, aller vérifier l’état du port série, sans oublié la gestion du clavier. Il faudrait également faire de même si la transmission vers la sortie est lente. Dans un tel cas, la décomposition du système en tâches distinctes permet d’éviter ces inconvénients. Nous pouvons imaginer une tâche responsable de lire les données venant du port série, et ce à une cadence garantissant le respect des échéances visant à ne pas perdre d’octet. Une autre tâche serait responsable de la gestion du clavier, une autre du traitement, et enfin une dernière de la gestion de la sortie. Des mécanismes de communication peuvent être mis en oeuvre pour permettre aux différentes tâche de s’échanger des données, et des priorités peuvent être assignées, notamment pour garantir que le port série est bien géré. Les systèmes temps réel étant généralement basés sur une architecture relativement semblable à cet exemple, nous traiterons donc de systèmes multi-tâches. A l’heure actuelle, les systèmes informatiques sont multi-tâches, et supportent l’exécution pseudo-parallèle de plusieurs tâches. Ceci a été rendu possible par la mise au point de systèmes d’exploitation nettement plus complexes. Un processeur peut donc voir plusieurs tâches en cours d’exécution se partager le temps de traitement. Dans le cadre d’une application particulière, il est dès lors possible d’avoir, par exemple, une tâche responsable d’exécuter un calcul lourd pendant qu’un autre gère les interactions avec l’utilisateur. Il est intéressant de noter que le concept de programmation multi-tâches est autant valable sur un processeur simple coeur que sur un multi-coeur. Sur un simple coeur, les parties de tâches s’exécutent tour à tour de manière transparente, et sur un multi-coeur, un réel parallélisme peut être observé, N PROGRAMMATION c Programmation Temps Réel Yann Thoma // HEIG-VD 2.2 CHAPITRE 2. INTRODUCTION AUX TÂCHES chaque coeur pouvant exécuter un ensemble de tâches. 2.2 Anatomie d’une tâche Un processus correspond à un fichier exécutable en cours d’exécution sur un processeur. Il est entre autre caractérisé par un code (le programme à exécuter), une pile et un tas qui permettent de stocker les données nécessaires à son bon fonctionnement, un identifiant unique, et une priorité. La priorité permet au système d’exploitation de gérer plusieurs processus en cours d’exécution sur un processeur, un processus à plus haute priorité se voyant attribuer un temps d’utilisation processeur plus important. Un processus peut également être décomposé en tâches (threads ou fils d’exécution), chaque tâche étant une sorte de processus léger. Dans ce cours, nous utiliserons le terme de tâche pour désigner une suite d’instructions séquentielles correspondant à une tâche. Toutefois, les notions d’ordonnancement introduites dans le chapitre correspondant peuvent être aussi bien appliquées à des processus. La figure 2.1 illustre la décomposition de l’espace d’adressage d’un processus en trois parties principales : • Le code contenant les instructions du programme (text segment en anglais). • Les variables globales et les variables allouées dynamiquement (data segment en anglais). • La pile, où les variables locales de ses sous-programmes, ainsi que diverses informations temporaires ayant une durée de vie égale au sous-programme sont stockées (stack segment en anglais). Figure 2.1 – Espace d’adressage d’un processus Espace d'adressage Processus stack segment data segment text segment (code) Un processus, dans un cadre multi-tâche, est décomposé en deux parties : • La première contenant les ressources globales, telles que les instructions du programme et les variables globales. Cette partie correspond au processus. Il s’agit des deux premiers points de l’espace d’adressage. c Programmation Temps Réel Yann Thoma // HEIG-VD 2.3. AVANTAGES DU MULTI-TÂCHE • La deuxième contenant des informations liées à l’état d’exécution, telles que le compteur de programme (aussi appelé compteur ordinal) et la pile d’exécution. Cette partie correspond à une tâche. Il est à noter que chaque tâche possède un compteur de programme et une pile. Il s’agit de la partie liée à la tâche. • Etant donné que les tâches d’un même processus partagent le même espace d’adressage, une tâche peut facilement corrompre les données utilisées par une autre tâche. Des outils de synchronisation permettent toutefois d’éliminer les risques de ces corruptions, s’ils sont utilisés de manière appropriée. • Toujours lié au partage de l’espace d’adressage, si une tâche effectue un accès mémoire erroné fatal, le processus entier risque de se terminer. Ce n’est évidemment pas le cas pour un système à plusieurs processus. • Une tâche est lié à un programme particulier, et ne peut donc pas être lancé par un autre programme. Les processus peuvent en revanche être lancé par un autre processus, et donc être plus aisément réutilisés. 2.3 Avantages du multi-tâche En comparaison d’un programme ne contenant qu’une seule tâche, un programme décomposé en tâches permet de mieux gérer les entrées/sorties et le calcul. Une tâche peut s’occuper du calcul, tandis que d’autres gèrent les entrées/sorties. De ce fait, l’usage d’un GUI (Graphical User Interface) est plus ergonomique et convivial. Prenons l’exemple d’une application visant à afficher la courbe de Mandelbrot. Pour chaque point de l’image, une grande quantité de calcul doit être effectuée. Supposons que l’utilisateur peut cliquer sur des boutons pour zoomer. Si nous ne disposons pas de multi-tâche, un clique sur le bouton va ensuite voir l’application se bloquer pendant que la nouvelle image est calculée. Si par contre nous disposons de plusieurs tâches, une tâche peut s’occuper du calcul pendant que l’autre gère l’interface graphique. Dès lors l’utilisateur a encore la possibilité d’interagir avec l’application sans devoir souffrir de l’accaparement du processeur pour le calcul. Dans le cas d’un processeur multi-coeur, un autre avantage du multi-tâche peut s’exploiter. En effet, chaque coeur peut prendre en charge un ou plusieurs tâches. En reprenant l’exemple de Mandelbrot, si nous supposons que nous sommes en présence d’un dual-core, alors nous pouvons décomposer notre calcul en deux tâches, chacune étant responsable de la génération de la moitié de l’image. Lorsque l’utilisateur clique sur le bouton, la nouvelle image pourra donc s’afficher en un temps réduit de moitié. Il s’agit là d’un parallélisme réel, rendu possible par l’amélioration des plateformes matérielles proposées sur le marché. Nous pouvons toutefois noter que l’accélération d’un facteur n pour un n-core reste théorique, la probable communication inter-tâche imposant une perte en performance. En comparaison d’un système multi-processus, une application multi-tâche requiert une surcharge (overhead) moindre pour la gestion des tâches. En effet, commuter d’une tâche à une autre est nettement moins coûteux en terme de temps processeur que de commuter d’un processus à un autre. Un avantage d’une application multi-processus est la possibilité d’exécuter les processus sur des machines différentes, ce qui n’est pas le cas du multi-tâche. Pour ce qui est de la commutation, le responsable de son fonctionnement, dans les deux cas, est le système d’exploitation. La table suivante (tirée de [9]) liste les avantages et les inconvénients du multi-tâche par opposition au multi-processing : c Programmation Temps Réel Yann Thoma // HEIG-VD 2.3 2.4 CHAPITRE 2. INTRODUCTION AUX TÂCHES Figure 2.2 – Espace d’adressage d’un processus multi-tâche stack segment threadA Thread ID ________ 212 Attributs ________ priorité = 2 taille = ... ... Registres ________ SP PC ... Attributs ________ priorité = 2 taille = ... ... Registres ________ SP PC ... threadB Thread ID ________ 315 Espace d'adressage Processus Pile du threadA fonctionA() i=100 Pile du threadB fonctionB() i=10 main() data segment ThreadA threadB x y text segment pthread_t threadA; pthread_t threadB; int x; int y; void fonctionA(void *p) { int i=100; } void fonctionB(void *p) { int i=10; } main() { pthread_create(&threadA,NULL,fonctionA,NULL); pthread_create(&threadB,NULL,fonctionB,NULL); ... } c Programmation Temps Réel Yann Thoma // HEIG-VD 2.4. TÂCHES DANS UN CONTEXTE TEMPS RÉEL 2.5 Avantage des tâches Désavantages des tâches Moins de ressources nécessaires lors d’un changement de contexte Améliore le débit de traitement des données dans une application Ne nécessite pas de mécanismes spéciaux de communication entre tâches Permet de simplifier la structure d’un programme Requiert des mécanismes de synchronisation lors d’accès mémoires concurrents Peut facilement polluer l’espace d’adressage du processus N’existe que dans un processus, et n’est donc pas réutilisable Dans le cadre de systèmes temps réel, un autre avantage du multi-tâche est la possibilité de mieux gérer les échéances et périodes des tâches. Nous reviendrons sur ces notions dans le chapitre sur l’ordonnancement. 2.4 2.4.1 Tâches dans un contexte temps réel Cycle de vie La figure 2.3 illustre les différentes étapes de la vie d’une tâche, dans un contexte temps réel. L’état initial d’une tâche est inexistante. Après sa création, elle passe à l’état passive lorsque toutes les ressources à son bon fonctionnement ont été réquisitionnées. Elle reste dans cet état jusqu’à être réveillée, ce qui peut n’est fait qu’une seule fois pour une tâche apériodique, ou de manière répétée pour une tâche périodique. Après le réveil, elle se trouve dans l’état prête, où elle se retrouve en compétition avec les autres tâches disposées à être exécutées. L’ordonnanceur a alors la charge de choisir les tâches à activer. De l’état prête, le système d’exploitation peut ensuite la faire passer dans l’état élue, état dans lequel la tâche s’exécute. Figure 2.3 – Etats et transitions d’une tâche dans un contexte temps réel. non existante prête création préemption requête réveil faute temp. exécution passive faute temporelle préemption bloquée attente terminaison requête élue terminaison terminée Ce passage n’est pas du ressort de la tâche, mais bien de l’ordonnanceur, qui s’occupe d’allouer le processeur aux différentes tâches concurrentes, et ce en suivant la politique d’ordonnancement choisie. A tout instant l’ordonnanceur peut replacer la tâche dans l’état prête, pour laisser une autre tâche s’exécuter. Il s’agit de la préemption d’une tâche, qui se fait sans que la tâche préemptée c Programmation Temps Réel Yann Thoma // HEIG-VD 2.6 CHAPITRE 2. INTRODUCTION AUX TÂCHES n’en soit consciente. Depuis l’état élue, la tâche peut aussi se retrouver dans l’état bloquée, lors de l’attente d’un événement ou du relâchement d’un mutex, par exemple. La tâche ressort de cet état par son réveil suite au relâchement d’un mutex ou au fait qu’un événement sur lequel la tâche attend a été déclenché. Dans ce cas, la tâche passe à l’état prête, prête à continuer son exécution. Lorsque la tâche s’exécute, elle peut se terminer, et se retrouver dans l’état de terminaison. Elle peut également terminer une occurence, et passer dans l’état passive, si elle est périodique. Il est intéressant de noter que des fautes temporelles peuvent survenir, la tâche n’ayant pas terminé son traitement à temps. Ceci peut faire passer la tâche de l’état élue ou prête à l’état passive. Elle y restera ensuite jusqu’à une nouvelle requête, pour le cas où elle est périodique. 2.4.2 Taxonomie Les tâches sont divisées en deux grandes catégories, en fonction de leur périodicité. • Les tâches périodiques (ou cycliques) sont des tâches qui sont exécutées à intervalles réguliers. Elles sont entre autre définies par la période avec laquelle elles sont censées s’exécuter, ce qui est géré par l’ordonnanceur. Une tâche périodique peut par exemple être responsable de l’observation d’un capteur à intervalles réguliers, de la régulation de moteurs, de monitoring, etc. • Les tâches apériodiques sont, quant à elles, exécutées à intervalles irréguliers, et peuvent donc survenir à n’importe quel instant. Il s’agira typiquemenent de tâches de configuration, et de tâches activées par une alarme, ou par une entrée de l’utilisateur. Les interruptions jouent un rôle important, étant le principal vecteur d’activation. Une tâche est dite sporadique si elle est apériodique et que l’intervalle minimal entre deux activations est connu. Au niveau de l’interaction entre tâches, nous pouvons distinguer : • Les tâches indépendantes, dont l’ordre d’exécution peut être quelconque. • Les tâches dépendantes, pour lesquelles il existe des contraintes de précédence. Il s’agit de cas où une tâche doit attendre qu’une autre ait terminé son traitement pour pouvoir commencer le sien. Au niveau des politiques d’ordonnancement, il est clair que les dépendances entre tâches auront un rôle crucial et devront être prises en compte de manière judicieuse. 2.4.3 Paramètres d’une tâche La définition d’une tâche est évidemment fortement liée au programme décrivant son exécution. Toutefois, dans le cadre de l’ordonnancement de systèmes temps réel, différents paramètres sont également nécessaires à la mise au point de solutions correctes. Les paramètres d’une tâche T sont : • r : la date de réveil de la tâche (ou date de demande d’activation) • C : sa durée d’exécution, calculée en temps processeur. Il s’agit du pire temps d’exécution. • D : son délai critique, au-delà duquel le résultat est jugé comme étant non pertinent • P : sa période, pour le cas d’une tâche périodique • d : pour une tâche à contraintes strictes, son échéance, calculée comme étant d = r + D • s : date de début d’exécution de la tâche • f : date de fin d’exécution de la tâche • u= C P : son facteur d’utilisation du processeur C • ch = D : son facteur de charge du processeur c Programmation Temps Réel Yann Thoma // HEIG-VD 2.4. TÂCHES DANS UN CONTEXTE TEMPS RÉEL 2.7 • L = D − C : sa laxité nominal. Indique le retard maximum que peut prendre la tâche sans dépasser son échéance • D(t) = d − t : son délais critique résiduel au temps t • C(t) : sa durée d’exécution résiduelle au temps t • L(t) = D(t) − C(t) : sa laxité résiduelle au temps t • T R = f − r : son temps de réponse. L’échéance est respectée si tr ≤ D Nous pouvons noter les propriétés suivantes : • • • • • u≤1 ch ≤ 1 0 ≤ D(t) ≤ D 0 ≤ C(t) ≤ C L(t) = D(t) − C(t) = D + r − t − C(t) Une tâche apériodique est donc représentée par le tuple T (r, C, D), alors qu’une tâche périodique l’est par le tuple T (r0 , C, D), r0 étant la date de réveil de la première occurence. Un tâche à échéance sur requête est une tâche périodique pour laquelle le délai critique est égal à la période (D = P ). La figure 2.4 illustre ces différents paramètres. Dans les diagrammes temporels utilisés, nous noterons l’occurence d’un réveil de tâche à l’aide d’une flèche pointant vers le haut, l’échéance de la tâche étant représentée par une flèche pointant vers le bas. Figure 2.4 – Paramètres typiques d’une tâche Ti . Ci Ti ri si fi di La figure 2.5 montre les paramètres d’une tâche périodique, et la figure 2.6 illustre les caractéristiques d’une tâche périodique à échéance sur requête, où la période est égale au délai critique. Figure 2.5 – Paramètres typiques d’une tâche périodique T . P P D D C T r0 s0 C f0 d0 r1 s1 d1 r2 f1 Figure 2.6 – Paramètres typiques d’une tâche périodique à échéance sur requête T . D=P C T r0 s0 D=P C f0 r1 s1 f1 r2 c Programmation Temps Réel Yann Thoma // HEIG-VD 2.8 CHAPITRE 2. INTRODUCTION AUX TÂCHES 2.5 Ordonnancement non temps réel Tout système d’exploitation dispose d’un ordonnanceur, qui a pour fonction de répartir l’utilisation du processeur entre les différentes tâches demandeuses. Diverses solutions existent, et peuvent être classées en fonction de différents paramètres. La section suivante propose une taxonomie des algorithmes d’ordonnancement. La section 2.6 vise à définir les moyens de calcul des performances des algorithmes, et la section 2.7 introduit plusieurs algorithmes d’ordonnancement classiques, au sens où ils sont utilisés dans des systèmes n’ayant pas recours à des contraintes temps réel. Définition 2.1. faisabilité Un ensemble de tâches est dit faisable s’il est possible d’en proposer un ordonnancement respectant l’ensemble des contraintes de temps imposées. 2.5.1 Taxonomie Les algorithmes d’ordonnancement peuvent être catégorisés selon les critères suivants (inspiré de [10]) : Monoprocesseur/multiprocesseur L’architecture physique du système a une influence sur la manière d’agencer les tâches. Certains algorithmes ne peuvent être appliqués qu’au des systèmes monoprocesseur (ordonnancement monoprocesseur), alors que d’autres se destinent à des systèmes multi-processeur (ordonnancement multiprocesseur). Dans le cadre de ce cours, nous ne traiterons que les algorithmes monoprocesseur. Préemptif/non-préemptif La préemption consiste en le fait qu’une tâche peut être interrompue à son insu pour céder sa place à une autre tâche. Les algorithmes préemptifs traitent donc des systèmes où les tâches peuvent être préemptées (cf. figure 2.8), tandis que les algorithmes non-préemptifs traitent de ceux dont une tâche ayant commencé son traitement doit obligatoirement le terminer avant qu’une autre ne s’exécute (cf. 2.7). Figure 2.7 – Exemple d’ordonnancement sans préemption T2 T1 T0 Figure 2.8 – Exemple d’ordonnancement avec préemption T2 T1 T0 c Programmation Temps Réel Yann Thoma // HEIG-VD 2.6. CALCUL DE PERFORMANCE La possibilité de préemption permet notamment d’éviter que le processeur ne soit occupé trop longtemps par une tâche, ce qui pourrait empêcher une autre tâche de se terminer à temps. Cette flexibilité a toutefois un coût lié au temps nécessaire pour le changement de contexte d’une tâche à l’autre. Ce coût peut être intolérable suivant l’application, et donc un choix doit être fait, pour un système particulier, de partir sur une solution avec ou sans préemption (cf. [14]). En-ligne/hors-ligne (online/offline) Un ordonnancement hors-ligne est effectué avant le lancement du système. Ceci implique que tous les paramètres des tâches soient connus a priori, et notamment les dates d’activation. L’implémentation du système est alors très simple : il suffit de stocker l’identifiant de la tâche à effectuer à chaque moment d’ordonnancement dans une table, l’ordonnanceur exploitant ensuite cette information pour sélectionner la tâche à exécuter. Le désavantage de cette approche est la grande dépendance au système cible, par contre l’analyse hors-ligne permet une meilleure prédiction de la satisfaction ou non des contraintes temporelles. De même, la puissance de calcul disponible hors-ligne permet de calculer un ordonnancement optimal, ce qui est un problème NP-complet, tâche impossible à réaliser dans un système embarqué. Un ordonnancement en-ligne est effectué durant le fonctionnement du système. L’ordonnanceur recalcule un nouvel ordonnancement à chaque fois qu’une nouvelle tâche est activée. L’avantage de cette approche est la flexibilité, et l’adaptabilité à l’environnement que procure le calcul en-ligne. Par contre, ce calcul devant être exécuté par le système, qui est temps réel, il doit être le plus simple et rapide possible, rendant impossible une solution dite optimale. Différents algorithmes permettent de résoudre ceci, notamment en utilisant des heuristiques. Statique/dynamique Un ordonnancement est dit statique s’il est uniquement basé sur les propriétés des tâches avant le lancement du système. Un ordonnancement est dynamique s’il est capable de réagir à la modification des propriétés des tâches durant le fonctionnement du système (typiquement un changement de priorité). Optimal/non optimal Un algorithme d’ordonnancement est dit optimal s’il est capable de fournir un ordonnancement qui respecte toutes les contraintes de temps si un tel ordonnancement existe. Si un algorithme optimal n’est pas capable de trouver un ordonnancement correct, aucun autre algorithme ne le pourra. Un algorithme non optimal (ou heuristique, ou best effort) n’a pas la prétention d’être optimal, mais doit faire de son mieux pour fournir un ordonnancement le plus proche possible de l’optimal. 2.6 Calcul de performance Comment calculer l’efficatité d’un algorithme d’ordonnancement ? La question est délicate et peut conduire à plusieurs réponses. Il faut en effet définir le facteur à évaluer. Le tableau 2.1 donne des fonctions de coût qui peuvent être exploitées pour la comparaison des algorithmes. c Programmation Temps Réel Yann Thoma // HEIG-VD 2.9 2.10 CHAPITRE 2. INTRODUCTION AUX TÂCHES Nom Fonction Temps de réponse moyen tr = Temps d’exécution total 1 n n P (fi − ri ) i=1 tc = max(fi ) − min(ri ) i i Pn Somme pondérée des fin d’exécution tw = Maximum lateness Lmax = max(fi − di ) Nombre maximum de tâche en retard i=1 wi fi i Nlate = Pn i=1 miss(fi ) ( miss(fi ) = 0 si fi ≤ di 1sinon Tableau 2.1 – Fonctions de coût pour évaluation des performances. 2.7 Ordonnancements classiques Comme déjà mentionné, un système d’exploitation classique contient un ordonnanceur responsable de répartir le temps CPU entre les différentes tâches présentes dans le système. Nous présentons ici quelques algorithmes d’ordonnancement, et nous traiterons des algorithmes spécifiques au temps réel dans le chapitre 3. 2.7.1 Algorithmes non préemptifs Les algorithmes non préemptifs sont appliqués lorsque les tâches ne peuvent être préemptées, c’està-dire où une tâche ayant commencé son exécution doit forcément la terminer avant qu’une autre tâche ne puisse débuter. Premier arrivé premier servi Cet algorithme fonctionne de la manière la plus simple qui soit. Les tâches sont stockées dans une structure de type FIFO, la première tâche est exécutée, et lorsqu’elle termine, la suivante est lancée. Les tâches nouvellement activées sont stockées à la fin de la file d’attente. La figure 2.9 illustre le concept de cet ordonnancement. Nous pouvons noter que les tâches T1 et T2 doivent attendre un temps important que la tâche T0 se termine. shortest job first Afin d’éviter que des tâches courtes ne doivent attendre trop longtemps sur des tâches longues, il est possible d’appliquer un algorithme où la tâche la plus courte est servie en premier. De ce fait, les tâches courtes sont favorisées, et le temps d’attente moyen est minimisé. Un des problèmes de cette approche est le risque de famine des tâches longues. Si de nouvelles tâches courtes son régulièrement réveillées, il se peut qu’elles passent toujours devant une tâche longue, empêchant ainsi celle-ci de s’exécuter. La figure 2.10 illustre le fonctionnement de cet algorithme. c Programmation Temps Réel Yann Thoma // HEIG-VD 2.7. ORDONNANCEMENTS CLASSIQUES 2.11 Figure 2.9 – Exemple d’ordonnancement premier arrivé premier servi Tâche Coût Arrivée T0 T1 T2 8 2 4 1 2 4 T2 T1 T0 0 5 10 15 Figure 2.10 – Exemple d’ordonnancement plus court d’abord Tâche Coût Arrivée T0 T1 T2 8 2 4 0 0 0 T2 T1 T0 0 2.7.2 5 10 15 Algorithmes préemptifs Dans le cadre préemptif, une tâche peut être momentanément interrompue par l’ordonnanceur afin de laisser une autre tâche s’exécuter. De ce fait, une tâche très longue n’a plus de risque de bloquer des tâches plus courtes trop longtemps. Round robin/tourniquet L’algorithme du tourniquet (round robin en anglais) vise à traiter les tâches avec le plus d’équité possible, en allouant un quantum de temps identique à toutes les tâches, et à les traiter dans un ordre FIFO. Les tâches sont placées dans une file d’attente, et la première de la file est exécutée pendant un quantum de temps. A la fin de ce quantum, si la tâche n’est pas terminée, elle est replacée à la fin de la file et une nouvelle tâche est sélectionnée depuis le début de la file. Si la file est vide, la tâche peut continuer son exécution. La figure 2.11 illustre cet algorithme, avec un quantum de temps égal à 2. Nous pouvons noter que la tâche T0 garde l’accès au processeur lorsqu’elle se retrouve être la seule en cours d’exécution. Il n’y a donc pas de changement de contexte si ceci n’est pas nécessaire. Cette technique de tourniquet c Programmation Temps Réel Yann Thoma // HEIG-VD 2.12 CHAPITRE 2. INTRODUCTION AUX TÂCHES correspond à la politique standard observée dans les systèmes d’exploitation, car elle permet à toutes les tâches de s’exécuter sans risque de famine. Nous pouvons toutefois noter qu’il s’agira d’une variante avec priorité. Figure 2.11 – Exemple d’ordonnancement tourniquet Tâche Coût Arrivée T0 T1 T2 8 2 4 0 1 3 T2 T1 T0 0 5 10 15 Priorité fixe L’algorithme du tourniquet traite toutes les tâches sur le même pied d’égalité. Hors il est souvent nécessaire de définir des priorités entre tâches, afin par exemple de garantir que la lecture d’un fichier audio se fasse de manière fluide, même lorsqu’une compilation est en cours. L’algorithme à priorité fixe fonctionne comme celui du tourniquet à l’intérieur d’une priorité, et les priorités sont traitées dans l’ordre. Les tâches d’une priorité particulière y sont traitées selon un tourniquet, pour autant qu’aucune tâche de plus haute priorité ne soit prête à être exécutée. La figure 2.12 illustre cet algorithme, avec un quantum de temps égal à 2. Nous pouvons noter que la tâche T1 , la plus prioritaire, est exécutée au départ, puis un tourniquet est effectué pour les deux autres tâches. Cette technique de tourniquet à priorité correspond à la politique standard observée dans les systèmes d’exploitation, car elle permet à toutes les tâches de s’exécuter sans risque de famine. Earliest deadline first L’algorithme Earliest Deadline First (EDF) donne la priorité à la tâche ayant l’échéance la plus proche. A chaque fois qu’une tâche est réveillée, l’ordonnanceur réévalue les tâches prêtes et sélectionne celle ayant l’échéance la plus courte. La figure 2.13 illustre cet algorithme. Nous reviendrons sur l’algorithme EDF dans le chapitre suivant, étant donné qu’il s’agit d’une des politiques d’ordonnancement les plus utilisées. c Programmation Temps Réel Yann Thoma // HEIG-VD 2.7. ORDONNANCEMENTS CLASSIQUES 2.13 Figure 2.12 – Exemple d’ordonnancement tourniquet avec priorité Tâche Coût Priorité Arrivée T0 T1 T2 6 6 4 1 2 1 0 0 0 T2 T1 T0 0 5 10 15 Figure 2.13 – Exemple d’ordonnancement EDF Tâche Coût Echéance Arrivée T0 T1 T2 6 4 2 12 8 5 0 2 3 T2 T1 T0 0 5 10 15 c Programmation Temps Réel Yann Thoma // HEIG-VD 2.14 CHAPITRE 2. INTRODUCTION AUX TÂCHES c Programmation Temps Réel Yann Thoma // HEIG-VD Chapitre 3 Algorithmes d’ordonnançement temps réel pour tâches périodiques 3.1 Introduction L fondamental en programmation concurrente est celui de garantir le respect des échéances des différentes tâches le constituant. Il existe en outre un grand nombre de manière d’ordonnancer un ensemble de tâches, et ce notamment en fonction des caractéristiques de celles-ci. Elles peuvent être indépendantes, dépendantes, périodiques, apériodiques, elles peuvent partager ou non des ressources, et échanger ou non des informations. Dans ce chapitre, nous nous intéressons au cas de tâches purement périodiques. Dans nombre de systèmes temps réel, certaines tâches doivent s’exécuter de manière répétée, à intervalles réguliers. Il pourra par exemple s’agir d’une tâche chargée d’aller vérifier la valeur d’un capteur tous les dixièmes de seconde. Ces tâches sont appelées tâches périodiques, puisqu’elles doivent s’exécuter régulièrement, en suivant une période fixe. Pour la mise au point des algorithmes d’ordonnancement dans le cadre des tâches périodiques, nous utiliserons la notation suivante : E PROBLÈME Variable Description Γ Un ensemble de tâches périodiques τi Une tâche périodique générique τi,j La jème instance de la tâche périodique τi ri,j Le temps d’arrivée de la jème instance de la tâche périodique τi Φi Le déphasage de la tâche τi ; il s’agit du temps d’arrivée de τi,0 (Φi = ri,0 ) Di Echéance relative de la tâche τi di,j Echéance absolue de la jème instance de la tâche τi (di,j = Φi + (j − 1)Ti + Di ) si,j Temps de début d’exécution de la jème instance de la tâche τi fi,j Temps de fin d’exécution de la jème instance de la tâche τi Dans la suite de ce chapitre, nous allons en outre faire les hypothèses suivantes : c Programmation Temps Réel Yann Thoma // HEIG-VD 3.2 CHAPITRE 3. ALGORITHMES D’ORDONNANÇEMENT TEMPS RÉEL POUR TÂCHES PÉRIODIQUES Hypothèse H1 Les instances d’une tâche périodique sont activées avec une période constante H2 Toutes les instances d’une tâche ont le même pire temps d’exécution Ci H3 Toutes les instances d’une tâche ont la même échéance relative Di H4 Toutes les tâches sont indépendantes. Il n’y a pas de dépendances entre tâches H5 Une tâche ne peut se suspendre elle-même H6 La surcharge liée aux opérations du noyau est négligée Nous traiterons ultérieurement les systèmes où l’hypothèse H4 n’est pas vérifiée. Dans le cas des tâches à échéance sur requête, nous pouvons calculer les temps d’arrivée des tâches, ainsi que leurs échéances respectives de cette manière : ri,j = Φi + (j − 1)Pi (3.1) di,j = ri,j + Pi = Φi + jPi (3.2) Une tâche est dite faisable si toutes ses instances peuvent se terminer en respectant leur échéance. L’ensemble Γ des tâches à ordonnancer est dit ordonnançable (ou faisable) si toutes ses tâches de Γ sont faisables. 3.1.1 Gigue La gigue observée peut l’être sur différentes variables du système. Nous pouvons définir les gigues suivantes : • La gigue de release relative (Relative Release Jitter) d’une tâche est la déviation maximale des temps de démarrage de deux instances consécutives : RRJi = max|(si,j − ri,j ) − (si,j−1 − ri,j−1 )| j (3.3) • La gigue de release absolue (Absolute Release Jitter) d’une tâche est la déviation maximale des temps de départ sur toutes les instances : ARJi = max(si,j − ri,j ) − min(si,j − ri,j ) j j (3.4) • La gigue de fin relative (Relative Finishing Jitter) d’une tâche est la déviation maximale des temps de fin de deux instances consécutives : RF Ji = max|(fi,j − ri,j ) − (fi,j−1 − ri,j−1 )| j (3.5) • La gigue de fin absolue (Absolute Finishing Jitter) d’une tâche est la déviation maximale des temps de fin sur toutes les instances : AF Ji = max(fi,j − ri,j ) − min(fi,j − ri,j ) j j c Programmation Temps Réel Yann Thoma // HEIG-VD (3.6) 3.1. INTRODUCTION 3.1.2 3.3 Période d’étude Les ordonnancements de ce chapitre son dédiés aux ensembles de tâches périodiques. Le fonctionnement complet du système est donc cyclique. L’algorithme d’ordonnancement doit trouver une séquence de tâches, valable sur toute la durée de fonctionnement du système. Cette séquence est toutefois caractérisée par une périodicité de longueur L, étant donné que les tâches sont périodiques. La période de longueur L est appelée période d’étude, ou période de base. Pour un ensemble de tâches périodiques synchrones (qui débutent toutes à l’instant 0), la période d’étude est : L = [0, P P CM (Pi )] (3.7) Où le P P CM dénote le plus petit multiple commun des périodes des tâches. Dès lors, si les tâches sont ordonnançables sur la période d’étude, nous pouvons garantir qu’elles le seront pour un temps infini, étant donné que l’ordonnancement pourra être calculé en répétant la séquence de tâches trouvée sur cette période d’étude. La période d’étude peut toutefois s’avérer très longue, suivant les relations entre les périodes. Pour des tâches asynchrones, ne débutant donc pas au même instant, la période d’étude est : L = [min{ri,0 }, 2 × P P CM (Pi ) + max{ri,0 ]} (3.8) Où ri,0 est la date d’activation de la première occurence de la tâche périodique Ti Et enfin, en présence de tâches apériodiques, la période d’étude devient : L = [min{ri,0 }, 2 × P P CM (Pi ) + max{ri,0 , rj + Dj }] (3.9) Où rj est la date d’activation de la tâche apériodique Tj , et où Di est son échéance relative. 3.1.3 Taux d’utilisation du processeur Pour un ensemble de n tâches périodiques Γ, le facteur d’occupation du processeur est la fraction de temps processeur nécessaire à l’exécution de cet ensemble de tâches. Pour une tâche τi , le facteur d’occupation est défini par : Ci ui = (3.10) Pi Pour l’ensemble des tâches Γ, le facteur d’utilisation est dès lors : U= n X Ci i=1 Pi (3.11) Ce facteur U correspond à la charge appliquée par l’ensemble des tâches sur le processeur. Il est évident que la charge maximale ne doit pas dépasser 1, sans quoi le processeur n’est pas à même d’exécuter l’ensemble des tâches. n X Ci ≤1 (3.12) U= P i=1 i U , qui doit donc être plus petit que 1, dépend en outre des caractéristiques des tâches, ainsi que de l’algorithme d’ordonnancement utilisé pour les ordonnancer. Pour un algorithme A particulier et un ensemble de tâches Γ, il existe une valeur Uub (Γ, A) au-delà de laquelle les tâches ne sont pas ordonnançables. Uub est la limite maximale d’utilisation du processeur (Upper Bound). c Programmation Temps Réel Yann Thoma // HEIG-VD 3.4 CHAPITRE 3. ALGORITHMES D’ORDONNANÇEMENT TEMPS RÉEL POUR TÂCHES PÉRIODIQUES Si U = Uub (Γ, A), alors l’ensemble Γ utilise entièrement le processeur. Une petite modification de Γ peut dès lors faire dépasser cette limite et rendre l’ensemble des tâches non ordonnançable. Pour un algorithme A donné, nous pouvons définir la valeur minimale des Uub (Least Upper Bound) : Ulub (A) = minUub (Γ, A) (3.13) Γ De ce fait, un ensemble de tâches Γ est ordonnançable si son facteur d’utilisation du processeur est inférieur à cette limite minimale : UΓ = n X Ci i=1 Pi ≤ Ulub (A) = minUub (Γ, A) → Γest ordonnançable Γ (3.14) La figure 3.1 représente, pour un algorithme donné, l’ordonnançabilité de quatre ensembles de tâches. Si le facteur d’utilisation du processeur est inférieur ou égal à la limite minimale, alors l’ensemble est ordonnançable (Γ3 ), s’il est supérieur à 1 il n’est pas ordonnançable (Γ3 ), et sinon un test supplémentaire est nécessaire pour établir son ordonnançabilité (Γ1 , et Γ2 ). Figure 3.1 – Signification du Least Upper Bound Ordonnançable ? Oui Non Peut-être Γ4 UΓ4 Γ3 UΓ3 Γ2 UΓ2 Γ1 UΓ1 0 3.2 1 Ulub Rate Monotonic L’algorithme Rate Monotonic est un algorithme statique, applicable à un ensemble de tâches à échéance sur requête, où les priorités des tâches sont fixes et décidée avant le lancement du système. La priorité des tâches est fixée en fonction de leur période d’activation. Plus une tâche a une petite période d’activation, plus sa priorité sera haute. Il s’agit d’un algorithme préemptif, l’activation d’une tâche de petite période devant permettre la préemption d’une tâche de période plus grande. Cet algorithme est optimal dans le cas des priorités fixes, pour . La figure 3.2 montre un exemple d’ordonnancement selon l’algorithme Rate Monotonic. 3.2.1 Analyse d’ordonnançabilité Une condition suffisante d’ordonnançabilité est : n X Ci i=1 Pi 1 ≤ UlubRM (n) = n(2 n − 1) Le tableau suivant donne la valeur de Ul ub(n) pour quelques valeurs de n : c Programmation Temps Réel Yann Thoma // HEIG-VD (3.15) 3.3. DEADLINE MONOTONIC 3.5 Figure 3.2 – Exemple d’ordonnancement selon Rate Monotonic Tâche Coût Période T0 T1 T2 2 3 4 6 8 24 T2 T1 T0 0 5 10 15 n Ulub 1 2 3 4 5 1.000 0.828 0.780 0.757 0.743 20 25 Nous pouvons calculer Ulub pour de grandes valeurs de n : 1 Ulub = lim n(2 n − 1) = ln2 ' 0.69 n→∞ (3.16) Donc, si UΓ est plus petit que 0.69, l’ensemble Γ est ordonnançable. S’il est plus grand, il faut le comparer à UlubRM (n). S’il est plus grand que cette dernière valeur, il reste un test permettant d’établir son ordonnançabilité. Il est décrit dans la section dévolue à l’algorithme Deadline Monotonic, en page 6. En 2001, une nouvelle condition d’ordonnançabilité, appelée Hyperbolic Bound, a été proposée. Il s’agit également d’une condition suffisante mais pas nécessaire : n Y (Ui + 1) ≤ 2 (3.17) i=1 Cette condition est moins restrictive que la précédente, et est donc intéressante. 3.3 Deadline Monotonic L’algorithme Rate Monotonic est basé sur l’hypothèse que les tâches sont à échéance sur requête, c’est-à-dire que l’échéance d’une tâche est égale à sa période. Dans certains systèmes cette hypothèse ne pourra s’avérer exacte, et les échéances pourront être plus petites que les périodes. Dans ce cas, l’algorithme Deadline Monotonic est un algorithme optimal dans le cas des algorithmes à priorité statique avec échéances plus petites que les périodes. Comme RM, il est préemptif. La priorité d’une tâche est fixée par son échéance. Plus son échéance est petite, plus sa priorité est grande. La figure 3.3 montre un exemple d’ordonnancement selon l’algorithme Deadline Monotonic. c Programmation Temps Réel Yann Thoma // HEIG-VD 3.6 CHAPITRE 3. ALGORITHMES D’ORDONNANÇEMENT TEMPS RÉEL POUR TÂCHES PÉRIODIQUES Figure 3.3 – Exemple d’ordonnancement selon Deadline Monotonic Tâche Coût Période Echéance T0 T1 T2 2 3 4 6 8 24 5 4 20 T2 T1 T0 0 3.3.1 5 10 15 20 25 Analyse d’ordonnançabilité Une condition suffisante d’ordonnançabilité est : n X Ci i=1 Di 1 ≤ n(2 n − 1) (3.18) Ce test n’est toutefois pas vraiment optimal, car la charge du processeur y est surestimée. Il est possible de déduire un autre test, en se basant sur les observations suivantes : 1. Le pire cas au niveau des demandes d’utilisation du processeur se trouve au moment où toutes les tâches sont activées simultanément ; 2. Le pire temps de réponse d’une tâche correspond à la somme de son temps d’exécution et des interférences des tâches de priorité supérieure. Si nous supposons les tâches ordonnées selon l’ordre ascendant de leurs échéances, le test correspond à vérifier que : ∀i : 1 ≤ i ≤ n Ci + Ii ≤ Di (3.19) Où Ii est l’interférence 1 mesurée sur la tâche τi : Ii = & ' i−1 X Di j=1 Pj Cj (3.20) Si ce test passe, alors l’ensemble de tâches est ordonnançable. l’interférence est calculée l Toutefois, m Di en partant du principe qu’une tâche τj interfère exactement Pj fois, ce qui n’est pas forcément le cas. La méthode suivante permet une meilleure approximation de cette interférence, et donc du temps de réponse de la tâche. Test en zone critique : calcul du temps de réponse Si le facteur d’utilisation du processeur de l’ensemble de tâches est plus petit que 1, mais plus grand que Ulub (n), nous pouvons appliquer le test suivant, proposé par Audlsey et al. Ce test est 1. A titre de rappel, pour r ∈ R, n = dre ∈ N, est l’entier immédiatemment supérieur à r. c Programmation Temps Réel Yann Thoma // HEIG-VD 3.3. DEADLINE MONOTONIC 3.7 valable pour l’algorithme Rate Monotonic et Deadline Monotonic. Ce test consiste en le calcul du pire temps de réponse des tâches, en prenant en compte les interférences des tâches de priorité supérieure. L’idée est de partir du pire cas, où toutes les tâches sont activées au même instant. Le temps de réponse Ri d’une tâche τi est alors la somme de son temps d’exécution et du temps d’exécution de toutes les tâches de priorité supérieure : R i = Ci + I i (3.21) Où Ii = & ' i−1 X Ri Pj j=1 Dans cette équation, le terme Ri . Nous avons donc : l Ri Pj m Cj (3.22) Cj représente l’interférence de la tâche j sur la tâche i le temps Ri = Ci + & ' i−1 X Ri Pj j=1 Cj (3.23) Il faut vérifier que le temps de réponse soit plus faible que l’échéance de la tâche. Nous nous trouvons toutefois devant une équation où Ri apparaît des deux côtés de l’égalité. Nous pouvons appliquer une méthode itérative pour son calcul, en observant que seuls certains points dans l’intervalle [0, Di ] sont pertinents. 1. Nous commençons par prendre un premier point d’approximation : Ri0 = i X Cj (3.24) j=1 2. Si ce temps de réponse est au-delà de l’échéance Di , la tâche n’est pas ordonnançable. Sinon nous passons au point suivant. 3. Nous calculons le point d’approximation suivant : Rin+1 = Ci + & ' i−1 X Rin j=1 Pj Cj (3.25) 4. Si Rin+1 = Rin , nous avons atteint le pire temps de réponse pour la tâche, qui peut alors être comparé à Di . Sinon, nous reprenons au point 3. Prenons comme exemple l’ensemble de tâches suivant : Tâche Coût Période Echéance Priorité T0 T1 T2 40 40 100 100 150 350 100 150 350 3 2 1 Pour T0 , qui est la tâche la plus prioritaire, il suffit de vérifier que son coût soit plus faible que son échéance. c Programmation Temps Réel Yann Thoma // HEIG-VD 3.8 CHAPITRE 3. ALGORITHMES D’ORDONNANÇEMENT TEMPS RÉEL POUR TÂCHES PÉRIODIQUES Pour T1 , nous pouvons appliquer la méthode : R10 = 1 X Cj = C0 + C1 = 80 j=0 R11 = C1 + & ' 0 X R10 j=0 Pj 80 Cj = 40 + 40 = 80 100 R11 = R10 , donc la phase itérative est terminée. Comme R1 = 80 ≤ D1 = 100, T1 est ordonnançable. Pour T2 , nous pouvons appliquer la méthode : R20 = 2 X Cj = C0 + C1 + C2 = 180 j=0 R21 = C2 + & ' 1 X R20 j=0 R22 = C2 + 2 j=0 R23 = C2 + Pj & ' 1 X R1 Pj ' & 1 X R2 2 j=0 Pj 180 180 40 + 40 = 260 Cj = 100 + 100 150 260 260 40 + 40 = 300 100 150 300 300 40 + 40 = 300 100 150 Cj = 100 + Cj = 100 + R13 = R12 , donc la phase itérative est terminée. Comme R2 = 300 ≤ D2 = 350, T2 est ordonnançable. 3.4 Earliest Deadline First L’algorithme Earliest Deadline First (EDF) donne la priorité à la tâche ayant l’échéance la plus proche. A chaque fois qu’une tâche est réveillée, l’ordonnanceur réévalue les tâches prêtes et sélectionne celle ayant l’échéance la plus courte. Cet algorithme est appliqué, ici, à des tâches périodiques à échéance sur requête où la préemption est autorisée. La figure 3.4 illustre cet algorithme. 3.4.1 Analyse d’ordonnançabilité L’algorithme EDF est optimal dans le cas préemptif. La condition nécessaire et suffisante d’ordonnançabilité est : n X Ci ≤1 (3.26) P i=1 i Un ensemble de tâches est donc ordonnançable si son taux d’utilisation du processeur est plus petit ou égal à 0. 3.4.2 Cas non-préemptif L’algorithme EDF est optimal dans le cas préemptif. A titre indicatif, observons son comportement si la préemption est impossible. Considérons l’exemple suivant à deux tâches : c Programmation Temps Réel Yann Thoma // HEIG-VD 3.4. EARLIEST DEADLINE FIRST 3.9 Figure 3.4 – Exemple d’ordonnancement EDF Tâche Coût Période T0 T1 T2 2 3 1 5 7 10 T2 T1 T0 0 5 10 15 Tâche Coût Arrivée Echéance T0 T1 4 2 0 1 7 5 20 Dans le cas préemptif, l’ordonnancement trouvé est : T1 T0 0 5 10 Nous pouvons observer que la tâche T0 est préemptée au temps 1 par la tâche T1 , et lorsque cette dernière est terminée, T0 peut reprendre son exécution. Dans le cas non-préemptif, l’arrivée de la tâche T1 au temps 1 voit la tâche T0 continuer son exécution, étant donné qu’elle ne peut être préemptée : T1 T0 0 5 10 Nous pouvons donc observer que l’échéance de T1 n’est pas respectée. L’ordonnancement suivant est une solution possible dans le cadre non-préemptif : T1 T0 0 5 10 c Programmation Temps Réel Yann Thoma // HEIG-VD 3.10CHAPITRE 3. ALGORITHMES D’ORDONNANÇEMENT TEMPS RÉEL POUR TÂCHES PÉRIODIQUES Cet ordonnancement montre qu’une solution existe dans ce cadre. Toutefois, EDF traite les tâches dès leur moment d’arrivée. Sans la connaissance a priori des temps d’arrivée des tâches, il n’est pas possible, au temps 0, de prendre la décision de retarder l’exécution de T0 . En effet, ici le processeur reste inactif durant un quantum de temps, ce qui aurait pu être préjudiciable suivant le temps d’arrivée de T1 . Si la préemption n’est pas autorisée, le problème de trouver un ordonnancement faisable devient NP-dur. 3.5 Least Laxity First L’algorithme Least Laxity First (LLF) est, à l’instar d’EDF, un algorithme à priorité dynamique. Il traite des tâches périodiques pour lesquelles la préemption est autorisée. La règle décrivant l’algorithme est simplement que la tâche dont la laxité est la plus faible est la plus prioritaire. Les définitions suivantes permettent d’aborder la notion de laxité : • L = D − C : sa laxité nominal. Indique le retard maximum que peut prendre la tâche sans dépasser son échéance • D(t) = d − t : son délais critique résiduel au temps t • C(t) : sa durée d’exécution résiduelle au temps t • L(t) = D(t) − C(t) : sa laxité résiduelle au temps t Figure 3.5 – Exemple d’évolution de la laxité d’une tâche, pour un ordonnancement EDF LT1 (t) T1 T0 0 5 10 15 20 25 30 35 A partir de la définition de la règle, nous pouvons déduire deux implémentations de LLF : 1. La laxité résiduelle est calculée au lancement de la tâche. Nous nous trouvons dans un cas de figure de type EDF 2. La laxité résiduelle est calculée à chaque instant t. La deuxième option est évidemment la plus intéressante. Elle permet d’exécuter, à un instant t, la tâche qui a le moins de marge à disposition pour tenir son échéance. 3.5.1 Analyse d’ordonnançabilité La condition d’ordonnançabilité est identique à celle d’EDF. Nous avons donc la condition suffisante et nécessaire suivante : n X Ci ≤1 (3.27) P i=1 i c Programmation Temps Réel Yann Thoma // HEIG-VD Bibliographie [1] « Site web Xenomai : http ://www.xenomai.org ». [2] Doug A BBOTT. Linux fo Embedded and Real-time Applications. Newnes, 2003. [3] Alan B URNS et Andy W ELLINGS. Real-Time Systems and Programming Languages. Addison-Wesley, second edition édition, 1997. [4] Giorgio C. B UTTAZZO. Hard Real-time Computing Systems. Kluwer Academic Publishers, 2000. [5] Francis C OTTET, Joëlle D ELACROIX, Claude K AISER et Zoubir M AMMERI. Ordonnancement temps réel. HERMES Science Publications, 2000. [6] Alain D ORSEUIL et Pascal P ILLOT. Le Temps Réel en Milieu Industriel. Dunod, 1991. [7] Bruce Powel D OUGLAS. Real Time UML. Addison-Wesley, third edition édition, 2004. [8] Jack G ANSSLE. The Art of Designing Embedded Systems. Newnes (Elsevier), 2008. [9] Cameron H UGHES et Tracey H UGHES. Parallel and Distributed Programming Using C++. Addison-Wesley, 2003. [10] Omar K ERMIA. « Ordonnancement temps réel multiprocesseur de tâches non-préemptives avec contraintes de prédédence, de périodicité stricte et de latence ». PhD thesis, Université Paris XI, UFR scientifique d’Orsay, 2009. [11] Mark H. K LEIN, Thomas R ALYA, Bill P OLLAK, Ray O BENZA et Michael González H AR BOUR . A Practioner’s Handbook for Real-Time Analysis : Guide to Rate Monotonic Analysis for Real-Time Systems. Kluwer Academic Publishers, 1993. [12] Phillip A. L APLANTE. Real-time Systems Design and Analysis. IEEE Press, second edition édition, 1997. [13] R.J. W IERINGA. Design Methods for Reactive Systems. Morgan Kaufmann Publishers, 2003. [14] P. Meumeu YOMSI et Y. S OREL. « Extending Rate Monotonic Analysis with Exact Cost of Preemptions for Hard Real-Time Systems ». Dans 19th Euromicro Conference on Real-Time Systems. ECRTS ’07., pages 280–290, 2007. [15] Luigi Z AFFALON. Programmation Synchrone de Systèmes Réactifs avec Esterel et les SynchCharts. Presses Polytechniques et Universitaires Romandes, 2005. 1