Exo 03. Signaux, mémoire partagée et synchronisation
Dans cet exercice
on met "le tout" en oeuvre pour simuler un cadre complexe, avec
des attentes pour des services, et plusieurs processus, de
plusieurs types et dans plusieurs états possibles, et avec de la
communication par "tous les moyens".
L'histoire
- Nous sommes chez un coiffeur qui a plusieurs employé(e)s, dans une
grande salle où il y a également des fauteuils comfortables pour
les clients qui peuvent attendre ainsi leur tour. Ce coiffeur est tellement
rennomé et les clients se pressent tant pour se faire couper les
cheveux chez lui, qu'il n'y a pas toujours assez de fauteuils pour
l'attente, et alors les clients se mettent à attendre debout.
- Il y a quand même des limites, au plus
nombreMaxClientsDebout
peuvent attendre, et il y a au total nombreMaxClientsAssis fauteuils
d'attente disponibles.
- Un nouveau client regarde toujours d'abord s'il y a une place d'attente,
assise ou debout, et sinon il s'en va en soupirant. S'il a pu rester, il cherche
à s'asseoir si possible, et sinon, il reste debout.
- Les coiffeurs travaillent tellement, qu'ils sont vite fatigués et
dès qu'ils ont une minute libre, ils s'endorment pour
récupérer.
- Tout client en attente regarde périodiquement si un des coiffeurs
devient libre, et dès qu'il en aperçoit un, il le
réveille. Aussitôt le coiffeur se remet alors au travail pour ce
client.
- Le client a tellement confiance, qu'il s'endort sous les ciseaux du
coiffeur, lequel travaille si délicatement qu'il ne le réveille
que lorsque le tout est fini.
- Lorsque la coupe est finie, le client paie à la caisse
centrale, ou
il peut y avoir au plus nombreMaxClientsSortant clients en attente
(si ce nombre est atteint, notre client reste sur la chaise de coiffeur, sans
dormir cette fois, et regarde périodiquement si la queue avance) ;
enfin, après avoir payé, il s'en va tout joyeux.
- De temps à autre, un inspecteur du travail pointe son nez et note
scrupuleusement l'état d'occupation de toutes les parties, et quels
clients sont chez quel coiffeur, faisant un petit compte rendu.
La modélisation
Chaque client et chaque coiffeur est représenté par un
processus. Ces processus sont tous issus d'un main() initial, et
exécutent le code d'une fonction client(), respectivement
coiffeur(). Ces processus
- communiquent entre eux avec un segment de
mémoire partagée, créé et attaché par le
main() initial, et dont le pointeur memCommStruct * memComm
leur est passé en paramètre .
- se synchronisent entre eux (i.e. au plus un seul accède à un
moment donné à *memComm) avec un sémaphore
int loquet, également créé par le
main() et passé en paramètre.
- s'envoient également des signaux (seulement SIGUSR1, de client
à son coiffeur et de coiffeur à son client), pour mimer que
- le client a "accaparé" un coiffeur libre et lui demande de lui
couper les cheveux
- le coiffeur a fini de couper les cheveux au client
- utilisent sigsuspend() pour l'assoupissement (avec "tout ce qu'il
faut")
Travail à faire
Copiez le répertoire exo_02 avec le nom exo_03
Changez le nom du fichier exo_02.cxx (de diripc) en exo_03.cxx
Dans le fichier exo_03.cxx, mettez ces déclarations
enum EtatCoiffeur { ATTENTE, TRAVAIL } ;
enum EtatClient { AUCUN, DEBOUT, ASSIS, COIFF, SORTANT, FIN } ;
struct memCommStruct {
int NbrClientsDebout;
int NbrClientsAssis;
int NbrCoiffeursAttente;
int NbrClientsSortant;
EtatCoiffeur *etatCoiffeur;
int *clientDuCoiffeur;
pid_t *pidCoiffeur;
EtatClient *etatClient;
int *coiffeurDuClient;
pid_t *pidClient;
};
void SigDfl(int NumSig) {
if(NumSig != SIGUSR1) {
cerr << "Signal " << NumSig << " recu en plein...\n";
exit(3);
}
}
Laissez dans le main() seulement le try .. catch() de la structure principale, et
- récupérez depuis argv le nombreCoiffeurs,
le nombreTotalClients, le nombreMaxClientsAssis,
le nombreMaxClientsDebout. Stockez
dans nombreMaxClientsSortant la différence
nombreTotalClients - nombreMaxClientsAssis - nombreMaxClientsDebout - nombreCoiffeurs
Attention, le nombreTotalClients représente la
"capacité" disponible. On va considérer durant
l'exécution qu'il arrive au total 4*nombreTotalClients
qui ont tous besoin d'aller chez le coiffeur (et certains seront
éventuellement "refusés", comme expliqué plus haut, etc.).
- Vérifiez que les nombres ont du sens (positifs, etc.)
- Calculez la taille de la mémoire partagé ainsi
const int memSize (sizeof(memCommStruct) +
(sizeof(int) + sizeof(EtatClient) + sizeof(pid_t))*
nombreTotalClients +
(sizeof(int) + sizeof(EtatCoiffeur) + sizeof(pid_t))*
nombreCoiffeurs);
- créez avec Shmget (clé IPC_PRIVATE) une zone de mémoire
partagée de cette taille
- associez le segment ainsi crée à à un pointeur memCommStruct * memComm
utilisant Shmat(). Attention, comme vous le
constatez, memComm pointera ainsi au début d'une zone
mémoire plus grande que sizeof(memCommStruct), mais
c'est normal. Dans cette zone mémoire on loge non seulement la
structure memCommStruct-même (qui est une sorte
d'en-tête), tout au début, mais aussi toutes les zones vers lesquelles pointent les
membres de memCommStruct. Autrement dit, on ne fera pas
de malloc()
pour les membres de memCommStruct, puisque le segment de
mémoire partagée offre déjà
également tout l'espace nécessaire.
- appellez la fonction initMem(memComm,
NombreTotalClients, NombreCoiffeurs) pour faire toutes ces assignations
- initialisez les etatClient à AUCUN,
les pidClient à zéro et les coiffeurDuClient
à -1.
- créez un sémaphore et initialisez-le à "vert"
- déroutez SIGUSR1 vers SigDfl
- lancez les coiffeurs -- autant de duplications que de coiffeurs,
et dans les fils, appel de la fonction coiffeur() (avec les bons
paramètres) et puis sortie
- lancez l'inspecteur() -- une seule duplication et dans le fils,
appel de la fonction inspecteur() (avec les bons
paramètres) et puis sortie
- lancez les 4*nombreTotalClients clients -- donc autant de duplications,
et dans les fils, appel de la fonction client() (avec les bons
paramètres) et puis sortie -- et dans cette boucle de duplications,
dormir un nombre aléatoire (et petit) de secondes d'une itération
à l'autre
- attendre tous les fils, détruire les ressources, etc. donc fermer proprement
Finissez d'écrire la fonction
void initMem(memCommStruct * memComm,
int NombreTotalClients, int NombreCoiffeurs) {
qui initialise tous les membres de la memCommStruct pointée
par memComm, sachant donc que memComm pointe au début
de la zone mémoire, complétant ainsi ceci :
void initMem(memCommStruct * memComm,
int NombreTotalClients, int NombreCoiffeurs) {
const char * offSet (((char *)memComm) + sizeof(memCommStruct));
memComm -> NbrClientsAssis = 0;
memComm -> NbrClientsDebout = 0;
memComm -> NbrCoiffeursAttente = 0;
memComm -> NbrClientsSortant = 0;
memComm -> etatCoiffeur = (EtatCoiffeur *)(offSet + 3*sizeof(int));
// ..... a vous de tout completer
}
Complétez ces fonctions
void dumpMem(memCommStruct * memComm,
int NombreTotalClients, int NombreCoiffeurs) {
// affiche effectivement le contenu des champs de memComm, et de toutes
// les cases des tableaux de memComm,
cerr << "\n";
}
void inspecteur(memCommStruct * memComm, CSemBase &loquet,
int nombreTotalClients, int nombreCoiffeurs) {
// afficher le contenu des champs de memComm, et de toutes les cases des
// tableaux de memComm, en appelant dumpMem()
}
Rédigez très soigneusement les diagrammes des états
du coiffeur et du client, avec leurs intéractions (envoi de signal,
etc.), suivant la description de l'"histoire" racontée ci-haut.
Attention, l'attente de signal devra se faire utilisant le blocage
suivi de sigsuspend(). Pourquoi?
Écrivez les fonctions coiffeur() et client() selon
ces diagrammes (et avec les bons paramètres). N'oubliez bien entendu pas d'utiliser le sémaphore
pour assurer l'exclusion mutuelle lors des accès en lecture ou
écriture à la mémoire partagé, tout en y faisant
bien attention.
Compilez et testez