Exercices Systèmes d'exploitation et annales
Une illustration classique du problème de la synchronisation est celui
du salon de coiffure.
Dans le salon de coiffure, il y a un coiffeur C, un fauteuil F dans lequel se
met le client pour être coiffé et N sièges pour attendre.
Il s'agit de synchroniser les activités du coiffeur et de ses clients
avec des sémaphores.
Les sémaphores utilisés sont initialisés
ainsi :
Init (SCF, 0);
Init (SP, 0);
Init (SX, 1);
| Programme client : | Programme coiffeur : |
Client() {
P(SX);
if(Attend < N) {
Attend = Attend + 1;
V (SP);
V (SX);
P (SCF);
SeFaireCoifferEtSortir();
}
else {
V(SX);
Sortir();
}
}
|
Coiffeur(){
while (1){
P(SP);
P(SX);
Attend = Attend -1;
V(SCF);
V(SX);
Coiffer();
}
}
|
a- Détailler le fonctionnement du coiffeur et de ses clients tels qu'ils sont représentés par les deux fonctions Coiffeur et Client.
b- Quel est le rôle de chacun des sémaphores SCF, SP et SX ?
Le carrefour ou le problème de la gestion des feux de circulation.
La circulation au carrefour de deux voies est réglée par des signaux lumineux (feu vert/rouge). On suppose que les voitures traversent le carrefour en ligne droite et que le carrefour peut contenir au plus une voiture à la fois.

On impose les conditions suivantes :
Les arrivées sur les deux voies sont réparties de façon quelconque. Le fonctionnement de ce système peut être modélisé par un ensemble de processus parallèles :
On demande d'écrire le programme Changement ainsi que les procédures Traversee1 et Traversee2.
Remarque :
Le processus P doit attendre que la voiture engagée dans le carrefour en
soit sortie avant d'ouvrir le passage sur l'autre voie.
On suppose que sur Unix on peut définir des variables x et y communes à deux processus comme suit :
shared long x = 0 ;
shared long y = 0 ;
Deux processus exécutent les codes suivants :
Processus P1 Processus P2
*** ***
x = x + 1; x = x * 2;
y = y + 1; y = y * 2;
printf("x=%d,y=%d\n", x, y); printf("x=%d,y=%d\n", x, y);
*** ***
a- Ces processus s'exécutent sur un système UNIX dont la politique d'ordonnancement est du type round robin . Quelles peuvent être les couples de valeurs affichées par chacun des deux processus ?
b- En utilisant un sémaphore, modifier le code pour assurer que les printf affichent toujours des valeurs identiques pour x et y.
Dans le cas de la stratégie d'allocation du processeur avec recyclage (algorithme du tourniquet, ou encore algorithme du quantum de temps), indiquer quels sont les effets des choix suivants pour le quantum q, sachant que s est le temps de changement de contexte et que t est le temps moyen d'utilisation du processeur entre deux événements bloquants (t >> s et [[epsilon]] << s) :
Soit TS le temps de service d'un travail, c'est à dire le temps écoulé entre la soumission du travail et sa fin. On considère un système de traitement séquentiel (batch ) dans lequel quatre travaux arrivent dans l'ordre suivant :
NO du travail Instant d'arrivée Durée
1 0 8
2 1 4
3 2 9
4 3 5
a- Donner le TS moyen dans le cas où l'on adopte la politique PAPS (Premier Arrivé, Premier Servi, ou encore FCFS, Fist Come Fisrt Served )
b- Donner le TS moyen dans le cas où l'on adopte la politique préemptive : PCA (le plus court d'abord, ou encore SJN, Shortest Job Next)
Ecrire les procédures d'accès à un objet de type barrière, c'est à dire dont le fonctionnement est le suivant :
struct{ Sema Sema1; /* Sémaphore de section critique */ Sema Sema2; /* Sémaphore de blocage sur la barriere */ int Count; /* Compteur */ int Maximum; /* Valeur déclenchant l'ouverture */ } Barriere; Question 1 :
Complétez le code de la procédure d'initialisation :
void Init (Barrière *B; int Maximum)
{
Init (B->Sema1, );
Init (B->Sema2, );
B->Count = ;
B->Maximum = ;
}
Question 2 :
Complétez le code de la procédure d'attente donné
ci-dessous :
void Wait (Barriere *B)
{
Boolean Do_Wait = True;
int I;
***;
B->Count++;
if (B->Count == B->Maximum )
{
for (I=0 ; I < (B->Maximum-1) ; I++ ) ***;
B->Count = 0;
Do_Wait = ***;
}
***;
if (Do_Wait) ***;
}
On se propose ici d'écrire deux procédures Wait (P,V) et Set (P,V) :
On donne ci-dessous le canevas (pseudo C) de Wait et Set
ainsi que les structures de données utilisées.
/*------------------------------------------------------------------
Structure de données utilisée pour gerer P
-------------------------------------------------------------------*/
struct {
Sema Sema1; /* Semaphore pour gérer la section critique
*/
Sema Sema2; /* Semaphore d'attente de remise a jour */
int Count; /* Nombre de processus en attente */
int X; /* Donnee protegee */
} Protected;
La fonction Set :
/*------------------------------------------------------------------
Set fait passer la variable P a la valeur V
et libere ensuite tous les processus qui attendent le changement
de valeur de P.
-------------------------------------------------------------------*/
void Set (Protected *P; int Valeur)
{
int I;
***;
P->X = Valeur;
for (I=0 ; I < P->Count ; I++) ***;
P->Count = 0;
***;
}
La fonction Wait:
/*------------------------------------------------------------------
Wait bloque un processus tant que la variable X ne vaut pas V
Le nombre de processus bloques est memorise dans Count
-------------------------------------------------------------------*/
void Wait (Protected *P; int Valeur)
{
Boolean Do_Wait = True;
while Do_Wait
{
***;
if (P->X == Valeur) {
Do_Wait = ***;
} else {
P->Count++;
}
***;
if Do_Wait ***; /* (1) */
}
}
Question 1 :
Comment doivent être initialisés Sema1 et Sema2 ?
Question 2 :
Compléter Wait en remplaçant les ***
par le code adéquat.
Question 3 :
Compléter Set en remplaçant les *** par le
code adéquat.
Question 4 :
Que se passe-t-il dans ce scénario très
particulier :
Suggérez une solution en utilisant un troisième sémaphore qui assure le bon fonctionnement de l'instruction commentée (1).
Soit la table de pages suivante :

