Gestion de processus Corrigé TD 1 EFREI I1 2003 - 2004 Systèmes d'Exploitation 2003 - 2004 Corrigé TD 1 Gestion de processus 1- Threads, mutexes et variables de conditions #include <pthread.h> #include <stdio.h> #define NUM_THREADS 3 #define TCOMPTEUR 10 #define COMPTEUR_LIMIT 12 int compteur = 0; int thread_ids[3] = {0,1,2}; pthread_mutex_t compteur_mutex; pthread_cond_t compteur_threshold_cv;
void *inc_compteur(void *idp) { int j,i; double result=0.0; int *my_id = idp; for (i=0; i < TCOMPTEUR; i++) { pthread_mutex_lock(&compteur_mutex); compteur++; /* Vérifier la valeur du compteur et la thread en attente de signal quand la condition est réalisée. A noter que cela se produit pendant que le mutex est verrouillé. */ if (compteur == COMPTEUR_LIMIT) { pthread_cond_signal(&compteur_threshold_cv); printf("inc_compteur(): thread %d, compteur = %d Threshold reached.\n", *my_id, compteur); } printf("inc_compteur(): thread %d, compteur = %d, unlocking mutex\n", pthread_mutex_unlock(&compteur_mutex); /* faire un travail pour que les threads puissent alterner sur le verrouillage du mutex */ for (j=0; j < 1000; j++) result = result + (double)random(); pthread_exit(NULL);
void *watch_compteur(void *idp) { int *my_id = idp; printf("Starting watch_compteur(): thread %d\n", *my_id); /* Verrouiller mutex et attendre signal. A noter que la routine pthread_cond_wait() déverrouille mutex d'une manière automatique et atomique quand elle est en attente. A noter aussi, que si COMPTEUR_LIMIT est atteint avant que cette routine ne soit exécutée par la thread en attente, la boucle ne sera pas exécutée pour empêcher pthread_cond_wait() de ne jamais retourner. */ pthread_mutex_lock(&compteur_mutex); while (compteur < COMPTEUR_LIMIT) { pthread_cond_wait(&compteur_threshold_cv, &compteur_mutex); printf("watch_compteur(): thread %d Condition signal received.\n", *my_id); } pthread_mutex_unlock(&compteur_mutex); pthread_exit(NULL); void main() int i, rc; pthread_t threads[3]; pthread_attr_t attr;
/* Initialiser mutex et les variables de conditions */ pthread_mutex_init(&compteur_mutex, NULL); pthread_cond_init (&compteur_threshold_cv, NULL); /* Pour des raisons de portabilité, il faut explicitement créer les threads dans l'état " undetached" pour pouvoir les joindre (join) plus tard. */ pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_UNDETACHED); pthread_create(&threads[0], &attr, inc_compteur, (void *)&thread_ids[0]); pthread_create(&threads[1], &attr, inc_compteur, (void *)&thread_ids[1]); pthread_create(&threads[2], &attr, watch_compteur, (void *)&thread_ids[2]); /* Attente pour la terminaison de toutes les threads */ for (i = 0; i < NUM_THREADS; i++) { pthread_join(threads[i], NULL); } printf ("Main(): Waited on %d threads. Done.\n", NUM_THREADS); /* Nettoyer et quitter */ pthread_attr_destroy(&attr); pthread_mutex_destroy(&compteur_mutex); pthread_cond_destroy(&compteur_threshold_cv); pthread_exit (NULL);
2 - Ordonnancement a - Diagramme de temps FIFO 1 2 3 4 5 RR SJF 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 FIFO 1 2 3 4 5 RR 1 2 3 4 5 1 3 5 1 5 1 5 1 5 1 1 1 1 1 SJF 2 4 3 5 1 Prio 4 1 3 5 2
b,c - Turnaround time - waiting time FCFS Round Robin SJF Prio Tâche b c b c b c b c 1 10 0 19 9 19 9 11 1 2 11 10 2 1 1 0 19 18 3 13 11 7 5 4 2 13 11 4 14 13 4 3 2 1 1 0 5 19 14 14 9 9 4 18 13 Moy 13.4 9.6 9.2 5.4 7.0 3.2 12.4 8.6 d - Avec SJF on a le temps d ’attente moyen minimal. Cependant, l ’implémentation de SJF est très difficile. En outre, comme tous les algorithmes d ’ordonnancement à priorités, SJF peut mener à un problème de famine.
3- Algorithme de Peterson pour 2 processus boolean drapeau[2]; int tour; drapeau[0] = false; drapeau[1] = false; void P0() { while(true) drapeau[0] = true; tour = 1; while (drapeau[1] && tour == 1) /* ne rien faire */; /* section critique */; drapeau[0] = false; /* section non critique */; } void P1() { while(true) drapeau[1] = true; tour = 0; while (drapeau[0] && tour == 0) /* ne rien faire */; /* section critique */; drapeau[1] = false; /* section non critique */; }
Algorithme de Peterson (suite) Considérons le processus P0 : une fois drapeau[0] = true, P1 ne peut pas entrer dans la section critique. Si P1 est déjà dans la section critique, alors drapeau[1] = true, P0 est bloqué et ne pourra pas entrer en section critique. D'autre part, il n'y a pas de blocage mutuel. Supposons P0 bloqué dans la boucle while. Cela veut dire que drapeau[1]=true et tour=1. P0 peut entrer dans la section critique quand drapeau[1] = false ou tour = 0. Considérons les 3 cas suivants: - P1 ne veut pas entrer dans la section critique. Ceci est impossible car cela impliquerait drapeau[1] = false - P1 est en attente pour entrer en section critique. Ce cas est impossible parce que si tour = 1, P1 peut entrer dans la section critique - P1 utilise la section critique souvent et monopolise l'accès à cette section. cela ne pourra pas se produire car P1 est obligé de donner une chance à P0 en mettant tour = 0 avant chaque essai d'entrer dans sa section critique.
4 - Synchronisation de processus avec des sémaphores Producteur - consommateur a – mutex gère un accès en exclusion mutuelle, sa valeur initiale est donc 1 plein compte les places occupées, sa valeur initiale est donc 0. vide compte les places inoccupées, sa valeur initiale est donc n. b – Processus producteur debut tant que vrai faire produire un element elt ; wait (vide) ; wait (mutex) ; ajouter elt dans le tampon ; signal (mutex) ; signal (plein) ; fin c – Processus consommateur debut tant que vrai faire wait (plein) ; wait (mutex) ; retirer elt du tampon ; signal (mutex) ; signal (vide) ; consommer elt ; fin
5 - Détection d'interblocage (deadlock detection) Détection et reprise : Le système ne cherche pas à empêcher les interblocages. Les ressources demandées sont allouées quand elles sont disponibles. La détection de l ’interblocage(attente circulaire) est faite périodiquement à postériori ou à chaque allocation, ce qui consomme beaucoup de temps processeur. a - Détection avec une seule ressource d’un type donné : Construire le graphe des ressources. Si le graphe contient un ou plusieurs cycles, il y a interblocage. S’il n 'y a pas de cycles, Il n’y a pas d’interblocage. b - Détection avec plusieurs exemplaires d’une même ressource : L’algorithme fait appel à une matrice pour détecter l'interblocage de m processus. Définissons les vecteurs et les matrices suivants : e = (e1, e2, ..., en) : ensemble de ressources disponibles (non allouées à un processus) t = r = Chaque rangée de la matrice r indique les besoins d’un processus, i.e, rij indique les besoins du processus i pour la ressource j. L’algorithme marque les processus qui ne sont pas en interblocage. Initialement, aucun processus n’est marqué. Ensuite, on fait les étapes suivantes : 1- marquer chaque processus qui a une rangée de zéros dans la matrice d’allocation t. 2- initialiser un vecteur temporaire W égal au vecteur des ressources disponibles. 3- trouver un indice i tel qu ’aucun processus i n’est marqué et que rik <= Wk pour 1 <= k <= n. Si aucune rangée n’existe, alors terminer l’algorithme. 4- si une telle rangée existe, marquer le processus i et ajouter la rangée correspondante de la matrice des ressources allouées t à W. Wk = Wk + tik. Aller à l ’étape 3. t11 t12 … t1n t21 t22 … t2n … … … … tm1 tm2 … tmn Matrice des ressources allouées r11 r12 … r1n r21 r22 … r2n … … … … rm1 rm2 … rmn Matrice des requêtes
Vecteur des ressources Vecteur des ressources disponibles Voici un exemple d’illustration de cet algorithme : R1 R2 R 3 R4 R5 R1 R2 R 3 R4 R5 P1 P2 P3 P4 0 1 0 0 1 P1 P2 P3 P4 1 0 1 1 0 0 0 1 0 1 1 1 0 0 0 0 0 0 0 1 0 0 0 1 0 1 0 1 0 1 0 0 0 0 0 Matrice des requêtes Matrice d’allocation R1 R2 R 3 R4 R5 Vecteur des ressources 2 1 1 2 1 R1 R2 R 3 R4 R5 Vecteur des ressources disponibles 0 0 0 0 1 Déroulement de l ’algorithme : 1- marquer P1 car P1 ne possède pas de ressources allouées 2- W = ( 0 0 0 0 1) 3- la requête de P3 est inférieure ou égale à W, alors marquer P3 et faire W = W + ( 0 0 0 1 0 ) = ( 0 0 0 11 ) 4- terminer l’algorithme P1 et P2 ne sont pas marqués, ce qui indique qu’ils sont en interblocage.
On suppose que dans les matrices, le numéro de ligne désigne le processus et le numéro de colonne le type de ressource. int executable (int m, int n, int *dispo, int **requete, int *exec); int interblocage (int m, int n, int *existe, int **tenu, int **requete) { int *dispo = malloc (n*sizeof (int)), *exec = malloc (m*sizeof (int)), i, j, k; /* Calcul des ressources disponibles */ for ( j = 0; j < n; j++) { dispo[j] = existe[j]; for ( i = 0; i < m; i++) dispo[j] -= tenu[i][j]; } /* exec sert à marquer les processus exécutés */ for (i = 0; i < m; i++) exec[i] = 0; for (i = 0; i < m; i++) { j = executable(m, n, dispo, requete, exec); if (j < 0) return 1; /* interblocage */ /* Rendre les ressources allouées à ce processus */ for (k = 0; k < n; k++) dispo[k] += tenu[j][k]; exec[j] = 1; /* tous les processus ont été exécutés, pas d ’interblocage */ return 0;
/* Retourne le numéro du premier processus exécutable (et non exécuté) -1 s ’il n ’en existe pas.*/ int executable (int m, int n, int *dispo, int **requete, int *exec) { int i, j; for (i = 0; i < m; i++) if (!exec[i]) { for (j = 0; j < n; j++) if (requete[i][j] > dispo[j]) break; if (j >= n) return i; } return -1; - La complexité de la fonction executable est comprise entre n (si le premier processus est exécutable) et mn (si c ’est le dernier); donc la complexité de la fonction interblocage est comprise entre mn et m2n.
6- Evitement de l'interblocage (Deadlock Avoidance) a- Cet état est sain avec la séquence {P2,P1,P3,P4} b- Cet état n'est pas sain parce que chaque processus a besoin d'un exemplaire supplémentaire de R1. La requête est refusée et le processus P1 est bloqué.
Exercices supplémentaires 1 - 2 - 3 - 4 - 5 -