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