Sachant que les pages virtuelles et physiques font 1K octets, quelle est
l'adresse mémoire correspondant à chacune des adresses
virtuelles suivantes codées en hexadécimal :
142A et 0AF1
Comment implanter le partage de code (code réentrant) dans un système qui gère la mémoire par pagination ?
Un programme a besoin de 5 pages virtuelles numérotées 0 à 4. Au cours de son déroulement, il utilise les pages dans l'ordre suivant : 012301401234
Question :
On donne le programme C suivant :
#include
Question :
En exécutant le programme, on obtient l'affichage suivant, pourquoi ?
P[0] = 0
P[1] = -2856
P[2] = 1
P[3] = 1764
P[4] = 4
a- Coiffeur
b- Rôle de chacun des sémaphores SCF, SP et SX :
Sémaphores utilisés :
Init (X1, 1), Init (X2, 1), Init (SF1, 1), Init (SF2, 0);
Changement Traversee1 Traversee2
{int Feu = 1; { P(SX1); { P(SX2);
while(1) P(SF1); P(SF2);
{sleep(Duree_du_feu); Traversee(); Traversee();
if Feu == 1) V(SF1); V(SF2);
{ P(SF1); V(SF2); Feu = 2;} V(SX1); V(SX2);
else } }
{ P(SF2); V(SF1); Feu = 1;}
}
}
SX1 et SX2 sont introduits pour éviter que les voitures attendent sur SF1 et SF2 et bloquent de ce fait les changements de feu effectués par Changement.
Réponse :
a- Couples possibles au final :
x = 1, y = 1
x = 2, y = 2
x = 1, y = 2
x = 2, y = 1
b-
On utilise un sémaphore S initialisé ainsi : Init(S, 1).
Processus P1 Processus P2
*** ***
P (S); P (S);
x = x + 1; x = x * 2;
y = y + 1; y = y * 2;
V (S); V (S);
printf("x=%d,y=%d\n", x, y); printf("x=%d,y=%d\n", x, y);
*** ***
Réponses :
Réponses :
a- Avec FCFS, le schéma d'ordonnancement est le suivant :

