SYSTÈMES D EXPLOITATION 1 ère année M. FONDA ENSI de Bourges 1
Cas 1 : Une tâche A est en attente de l information provenant d une tâche B Cas 2 : A positionne une valeur en vue de son exploitation ultérieure, mais B la modifie avant que A n ait pu s en servir. Et A ignore que la valeur n est plus la sienne 2
2 processus veulent l accès à une mémoire partagée en même temps 3
En informatique (programmation concurrente), une section critique (SC) = une portion de code dans laquelle il doit être garanti qu'il n'y aura jamais plus d'une tâche (processus, thread) simultanément. Il est nécessaire d'utiliser des sections critiques lorsqu'il y a accès à des ressources partagées par plusieurs tâches, lorsque ces ressources sont modifiées durant les exécutions. Une section critique peut être protégée par un mutex, un sémaphore ou d'autres primitives de programmation concurrente. 4
Exclusion mutuelle sur SC 5
Il existe de nombreuses façons d implémenter les protections de sections critiques Mutex simples Utilisés avec attente active (Dekker, Peterson, ) Sémaphores (mutex avec compteurs) Variables conditionnelles Aucune solution/algorithme n est parfait pour tous les cas (deadlock) 6
7
Sémaphore : mécanisme permettant le définition d un bloc d instructions atomique Structure sémaphore Compteur : nombre d'accès disponibles avant blocage File d attente : processus bloqués en attente d'un accès Opération P «P - proberen ou puis-je?» (test compteur si >0 alors décrémentation et utilisation de la ressource sinon attend) Opération V «V - verhogen ou vas-y» (incrémente le compteur et libère la ressource) Remarque : un mutex est en fait un sémaphore binaire (0 ou 1). 8
Ne pas confondre les sémaphores IPC systemv (préfixe des fonctions sem) avec ceux de la norme POSIX.1b (préfixe des fonctions sem_) Ils servent à limiter la portion critique de code. int sem_init (sem_t * semaphore, int partage, unsigned int valeur); premier argument : le sémaphore deuxième argument : indique si le sémaphore est partagé au delà de l application troisième argument : valeur initiale du compteur du sémaphore. La portion de code critique ne peut être atteinte que si le compteur est supérieur à zéro la fonction sem_wait() permet l attente en utilisant un compteur strictement positif puis décrémente ce compteur en entrant. La fonction sem_post() permet d incrémenter le compteur en sortant de la portion critique. int sem_getvalue(sem_t * semaphore, int *valeur); permet de consulter le compteur 9
Problème : une base de données a des lecteurs et des rédacteurs, qu'il faut programmer : plusieurs lecteurs doivent pouvoir lire la base de données en même temps ; si un rédacteur est en train de modifier la base de données, aucun autre utilisateur (ni rédacteur, ni même lecteur) ne doit pouvoir y accéder. Solution 1 : Il est assez simple de faire en sorte que le rédacteur soit mis en attente tant qu'il y a encore des lecteurs. Mais cette solution présente de gros problèmes, si le flux de lecteurs est régulier : le rédacteur pourrait avoir à patienter un temps infini. Solution 2 (Dijkstra) utilisation des sémaphores et priorité aux lecteurs : mettre en attente tous les lecteurs ayant adressé leur demande d'accès après celle d'un rédacteur. nécessite trois sémaphores et une variable : sémaphore M_Lect, initialisé à 1 : protége Lect (mutex) sémaphore M_Red, initialisé à 1 : bloque les tâches de rédaction. (mutex aussi) sémaphore Red, initialisé à 1 : bloque les tâches de rédaction. variable Lect : compte le nombre de lecteurs. 10
Commencer une lecture Commencer_Lire : P(M_Lect) Lect++ SI Lect==1 ALORS P(Red) FIN SI V(M_Lect) Finir une lecture Finir_Lire : P(M_Lect) Lect-- SI Lect==0 ALORS V(Red) FIN SI V(M_lect) Commencer une écriture Commencer_Ecrire P(M_Red) P(Red) Finir une écriture Finir_Ecrire V(Red) V(M_Red) 11
int semget (key_t key, int nsems, int semflg); idem à msgget mais pour un tableau de sémaphores nsems : nombre de sémaphores int semop (int semid, struct sembuf *sops, unsigned nsops); nsops nombre d opérations à effectuer sembuf structure contenant un ensemble de sémaphores Opérations à effectuer de manière atomique 12
int semctl (int semid, int semnun, int cmd, union semun arg); Selon cmd, semnun représente soit : le nombre le numéro du sémaphore 13
Exemple simple d interblocage entre deux threads T1 et T2 accédant à deux ressources R1 et R2 T1 : déjà acquis R1 demande l'accès à R2. T2 : déjà acquis R2, demande accès à R1. Chacun des deux threads attend alors la libération de la ressource possédée par l'autre. La situation est donc bloquée 14
B. Inter Thread Communication 15
Les threads partageant des ressources communes il faut donc synchroniser les accès. Le mécanisme utilisé est appelé MUTEX (mutuelle exclusion) de type pthread_mutex pouvant prendre deux états : disponible ou verrouillé. L initialisation du mutex peut se faire de manière statique ou dynamique pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER int pthread_mutex_init (pthread_mutex_t *mutex, const pthread_mutexattr_t *attributs); EXEMPLE : pthread_mutex_t mutex; pthread_mutexattr_t mutexattr; initialisation de mutex attribut; if ((mutex =malloc (sizeof (pthread_mutex_t)) == null) return (-1); pthread_mutex_init (&mutex, &mutexattr); La destruction du mutex se fait avec : pthread_mutex_destroy 16
La fonction de verrouillage est : int pthread_mutex_lock ( pthread_mutex_t * mutex); si le mutex est libre, il se verrouille alors et un appel à cette fonction devient bloquant pour un autre thread. Attention si le thread ayant verrouillé un mutex recommence l appel alors le système est bloqué. Fonction de déverrouillage : int pthread_mutex_unlock(pthread_mutex_t * mutex); 17
Mise dans une pile la fonction de libération de ressources. void pthread_cleanup_push (void (* fonction) void *argument), void *argument); premier argument, adresse de la routine à exécuter, deuxième argument élément passé à cette routine. Mise en route de cette fonction de libération de la ressource void pthread_cleanup_pop (int execution_routine); si execution_routine est nul alors la fonction est retirée de la pile sans exécution sinon la fonction est retirée de la pile avec exécution. 18
Exemple FILE * fp; fp= fopen («monfichier», «r»); pthread_cleanup_push (fclose,fp); char * buffer; buffer=malloc(bufsize); pthread_cleanup_push (free,buffer);.. Pthread_cleanup_pop (1); /* free(buffer); */ Pthread_cleanup_pop (1); /* close(fp); */ 19
Autre technique de synchronisation, les variables conditions. Si un thread doit attendre le déblocage d un mutex ou l arrivée d un événement venant d un autre thread, alors on emploie des variables condition. Initialisation d une variable condition : pthread_cond_t condition = PTHREAD_COND_INITIALIZER. libération d une variable condition int pthread_cond_destroy (pthread_cond_t * condition); 20
Moniteurs Posix. Un moniteur Posix est l'association : D un mutex (type pthread_mutex_t) qui sert à protéger la partie de code où l on teste les conditions de progression D une variable condition ( type pthread_cond_t ) qui sert de point de signalisation On se met en attente sur cette variable par la primitive pthread_cond_wait(&lavariablecondition,&lemutex); on est réveillé sur cette variable avec la primitive : pthread_cond_signal(&lavariablecondition);(pthread_c ond_t * condition); 21
Déroulement des étapes de mise en place de la variable condition: thread qui attend la condition : initialise la variable condition et le mutex bloque le mutex appelle la fonction pthread_cond_wait() débloque le mutex attente de la condition arrivée de la condition, bloque le mutex, revient au thread. Libère le mutex thread réalisant la condition réalise la condition bloque le mutex lié à la condition appel de pthread_cond_signal() pour informer que la condition est remplie débloque le mutex 22
Schéma d utilisation Soit la condition de progression C, le schéma d utilisation des moniteurs Posix est le suivant : pthread_mutex_lock(&lemutex); évaluer C; While (! C ) { pthread_cond_wait(&lavariablecondition,&lemutex); ré-évaluer C si nécessaire } Faire le travail; pthread_mutex_unlock(&lemutex); 23
Multi-threading sur processeur hyperthreadés : C. Percival (2005) a expliqué qu un thread malveillant, lancé avec des privilèges limités, pourrait en théorie accéder à des informations provenant d un autre thread. Ce genre d attaques, nommées attaques par canaux auxiliaires, permettent d obtenir des informations secrètes comme des clés de chiffrement. 24