Remarques préliminaires :
Classe d'exceptions spécifique aux IPCsBibliothèque de fichiers objets
Classe COperation
Wrappers
Wrappers de l'I.P.C. "Ensemble de sémaphores"Fonction semget()Classe CSemBase
Fonction semctl()
Fonctions spécifiques aux sémaphores : semop()
Modifications de MakeUtilPartage de ressources
Modifications de Makefile
Section critique exo_01 Primitive P() non bloquante exo_02 Simulation du contrôle d'accès à une piscine exo_03 Simulation du problème des cinq philosophes exo_04
Toutes les fonctions système qui manipulent les I.P.C.s (Inter Process Communication) ont pour premier paramètre un identificateur d'IPC :
Dans les fichiers CExcFctSyst.h et CExcFctSyst.hxx dériver la nouvelle classe CExcFctSystIPC de CExcFctSyst, analogue à CExcFctSystFile, en remplaçant le nom de fichier et le file descriptor par la clé de l'I.P.C. (m_key) et son identificateur (m_id).
Corrigé : CExcFctSyst.h - CExcFctSyst.hxx
Cela conduit à écrire plus de 20 wrappers différents pour seulement quelques fonctions système de base, travail indispensable pour faciliter une programmation ultérieure sécurisée, mais gros consommanteur de temps. Nous supposons que le principe d'écriture en C++ de plusieurs wrappers d'une même fonction système est maintenant connu, les corrigés vous sont donc fournis. Cependant, pour pouvoir les utiliser correctement par la suite, vous devez en prendre connaissance de façon attentive avant de les intégrer dans les fichiers nsSysteme.h, nsSysteme.hxx et nsSysteme.cxx. C'est pourquoi nous vous recommandons de suivre la procédure ci-dessous et de ne pas récupérer directement les versions finales des corrigés des fichiers nsSysteme.h et nsSysteme.hxx, accessibles ci-dessous.
Bien que les fonctions système de la même famille (par exemple xxxget()) aient des profils très semblables, nous ne développerons pas leurs wrappers simultanément. Au contraire, nous introduirons ceux-ci au fur et à mesure de la nécessité de chaque type d' I.P.C.s. Ainsi, les wrappers s'appliquant aux familles de sémaphores seront développées dans ce TP ("Partage de ressources"), ceux qui concernent les files de messages seront introduits dans le TP consacré à l'implémentation du modèle "Producteurs/Consommateurs", ceux qui concernent les mémoires partagées seront introduits dans le TP consacré à l'implémentation du modèle "Lecteurs/Rédacteurs". Dans ces différents TPs seront introduits des wrappers d'autres fonctions système lorsqu'elles devront être utilisées.
A ajouter au fichier nsSysteme.h :
// Fonctions concernant les ensembles de sémaphores
// ================================================ int Semget (::key_t key, int nsems, int semflg)
int Semget (::key_t key)
|
A ajouter au fichier nsSysteme.hxx (Rq2) :
//=================================================
// Fonctions concernant les ensembles de sémaphores //================================================= inline int nsSysteme::Semget (::key_t key, int nsems, int semflg)
} // Semget() |
A ajouter au fichier nsSysteme.cxx :
//=================================================
// Fonctions concernant les ensembles de sémaphores //================================================= int nsSysteme::Semget (::key_t key) throw (CExcFct)
int SemId;
} // Semget() |
La constante CstSemgetErrArg = 133 doit être ajoutée au fichier CstCodErr.h.
void Semctl (int semid) throw (CExcFctSystIPC); |
int Semctl (int semid, int semnum, int cmd) throw (nsUtil::CExcFct); |
void Semctl (int semid, int semnum, int cmd, int val) throw (nsUtil::CExcFct); |
void Semctl (int semid, int cmd, ::semid_ds * buf) throw (nsUtil::CExcFct); |
void Semctl (int semid, int cmd, unsigned short array []) throw (nsUtil::CExcFct); |
A ajouter au fichier nsSysteme.h :
// uniquement pour détruire l'ensemble de sémaphores
void Semctl (int semid)
// si cmd == GETVAL ou GETPID ou GETNCNT ou GETZCNT : int Semctl (int semid, int semnum, int cmd)
// si cmd == SETVAL : void Semctl (int semid, int semnum, int cmd, int val)
// si cmd == IPC_STAT ou IPC_SET : void Semctl (int semid, int cmd, ::semid_ds * buf)
// si cmd == GETALL ou SETALL void Semctl (int semid, int cmd, unsigned short array [])
|
A ajouter au fichier nsSysteme.hxx (Rq3) :
#if defined (__GNU_LIBRARY__) && !defined (_SEM_SEMUN_UNDEFINED)
/* union is defined by including <sys/sem.h> */ #else /* according to X/OPEN we have to define it ourselves */ union semun { int val; // <= value for SETVAL struct ::semid_ds *buf; // <= buffer for IPC_STAT & IPC_SET unsigned short int *array; // <= array for GETALL & SETALL struct ::seminfo *__buf; // <= buffer for IPC_INFO }; // semun #endif __GNU_LIBRARY__ & !_SEM_SEMUN_UNDEFINED #define semun semun // uniquement pour détruire l'ensemble de sémaphores inline void nsSysteme::Semctl (int semid) throw (CExcFctSystIPC)
} // Semctl() |
A ajouter au fichier nsSysteme.cxx :
#ifndef semun
#endif __GNU_LIBRARY__ & !_SEM_SEMUN_UNDEFINED
// si cmd == GETVAL ou GETPID ou GETNCNT ou GETZCNT : int nsSysteme::Semctl (int semid, int semnum, int cmd) throw (nsUtil::CExcFct)
int Val;
return Val; } // Semctl() // si cmd == SETVAL : void nsSysteme::Semctl (int semid, int semnum, int cmd, int val)
::semun Union;
} // Semctl() // si cmd == IPC_STAT ou IPC_SET : void nsSysteme::Semctl (int semid, int cmd, ::semid_ds * buf)
::semun Union;
} // Semctl() // si cmd == GETALL ou SETALL void nsSysteme::Semctl (int semid, int cmd, unsigned short array [])
::semun Union;
} // Semctl() |
A ajouter au fichier nsSysteme.h :
void Semop (int semid, ::sembuf * sops, unsigned int nsops)
throw (CExcFctSystIPC); |
A ajouter au fichier nsSysteme.hxx :
inline void nsSysteme::Semop (int semid, ::sembuf * sops,
unsigned int nsops) throw (CExcFctSystIPC) { if (::semop (semid, sops, nsops)) throw CExcFctSystIPC ("semop()", -1, semid); } // Semop() |
Corrigé : nsSysteme.h - nsSysteme.hxx - nsSysteme.cxx - CstCodErr.h
A partir du répertoire des corrigés tppartagersc/include, recopier dans votre répertoire correspondant le fichier CSemBase.h. Il contient la déclaration d'une classe CSemBase, de l'espace de noms nsSysteme, permettant de créer un sémaphore n-aire (par défaut binaire). La donnée membre m_Proprietaire contient le pid du processus qui a créé le sémaphore par la fonction Semget() et qui est le seul habilité à le détruire par Semctl()). La donnée membre m_id contient l'identificateur de sémaphore renvoyé par Semget().
Ecrire dans le fichier CSemBase.hxx le corps des fonctions membres de la classe.
Modifier le fichier INCLUDE_H pour prendre en compte les fichiers inclus COperation.h, COperation.hxx, CSemBase.h, CSemBase.hxx. En profiter pour supprmier la macro COMPILER (explications plus loin).
Corrigé : INCLUDE_H - CSemBase.hxx
$(nom) : cible $(nom).o
g++ -s -o $(nom) $(nom).o \ $(UTIL)/CException.o $(UTIL)/SaveSig.o \ $(UTIL)/main.o $(UTIL)/nsSysteme.o \ $(UTIL)/TestFdOuverts.o $(UTIL)/Autopsie.o |
ainsi que la liste des fichiers .o à raffraîchir :
cible :
cd $(UTIL); make -f MakeUtil nsSysteme.o main.o nsUtil.o \ CException.o SaveSig.o \ TestFdOuverts.o Autopsie.o |
De plus, il devient facile de commettre des erreurs.
Une bonne solution est de constituer une bibliothèque (library) de fichiers objets (.o), mise à jour à chaque recompilation, et d'indiquer au compilateur g++ d'aller chercher dedans les fichiers dont il peut avoir besoin. Cela implique la modification des fichiers MakeUtil et Makefile. Une bibliothèque doit commener par le préfixe lib, nous l'appellerons donc libSys.
COMPILER = g++ -c -I$(INCLUDE) -Wall $*.cxx; ar -cqs libSys.a $*.o |
De plus le nettoyage des fichiers compilés doit être modifié :
#
# Nettoyage du répertoire courant : bibliothèque et fichiers .o # clean : clear; rm -f *.o *.a -v |
Enfin, pour éviter de relancer toutes les compilations en énumérant chaque cible, il est préférable de les regrouper en une seule, placée en début de fichier :
general : nsUtil.o CException.o nsSysteme.o main.o \
TimeBase.o Time.o CTimer.o SaveSig.o \ TestFdOuverts.o Autopsie.o |
Noter qu'il n'y a pas de commande associée. Modifications de Makefile Recopier le fichier ../dirprocess/Makefile dans le répertoire de travail (dirpartagersc).
Puisque la macro COMPILER a été supprimée du fichier INCLUDE_H, il est nécessaire de la réintroduire dans le fichier Makefile :
COMPILER = g++ -I$(INCLUDE) -c $*.cxx -Wall |
Dans l'exemple ci-dessous, nous supposons que le programme ne dépend pas de fichiers objets .o dans le répertoire courant.
Pour l'édition de liens, il faut indiquer au compilateur g++, au moyen de
l'option -L
(assez semblable à l'option -I)
qu'il doit aller chercher les références dans le répertoire util (-L$(UTIL)), dans la bibliothèque libSys, par
l'option -l
(-lSys).
$(nom) : cible $(nom).o
g++ -s -o $(nom) $(nom).o \ -L$(UTIL) -lSys |
Noter la suppression du préfixe lib.
Il ne reste plus qu'à mettre à jour la cible :
cible :
cd $(UTIL); make -f MakeUtil |
exo_01
Modifier le fichier Makefile, compiler et tester.
Vous constatez que le programme ne fonctionne pas correctement. En effet, l'algorithme donne le droit de détruire l'ensemble de sémaphores seulement à son processus propriétaire (celui qui l'a créé). Il faut en réalité que ce soit le dernier processus à l'utiliser qui ait ce droit. Malheureusement, il est impossible de le connaître. Une solution simple, dans ce problème, consiste à obliger le processus créateur à être le dernier à se terminer : il suffit qu'il attende la mort de son fils.
Faire la modification, compiler et tester.
Cette fois, vous devez constater que deux seules séquences sont possibles : le père s'exécute complètement avant le fils, ou l'inverse. Cette solution est souvent utilisée, et le père peut être réduit à créer des ressources, puis lancer tous les processus qui doivent coopérer, et enfin attendre la fin de tous ces processus pour libérer les ressources (ici par simple appel au destructeur).
Corrigé : Makefile - exo_01.cxx
Remarque : que pensez-vous de la solution suivante ?
char c;
const int fd = Open ("exo_01.txt", O_RDWR); if (Fork ())
/* 01 */
|
Vous pouvez l'essayer, il s'agit du fichier exo_01b.cxx dans le répertoire des corrigés dirpartagersc
exo_02
Plusieurs processus nécessitent l'écran pour leurs affichages mais, afin de ne pas mélanger les éditions, son utilisation est mise en exclusion mutuelle au moyen d'un sémaphore binaire. Si après plusieurs tentatives infructueuses, séparées d'un délai d'attente variable, un processus ne peut accéder à l'écran, il finit par se décider à rediriger ses éditions sur un fichier. Pour réaliser cet algorithme, il faut modifier la primitive P(). En effet, cette dernière est bloquante et le processus ne peut pas "l'essayer" pour voir s'il peut accéder à la ressource. Il faut donc enrichir la classe CSemBase.
Dans les fichiers include/CSemaphore.h, include/CSemaphore.hxx et util/CSemaphore.cxx, dériver la classe CSemBase en CSemaphore.
Lui ajouter la fonction membre :
bool P_WouldBlock (void); |
qui ne bloque jamais le processus mais qui renvoie vrai chaque fois que le processus aurait dû être bloqué (utiliser le flag IPC_NOWAIT de la fonction semop()).
Modifier les fichiers INCLUDE_H, Makefile et MakeUtil.
Pour créer et détruire les ressources (un ensemble de sémaphores) nous utiliserons ici une nouvelle technique : dans le fichier exo_02d.cxx, écrire un programme qui :
Dans le fichier exo_02.cxx, écrire un programme qui, à intervalles réguliers, essaie de s'accaparer l'écran en bloquant le sémaphore créé par exo_02d (utilisation de P_WouldBlock()). S'il y arrive, il affiche un certain nombre de fois son numéro de pid (une fois/seconde) puis libère l'écran. S'il n'y arrive pas, il effectue la même opération sur un fichier de secours. Les paramètres de la commande exo_02 sont :
Corrigés : exo_02d.cxx - exo_02.cxx - CSemaphore.h - CSemaphore.hxx - CSemaphore.cxx - INCLUDE_H - Makefile - MakeUtil - ScriptEcran
exo_03
Il est fastidieux de retrouver le pid du démon qui doit détruire les ressources lorsque tous les processus utilisateurs sont terminés, et de lui envoyer un signal particulier (passé en paramètre). Pour simplifier cette tâche, le démon génèrera un script, de nom prédéfini (ou passé en paramètre), qui effectuera ces opérations.
Dans les fichiers Reveil.h et Reveil.cxx
des répertoires include et util, déclarer
et définir la fonction de profil :
void nsUtil::Reveil (int NumSig
= SIGKILL,
const std::string & NomScript = "Reveil") throw (nsSysteme::CExcFctSystFile); |
La fonction doit générer un script (fichier exécutable) d'identificateur NomScript contenant :
Tableau 1 : fonctions de synchronisation
Dans le fichiers nsPiscine.cxx correspondant, étendre l'espace de noms nsPiscine pour lui ajouter :
namespace nsPiscine
{ int SemId; enum { CstSemPanier = 0, CstSemCabine = 1, }; } // nsPiscine |
Ajouter au fichier les corps des différentes fonctions de synchronisation.
Recopier le fichier exo_02d.cxx dans exo_03d.cxx. En utilisant les fonctions écrites ci-dessus, modifier le démon pour qu'il réalise les opérations suivantes :
Recopier le fichier Chrono.cxx de votre répertoire dirprocess ou du répertoire tppartagersc des corrigés. Le compiler.
Compiler, écrire le script ScriptPiscine pour lancer simultanément plusieurs baigneurs et le programme Chrono en tâche de fond, qui permettra de reconstituer le chronogramme. Tester.
Corrigé : Baigneur.cxx - exo_03d.cxx - ScriptPiscine - Reveil.h - Reveil.cxx - Makefile - MakeUtil - INCLUDE_H - nsPiscine.h - Chrono.cxx - nsPiscine.cxx
Recopier le fichier exo_03d.cxx dans exo_04d.cxx. Modifier le démon pour qu'il crée les ressources nécessaires.
Dans le fichier Philosophe.cxx, écrire un programme qui simule le comportement d'un philosophe. Les arguments des commandes Philosophe sont :
Comme précédemment, on peut proposer un ensemble de fonctions, regroupées dans un espace de noms nsPhilosophe (fichiers nsPhilosophe.h et nsPhilosophe.cxx) permettant de modéliser le comportement des philosophes :
void DresserLaTable (key_t Cle)
throw (nsSysteme::CExcFctSystIPC);
void DebarrasserLaTable (void) throw (nsSysteme::CExcFctSystIPC); void ArriverATable (key_t Cle) throw (nsSysteme::CExcFctSystIPC); void DebutManger (const int i) throw (nsSysteme::CExcFctSystIPC); void FinManger (const int i) throw (nsSysteme::CExcFctSystIPC); void QuitterLaTable (void) throw (nsSysteme::CExcFctSystIPC); |
Compléter le fichier Makefile pour qu'il puisse compiler les fichiers nsPhilosophe.cxx, exo_04d.cxx et Philosophe.cxx. Il est inutile de modifier le fichier INCLUDE_H.
Compiler et tester au moyen d'un script qui lance le programme Chrono en tâche de fond, puis les cinq philosophes.
Corrigé : nsPhilosophe.h - nsPhilosophe.cxx - Philosophe.cxx - ScriptPhilosophe - Makefile - exo_04d.cxx
[1] Rappel : un script est un fichier texte qui doit être exécutable par le c-shell. Pour cela il doit :
© D. Mathieu
mathieu@romarin.univ-aix.fr
I.U.T.d'Aix en Provence - Département Informatique