Rappel sur la synchronisation des processus Chapitre 02 Rappel sur la synchronisation des processus
Synchronisation des processus Introduction Sections critiques et exclusion mutuelle Exclusion mutuelle par attente active Le masquage des interruptions Les variables de verrouillage L’alternance stricte La solution de Peterson Exclusion mutuelle sans attente active Les primitives sleep et wakeup Les sémaphores Exercices Les moniteurs
Introduction Sur une plateforme multiprogrammée les processus ont généralement besoin de communiquer pour compléter leurs tâches. L’exécution d’un processus peut être affecter par l’exécution des autres processus ou il peut affecter lui-même leurs exécutions. La communication interprocessus est assurée généralement via des données partagées qui peuvent se trouver dans la mémoire principale ou dans un fichier. Les accès concurrents (simultanés) à des données partagées peuvent conduire à des incohérences dans les résultats obtenus.
Introduction: exemple illustratif Exemple : la spoule d’impression Démon d’impression Processus A Processus B … 3 2 1 0 f1 f2 … f0 Répertoire de spoule Variable partagée in=3 Schéma d’exécution: A : lire in, next_free_slot = 3 Interruption: la CPU bascule vers le processus B B : lire in, next_free_slot = 3, entrée3 = fichierB, in = 4 A : entrée3 = fichierA, in = 4 Problème: le fichierB ne sera jamais imprimé
Sections critiques et exclusion mutuelle Le problème précédent est dû aux conflits d’accès à la même ressource. La partie du programme à partir de laquelle on accède à la ressource partagée est appelée section (région) critique. Solution: L’exclusion mutuelle est une méthode qui assure qu’un seul processus est autorisé d’accéder à une ressource partagée; les autres processus seront exclus de la même activité. A B t1 t2 t3 t4 A entre dans sa section critique B tente d’entrer dans sa section critique A quitte sa section critique B entre dans sa section critique B quitte sa section critique
Sections critiques et exclusion mutuelle Quatre conditions doivent être vérifier pour assurer l’exclusion mutuelle: Exclusion Mutuelle: Deux processus ne doivent pas se trouver simultanément dans leurs sections critiques. Progression : Aucun processus à l’extérieur de sa section critique ne doit bloquer les autres processus. Attente bornée : Aucun processus ne doit attendre indéfiniment pour entrer dans sa section critique. Aucune hypothèse : Il ne faut pas faire d’hypothèse quant à la vitesse ou le nombre de processeurs
Synchronisation des processus Introduction Sections critiques et exclusion mutuelle Exclusion mutuelle par attente active Le masquage des interruptions Les variables de verrouillage L’alternance stricte La solution de Peterson Exclusion mutuelle sans attente active Les primitives sleep et wakeup Les sémaphores Exercices Les moniteurs
Exclusion mutuelle par attente active Un processus désirant entrer dans une section critique doit être mis en attente jusqu’a ce que la section critique devient libre. Un processus quittant la section critique doit le signaler aux autres processus. Algorithme d’accès à une section critique : Entrer_Section_Critique () /* attente si SC non libre */ Section_Critique() /* un seul processus en SC */ Quitter_Section_Critique() L’attente peut être : Active : la procédure Entrer_Section_Critique est une boucle dont la condition est un test qui porte sur des variables indiquant la présence ou non d’un processus en Section critique. Non active : le processus passe dans l’état endormi et ne sera réveillé que lorsqu’il sera autorisé à entrer en section critique.
Solutions de l’exclusion mutuelle par attente active Solution 1: Masquage des interruptions Lorsqu’un processus entre en section critique il doit masquer les interruptions. Pas de commutation de processus Lorsqu’ il quitte sa section critique il doit restaurer les interruptions. C’est une solution matérielle qui permet de résoudre complètement le problème. Mais elle est dangereuse en mode utilisateur s’il oublie de restaurer les interruptions.
Solutions de l’exclusion mutuelle par attente active Solution 2: Variables de verrouillage Un verrou est variable binaire partagée qui indique la présence d’un processus en section critique. si verrou=0 alors section critique libre si verrou=1 alors section critique occupée void entrer_Section_Critique () { while (verrou == 1) ; /* attente active */ verrou=1 ; } Void quitter_Section_Critique () { verrou=0 ; } Cette solution ne garantie pas l’exclusion mutuelle car le verrou est une variable partagée qui peut constituer aussi une section critique.
Solutions de l’exclusion mutuelle par attente active Solution 3: Alternance stricte Tour est une variable partagée qui indique le numéro de processus autorisé a entrer en section critique. void entrer_Section_Critique (int process) { while (Tour!=process) ; /* attente active */ } Void quitter_Section_Critique () { Tour = (Tour+1) %N ; } L’alternance stricte est une solution simple et facile a implémenter. Mais, un processus qui possède Tour peut ne pas être intéressé immédiatement par la section critique et en même temps il bloque un autre processus qui demandeur. Problème de progression
Solutions de l’exclusion mutuelle par attente active Solution 4: Solution de Peterson #define FAUX 0 #define VRAI 1 #define N 2 int tour ; /* à qui le tour */ int interesse[N] ; /* initialisé à FAUX */ void entrer_Section_Critique (int process) { int autre ; (1) autre = 1-process ; (2) interesse[process]=VRAI; /* process est intéressé */ (3) tour = process ; /* demander le tour */ while (tour == process && interesse[autre] == VRAI) ; } (A) (B) Void quitter_Section_Critique () { (4) interesse[process]=FAUX ; } Cette solution assure complètement l’exclusion mutuelle. Mais, le processus qui attend sa section critique consomme du temps processeur inutilement (attente active).
Synchronisation des processus Introduction Sections critiques et exclusion mutuelle Exclusion mutuelle par attente active Le masquage des interruptions Les variables de verrouillage L’alternance stricte La solution de Peterson Exclusion mutuelle sans attente active Les primitives sleep et wakeup Les sémaphores Exercices Les moniteurs
Exclusion mutuelle sans attente active L’idée est qu’un processus qui ne peut pas entrer en section critique passe à l’état bloqué au lieu de consommer le temps processeur inutilement. Il sera réveillé lorsqu’il pourra y entrer. Les primitives Sleep et Wakeup: Le système d’exploitation offre deux appels système: 1. Sleep (dormir) qui bloque le processus appelant. 2. Wakeup (réveiller) qui réveille le processus donné en argument.
Exclusion mutuelle sans attente active Application des primitives Sleep et Wakeup au modèle Producteur Consommateur: Producteur f1 f2 … f0 Tampon Variable partagée compteur=3 Consommateur Deux processus (le producteur et le consommateur) coopèrent en partageant un même tampon: Le producteur produit des objets qu’il dépose dans le tampon. Le consommateur retire des objets du tampon pour les consommer.
Exclusion mutuelle sans attente active #define N 100 /* taille du tampon */ int compteur = 0 ; /* objets dans tampon */ void producteur () { while (TRUE) { produire_objet() ; if (compteur == N) sleep () ; mettre_objet() ; compteur = compteur + 1 ; if (compteur == 1) wakeup(consommateur) ; } void consommateur () { while (TRUE) { if (compteur == 0) sleep() ; retirer_objet() compteur = compteur – 1 ; if (compteur == N-1) wakeup (producteur) ; consommer_objet(…) ; }
Exclusion mutuelle sans attente active Analyse de cette solution : L’accès à la variable compteur n’est pas protégé, ce qui peut entraîner des incohérences dans les valeurs prises par cette Variable. Réveils perdus : c’est le principal défaut de ce mécanisme. Un signal wakeup envoyé à un processus qui ne dort pas (encore)est perdu.
Les sémaphores Pour remédier au problème es réveils en attente (les wakeup perdus), l’idée est d’employé une variable entière appelée: Sémaphore à laquelle est associée une file d’attente des processus bloqués. sémaphore=0 aucun réveil n’est mémorisé sémaphore>0 un ou plusieurs réveils sont en attente Un sémaphore s est manipulé par les opérations : down(s) : - décrémente la valeur de s si s>0, - si s=0, alors le processus est mis en attente. up(s) : - incrémente la valeur de s, - si un ou plusieurs processus sont en attente sur ce sémaphore, l'un d'entre eux est réveillé,
Les sémaphores Pour assurer l’exclusion mutuelle un sémaphore peut être programmé de la manière suivante : Nom du sémaphore initialisation mutex = 1 /* nombre de processus autorisés à entrer simultanément dans la section critique */ down (mutex) <section_critique> up (mutex)
Les sémaphores Application au modèle Producteur / Consommateur : Trois sémaphores sont nécessaires: plein: compte le nombre de places occupées vide : compte le nombre de places libres Mutex : assure que le producteur et le consommateur n'accèdent jamais en même moment à la mémoire tampon.
Rappel Producteur/Consommateur avec sémaphores #define N 100 // taille du tampon semaphore mutex 1 ; // contrôle d’accès section critique semaphore vide N; // contrôle les emplacements vide Semaphore plein 0; // contrôle les emplacements plein void producteur () { while (TRUE){ produire_objet() ; down(vide); down(mutex); mettre_objet() ; //SC up(mutex); up(plein) } void consommateur () { while (TRUE){ down(plein); down(mutex); retirer_objet() //SC up(mutex); up(vide); consommer_objet(…) ; }
Synchronisation des processus - Exercices Ordonnancement & Synchronisation Pb des Lecteurs/rédacteur Pb du coiffeur endormi Autres utilisations des sémaphores & problèmes classiques
Exercice 1 Q1 : Round robin ( tourniquet) avec q=5ms Q2 : Round robin ( tourniquet) avec q=5ms + Synchronisation Peterson Prêt à l’instant t= Durée d’exécution P0 0 ms 23 ms P1 1 ms 17 ms P2 2 ms 15 ms Durée d’exécution SR+SC Date d’entrée en section critique t= P0 23 ms 3 ms P1 17 ms 7 ms P2 15 ms X Prêt à l’instant t= Durée d’exécution en section critique P0 0 ms 12 ms P1 1 ms 10 ms P2 2 ms X
Q1 Round robin ( tourniquet) avec q=5ms Prêt à l’instant t= Durée d’exécution P0 0 ms 23 ms P1 1 ms 17 ms P2 2 ms 15 ms P0 P1 P2 10 15 20 25 30 35 40 Bien vérifier que à t=52 il ya changement de contexte (avant fin quantum) Bareme 1.5 : 0.5 pt ordre processus, 1pt fini a 55 5 40 45 50 52 55
Date d’entrée en section critique t= Round robin ( tourniquet) avec q=5ms Peterson Attente active SC AA Prêt à l’instant t= Durée d’exécution Date d’entrée en section critique t= Durée d’exécution en section critique P0 0 ms 23 ms 3 ms 12 ms P1 1 ms 17 ms 7 ms 10 ms P2 2 ms 15 ms X P0 P1 P2 5 10 15 20 25 30 35 40 Vérifier que t=5 ms il y a changement de contexte Vérifier que le temps d’ordonancement total > 55 (il y a eu attente active) Bareme 2.5 : 1 pt P1 commence a t=5ms, 1 pt P1 fait attente active(ie fin a t>55) , 0.5 pt ordre processus 40 45 50 55 58
Date d’entrée en section critique t= Round robin ( tourniquet) avec q=5ms Sommeil et activation SC Prêt à l’instant t= Durée d’exécution Date d’entrée en section critique t= Durée d’exécution en section critique P0 0 ms 23 ms 3 ms 12 ms P1 1 ms 17 ms 7 ms 10 ms P2 2 ms 15 ms X P0 P1 P2 Vérifier que t=5 ms il y a changement de contexte Vérifier que le temps d’ordonancement total =55 (synchro par sommeil et activation) Vérifier que à t=22 ms il ya changement de contexte Bareme 2.5 : 1 pt P1 commence a t=5ms, 1 pt P1 s’endors (P2 commence a t=22, fin a t=55) , 0.5 pt ordre processus 5 10 15 20 22 27 32 37 40 40 42 47 52 55
Lecteurs/Rédacteur Une base de données peut être accessible par un seul rédacteur ou N lecteurs en même temps Exclusion mutuelle entre le rédacteur et les N lecteurs Donner le pseudo code d’un processus lecteur et celui du processus rédacteur Base de données
Lecteurs/Rédacteur Semaphore mutex 1 // contrôle l’accés à nb_lect Semaphore db 1 // contrôle l’accés à la base de données int nb_lect = 0 ; // var partagées entre lecteurs pour // compter le nombre de lecteurs accédant // actuellement à la BD //lecteur void lecture(){ while (true) { //boucle sans fin lire_la_BD(); //accés à la BD utiliser_données (); //Section restante } //redacteur void ecriture(){ while (true) { créer_données (); ecrire_dans_la_BD }
Lecteurs/Rédacteur Semaphore mutex 1 // contrôle l’accés à nb_lect Semaphore db 1 // contrôle l’accés à la base de données int nb_lect = 0 ; // var partagées entre lecteurs pour // compter le nombre de lecteurs accédant // actuellement à la BD //lecteur void lecture(){ while (true) { //boucle sans fin down (mutex); // la modif de la var. partagée nb_lect est une nb_lect ++; // section critique entre lecteurs if (nb_lect == 1) down (db); //si le premier lecteur up(mutex); // libère l’accés exclusif à nb_lect lire_la_BD(); //accés à la BD down(mutex); nb_lect --; if (nb_lect == 0) up (db); //si le dernier lecteur up (mutex) utiliser_données (); //Section restante }
Lecteurs/Rédacteur Semaphore mutex 1 // contrôle l’accés à nb_lect Semaphore db 1 // contrôle l’accés à la base de données int nb_lect = 0 ; // var partagées entre lecteurs pour // compter le nombre de lecteurs accédant // actuellement à la BD //redacteur void ecriture(){ while (true) { //boucle sans fin créer_données (); //Section restante down (db); ecrire_dans_la_BD(); //accés à la BD up (db); }
Coiffeur endormi Un coiffeur possède un salon avec un siège de coiffeur et une salle d’attente comportant un nombre fixe F de fauteuils. S’il n’y a pas de client, le coiffeur se repose sur son siège de coiffeur. Si un client arrive et trouve le coiffeur endormi, il le réveille, s’assoit sur le siège de coiffeur et attend la fin de sa coupe de cheveux. Si le coiffeur est occupé lorsqu’un client arrive, le client s’assoit et s’endort sur une des chaises de la salle d’attente ; si la salle d’attente est pleine, le client repasse plus tard. Lorsque le coiffeur a terminé une coupe de cheveux, il fait sortir son client courant et va réveiller un des clients de la salle d’attente. Si la salle d’attente est vide, il se rendort sur son siège jusqu’à ce qu’un nouveau client arrive.
Coiffeur endormi Résoudre le pb en évitant les conditions de concurrence Utilisation de 3 sémaphores et un compteur Semaphore Clients 0; //bloque le coiffeur s’il n’y a pas de clients Semaphore Mutex 1; //accés exclusif à la zone critique Semaphore Coiffeurs 0; //bloque le client si le coiffeur est occupé avec un //autre client Int Attente = 0 ; //Le nombre de clients en attente
Coiffeur endormi Semaphore Clients 0; Semaphore Mutex 1; Semaphore Coiffeurs 0; Int Attente = 0 ; //Coiffeur void coiffeur() { while(1){ Attente = attente – 1; Couper_cheveux(); } //Client void client() { if ( ) { Attente = attente + 1 Obtenir_coupe } else {
Coiffeur endormi Semaphore Clients 0; Semaphore Mutex 1; Semaphore Coiffeurs 0; Int Attente = 0 ; //Coiffeur void coiffeur() { while(1){ down(Clients); down(Mutex); Attente = attente – 1; up(Coiffeurs); up(Mutex); Couper_cheveux(); } //Client void client() { down(Mutex) if (Attente < Chaises) { Attente = attente + 1 up(Clients) up(Mutex) down(Coiffeurs) Obtenir_coupe } else {
Réfléchissons ! Soient les deux processus P1 et P2 suivants. Ils se partagent deux sémaphores S1 et S2 initialisés à 0. Quelle synchronisation a-t-on imposée sur les exécutions des procédures A1, A2, B1 et B2 (càd, quel ordre d’exécution) ? Semaphore S1 0 Semaphore S2 0 P1 { procedure A1 ; Up(S2) ; Down(S1) ; procedure B1 ; } P2 { procedure A2 ; Up(S1) ; Down(S2) ; procedure B2 ; }
Barrière de synchronisation Les Ai s’exécutent toujours avant les Bi, quelque soit l’ordre de l’exécution des processus P1 et P2 Ex: Ecrire le code à fin d’imposer la même synchronisation pour N processus en utilisant : N sémaphores 2sémaphores et un compteur Semaphore S1 0 Semaphore S2 0 P1 { procedure A1 ; Up(S2) ; Down(S1) ; procedure B1 ; } P2 { procedure A2 ; Up(S1) ; Down(S2) ; procedure B2 ; }