suivant: Développement d'un système d'exploitation
monter: Table des matières
précédent: Architecture du palm
  Table des matières
Sous-sections
Un système d'exploitation est un programme qui agit comme un intermédiaire entre l'utilisateur et la machine. Le but d'un système
d'exploitation est de fournir un environnement dans lequel un utilisateur peut exécuter des programmes. Le système d'exploitation doit
coordonner l'exécution simultanée de plusieurs tâches utilisateurs. Il a donc deux grandes fonctions :
- Répartir les ressources du système : CPU, mémoire, périphériques, etc.
- Fournir des services : communication, synchronisation, affichage, etc.
Un système temps-réel est utilisé quand il y a des exigences temporelles fixe sur les opérations d'un processeur ou sur le flux de
données. Un système temps-réel possède des contraintes de temps fixes et bien définies. Le traitement doit être effectué dans la
contrainte de temps sinon le système échoue. Le système de gestion des airbags dans une voiture possède des contraintes temps-réel
très importantes. Quand un capteur détecte une déformation de la carrosserie suffisamment importante, il envoie un signal au
contrôleur qui doit avoir gonflé les airbags dans les 10 ms sous peine d'arriver trop tard, ce qui aurait des conséquences
désastreuses pour les occupants de la voiture. Même si un capteur tombe en panne et qu'il n'envoie plus d'informations, le système
doit continuer à fonctionner et fournir les meilleures réponses possibles.
Citons quelques concepts cléfs concernant les systèmes d'exploitation temps-réel. Les chapitres suivants seront consacrés à
l'analyse et au développement du système d'exploitation temps-réel appelé Expresso.
Une section critique est une partie de code qui doit être traitée indivisiblement. Dès qu'une section critique
commence sont exécution, elle ne peut pas être interrompue. Pour assurer cela, les interruptions sont désactivées avant de rentrer
dans le code de la section critique et réactivées quand le code est terminé.
Une ressource partagée est une ressource (LCD, port série, variable partagée, etc) qui peut être utilisée par plus d'une tâche.
Chaque tâche doit gagner l'accès exclusif à la ressource partagée pour éviter la corruption des données, on appele ce mécanisme
l'exclusion mutuelle qui est facilement implémentée grâce au sémaphore.
Une tâche est un simple programme qui s'exécute comme s'il était le seul à utiliser le CPU. Le processus de conception pour des
applications temps-réel implique de diviser le travail à effectuer entre différentes tâches qui sont chacune responsable d'une
partie du problème. Chaque tâche possède une priorité, du code à exécuter, une partie de la mémoire et une zone de pile (stack).
Une tâche est typiquement une boucle infinie qui peut être dans un des états suivants :
- Dormante :
- la tâche est toujours en mémoire mais n'est pas considérée par les opérations d'ordonnancement.
- Exécutable :
- la tâche est prête à être exécutée mais n'a pas à ce moment l'usage du CPU.
- Active :
- la tâche est celle dont les instructions sont actuellement exécutées par le CPU.
- Bloquée :
- la tâche attend un signal ou une ressource afin de poursuivre son exécution.
- Interrompue :
- la tâche possédait l'usage du processeur lorsqu'elle a été suspendue par une interruption.
Figure:
Etats d'une tâche
 |
Un système d'exploitation permet l'exécution simultanée de plusieurs tâches. Mais la machine ne dispose que d'un CPU unique qui exécute
des instructions séquentiellement. Pour exécuter plusieurs tâches simultanément, on utilise le principe de l'entrelacement.
On entrelace l'exécution des différentes tâches. Pour cela, il faut effectuer un ordonnancement des différentes tâches et changer
l'attribution du CPU entre celles-ci.
L'ordonnanceur (scheduler) est le composant du noyau responsable de déterminer quelle tâche s'éxécute à un moment donné. Il gère
l'état des tâches et répartit l'usage du processeur entre celles-ci. Un ordonnanceur temps-réel est basé sur la priorité, Expresso
donne une priorité unique à chaque tâche.
- Chaque tâche possèdent une priorité (fixe ou variable au cours de son exécution) basée sur son importance
- L'ordonnanceur attribue toujours le processeur à la tâche exécutable (non dormante et non bloquée) de plus haute priorité.
Expresso possède un ordonnanceur préemptif. Expliquons rapidement ce concept. Quand une tâche
plus prioritaire que la
tâche active
passe de l'état bloqué à l'état éxécutable, la tâche
est suspendue (état exécutable) et l'ordonnanceur
attribue le processeur à
. A tout moment, c'est la tâche de plus haute priorité exécutable qui reçoit le contrôle du
processeur. Avec ce type d'ordonnanceur, le temps de réponse des tâches est optimum et déterministe.
Figure:
Ordonnanceur préemptif
 |
