Le but de cet exercice est de transformer le Colisée que vous avez
créé précédemment (voir
ici)
en boîte à Meuh, c'est-à-dire que les processus vont afficher
Meuh les uns après les autres.
Même si afficher
Meuh sur le terminal peut sembler quelque peu inutile,
l'algorithme que vous allez mettre en œuvre est souvent
utilisé dans les systèmes répartis et réseaux pour synchroniser un ensemble de processus.
Par exemple, l'algorithme qu'on vous demande de mettre en œuvre est utilisé pour construire
le protocole
Token-Ring qui permet un accès équitable à une infrastructure réseau.
Techniquement, les processus sont organisés en anneau, comme présenté dans
la figure ci-dessus.
Chaque processus de la chaîne crée un tube nommé
tube-X-N,
où
X est le
PID du processus initial (stocké dans la variable
pidInitial)
et
N l'argument donné au script, c.-à-d., le nombre de processus restant
à créer dans la chaîne.
Pendant l'exécution, les processus communiquent soit avec leur prédécesseur en accédant au tube
du prédécesseur, soit avec leur successeur, en accédant à leur propre tube.
À partir de cette topologie, les processus échangent un jeton, symbolisé par le
mot
Meuh.
Un processus reçoit le jeton lorsqu'il reçoit le mot
Meuh
via le tube de son prédécesseur.
Le processus conserve ensuite le jeton pendant une seconde avant de le passer à son successeur
en écrivant
Meuh dans son tube.
Pour commencer, il faut que chaque processus trouve (i) le nom du tube pour communiquer avec
son prédécesseur et (ii) le nom du tube pour communiquer avec son successeur.
- Le nom du tube successeur est toujours tube-$pidInitial-N, où
pidInitial est le PID du processus initial et N l'argument
du script (i.e., $1).
- Pour le nom du tube prédécesseur :
- si le processus est le processus initial (c.-à-d., le seul processus démarrant
avec une variable pidInitial non initialisée), ce nom
est tube-$$-1, où $$ est le PID du processus courant,
puisque le processus courant est le processus initial,
- sinon, le nom du tube est tube-$pidInitial-M, où M
vaut $1+1,
Après avoir copié
gladiateur.sh en
meuh.sh,
modifiez votre script pour :
- stocker dans les variables pred et succ les noms des tubes
du prédécesseur et du successeur,
- modifier l'affichage Processus $$ démarre avec le processus initial $pidInitial
de façon à afficher les noms de ces tubes.
On ne vous demande pas de créer les tubes à cette question.
$ ./meuh.sh 3
Processus 3158 démarre avec le processus initial 3158 (tube-3158-1 - tube-3158-3)
Il reste 2 processus à créer
3158: Ave César
Processus 3160 démarre avec le processus initial 3158 (tube-3158-3 - tube-3158-2)
Il reste 1 processus à créer
3160: Ave César
Processus 3164 démarre avec le processus initial 3158 (tube-3158-2 - tube-3158-1)
Fin de chaîne
3164: Ave César
3158: Ave César
3160: Ave César
3164: Ave César
^C3164: Morituri te salutant
3160: Morituri te salutant
3158: Morituri te salutant
Modifiez votre script de façon à ce que chaque processus de la chaîne crée son
tube successeur après avoir identifié les noms des tubes successeurs et prédécesseurs.
Avant d'aller plus loin, il faut être capable de supprimer les tubes créés
par notre application.
Modifiez
cesar.sh de façon à supprimer chacun des tubes créés.
Vous pouvez remarquer que, comme meuh.sh exporte pidInitial,
cette variable est positionnée dans cesar.sh.
Pour cette raison, il suffit de supprimer tous les fichiers dont le nom
commence par tube-$pidInitial- dans cesar.sh. Pensez à utiliser l'option -f
de la commande rm, qui évite de demander une confirmation à l'utilisateur.
Pour accéder aux tubes, nous utilisons des redirections avancées. En effet, des redirections
simples ouvrent et ferment continuellement les tubes, ce qui entraîne des erreurs ou des blocages
lorsque les tubes sont fermés pendant qu'il existe des interlocuteurs.
Avant la boucle principale qui affiche
Ave César, ouvrez
en lecture/écriture les tubes prédécesseurs et successeurs (2 ouvertures différentes).
Vous devriez remarquer que vous avez un message d'erreur du type :
« mkfifo: tube-3665-1: File exists ». Nous nous occuperons de ce message
à la question suivante.
Le message d'erreur que vous avez est dû à un problème de synchronisation.
Le processus initial arrive à atteindre l'ouverture du tube du prédécesseur avant
que le processus final ait eu le temps créer son tube.
Comme l'ouverture par le processus initial crée un fichier normal, la création du tube dans le processus
final échoue avec un message d'erreur.
Pour éviter ce problème, il ne faut donc ouvrir le tube du prédécesseur que si le
tube existe.
Avant d'ouvrir le tube du prédécesseur, ajoutez donc une boucle qui attend tant que le tube n'existe pas
(donné par
[ ! -p $pred ]) et qui dort une seconde à chaque pas de boucle.
Vérifiez que le message d'erreur a bien disparu.
De façon à mieux voir comment fonctionne le protocole, nous lançons chaque processus dans un terminal différent.
Remplacez la création récursive d'un processus (meuh.sh K, où K est égal au nombre de processus restant à créer dans la chaîne), par le lancement de la même commande mais dans un nouveau terminal.
Si vous travaillez sur une machine de la salle de TP, vous pouvez utiliser : xterm -reverse -e ./meuh.sh K.
-
-e indique à xterm le programme à exécuter dans ce nouveau terminal ;
-
-reverse est optionnelle : elle crée un terminal blanc sur fond noir, pour vous permettre de mieux l'identifier.
Sinon, essayez quand même avec
xterm ou remplacez-le par votre émulateur de terminal actuel.
Par exemple :
- sur Ubuntu avec l'environnement de bureau GNOME : gnome-terminal -- ./meuh.sh K ;
- sur Mac OS : osascript -e 'tell application "Terminal" to do script "./meuh.sh 'K'"' (attention aux multiples guillemets avec l'utilisation d'une variable dans la commande).
Vous pouvez admirer votre protocole de terminaison qui permet de fermer tous les terminaux en faisant la combinaison de contrôle Ctrl-C dans n'importe quel terminal.
Nous mettons maintenant en place notre boîte à Meuh. Dans la boucle principale, remplacez
l'affichage de
Ave César et l'attente de cinq secondes par :
- une lecture d'une ligne à partir du tube prédécesseur,
- l'affichage de la ligne lue,
- une attente d'une seconde,
- l'écriture de la ligne lue dans le tube successeur.
Pour amorcer votre boîte à Meuh, écrivez «
Meuh » dans un des tubes
à partir d'un autre terminal.
Amorcer la boîte à Meuh à partir d'un autre terminal est relativement fastidieux.
Pour cette raison, c'est le processus final qui va générer le premier jeton.
Modifiez votre script pour que le processus final (celui qui a pour paramètre 1)
écrive
Meuh dans le tube de son successeur juste avant d'exécuter la boucle principale
du programme.
Félicitation, vous venez d'écrire votre premier protocole multiprocessus complexe !