CSC 3102 – Introduction aux systèmes d’exploitation

Portail informatique

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.
Testez votre script avec le fichier arene.txt que vous avez généré à la question précédente.
$ 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.