Génie logiciel et design de systèmes temps réel H. Mounier 0-0
MOTIVATIONS Utiliser des techniques modernes de conception de logiciel Ceci pour assurer la fiabilité Ces techniques doivent être adaptées au contexte temps réel H.M. Design de systèmes temps réel 1
PLAN DE L EXPOSÉ 1. Guides de design 2. Patrons de conception temps réel 3. Outils de synchonisation, du capteur à l actionneur H.M. Design de systèmes temps réel 2
Guides de design
GUIDES GÉNÉRAUX Faible couplage Un composant ne doit pas se reposer sur les détails internes d un autre. Forte cohésion Un composant doit avoir une définition et un comportement précis et étroit. H.M. Design de systèmes temps réel 3
ENCAPSULATION : PILE BASIQUE EN C Voici un exemple de pile avec comme structure de données interne un tableau #include <stdio.h> #include <stdlib.h> #define TAILLE 200 typedef struct pile { int elements[taille]; /* donnees, ici un tableau */ int *dessus; /* bas de la pile; ne varie pas */ int *dessous; /* haut de la pile; variable */ *pile; pile creer_pile(); void empiler(pile ma_pile, int i); int depiler(pile ma_pile); void detruire_pile(pile ma_pile); H.M. Design de systèmes temps réel 4
ENCAPSULATION : PILE BASIQUE EN C Création de pile pile creer_pile() { pile nouvelle_pile = (pile) NULL; nouvelle_pile = (pile)calloc(1, sizeof(struct pile)); if (nouvelle_pile == (pile)null) { fprintf(stderr, "Creation de pile impossible\n"); perror("message systeme : "); exit(1); /* bas de la pile */ nouvelle_pile->dessous = &(nouvelle_pile->elements)[0]; /* au debut la pile est vide */ nouvelle_pile->dessus = &(nouvelle_pile->elements)[0]; return(nouvelle_pile); /* creer_pile() */ H.M. Design de systèmes temps réel 5
ENCAPSULATION : PILE BASIQUE EN C Empiler une valeur void empiler(pile ma_pile, int i) { (ma_pile->dessus)++; if( ma_pile->dessus == ((ma_pile->dessous) + TAILLE) ) { printf("debordement de pile\n"); exit(1); *(ma_pile->dessus) = i; /* empiler() */ H.M. Design de systèmes temps réel 6
ENCAPSULATION : PILE BASIQUE EN C Dépiler une valeur int depiler(pile ma_pile) { if(ma_pile->dessus == ma_pile->dessous) { printf("assechement de pile\n"); exit(1); (ma_pile->dessus)--; return(*((ma_pile->dessus)+1)); /* depiler() */ H.M. Design de systèmes temps réel 7
ENCAPSULATION : PILE BASIQUE EN C Destruction d une pile void detruire_pile(pile ma_pile) { if (ma_pile == (pile)null) { fprintf(stderr, "Destruction de pile impossible\n"); return; free((void *)ma_pile); /* detruire_pile() */ H.M. Design de systèmes temps réel 8
ENCAPSULATION : PILE BASIQUE EN C Programme principal /** main() **/ void main(void) { int valeur_entree; pile une_pile; une_pile = creer_pile(); do { printf("\t\tentrez une valeur a empiler \n \ (-1 pour sortir, 0 pour depiler) : "); scanf("%d", &valeur_entree); if(valeur_entree!= 0) empiler(une_pile, valeur_entree); else printf("valeur du dessus de pile : %d\n", depiler(une_pile)); while(valeur_entree!= -1); detruire_pile(une_pile); /* main() */ H.M. Design de systèmes temps réel 9
ENCAPSULATION : DISSIMULATION D INFORMATIONS Entre cette partie de code : int pile i, elt_pile; ma_pile; ma_pile = creer_pile(); /* remplissage de la pile */ for(i = 0; i < 100; i++) elt_pile = depiler(ma_pile); où l on utilise depiler() et celle-ci int pile elt_pile; ma_pile; ma_pile = creer_pile(); /* remplissage de la pile */ elt_pile = (ma_pile->elements)[99]; la deuxième solution doit être rejetée. H.M. Design de systèmes temps réel 10
ENCAPSULATION : DISSIMULATION D INFORMATIONS Si l on change la structure de données utilisée pour une pile (par exemple une liste chaînée au lieu d un tableau), la dernière partie de code est désastreuse. L abstraction aide à la maintenabilité et à l intelligibilité en réduisant le nombre de détails à connatre à chaque niveau. La dissimulation d informations aide à la fiabilité (empcher toute opération non autorisée). H.M. Design de systèmes temps réel 11
GUIDES DÉTAILLÉS Pour le faible couplage Minimiser le couplage des méthodes (fonctions) : ne prendre comme entrées que les données nécessaires ; ne renvoyer que les données produites par la méthode Utiliser des hiérarchies appropriées ; par exemple découpage en échelles de temps. Réduit la complexité et accroît donc la fiabilité. Découpler l interface de l implantation (cf. l exemple de la pile) H.M. Design de systèmes temps réel 12
GUIDES DÉTAILLÉS Pour la forte cohésion Chaque composant (chaque méthode) doit avoir un but précis Éviter de passer des données de commande aux méthodes (données décidant comment effectuer les traitements) H.M. Design de systèmes temps réel 13
PATRONS DE CONCEPTION Un patron de conception est une solution éprouvée, constructive à un problème connu. L utilisation de patrons (comme briques de base) permet d accroître la fiabilité (las patrons étant des problèmes déja résolus) H.M. Design de systèmes temps réel 14
PATRONS DE CONCEPTION Exemples de patrons Médiateur, servant à connecter deux composant en introduisant un minimum de couplage. Moniteur, une seule méthode de cet objet peut s exécuter à la fois (accès concurrent). Producteur/consommateur, le producteur peut continuer à produire aussi indépendemment que possible du consommateur Machine à états finis H.M. Design de systèmes temps réel 15
Outils de synchonisation, du capteur à l actionneur
SYNCHRONISATION : MOTIVATION Exemple de mauvaises manières. Deux tâches : capteur qui acquiert des données capteur et loi de commande qui calcule une loi de commande extern int nbsensvals, // indice de lect-ecr ds tampon capteur nbactsvals, // indice de lect-ecr ds tampon actionneur sensorsampletime, // ~ "periode" carte capteur controllawsampletime; // ~ "periode" calcul loi de commande sensordatabuff[sens_buff_size]; // tampon de donnees capteur // methode associee a la thread "capteur" void gathermeasure() { float sensorvalue; // valeur de signal capteur while (1) { // Une valeur de capteur est lue sur la carte associee read(&sensorvalue); // Elle est ecrite dans un tampon capteur sensordatabuff[nbsensvals] = sensorvalue; // On dort un moment sleep(sensorsampletime); H.M. Design de systèmes temps réel 16
SYNCHRONISATION : MOTIVATION // methode associee a la thread "loi de commande" void SensorToActuator(float measure) { float sensorvalue, actuatorvalue; // Valeur d actionneur while(1) { // Lecture du tampon capteur sensorvalue = sensordatabuff[nbsensvals]; // Le prochain echantillon capteur peut etre ecrit if (nbsensvals > SENS_BUFF_SIZE) nbsensvals = 0; else nbsensvals++; // Synthese de la loi de commande actuatorvalue = controllawsynthesis(sensorvalue); // La valeur d actionneur est ecrite dans un tampon actionneur actuatordatabuff[nbactvals] = actuatorvalue; // On dort a nouveau sleep(controllawsampletime); Les variables globales accroissent le couplage H.M. Design de systèmes temps réel 17
SYNCHRONISATION : MOTIVATION L attente active ajustée au moyen de sleep(), très dépendante des fréquences d acquisition capteur et de calcul de loi de commande Le temps de calcul de la loi de commande est très variable (loi linéaire, non linéaire), donc controllawsampletime sera une valeur de pire cas, donc très sous-optimale L utilisation du même indice nbsensvals dans les deux fonctions peut induire divers problèmes Si controllawsampletime > sensorsampletime, la tâche capteur va écraser la même valeur ; Si controllawsampletime < sensorsampletime, la tâche loi de commande va lire des valeurs de capteur très anciennes. H.M. Design de systèmes temps réel 18
SYNCHRONISATION : MOTIVATION Une variable globale, semblerait rǵler les choses extern int nbsensvals, // indice de lect-ecr ds tampon capteur nbactsvals, // indice de lect-ecr ds tampon actionneur sensorsampletime, // ~ "periode" carte capteur controllawsampletime; // ~ "periode" calcul loi de commande littletime; // a small sleep time extern float sensordatabuff[sens_buff_size]; // tampon de donnees capteur extern int lock = 0; // verrou de lecture/ecriture void gathermeasure() { // methode associee a la thread "capteur" float sensorvalue; // Valeur capteur while (1) { // Une valeur de capteur est lue sur la carte associee read(&sensorvalue); if (lock == 0) { // Le verrou a-t-il ete acquis? lock = 1; // Acquerir le verrou sensordatabuff[nbsensvals] = sensorvalue; lock = 0; // Relacher le verrou sleep(sensorsampletime); // Dormir suffisamment longtemps else sleep(littletime); H.M. Design de systèmes temps réel 19
SYNCHRONISATION : MOTIVATION void SensorToActuator(float measure) { de commande" float sensorvalue, actuatorvalue; // associee a "loi // Valeur d actionneur while(1) { if (lock == 0) { lock = 1; // Acquerir le verrou sensorvalue = sensordatabuff[nbsensvals]; if (nbsensvals > SENS_BUFF_SIZE) { nbsensvals = 0; else { nbsensvals++; lock = 0; // Relacher le verrou sleep(controllawsampletime); // Dormir suffisamment longtemps else sleep(littletime); // Synthese de la loi de commande et ecriture dans un tampon actuatorvalue = controllawsynthesis(sensorvalue); actuatordatabuff[nbactvals] = actuatorvalue; H.M. Design de systèmes temps réel 20
SYNCHRONISATION : MOTIVATION Premier problème : Supposons une valeur déja lue par la tâche loi de commande, donc lock == 0. C est un peu plus tard au tour de capteur de s exécuter et elle est après le test. if (code == 0) lorsqu elle est interrompue par loi de commande Cette dernière tâche voit lock égal à 0 et va lire une valeur qui n a pas été écrite. H.M. Design de systèmes temps réel 21
SYNCHRONISATION : MOTIVATION Deuxième problème : Supposons que la tâche capteur soit de plus haute fréquence que la tâche loi de commande Lorsque la tâche capteur a fini de lire une valeur et de la stocker dans un tampon, elle dot dormir suffisamment longtemps ; sinon, elle pourrait reacquérir le verrou lock et lire une autre valeur capteur. Le problème vient du fait que l opération test de lock et mise à 1 de lock n est pas atomique (c.à.d. non interruptible) H.M. Design de systèmes temps réel 22
CONDITION DE COURSE ET SYNCHRONISATION Compétition de plusieurs tâches pour un nombre limité de ressources : condition de course Solution simpliste : Désactiver les interruptions Arrêter l ordonnanceur H.M. Design de systèmes temps réel 23
CONDITION DE COURSE ET SYNCHRONISATION Deux tâches utilisant un verrou de synchronisation doivent être d accord sur le verrou à utiliser pour protéger les données en accès mutuel retreindre au maximum les sections de code critiques (non interruptibles) Ce qui fait défaut ici est une opération atomique de type test and set (tester un mot et le mettre à 1 si ce n est déja fait). int test_and_set(int *lock) { int temp = *lock; *lock = 1; return temp; H.M. Design de systèmes temps réel 24
SÉMAPHORE Structure de base d un sémaphore struct semaphore { int count; // compte du semaphore queue Queue; // liste des taches bloquees par le semaphore Fonction d acquisition de verrou sem wait() sem_wait(semaphore S) { if (S.count > 0) S.count = S.count - 1; else block the task in S.Queue; Fonction d acquisition de verrou sem signal() sem_signal(semaphore S) { if (S.Q is non-empty) wakeup a task in S.Queue; else S.count = S.count + 1; H.M. Design de systèmes temps réel 25
SÉMAPHORE Une autre implantation utilise test and set(), ce qui rend les opérations sem wait() et sem signal() atomique boolean test_and_set(boolean lock) { boolean temp = lock; lock = 1; return temp; Deux fonctions entry() and exit() seront utilisées ; marquage d entrée et de sortie d une tâche dans une section critique // variables partagees par les N tachaes, numerotees 0, 1,..., N-1 boolean lock initially FALSE; boolean waiting[n-1] initially FALSE; // liste des taches en attente entry(int i) { boolean key = FALSE; waiting[i] = TRUE; while (waiting[i] && key) { key = test_and_set(lock); waiting[i] = FALSE; // i est l identificateur de la tache H.M. Design de systèmes temps réel 26
SÉMAPHORE exit(int i) { int p; p = (i+1) mod N; // recherche de la prochaine tache en attente while (p!= i && waiting[p] == FALSE) p = (p+1) mod N; if (p == i) lock = FALSE; // pas de tache en attente else waiting[p] = FALSE; // p est la prochaine tache en attente. La liberer H.M. Design de systèmes temps réel 27
SÉMAPHORE Ces fonctions permettent de résoudre un problème d exclusion mutuelle pour N tâches. Les fonctions sem wait() and sem signal() sont alors sem_wait(semaphore S) { S.entry(i); if (S.count == 0) { S.exit(i); waitin(i, S.Queue); else { S.count = S.count - 1; S.exit(i); // l identificateur de la tache est i waitin(int i, queue Q) { move task i to Q; schedule(); H.M. Design de systèmes temps réel 28
SÉMAPHORE sem_signal(semaphore S) { S.entry(i); if S.Queue not empty move task i to readyqueue; else S.count = S.count + 1; S.exit(i); Ici readyqueue est la queue des tâches prètes à être exécutées. H.M. Design de systèmes temps réel 29
MUTEX Un verrou d exculion mutuelle, ou verrou mutex est un mécanisme de synchronisation partageant certaines des caractéristiques d un sémaphore binaire. Comme pour un sémaphore, il y a des opérations de verrouillage et de déverrouillage, avec blocage sur le verrou déja acquis Contrairement à un sémaphore, un mutex doit être relâché par la tâche qui l a acquise ; il y a une forme de possession du mutex. Deux appels y sont associés : L acquisition du verrou par mutex lock() et La libération du verrou par mutex unlock() H.M. Design de systèmes temps réel 30
VARIABLES DE CONDITION Les variables de condition permettent à une tâche de dormir au sein d une section critique, jusqu à ce qu une expression booléenne définie par le programmeur soit satisfaite. Ce sont des formes d événements associés à un mutex. On utlise la combinaison Un verrou mutex Une expression booléenne Un événement, que d autres tâches peuvent générer pour réveiller la tâche bloquée par la variable de condition Deux appels sont associés à une variable de condition : cond wait() et cond broadcast() H.M. Design de systèmes temps réel 31
VARIABLES DE CONDITION L appel cond wait() apparaît dans une section critique, entre mutex lock() et mutex unlock() L appel cond wait() bloque atomiquement la tâche et relâche le verrou mutex. L appel cond broadcast() relâche le verrou mutex et réveille toutes les tâches bloquées par la variable de condition. H.M. Design de systèmes temps réel 32
Patrons de conception temps réel
MÉDIATEUR Un médiateur est un patron pour affaiblir le couplage entre composants. Le découplage intervient dans le nommage ; les composants n ont besoin de connaître que le médiateur la gestion des données ; on accède aux données uniquement via l interface du médiateur la synchronisation ; toute la synchronisation réside au sein du médiateur Un médiateur est une politique d utilisation du mécanisme offert par le patron moniteur. H.M. Design de systèmes temps réel 33
MONITEUR Un moniteur fournit des méthodes de synchronisation pour accéder aux données La synchronisation est effectuée grâce à un verrou de moniteur. Une seule méthode synchronisée d un moniteur peut s exécuter à la fois. Ces méthodes déterminent quand elles sont bloquées au moyen de conditions de moniteur. H.M. Design de systèmes temps réel 34
MÉTHODES D INTERFACE DU MONITEUR Méthodes d interface (vue externe du moniteur) : put() insère un message dans la file, get() enlève un message de la file empty() et full() testent si la file est vide ou pleine. put() (resp. get()) bloque si la file est pleine (resp. vide). class Message_Queue { public: enum { MAX_MESSAGES = /*... */; ; // The constructor defines the maximum number // of messages in the queue. This determines // when the queue is full. Message_Queue (size_t max_messages = MAX_MESSAGES); // = Message queue synchronized methods. // Put the <Message> at the tail of the queue. H.M. Design de systèmes temps réel 35
MÉTHODES D INTERFACE DU MONITEUR // If the queue is full, block until the queue // is not full. void put (const Message &msg); // Get the <Message> at the head of the queue. // If the queue is empty, block until the queue // is not empty. Message get (void); // True if the queue is full, else false. // Does not block. bool empty (void) const; ; // True if the queue is empty, else false. // Does not block. bool full (void) const; private: //... H.M. Design de systèmes temps réel 36
MÉTHODES D IMPLANTATION DU MONITEUR Méthodes d implantation, qui simplifient le codage des méthodes d interface Découplage synchronisation ordonnancement On utilise les conventions suivantes (idiome de sûreté des thread) : (i) Les méthodes d interface ne font qu acquérir ou relâcher les verrous du moniteur et attendre ou réveiller selon des conditions du moniteur. Elles passent ensuite le contrôle aux méthodes d implantation (ii) Les méthodes d implantation n effectuent aucun appel de synchronisation (ceci est délégué aux méthodes d interface). Ces méthodes ne sont pas bloquantes. H.M. Design de systèmes temps réel 37
MÉTHODES D IMPLANTATION DU MONITEUR Méthodes d implantation : put i() insère un message dans la file get i() enlève un message de la file empty i() et full i() testent si la file est vide ou pleine. Selon l idiome de sûreté des thread, put i() (resp. get i(), empty i(), full i()) est appelée per put() (resp. get(), empty(), full()). H.M. Design de systèmes temps réel 38
MÉTHODES D IMPLANTATION DU MONITEUR class Message_Queue { public: //... See above... private: // = Private helper methods (non-synchronized // and do not block). // Put the <Message> at the tail of the queue. void put_i (const Message &msg); // Get the <Message> at the head of the queue. Message get_i (void); // True if the queue is full, else false. // Assumes locks are held. bool empty_i (void) const; // True if the queue is empty, else false. // Assumes locks are held. bool full_i (void) const; //... H.M. Design de systèmes temps réel 39
ÉTAT INTERNE DU MONITEUR L état interne consiste en 3 groupes Les données représentant la file ; les données elle-même (par ex. un tampon circulaire), et des informations pour déterminer si la file est vide ou pleine (champs message count et max messages ). Les données associées au verrou du moniteur. monitor lock est une instance de Thread mutex défini ci-dessous. Les données associées à la condition du moniteur.les conditions not empty et not full sont utilisées pour bloquer le client si la file est vide ou pleine. Ce sont des instances de Thread Condition défini ci-dessous. H.M. Design de systèmes temps réel 40
ÉTAT INTERNE DU MONITEUR Les classes Thread mutex et Thread Condition sont des instances du patron de facade ( wrapper facade pattern ), c.à.d., ils représentent une interface orientée-object pour les fonctions écrites dans un langage qui ne l est pas (ici le langage C) et sous différents environnements (systèmes d exploitation). class Message_Queue { //... See above... private: // Internal Queue representation.... // Current number of <Message>s in the queue. size_t message_count_; // The maximum number <Message>s that can be // in a queue before its considered full. size_t max_messages_; // = Mechanisms required to implement the // monitor objects synchronization policies. H.M. Design de systèmes temps réel 41
ÉTAT INTERNE DU MONITEUR // Mutex that protect the queues internal state // from race conditions during concurrent access. mutable Thread_Mutex monitor_lock_; // Condition variable used to make synchronized // method threads wait until the queue is no // longer empty. Thread_Condition not_empty_; // Condition variable used to make synchronized // method threads wait until the queue is // no longer full. Thread_Condition not_full_; H.M. Design de systèmes temps réel 42
CLASSE Thread Mutex La classe Thread Mutex est utilisée pour le verrouillage interne du moniteur C est un exemple du patron de facade H.M. Design de systèmes temps réel 43
CLASSE Thread Mutex Voici une implantation sous Solaris: class Thread_Mutex { public: Thread_Mutex (void) { mutex_init (&mutex_, 0, 0); Thread_Mutex (void) { mutex_destroy (&mutex_); int acquire (void) { return mutex_lock (&mutex_); int release (void) { return mutex_unlock (&mutex_); private: // Solaris-specific Mutex mechanism. mutex_t mutex_; // = Disallow copying and assignment. Thread_Mutex (const Thread_Mutex &); void operator= (const Thread_Mutex &); ; H.M. Design de systèmes temps réel 44
CLASSE Thread Mutex Voici une implantation sous Windows NT : class Thread_Mutex { public: Thread_Mutex (void) { InitializeCriticalSection (&mutex_); Thread_Mutex (void) { DeleteCriticalSection (&mutex_); int acquire (void) { EnterCriticalSection (&mutex_); return 0; int release (void) { LeaveCriticalSection (&mutex_); return 0; private: // Win32-specific Mutex mechanism. CRITICAL_SECTION mutex_; // = Disallow copying and assignment. Thread_Mutex (const Thread_Mutex &); void operator= (const Thread_Mutex &); ; H.M. Design de systèmes temps réel 45
CLASSE Thread Mutex Les méthodes ne font qu appeler les fonctions correspondantes du système (d où le nom de patron de facade) H.M. Design de systèmes temps réel 46
CLASSE Thread Condition Lorsque la file est pleine put() doit être bloquante, et lorsqu elle st vide, get() doit être bloquante. Ceci est obtenu en utilisant l implantation de facade Thread Condition class Thread_Condition { public: // Initialize the condition variable and // associate it with the <mutex_>. Thread_Condition (const Thread_Mutex &m) // Implicitly destroy the condition variable. Thread_Condition (void); // Wait for the <Thread_Condition> to be, // notified or until <timeout> has elapsed. // If <timeout> == 0 wait indefinitely. int wait (Time_Value *timeout = 0) const; // Notify one thread waiting on the // <Thread_Condition>. int notify (void) const; H.M. Design de systèmes temps réel 47
CLASSE Thread Condition // Notify *all* threads waiting on // the <Thread_Condition>. int notify_all (void) const; private: #if defined (_POSIX_PTHREAD_SEMANTICS) pthread_cond_t cond_; #else // Condition variable emulations. #endif /* _POSIX_PTHREAD_SEMANTICS */ ; // Reference to mutex lock. const Thread_Mutex &mutex_; H.M. Design de systèmes temps réel 48
TEST DE LA FILE : INTERFACE Voici le code des méthodes d interface empty() et full() bool Message_Queue::empty(void) const { Thread_Mutex_Guard guard(monitor_lock_); return empty_i(); bool Message_Queue::full(void) const { Thread_Mutex_Guard guard(monitor_lock_); return full_i(); Ces méthodes utilisent l idiome de verrou avec portée ( scoped locking idiom ) Le verrou est automatiquement acquis lorsque l on entre en portée (au travers d un constructeur) et est automatiquement relâché lorsque l on est hors de portée (au travers du destructeur correspondant). H.M. Design de systèmes temps réel 49
TEST DE LA FILE : INTERFACE class Thread_Mutex_Guard { public: // Store a pointer to the lock and acquire the lock. Thread_Mutex_Guard (Thread_Mutex &lock) : lock_ (lock) { result_ = lock_.acquire (); // Release the lock when the guard goes // out of scope. Thread_Mutex_Guard (void) { // Only release the lock if it was acquired. if (result_!= -1) lock_.release (); private: // Reference to the lock were managing. Thread_Mutex &lock_; // Records if the lock was acquired successfully. int result_; ; H.M. Design de systèmes temps réel 50
TEST DE LA FILE : IMPLANTATION Voici le code pour les méthodes d implantation empty i() et full i() implementation methods: bool Message_Queue::empty_i(void) const { return message_count_ <= 0; bool Message_Queue::full_i(void) const { return message_count_ > max_messages_; Ces méthodes supposent que le verrou monitor lock a été acquis. Elles ne font que des tests. H.M. Design de systèmes temps réel 51
TEST DE LA FILE : IMPLANTATION void Message_Queue::put (const Message &msg) { // Use the Scoped Locking idiom to // acquire/release the <monitor_lock_> upon // entry/exit to the synchronized method. Thread_Mutex_Guard guard (monitor_lock_); // Wait while the queue is full. while (full_i ()) { // Release <monitor_lock_> and suspend our // thread waiting for space to become available // in the queue. The <monitor_lock_> is // reacquired automatically when <wait> returns. not_full_.wait (); // Enqueue the <Message> at the tail of // the queue and update <message_count_>. put_i (msg); // Notify any thread waiting in <get> that // the queue has at least one <Message>. not_empty_.notify (); // Destructor of <guard> releases <monitor_lock_>. H.M. Design de systèmes temps réel 52
TEST DE LA FILE : IMPLANTATION Le client appelant put() est bloqué jusqu à ce que la file ne soit plus pleine (il est bloqé à not full.wait()). Puis, il transmet à put i() le message qui doit être inséré dans la file. La méthode put i() n a pas besoin d être synchronisée, puisqu elle n est appelée que lorsque le verrou est acquis. Elle n a pas besoin de vérifier non plus si la file est pleine, puisqu elle n est appelée que lorsque full i() renvoie false. De manière similaire à ce qui précède, get() effectue les operations duales H.M. Design de systèmes temps réel 53
TEST DE LA FILE : IMPLANTATION Message Message_Queue::get (void) { // Use the Scoped Locking idiom to acquire/release the // <monitor_lock_> upon entry/exit to the synchronized method. Thread_Mutex_Guard guard (monitor_lock_); // Wait while the queue is empty. while (empty_i ()) { // Release <monitor_lock_> and wait for a new <Message> // to be placed in the queue. The <monitor_lock_> is reacquired // automatically when <wait> returns. not_empty_.wait (); // Dequeue the first <Message> in the queue // and update the <message_count_>. Message m = get_i (); // Notify any thread waiting in <put> that the // queue has room for at least one <Message>. not_full_.notify (); return m; // Destructor of <guard> releases <monitor_lock_>. H.M. Design de systèmes temps réel 54
IMPLANTATION COMPLÈTE L implantation complète est la suivante, sans les commentaires class Message_Queue { public: enum { MAX_MESSAGES = /*... */; ; Message_Queue (size_t max_messages = MAX_MESSAGES); void put (const Message &msg); Message get (void); bool empty (void) const; bool full (void) const; private: private: void put_i (const Message &msg); Message get_i (void); bool empty_i (void) const; bool full_i (void) const; size_t message_count_; size_t max_messages_; mutable Thread_Mutex monitor_lock_; Thread_Condition not_empty_; Thread_Condition not_full_; H.M. Design de systèmes temps réel 55
IMPLANTATION COMPLÈTE class Thread_Condition { public: Thread_Condition (const Thread_Mutex &m) Thread_Condition (void); int wait (Time_Value *timeout = 0) const; int notify (void) const; int notify_all (void) const; private: pthread_cond_t cond_; const Thread_Mutex &mutex_; ; H.M. Design de systèmes temps réel 56
IMPLANTATION COMPLÈTE bool Message_Queue::empty(void) const { Thread_Mutex_Guard guard(monitor_lock_); return empty_i(); bool Message_Queue::full(void) const { Thread_Mutex_Guard guard(monitor_lock_); return full_i(); H.M. Design de systèmes temps réel 57
IMPLANTATION COMPLÈTE bool Message_Queue::empty_i(void) const { return message_count_ <= 0; bool Message_Queue::full_i(void) const { return message_count_ > max_messages_; H.M. Design de systèmes temps réel 58
IMPLANTATION COMPLÈTE void Message_Queue::put (const Message &msg) { Thread_Mutex_Guard guard (monitor_lock_); while (full_i ()) { not_full_.wait (); put_i (new_item); not_empty_.notify (); Message Message_Queue::get (void) { Thread_Mutex_Guard guard (monitor_lock_); while (empty_i ()) { not_empty_.wait (); Message m = get_i (); not_full_.notify (); return m; H.M. Design de systèmes temps réel 59