Ave César (∼1h30)
Le but de cet exercice est de vous faire manipuler les signaux.
Pour cela, nous concevons un (tout petit) Colisée de Rome à partir de la
chaîne de processus que vous avez mise en œuvre
à la séance précédente (voir
ici).
Le Colisée est constitué de gladiateurs qui saluent César, et de
César qui les trucide dans la joie.
Les gladiateurs
Dans cette première partie, nous créons les gladiateurs.
Un gladiateur est un processus qui affiche toutes les 5 secondes
« X: Ave César », où X est le PID du gladiateur,
et qui meurt en criant « X: Morituri te salutant »
lorsqu'il reçoit un signal USR1.
Copiez le script
de l'exercice sur les chaînes de processus
en gladiateur.sh.
Vous pouvez soit partir de votre solution, soit télécharger le fichier se trouvant
ici.
Dans votre script,
au lieu d'attendre la mort d'un enfant, d'afficher
« Processus X termine » et de retourner un code d'erreur
vrai, gladiateur.sh doit maintenant exécuter une boucle
infinie (while true; do ... done)
qui affiche toutes les 5 secondes « X: Ave César »,
où X est le PID du gladiateur.
Dans cette question, comme dans toute cette partie, vous devez systématiquement tester votre script
en ne lançant qu'un unique gladiateur (gladiateur.sh 1).
Rappelez-vous que vous pouvez interrompre votre script en saisissant la combinaison
de touches control+c.
Complétez votre script pour qu'il affiche « X: Morituri te salutant »
lorsqu'il reçoit un signal USR1.
Testez votre script en lançant un gladiateur dans un terminal (./gladiateur.sh 1), et
en envoyant un signal USR1 au gladiateur avec la commande
kill dans un autre terminal.
Complétez votre script pour qu'il termine le processus en renvoyant un code de retour vrai (0)
juste après avoir affiché « X: Morituri te salutant ».
Vous avez dû remarquer que lorsque vous envoyez un signal USR1,
votre processus ne reçoit le signal qu'après avoir terminé son attente de 5 secondes.
Ce comportement est dû au fait que la commande interne sleep
masque les signaux pendant qu'elle s'exécute.
De façon à éviter cette attente, nous utilisons le fait
que la commande wait, elle, peut être interrompue par un signal.
Au lieu de lancer la commande sleep en avant plan, lancez-la
en arrière plan, et utilisez wait $! pour attendre la fin
de la commande sleep (le $! permet de n'attendre
que la fin de la dernière commande, c.-à-d., sleep, et non celle de tous les enfants).
Testez votre programme avec un unique gladiateur et vérifiez que le processus n'attend plus
la fin de la commande sleep.
Le problème de la terminaison
Cette seconde partie de l'exercice a pour but de vous montrer qu'il est difficile
de terminer plusieurs gladiateurs.
Lancez deux gladiateurs avec la commande ./gladiateur.sh 2.
Envoyez un signal USR1 a un des gladiateurs.
Que constatez vous ?
Seul un gladiateur meurt.
Débarrassez-vous du second gladiateur.
$ ps aux | grep glad
gthomas 2361 0,0 0,0 2444056 808 s002 S+ 8:40 0:00.00 grep glad
gthomas 2356 0,0 0,0 2435432 1120 s000 S 8:40 0:00.01 /bin/bash ./d/gladiateur.sh 1
$ kill 2356
Lancez deux gladiateurs avec la commande ./gladiateur.sh 2.
Saisissez la combinaison de touches control-c.
Que constatez-vous ?
Seul le gladiateur en avant plan meurt.
Le cas échéant, débarrassez-vous des gladiateurs qui seraient encore en train de s'exécuter.
Retrouver les PIDs des gladiateurs
Comme vous avez pu le constater, il est difficile de terminer plusieurs gladiateurs car
lorsque vous envoyez un signal, il n'est envoyé qu'à un unique gladiateur.
Dans cette partie, nous résolvons le problème avec le processus César.
César, qui s'ennuie souvent au Colisée, apprécie d'envoyer des USR1 à tous les gladiateurs
pour les tuer.
Pour connaître les PIDs des gladiateurs, César lit un parchemin nommé
arene.txt, dans lequel se trouve un PID de gladiateur par ligne.
Un gladiateur doit maintenant enregistrer son PID
dans le fichier arene.txt de façon à ce que César puisse le trouver.
Modifiez le script ./gladiateur.sh pour qu'il ajoute son PID
au fichier arene.txt après avoir vérifié que les paramètres sont corrects.
Testez votre script en lançant deux fois un unique gladiateur que vous interromprez
avec un signal INT, et en vérifiant que arene.txt contient bien les PID
des gladiateurs :
$ ./gladiateur.sh 1
Processus 2460 démarre avec le processus initial 2460
Fin de chaîne
2460: Ave César
2460: Ave César
^C$ cat arene.txt
2460
$ ./gladiateur.sh 1
Processus 2473 démarre avec le processus initial 2473
Fin de chaîne
2473: Ave César
^C$ cat arene.txt
2460
2473
Avant d'envoyer des signaux,
écrivez un script cesar.sh qui :
- affiche un message d'erreur et renvoie faux si arene.txt n'existe pas,
- lit ligne à ligne arene.txt, et affiche chaque ligne sur la sortie standard,
- supprime le fichier arene.txt après avoir lu chaque ligne,
- renvoie une code retour vrai.
$ echo 42 >arene.txt; echo 666 >>arene.txt; ./cesar.sh
42
666
Au lien d'afficher les PIDs des gladiateurs, cesar.sh doit maintenant envoyer un USR1
à chaque gladiateur enregistré dans arene.txt.
Modifiez le script cesar.sh en conséquence.
Nous pouvons maintenant utiliser le script cesar.sh pour terminer proprement
tous les gladiateurs lorsque l'utilisateur saisie control-c ou utilise
la commande kill.
Modifiez gladiateur.sh de façon à lancer cesar.sh
à la réception des signaux INT et TERM.
Félicitation ! Vous venez d'écrire votre premier protocole de terminaison !
Techniquement, le protocole que vous venez de mettre en œuvre permet de terminer
proprement un ensemble de processus qui collaborent.
Les gladiateurs sont des processus qui offrent un service à l'utilisateur (par exemple, chaque gladiateur pourrait être associé
à un utilisateur connecté à un serveur Web), et César est le processus permettant de terminer
proprement l'application.