5 Une application temps réel sous RT-Linux
Le système RT-Linux est une extension du système Linux classique vers le temps réel. Il est constitué par un noyau temps réel
Rt-Linux qui partage le processeur avec le noyau de base Linux et exécute des tâches temps réel.
5.1 Les tâches RT-Linux.
5.1.1 Mise en uvre.
Les tâches RT-Linux s'exécutent dans l'espace adresse du noyau Linux au même titre que l'
ordonnanceur
temps réel. Il existe dans les versions récentes de Linux la possibilité de charger dynamiquement des modules dans l'espace
adresse du noyau et de les "lier" au code du noyau. On parle de "modules chargeables". En effet, pour limiter la taille du
noyau Linux et libérer ainsi plus de place mémoire pour l'utilisateur, on évite de compiler le noyau avec des composants qui
ne sont pas nécessaires à tout moment. Ces composants sont par exemple des gestionnaires de périphériques. Lorsqu'on a besoin
d'ajouter ou de supprimer l'un de ces composants, on n'a plus besoin de recompiler tout le noyau comme cela était le cas auparavant:
le composant est chargé ou déchargé dynamiquement à l'aide d'un module. Il faut simplement configurer le noyau avant sa compilation
pour qu'il accepte de gérer les modules chargeables:
Ainsi, les tâches RT-Linux sont créées à l'aide d'un "module chargeable".
Il y a plusieurs avantages à mettre les tâches dans l'espace adresse du noyau:
- Elles partagent le même espace adresse.
- Comme elles sont dans l'espace adresse du noyau, on élimine la charge occasionnée par tout changement de niveau de protection.
- Un avantage plus pratique que performant. Dans l'espace adresse du noyau, il est permis de faire référence aux objets et aux
fonctions par leur nom plutôt que par leur descripteur. C'est l'édition de lien dynamique des "modules chargeables" qui résoudra
les symboles en adresse.
En conclusion cette solution, qui consiste à mettre les tâches temps réel dans l'espace adresse du noyau permet d'optimiser
les performances de RT-Linux.
En contrepartie,
cette solution com
port
e un risque : Un "Bug" dans une tâche temps réel peut mettre en danger tout le système. Ce risque est renforcé avec l'utilisation
du langage C, notamment les pointeurs. La seule garantie à ce niveau est la rigueur du programmeur.
Le mode opératoire consiste en général à créer toutes les tâches lors du chargement du module (fonction init_module()) et
à les supprimer uniquement au déchargement (fonction cleanup_module). La structure "rt_task_struct" (ou RT_TASK par " typedef
struct rt_task_struct RT_TASK ;") des tâches temps réel dans RT-Linux est la suivante :
- int *stack ; /* hardcoded */
- int uses_fp ; /* this one is too*/
- int state; Etat de la tâche : RT_TASK_READY,...
- int *stack_bottom ; Pointeur de pile.
- int priority ; Politique d'
ordonnancement
basée sur la priorité seule.
- RTIME period; P : Période si la tâche est périodique.
- RTIME resume_time; r : Prochaine heure de réveil .
- struct rt_task_struct *next; Chaînage simple de l'ensemble des tâches créées dans l'application.
- RTL_FPU_CONTEXT fpu_regs ;
Les fonctions de gestion des tâches temps réel dans RT-Linux sont listées ci-dessous. Les états évoqués sont présentés dans
les figures qui suivent.
- int rt_task_init (RT_TASK *task, void (fn)(int data), int data, int stack_size, int priority) :
Création d'une tâche RT_Linux pointée par "task". Il y a réservation d'espace mémoire dans le noyau (kmalloc) à hauteur de
stack_size. "fn" est le code exécuté par la tâche. "data" est le paramètre passé à "fn" au démarrage. "priority" est la priorité
de la tâche dont la valeur peut aller de 1, la plus forte priorité, à "RT_LOWEST_PRIORITY" (1000000), la plus faible.
- int rt_task_delete (RT_TASK *task) :
Suppression logique d'une tâche : Pas de libération de l'espace mémoire "kfree" à ce niveau. Son état passe à RT_TASK_ZOMBIE.
- int rtl_delete_zombies (void) :
Suppression réelle de l'ensemble des tâches logiquement supprimées par "rt_task_delete". "kfree" est effectué ici.
- int rt_task_make_periodic (RT_TASK *task, RTIME start_time, RTIME period) :
Rend la tâche "*task" périodique avec une période "period" à partir de la première date de réveil "start_time".
- int rt_task_wait (void) :
Suspension de la tâche périodique jusqu'à sa prochaine date de réveil. Elle met la tâche qui l'a appelée dans l'état RT_TASK_DELAYED.
En général toute tâche périodique indique par cette fonction qu'elle a terminé son traitement : ce doit donc être la dernière
instruction du code de la tâche. Les tâches apériodiques ne s'en servent pas car l'état RT_TASK_DELAYED ne leur correspond
pas.
- int rt_task_wakeup (RT_TASK *task) :
Déclenche la tâche "task", c'est-à-dire la met dans l'état prêt RT_TASK_READY.
- int rt_task_suspend (RT_TASK *task) :
Inactive la tâche "task", c'est-à-dire la met dans l'état RT_TASK_DORMANT. En général, cette routine est utilisée par les
tâches apériodiques à la place de rt_task_wait pour signaler la fin de leur traitement. Une tâche périodique ou apériodique
peut être suspendue par une autre tâche ou par l'
ordonnanceur
.
Les figures jointes donnent les graphes d'état des tâches périodiques et apériodiques pour Rt-Linux
 |
Tâches périodiques.
|
|
 |
Tâches apériodiques.
|
|
5.1.2 Format d'une application.
La déclaration des tâches périodiques et apériodiques dans RT-Linux se fait à l'aide d'un module noyau dont nous présentons
le squelette ci-dessous :
On suppose que ce module s'appelle RT_PROCESS.C et qu'il va créer deux tâches : T1 et T2. T1 est une tâche périodique et T2
apériodique.
Le module s'installe dans le noyau par : insmod RT_PROCESS.O On le décharge du noyau par : rmmod RT_PROCESS
#define MODULE
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/version.h>
#include <linux/errno.h>
#include <rtl_sched.h >
#include <linux/arch/i386/kernel/irq.h>
#define NTASKS 2
RT_TASK tasks[NTASKS]
|
/*-- Fonction 1 = traitement de la tâche périodique 1 --*/
Code de la fonction exécutée par la tâche périodique 1; l'affectation de cette fonction à la tâche 1 est faite par rt_task_init
(Cf. plus bas). Toute tâche périodique doit terminer son traitement par l'appel à la fonction rt_task_wait(). Cette routine
met la tâche dans l'état RT_TASK_DELAYED, réinitialise la prochaine date de réveil et fait appel à l'ordonnanceur rtl_schedule().
void f_tâche_1(int par1)
{
rt_task_wait() ;
}
Fonction 2
/*-- Fonction 2 = traitement de la tâche apériodique 2 --*/
Code de la fonction exécutée par la tâche apériodique 2. Toute tâche apériodique doit terminer son traitement par l'appel
à la fonction rt_task_suspend() sur elle-même. Cette routine met la tâche dans l'état RT_TASK_DORMANT et fait appel à l'ordonnanceur
rtl_schedule().
void f_tâche_2(int par2)
{
rt_task_suspend(&(tasks[2])) ;
}
Interruption Tache 2
/*-- Gestionnaire de l'interruption associée à la tâche 2 --*/
La tâche apériodique 2 est associée à un niveau d'interruption dont l'apparition active le gestionnaire T2_Handler(). L'association
entre le numéro d'interruption et le gestionnaire sera vue plus bas. Ce gestionnaire a au moins pour rôle de réveiller la
tâche apériodique concernée. Cette fonction met la tâche dans l'état RT_TASK_READY et appelle l'ordonnanceur.
int T2_handler()
{
rt_task_wakeup(&(tasks[2])) ;
}
/*-- Ordres lancés à l'installation du module: RT_PROCESS --*/
Lorsqu'un module est chargé sous Linux (>insmmod RT_Process.o), il commence par effectuer la fonction init_module(). Cette
fonction doit donc figurer dans tous les modules chargeables. Dans RT-Linux, on lui attribue le rôle de l'initialisation des
tâches temps réel (rt_task_init), de l'initialisation des périodes et des dates de réveil pour les tâches périodiques (rt_task_make_periodic)
et de l'association des niveaux d'interruptions aux gestionnaires associés (request_RTirq).
int init_module(void)
{
rt_task_init(&(tasks[0]), f_tâche_1, 0, 3000, 4) ;
rt_task_init(&(tasks[1]), f_tâche_2, 1, 3000, 5) ;
rt_task_make_periodic(&(tasks[0]), 5, 10) ;
request_RTirq(2, &T2_handler) ;
return 0 ;
}
/*-- Ordres lancés à la suppression du module: RT_PROCESS --*/
void cleanup_module(void)
Lorsqu'un module est déchargé sous Linux, il exécute systématiquement la fonction cleanup_module(). Cette fonction doit donc
figurer dans tous les modules chargeables. Dans RT-Linux, on lui attribue le rôle de suppression des tâches temps réel (rt_task_delete)
ainsi que celui de la libération des interruptions (free_Rtirq).
{
rt_task_delete(&(tasks[0])) ;
rt_task_delete(&(tasks[1])) ;
free_Rtirq(2) ;
}
|
5.2 L'ordonnancement.
Dans la version 1.2 de RT-Linux, présentée sur le site officiel du système, trois
ordonnanceur
s préemptifs ont déjà été installés :
- Un ordonnanceur "à priorité fixe" : il s'appuie sur le paramètre "priority" défini au niveau de chacune des tâches. Lorsque
plusieurs tâches sont prêtes, celle qui a la priorité la plus forte (la valeur la plus petite) est élue. Une tâche qui se
réveille (RT_TASK_READY) pourra préempter la tâche active si sa priorité est plus forte.
- Un ordonnanceur "Rate-Monotonic" : nous ne nous y intéresserons pas, compte-tenu des limites de cette politique vis-à-vis
des tâches apériodiques.
- Un ordonnanceur "EDF" ("Earliest Deadline First") : Il utilise l'urgence comme critère de sélection de la prochaine tâche
à élire.
Ordonnancement
Ordonnancement de processus
LinuxRT
|