Travailler avec des processus Mémoire partagée, communication et synchronisation
Rappels On peut faire plusieurs processus à partir d’un père Clonage : fork() Faire attention aux variables : Toutes celles déclarées avant le fork sont visibles aux 2 Toutes les modifications après sont indépendantes
Rappels Utilisation courante : fork()+exec() On fait deux programmes différents On les exécute en parallèle Ex : le prog principal est un serveur, le sous prog se charge de la communication avec un client…
Rappels sur la communication Une fois la séparation des processus, on communique avec une mémoire partagée. Lors de la création du fils (avec exec), il est possible de lui passer des paramètres (rappelez vous du tableau de fou…), comme l’id de la mémoire partagée… Possibilités : Utilisation mémoire partagée Création mémoire partagée Utilisation mémoire partagée
Rappels fin Donc pour utiliser une mémoire partagée, il faut la créer int idShm = shmget (key, sizeof(Float), IPC_CREAT|0666) ; On peut ensuite l’utiliser : float * maVar = (float *) shmat (idShm, NULL, 0) ; Après il est possible de mettre des choses dans la variable Et il est bien sûr possible de lire le contenu…
Notion de ressources Un système d’exploitation gère l’accès à des ressources. Il existe différents types de ressources : Non critiques : peuvent être utilisées conjointement par un nombre quelconque de processus (uniquement en lecture). Critiques : ne peuvent être utilisées que par un nombre limité de processus à un instant donné. Critiques exclusives : ne peuvent être utilisées que par un seul processus à un instant donné.
Ressource critique Une ressource est qualifiée de critique exclusive si : "il est physiquement impossible que plusieurs processus l’utilisent simultanément (p.ex. un marteau). "il est dangereux (risque d’incohérence) que plusieurs processus l’utilisent simultanément (p. ex. un cahier).
Exemple de problèmes Expédition de messages complets à une console Expédition d’une suite de lignes à une imprimante Utilisation d’un dérouleur de bandes magnétiques Utilisation d’un fichier en écriture Utilisation d’une table en mémoire principale, en modification Utilisation conjointe, par plusieurs processus, d’un traceur de courbes réservation de places dans les trains ou les avions, par plusieurs agences de voyage simultanément
Notion de région critique Région critique ou section critique Une section critique d’un processus correspond à une partie de son code pendant l’exécution duquel il doit se voir attribuer de façon exclusive une ressource critique exclusive spécifique. Il faut donc trouver une manière de protéger une portion du code : celle qui s’occupe de la ressource critique.
Notre section critique Par soucis de simplicité, on utilisera une ressource critique simple : un tableau. Mais tout ce qu’on verra sera applicable sur d’autres ressources (imprimante, écran…)
Le problème Il faut avoir un mécanisme qui empêche d’avoir plus de X processus qui utilisent la ressource
Solution 1 Utiliser une variable booléenne (libre/occupé) : Tant que marqueur != libre; FinTantQue marqueur = occupé; RC; marqueur = libre; Mais… Si deux processus testent à la suite la condition du tant que, on aura deux processus qui utilisent la ressource critique
Solution 2 Tant que tour != monNum; FinTantQue RC; Utiliser une variable entière : Tant que tour != monNum; FinTantQue RC; tour = 3-monNum;//passe au suivant pour deux processus (3-2=1;3-1=2) Mais… Si un processus plante en dehors de la section critique, plus personne n’y rentrera…
Solution 3 Mix des deux solutions : Utiliser deux variables : Un tableau de booléen du nombre de processus Un identifiant (un numéro) oùça[moi]:=dedans; Tantque oùça[3-moi]!=dehors; finTantQue RC; oùça[moi]:=dehors;
Solution 3 Mais… oùça[moi]:=dedans; Tantque oùça[3-moi]!=dehors; finTantQue RC; oùça[moi]:=dehors; Mais… On peut bloquer indéfiniment sur le tantque…
Solution 4,5,6… Je ne veux pas vous embrouiller plus, mais pour info, y’a eu : Algorithme de Dekker Algorithme de Peterson Solution avec XW Solution avec TST … Enfin, il y a eu Dijkstra qui simplifia (Hum…) le problème
Les sémaphores
P(S) function P(semaphore sem) { disable_interrupt; sem.K = sem.K-1; if (sem.K < 0) { L.suivant = processus_courant; processus_courant.state= bloque; reordonnancement = vrai; } enable_interrupt;
V(S) function V(semaphore sem) { disable_interrupt; sem.K=sem.K+1; if (sem.K <= 0) { processus_reveille= L.tete; processus_reveille.state = prêt; reordonnancement = vrai; } enable_interrupt;
L’utilisation des sémaphores var mutex : sémaphore; process p(moi:pNum); begin while true do P(mutex) R.C. V(mutex); end; end {de p}
Les sémaphores Initialisation du sémaphore à 1 Exclusion mutuelle Initialisation du sémaphore à 1 Initialisation du sémaphore avec un nombre égal au nombre max de processus autorisés Exclusion générale
Les sémaphores Bon, ça c’est pas trop difficile… P(mutex) R.C. Toujours la même chose : P(mutex) R.C. V(mutex); Mais d’autres problèmes plus complexes… Synchroniser deux processus Communiquer entre deux processus
On complique un peu On veux que la solution soit compatible avec la précédente solution, i.e. utiliser les sémaphores Le problème du rendez-vous simple: Pb ne peut passer le point de synchronisation Sb que quand Pa aura auparavant passé le point de synchronisation Sa. "Cas 1 : Pb arrive en Sb et Pa n’est pas encore passé en Sa : il attend. "Cas 2 : Pb arrive en Sb alors que Pa est déjà passé en Sa : il continue.
Remarques Dans certain ouvrage, on trouve P() est parfois bloquant P() Wait() V() Signal() P() est parfois bloquant V() n’est jamais bloquant
Solution process Pa; begin repeat Avant; V(sem); Après; until false end; { de Pa } process Pb; begin repeat Avant; P(sem); Après; until false end; { de Pb }
Problème suite Un grand classique : Producteur/Consommateurs
Producteur Consommateur Définition du problème : Un producteur ne peut produire que quand il reste de la place Un consommateur doit attendre qu’une valeur soit produite Un consommateur consomme tant qu’il y a des valeurs… D’où des problèmes : Empêcher les consommateur de lire dans le tampon quand il n’y a rien dedans Empêcher les producteur de produire quand il n’y a plus de place
Solution 3 choses a définir : Nombre de sémaphores et leurs initialisations Processus Producteur Processus Consommateur
Producteur Consommateur Initialisation des sémaphores : Const N = 8; {nb cases} Var inDex, exDex : Case init 0; mutexProd, mutexCons : semaphore init 1; nbPlein : semaphore init 0; nbVide : semaphore init N; T : array [Case] of Portion;
Producteur process Producteur; begin repeat Produire; P(nVide); P(mutexProd); {insérer dans le tampon}; V(mutexProd); V(nPlein); until false end; { de Producteur }
Consommateur process Consommateur; begin repeat P(nPlein); P(mutexCons); {extraire du tampon}; V(mutexCons); V(nVide); Consommer; until false end; { de Consommateur }
Producteur/Consommateur process Producteur; begin repeat Produire; P(nVide); P(mutexProd); {insérer}; V(mutexProd); V(nPlein); until false end; { de Producteur } process Consommateur; begin repeat P(nPlein); P(mutexCons); {extraire}; V(mutexCons); V(nVide); Consommer; until false end; { de Consommateur }
En C++ Il faut utiliser la librairie zthread