Quand l'ordonnanceur doit transférer l'usage du processeur d'une tâche à une autre, il opère un changement de contexte. Le contexte
représente l'état du processeur à un moment donné.
- Registre
- La tâche suspendue devra pouvoir continuer son exécution sans être affectée. La première opération à effectuer est la sauvegarde de
l'état du processeur au moment de la suspension. Le noyau possède pour chaque tâche non dormante un espace mémoire réservé à cet effet.
- Pile
- Les données temporaires utilisées par la tâche suspendue doivent être préservées lors des opérations de la nouvelle tâches active.
Ces données sont organisées sous la forme d'une pile contenant :
- le contexte (adresse de retour, valeur des registres) des appels de sous-routines en cours
- les paramètres et les variables temporaires de ces sous-routines
Une tâche peut être suspendue à tout moment et l'usage du processeur transféré vers une autre tâche susceptible d'appeler des
sous-routines et d'allouer des variables temporaires. Chaque tâche possède sa propre pile.
En général, les pointeurs des sommets et de base de pile sont des registres du processeur. Ils sont donc sauvegardés avec le reste de
l'état de celui-ci lors d'un changement de contexte. Le noyau possède également sa propre pile.
Le changement de contexte ajoute de l'overhead à l'application. Plus le processeur possède de registres, plus il y a d'overhead.
Le temps requit par un changement de contexte est déterminé par le nombre de registres à sauver et à restaurer par le CPU.
Chaque tâche possède une priorité, plus la tâche est importante plus sa priorité doit être élevée.
- Priorité statique :
- la priorité de chaque tâche ne change pas durant l'exécution. Chaque tâche reçoit une priorité fixe lors de sa
création. Toutes les tâches du système et leurs contraintes temporelles sont connues à la compilation.
- Priorité dynamique :
- la priorité de chaque tâche peut changer durant l'exécution, chaque tâche peut changer sa priorité durant
l'exécution. Cette caractéristique des noyaux temps-réel permet d'éviter l'inversion de priorité. Expresso permet ce changement
de priorité.
- L'inversion de priorité
- est un problème qui survient lorsqu'une tâche est suspendue dans l'attente d'une ressource contrôlée
par une tâche moins prioritaire.
Soit 3 tâches
,
,
avec des priorités respectives
,
,
:
.
et
attendent qu'un événement survienne dans
(par exemple la libération d'un sémaophore). Dans ce cas, la priorité effective
de
est réduite à celle de
car elle est en attente d'une ressource détenue par
.
Solution :
Il suffit d'élever temporairement la priorié de
(en la rendant égale à celle de
) pendant le laps de temps qu'elle
utilise la ressource et ensuite de restaurer sa priorité normale. Certains systèmes d'exploitation fournissent un mécanisme d'héritage
de priorité qui change automatiquement la priorité d'une tâche pour éviter les problèmes d'inversion de priorité.
Le transfert correct de données d'une tâche à une autre soulève des problèmes plus importants que dans le cas d'un programme unique
communiquant avec des routines d'interruption.
- Chacune des deux parties peut ici voir son exécution suspendue après chaque instruction afin de transférer l'usage du processeur
à l'autre tâche.
- Les changements de contexte ne peuvent pas toujours être évités par une simple désactivation des interruptions. Cela ne peut
se faire qu'en interragissant avec l'ordonnanceur
On utilise des services fournis par le noyau destinés à
- assurer la synchronisation des tâches communicantes
- organiser le transfert de données d'une tâche à une autre
Un sémaphore
est un objet possédant une valeur entière. Sa valeur est positive ou nulle et est uniquement manipulable à l'aide de
deux opération wait(s) et signal(s) :
- wait(s) décrémente s si s > 0; sinon, la tâche est suspendue (état bloqué).
- signal(s) si au moins une tâche est suspendue à la suite d'une opération wait(s), rend exécutable une de ces
tâches; sinon incrémente s
- L'exécution d'une opération wait(s) ou signal(s) se fait sans intéraction possible (de façon atomique).
Notes :
- Les sémaphores binaires sont des variantes de sémaphores ne pouvant prendre que les valeurs 0 à 1.
- Dans Expresso, la sélection d'une tâche bloquée sur un sémaphore en vue de la rendre à nouveau exécutable se fait par priorité.
Une file d'attente est un objet permettant la communication synchrone ou asynchrone de valeurs entre des tâches. Caractéristiques :
- La capacité maximum de la file (représentant le nombre maximum de valeurs écrites et non encore lues) et la taille de chaque valeur
est fixée
- L'écriture et la lecture d'une valeur s'effectuent de façon atomique
- Une tâche en attente de données depuis la file est suspendue par l'ordonnanceur (état bloqué)
Sous Expresso :
- l'accès aux données se fait avec la stratégie FIFO.
- l'écriture dans une file saturée conduit à la perte du message
- la suspension d'une tâche en attente peut être assortie d'un délai de blocage maximum.
suivant: Développement d'un système d'exploitation
monter: Table des matières
précédent: Architecture du palm
  Table des matières
Fabian Skivee
2002-06-04