Académique Documents
Professionnel Documents
Culture Documents
: LES TACHES.
1. Introduction
Les programmes simples sont conçus pour être exécutés séquentiellement, une instruction à
chaque instant, et dans un ordre prédéterminé. Cette structure est inappropriée pour une
application RT embarquée qui possède plusieurs entrées et des contraintes temporelles.
Les systèmes RT sont conçus pour permettre la concurrence.
Dans une conception avec concurrence, le développeur décompose une application en petites
unités ordonnançables de programme séquentiel. Lorsque ce travail est réalisé correctement, la
programmation concurrente permet au système multitâche d’atteindre les performances et les
contraintes temporelles.
Les noyaux RTOS fournissent des objets taches et des services associés pour faciliter la
conception concurrente d’une application.
Un nom associé,
Un unique ID (identifier),
Une priorité (nécessaire pour l’algorithme d’ordonnancement),
Un bloc de contrôle ou TCB (Task Control Block),
Une pile (stack),
Une routine ou fonction (task routine).
1
Lorsqu’un noyau démarre, il créé son propre ensemble de taches systèmes (system tasks) qui
attribue un niveau de priorité pour chacune des taches à partir d’un ensemble de priorité
réservées pour les taches systèmes. L’application doit éviter d’utiliser ces niveaux de
priorités réservé au système. Ces priorités des taches systèmes ne doivent pas être modifiées.
On trouve comme exemples de taches systèmes :
Etat prêt (ready) : la tâche est prête à être exécutée n’est pas en exécution car une
tache de priorité supérieure s’exécute.
Etat en execution (running) : La tache possède la plus haute priorité et est en train
d’etre executée.
Etat bloqué (blocked) : La tache a besoin d’une ressource non disponible, ou est en
attente de l’apparition d’un événement (event), ou s’est retardée (delayed) elle-même
pour une certaine durée.
2
Il faut noter que certains noyaux (commerciaux) comme VxWorks, définissent une FSM plus
détaillée, avec un nombre d’états plus importants comme les états suivants:
Dans la suite, nous détaillons les états prêt (ready), en execution (running) ou bloqué
(blocked) dans le cas d’un système monoprocesseur avec un noyau à ordonnancement
préemptif à priorités.
3.1 Etat prêt (ready) : Lorsqu’une tache est créée et prête à être exécutée, le noyau la passe
dans l’état ready. Dans l’état ready, la tâche entre en compétition avec les autres taches
3
pour être exécutée. Comme le montre la figure suivante, une tache en état ready ne peut pas
passer en état blocked.
Une tache a besoin de s’exécuter pour pouvoir faire un appel bloquant (blocking call), qui
est un appel à une fonction qui ne peut pas s’exécuter entièrement (au sens jusqu’à la fin), et
qui va placer alors la tache dans l’état bloqué. Seules les taches dans l’état ready prêt peuvent
alors passer dans l’état Running. Comme plusieurs taches peuvent se trouver dans l’état
Ready, c’est l’ordonnanceur du noyau qui décidera parmi les taches en état Ready celle qui
passera dans l’état running.
Pour un noyau qui ne tolère qu’un seule niveau de priorité par une tache (2 taches ne
peuvent pas avoir la même priorité), c’est toujours la tache de plus haute priorité parmi les
taches ready qui sera exécutée. Dans ce cas le noyau limite le nombre de taches au nombre de
priorités possibles.
Pour le cas fréquent où un noyau qui ne tolère plusieurs tache avec la même priorité, le
nombre de taches possibles devient beaucoup plus élevé. Dans ce cas, l’algorithme
d’ordonnancement devient plus complexe et implique de maintenir une liste des taches en
état prêt (task-ready list).
La figure de la page suivante illustre le concept de liste des taches en état prêt (task-ready
list), toujours dans le cas d’un système monoprocesseur avec un noyau à ordonnancement
préemptif à priorités. On suppose que la priorité la plus faible est 255 et la plus élevée est
0. Notez que par souci de simplification, cet exemple ne montre pas les taches systèmes, ni
la tache Idle.
Au départ, 5 taches (1, 2, 3, 4,5) sont dans l’état Ready et le noyau les a placés dans liste des
taches en état prêt (task-ready list). La tache 1 possède la priorité la plus élevée (70) ; les
taches 2,3 et 4 possèdent une priorité moins élevé (80). Enfin la tache 5 possède la plus faible
priorité (90).
4
Etape 1 : Les taches 1, 2, 3, 4 et 5 sont dans l’état ready en attente dans la liste ready.
Etape 2 : La tache 1 possède la plus haute priorité (70), elle est la première à passer
dans l’état Running si aucune tache de priorité supérieure n’est déjà en phase
d’exécution.
Etape 3 : Durant son exécution, la tache 1 fait un appel bloquant. Le noyau fait
alors passer la tache 1 dans l’état blocked, prend la tache 2, qui est la première dans la
liste avec une priorité directement inférieure (80) à celle de la tache 1, la sort de la
liste et la (tache 2) place dans l’état running.
Etape 4 : Puis la tache 2 fait un appel bloquant. Le noyau déplace la tache 2 vers
l’état blocked, prend la tache 3 qui est la prochaine des taches avec une priorité de 80,
la sort de la liste et place cette tache 3 dans l’état running.
Etape 5 : Alors que la tache 3 s’exécute, elle libère la ressource que demandait la
tache 2. Le noyau renvoi la tache 2 dans l’état ready, et l’insère dans la liste des
taches ready, en dernière position des taches de priorité 80. La tache 3 continue son
exécution.
Bien que ce ne soit pas illustré, si la tache 1 devient débloquée à ce moment, le noyau fera
passer la tache 1 dans l’état running car elle possède une plus haute priorité que la tache 3. La
tache 3 passera alors dans l’état ready, et sa place dans la liste sera après la tache 2 (priorité
80) et avant la tache 5 (priorité 90).
5
3.2 Etat en exécution (ou running) : sur un système monoprocesseur, une seule tache peut être
exécutée à la fois. Lorsqu’une tache est placée dans l’état running, le processeur charge ses
registres avec le contexte de la tâche. Le processeur peut alors exécuter les instructions de
la tâche et manipuler la pile associée.
Lorsqu’une tache passe de l’état running vers l’état ready, elle est préemptée par une tache
de priorité supérieure. La tache préemptée est placée dans la liste des taches en état prêt
(task-ready list) à l’endroit correspondant à sa priorité, et la tache de priorité la plus élevée
de la liste passe en état running.
Contrairement à une tache en état ready, une tache en état running peut passer en état
blocked à partir des conditions suivantes :
3.3 Etat bloqué (blocked) : la possibilité d’une tache de passer dans l’état blocked est
extrêmement importante. En effet si sans état blocked, les taches de priorités inférieures ne
pourraient jamais être exécutées. Si les taches de plus hautes priorités sont conçues de telle
sorte qu’elles ne sont jamais bloquées, une privation CPU pour d’autres taches peut en
résulter. On parle de CPU starvation ou de Famine.
Une tache peut uniquement passer dans l’état blocked en faisant un appel bloquant, qui
nécessite que certaines conditions soient réunies. Une tache en état blocked y reste tant que
certaines conditions ne sont pas réunies. On devrait appeler ces conditions de sortie de
l’état blocked « unblocking conditions », mais attention le monde des systèmes RT les
appelle blocking conditions !
Les exemples suivants permettent la sortie de l’état blocked :
6
4. Opérations typiques sur les taches.
En plus de fournir des objets taches, les noyaux offrent des services de gestion des taches
(task-management services). Ces services de gestions des taches incluent les actions que le
noyau peut réaliser en arrière-plan (comme maintenir le TCB et la pile associée à une tâche).
Le noyau offre des API qui permettent aux développeurs de manipuler les taches. Les plus
courantes sont :
Opération Description
Create Créé une tache.
Delete Supprime une tache.
En fonction des API des noyaux, les opérateurs créée une tache en utilisant une ou deux
opérations. Certains noyaux permettent aux développeurs de d’abord créer la tache puis de la
démarrer. Dans ce cas, la tache est d’abord créée puis passe dans l’état suspendu. La tache
passe ensuite dans l’état ready lorsqu’elle est démarrée (rendue prête à être exécutée).
Cette méthode de création des taches est utile pour le débogage ou lorsqu’une initialisation
particulière doit être réalisée entre le moment ou la tâche est créée et celui où elle est
démarrée.
Dans la majorité des cas, il est possible de créer et démarrer la tache en un seul appel au
noyau.
La plupart des noyaux fournissent des hooks configurables par l’utilisateur. Il s’agit de
mécanismes qui exécute une fonction particulière (morceau de programme) lors de
l’occurrence d’un événement. Des exemples d’évènements déclenchant cette fonction sont :
7
Lorsqu’une tache s’exécute, elle peut réserver de la mémoire ou accéder à des ressources
utiles à d’autres objets du noyau. Si la tache n’est pas supprimée d’une manière correcte, la
tache peut ne pas libérer ces ressources. Par exemple, une tache peut acquérir un sémaphore
pour avoir un accès exclusif à une zone mémoire. Alors que la tache opère sur cette zone
mémoire, elle est effacée. Si cette opération n’est pas réalisée correctement, cette suppression
mal réalisée peut produire :
Opération Description
Suspend Suspend une tache
Resume Arrête la suspension d’une tache.
Delay Retarde une tache
Restart Redémarre une tache
Get Priority Demande la priorité de la tache courante.
Set Priority Impose une priorité pour la tache courante.
Preemption lock Empêche la tache de plus haute priorité de
préempter la tache courante.
Preemption unlock invalide l’opération de preemption lock.
8
Un développeur peut aussi souhaiter redémarrer une tache (restart), ce qui est différent de
reprendre (resuming) une tache suspendue (suspended). L’opération de restart sur une tache
est l’équivalent de reprendre la tache comme si elle n’avait jamais été exécutée. Dans un
restart, l’état interne de la tâche (au moment où elle a été suspendue), c’est-à-dire les registres
CPU et les ressources acquises, est complétement perdu. En revanche, l’opération de
resuming reprend la tâche en ayant conservé son état avant la suspension.
Le restart d’une tache est utile lors du débogage (pour faire du pas à pas du début jusqu’à la
fin de la tache) ou lorsqu’on réinitialise une tache après une erreur catastrophique (ce qui
permet que le système continue de fonctionner sans avoir à le réinitialiser complétement).
Les opérations get/set Priority permettent au développeur de réaliser de l’ordonnancement
manuel. Cette opération est utile durant le problème de l’inversion de priorités (Priority
inversion). L’inversion de priorité apparait lorsqu’une tache 1 de priorité très faible détient
une ressource qu’une tache 2 de priorité très élevée à besoin pour s’exécuter, mais qu’une
tache 3 de priorité moyenne s’exécute et empêche la libération de la ressource détenue par la
tâche 1 de priorité la plus basse. La tâche de priorité supérieure ne peut pas s’exécuter.
Une solution à ce problème est d’augmenter dynamiquement la priorité de la tâche 1 de plus
faible priorité pour l’amener à une priorité équivalente à celle de la tache 2, cette tache 1
pourra donc s’exécuter, libérer sa ressource, puis ramener la tache 1 à sa priorité initiale
(basse) permettant la tache 2 en attente de la ressource de s’exécuter.
Le noyau peut aussi supporter les preemption locks, une paire d’appel utilisée pour invalider
et valider la préemption dans une application. Ces opérations sont utiles lorsque tache exécute
une section critique du code (critical section of code) durant laquelle il ne faut surtout pas
arrêter son exécution, donc il faut interdire temporairement la préemption par d’autres taches.
Opération Description
Get ID Lecture ID de la tache courante
Get TCB Lecture TCB de la tache courante
L’ID peut être utile comme paramètre nécessaires lors de l’utilisation de certaines API.
Il faut être prudent lors de l’opération de lecture du TCB. Si la tache sur laquelle s’effectue
cette lecture n’est pas dans un état suspended, son contexte peut être dynamique, et
l’information obtenue sur le TCB faussée par un rapide changement d’état. Entre le moment
où la lecture du TCB est faite et renvoyée, et l’état présent la tache a peut être changé d’état.
Son TCB n’est plus le même !
9
5. Structures typiques des taches.
Les taches possèdent deux structures de code possibles :
Les taches d’initialisation ont généralement des priorités plus élevées que les taches
qu’elles créée, de telles sorte qu’elles ne soient pas préemptée par les taches créés. Souvent,
les taches créées par les taches d’initialisation sont des Endless Loop Tasks. Cette tâche,
après avoir effectué son travail, se termine soit en se supprimant ou en se plaçant dans l’état
suspendu.
5.2 Les Endless Loop Tasks :
La structure de cette tache peut aussi contenir du code d’initialisation qui sera exécuté une
seule fois lorsque la tâche s’exécutée pour la première fois. Ensuite la tâche s’exécutera
dans une boucle infinie, comme le montre le pseudo code suivant :
Ces taches doivent faire un (ou plusieurs) appel bloquant dans le corps de la boucle. Ce
dernier permet alors à une tache de priorité inférieure de s’exécuter.
10
6. Synchronisation, communication et concurrence.
Les taches se synchronisent et communiquent entre elles en utilisant des primitives inter
taches (intertask primitives) qui sont des objets du noyau. On y trouve des sémaphores, des
files de message, des signaux, des tuyaux (pipes), ….
11