Services offerts par OSEK
La gestion de tâches
Une tâche est une portion de code séquentiel. OSEK OS fournit deux types de tâches :
- les tâches basiques
- les tâches étendues
Les tâches basiques
Une tâche basique rend la main au processeur si :
- elle est terminée
- OSEK OS laisse la main à une tâche de priorité plus haute
- une interruption arrive
Une tâche basique peut prendre trois états différents :
- suspended : la tâche est passive mais peut être activée
- ready : la tâche est candidate à l’exécution
- running : la tâche est en cours d’exécution
Les tâches étendues
Les tâches étendues ont les mêmes caractéristiques que les tâches basiques avec en plus la possibilité d’utiliser la gestion des évènements.
Cette possibilité leur « donne accès » a un quatrième état : Waiting. Les tâches étendues ont par ailleurs les trois mêmes états que les tâches basiques (Running, Suspended, Ready).
Dans l’état « Waiting », la tâche étendue ne peut pas continuer à s’exécuter car elle doit attendre l’arrivée d’au moins un évènement.
Quelques primitives (API) de gestion des tâches
La déclaration d’une tâche se fait grâce à la primitive void DeclareTask( TaskIdentifier).
Au démarrage, une tâche est à l’état « Suspended ». Pour l’activer, c’est-à-dire passer à l’état « Ready », on utilise la primitive StatusType ActivateTask(TaskType )
Pour terminer une tâche, c’est-à-dire la passer à l’état « Suspended », on utilise la primitive StatusType TerminateTask(void).
Il existe d’autres primitives pour la gestion des tâches, qui permettent par exemple de connaître le numéro d’identification d’une tâche ou encore l’état courant d’une tâche.
Gestion des priorités
Les priorités données aux tâches sont primordiales dans le sens où elle vont servir à l’ordonnanceur à déterminer quelle est la tâche actuellement à l’état « Ready » qui va passer la prochaine à l’état « Running ».
Les priorités données aux tâches sont définies de manière statique à la création.
La valeur 0 représente la priorité la plus basse. Il n’y a pas de valeur limite pour les priorités les plus hautes.
Dans le cas de tâches ayant la même priorité, on donne la main à celle qui a été activée en premier.
Une tâche qui a été préemptée est considérée comme étant la plus ancienne dans la file FIFO des tâches de même priorité, et sera donc la première à en sortir. A l’inverse, une tâche qui sort de l’état « Waiting » est placée comme la première dans la file FIFO des tâches de même priorité, et sera donc la dernière à en sortir.
Pour déterminer la prochaine tâche qui sera exécutée, l’ordonnanceur suit l’algorithme suivant :
- recherche de toutes les tâches qui sont dans l’état « Ready »
- à partir de cet ensemble de tâche, l’ordonnanceur détermine celles qui ont la plus haute priorités
- parmi cet ensemble de tâches à la priorité la plus haute, l’ordonnanceur détermine la plus ancienne (celle qui a été activée en premier), et l’exécute.
- Les interruptions et l’ordonnanceur
OSEK définit trois niveaux de traitements :
- les interruptions
- l’ordonnanceur
- les tâches
Les interruptions ont une priorité supérieure aux tâches. L’ordonnanceur a une priorité inférieure aux interruptions mais supérieure aux tâches.
Les fonctions servant à traiter les interruptions (ISR : Interrupt Service Routine) existent sous deux formes :
- ISR catégorie 1 : l’ISR ne fait pas appel aux services de l’OS. Ces interruptions sont transparentes pour l’OS, sont rapides et ne nécessitent pas de synchronisation avec l’OS.
- ISR catégorie 2 : l’ISR fait appel aux services de l’OS, c’est-à-dire que le code de l’interruption contient des API.
N.B : dans la version 2.1 d’OSEK, il existe également une catégorie 3 d’ISR, qui est une combinaison entre les catégories 1 et 2. Mais cette troisième catégorie a été supprimée dans la version 2.2.
Les évènements
Les évènements représentent, comme les interruptions, des événements externes. Le traitement d’un évènement ne s’effectue pas dans une ISR mais dans une tâche dans laquelle l’évènement a été défini.
Le mécanisme d’évènement n’est utilisable que pour les tâches étendues et conditionne l’entrée ou la sortie de l’état « Waiting » de ce type de tâches.
Les évènements ne sont pas des objets indépendants, ils sont rattachés à une tâche étendue, qui est dite « propriétaire » de cet évènement. Un évènement est ainsi identifié par son propriétaire et son nom (ou son masque).
Les évènements peuvent être utilisés comme un moyen de synchronisation entre les tâches ou pour communiquer des informations (binaires) à la tâche étendues auxquelles ils sont rattachés.
Un évènement peut être déclenché aussi bien par une tâche étendue que par une tâche basique. Le déclenchement est réalisé grâce à un service de l’OS et peut également être réalisé par des alarmes, des messages ou des ISR. Par contre, la remise à zéro de l’évènement ne peut être effectuée que dans la tâche dans laquelle il a été défini.
Alarmes et compteurs
OSEK fournit des objets permettant de traiter des phénomènes récurrents dans le temps. Un tel phénomène pourrait être par exemple l’activation cyclique d’une interruption. Ces objets sont les compteurs et les alarmes.
Les compteurs sont destinés à l’enregistrement des ticks en provenance d’un timer ou d’un dispositif émettant des stimuli (par exemple des capteurs). OSEK ne fournit pas d’API permettant de manipuler directement les compteurs.
L’alarme a pour but de superviser la valeur d’une référence (par exemple celle d’un compteur). L’alarme se déclenchera quand une certaine valeur de cette référence sera atteinte. Les alarmes sont statiquement associées à un compteur et à une tâche. Plusieurs alarmes peuvent être affectées à un même compteur.
N.B : la norme OSEK impose l’implémentation d’au moins une alarme.
Si l’affectation d’une alarme à un compteur et à une tâche est statique, la valeur à laquelle l’alarme expire est dynamique et peut donc être changé en cours d’exécution.
Une alarme peut être utilisée pour effectuer les actions suivantes :
- activation d’une tâche
- déclenchement d’un évènement
Gestion des ressources
La gestion des ressources comprend la coordination de l’accès aux ressources partagées. Celles-ci peuvent être de l’espace mémoire, des composants hardware, des applications ou encore des instances de l’OS (par exemple le scheduler).
La gestion des ressources assure que :
- deux tâches ne peuvent pas occuper la même ressource au même instant
- il ne peut pas y avoir d’inversion de priorité
- il ne peut pas y avoir d’interblocage
- l’accès à une ressource n’engendre pas d’état « Waiting » pour une tâche
Les phénomènes d’inversion de priorité et d’interblocage peuvent être évités grâce au Priority Ceiling Protocol mise en place par OSEK.
Gestion des erreurs
Les routines crochets (hook routines)
OSEK fournit des mécanismes de « crochets » qui permettent à l'utilisateur de dérouter le déroulement normal de l’OS de façon à prendre temporairement le contrôle du système.
Les routines crochets sont appelées par l’OS et ont une priorité supérieure à toutes les tâches. Elles sont implémentées par le développeur et doivent obligatoirement être implémentées.
Les routines crochets disponibles pour le développeur sont les suivantes :
- StartupHook : utilisé au démarrage de l’application avant l’activation des tâches
- ShutdownHook : utilisé avant l’arrêt de l’OS
- PreTaskHook : utilisé avant chaque changement de contexte d’une tâche
- PostTaskHook : utilisé après chaque changement de contexte d’une tâche
- ErrorHook : utilisé lors de détection d’erreur système
Les différents types d’erreur
On distingue deux types d’erreur :
- les « application errors » qui arrivent lorsque l’OS n’a pas pu correctement exécuter l’API demandée
- les « fatal errors » qui arrivent lorsque l’OS n’a plus la garantie de l’intégrité des données internes
C’est grâce à la routine crochet ErrorHook que l’utilisateur peut réaliser des actions, qu’il aura lui-même définies, au moment de l’arrivée d’une erreur. La routine crochet ErrorHook sera appelée si la variable StatusType retournée par une API n’est pas égale à « E_OK ».