on obtient : ( (8) + ((8 - 1) + 4) + (12 - 2) + 9) + ((21 - 3) + 5) ) / 4 = 61 /4
b- Avec SJN, le schéma d'ordonnancement est le suivant :

on obtient :
( (17 - 0 + 1 ) + (4 - 1+ 1 ) + (9 - 3 + 1 ) + (25 - 2 + 1 ) ) / 4
= 53 /4
Complétez le code de la procédure d'initialisation :
void Init (Barriere *B; int Maximum)
{
Init (B->Sema1, 1);
Init (B->Sema2, 0);
B->Count = 0 ;
B->Maximum = N ;
}
Complétez le code de la procédure d'attente :
void Wait (Barriere *B)
{
Boolean Do_Wait = True;
int I;
P (P->Sema1);
B->Count++;
if (B->Count == B->Maximum )
{
for (I=0 ; I < (B->Maximum-1) ; I++ ) V (P->Sema2);
B->Count = 0;
Do_Wait = false;
}
V (P->Sema1);
if (Do_Wait) P (P->Sema2);
}
Compléter Wait :
/*------------------------------------------------------------------
Wait bloque un processus tant que la variable X ne vaut pas V
Le nombre de processus bloques est memorise dans Count
-------------------------------------------------------------------*/
void Wait (Protected *P; int Valeur)
{
Boolean Do_Wait = True;
while Do_Wait
{
P (P->Sema1);
if (P->X == Valeur) {
Do_Wait = False;
} else {
P->Count++;
}
V (P->Sema1);
if Do_Wait P (P->Sema2); /* (1) */
}
}
Compléter Set :
P (P->Sema1);
P->X = Valeur;
for (I=0 ; I < P->Count ; I++) V (P->Sema2);
P->Count = 0;
V (P->Sema1);
}
/*------------------------------------------------------------------
Set fait passer la variable P a la valeur V
et libere ensuite tous les processus qui attendent le changement
de valeur de P.
-------------------------------------------------------------------*/
void Set (Protected *P; int Valeur)
{
int I;
Question I-4 :
Scénario très particulier :
Solution :
dans Set :
...
for (I=0 ; I < P->Count ; I++)
{ V (P->Sema2); P (S3);}
...
dans Wait :
if Do_Wait { P (P->Sema2); V (S3)} /* (1) */
Réponses :
1K = 1024 = 210, le déplacement dans une page est donc
codé sur 10 bits.
page virtuelle 5, octet 2A dans cette page -> page mémoire 1, octet
2A dans cette page
page virtuelle 2, octet 2F1 dans cette page ->page mémoire 8, octet
2F1 dans cette page
Partage de code :
Réponse :
il suffit de faire pointer les pages virtuelles de deux processus sur les memes pages physiques.
Réponses :
Contenu de page mém. 1 : 0 3 3 3 4 4 4 Contenu de page mém. 2 : 1 1 0 0 0 2 2 Contenu de page mém. 3 : 2 2 2 1 1 1 3 Défaut sur la page virtuelle : 3 0 1 4 2 3
Contenu de page mém. 1 : 0 4 4 4 4 3 3 Contenu de page mém. 2 : 1 1 0 0 0 0 4 Contenu de page mém. 3 : 2 2 2 1 1 1 1 Contenu de page mém. 4 : 3 3 3 3 2 2 2 Défaut sur la page virtuelle : 4 0 1 2 3 4
Avant l'appel à la fonction Fonc, le pointeur de pile (stack pointer),
sp, contient l'adresse de la première case libre sur la pile.
On schématise ci-dessous l'état de la pile avant l'appelà
Fonc.
Les cases libres sont en blanc, les cases occupées sont en grisé.
Chaque case représente un octet, le pointeur de pile contient donc des adresses d'octets.



Si l'on a à nouveau besoin de la pile, ici pour l'appel à printf,
l'espace disponible sur la pile va être
réutilisé, et le tableau T, c'est à dire l'espace mémoire dont la
taille est :
5 * sizeof(short),
alloué à partir
de l'adresse contenue dans T, va donc être "écrasé" par de nouvelles variables temporaires.
T contient donc l'adresse d'une zone dans laquelle on trouvera
des valeurs différentes à chaque fois
qu'elle est réallouée à de nouvelles variables.