Table des matières


Objectifs :

Ave Caesar


Le but de cet exercice est de vous faire manipuler les signaux. Pour cela, nous recréons une arène à partir de la chaîne de processus que vous avez mise en œuvre à la séance précédente. L’arène est constituée de gladiateurs qui saluent César, et de César qui ordonnent leurs morts pour animer la soirée.

Partie 1 : 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 Caesar, où X est le PID du gladiateur, et qui meurt en criant X : Morituri te salutant lorsqu’il reçoit un signal USR1.

Q1 : Copiez le script chaine.sh de l’exercice sur les chaînes de processus en gladiateur.sh. 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 qui affiche toutes les 5 secondes X : Ave Caesar 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).

Q2 : 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.

Q3 : 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.

Q4 : 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 cette 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.

Partie 2 : Dur à Cuire

Cette partie va vous montrer qu’il est difficile de tuer plusieurs gladiateurs à la fois.

Q5 : Lancez deux gladiateurs avec la commande ./gladiateur.sh 2. Envoyez un signal USR1 a un des gladiateurs. Que constatez vous?

Q6 : Abattez le deuxième gladiateur.

Q7 : Lancez encore une fois deux gladiateurs. Faites un Ctrl+C. Que constatez-vous?

Q8 : Mettez à mort cet homme innocent qui avait sûrement une famille et des rêves.
Plus aucun gladiateur n’est censé être actif à ce stade.

Partie 3 : Dura Lex Sed Lex

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 seul gladiateur. Dans cette partie, nous résolvons le problème avec le processus Caesar. Caesar, qui s’ennuie souvent au Colisée, apprécie envoyer des USR1 à tous les gladiateurs pour ordonner leur mise à mort.
Pour connaître les PIDs des gladiateurs, Caesar lit un parchemin nommé arene.txt, dans lequel se trouve un PID de gladiateur par ligne.

Q9 : Un gladiateur doit maintenant enregistrer son PID dans le fichier arene.txt de façon à ce que Caesar puisse le trouver. Modifiez le script gladiateur.sh pour qu’il ajoute le 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 interrompez 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 Caesar
$ cat arene.txt
2460
$ ./gladiateur.sh 1
Processus 2473 démarre avec le processus initial 2473
Fin de chaîne
2473 : Ave Caesar
$ cat arene.txt
2460
2473

Q10 : Avant d’envoyer des signaux, écrivez un script caesar.sh qui :

Q11 : Au lieu d’afficher les PIDs des gladiateurs, caesar.sh doit maintenant envoyer un USR1 à chaque gladiateur enregistré dans arene.txt. Modifiez le script en conséquence.

Q12 : Nous pouvons maintenant utiliser le script caesar.sh pour terminer proprement tous les gladiateurs lorsque l’utilisateur utilise un Ctrl+C ou la commande kill. Modifiez gladiateur.sh de façon à lancer caesar.sh à la réception d’un des signaux de ces deux commandes.

Outre la culpabilité d’être responsable du décès d’un nombre important de pauvres processus, vous venez d’écrire votre premier protocole de terminaison. Techniquement, le protocole que vous venez de mettre en oeuvre 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.

Corrections


caesar.sh

#! /bin/bash
if [ ! -f arene.txt ]; then
  echo "arene.txt does not exist" >&2 # &2 est la sortie d’erreur
  exit 1 # un code de retour faux
fi

while read line; do
  echo Ceasar kill $line
  kill -USR1 $line       
done < arene.txt

rm arene.txt
exit 0

gladiator.sh

#! /bin/bash
if [ $# -ne 1 ]; then
  echo "Please provide exactly one argument" >&2 # &2 est la sortie d’erreur
  exit 1 # un code de retour faux
fi
  echo $$ >> arene.txt

  trap "./caesar.sh" INT

  if [ -z "$pidInitial" ]; then
    echo Processus $$ demare
  else
    echo Processus $$ demare avec le processus initial $pidInitial
  fi

  export pidInitial=$$

  if [ $1 -gt 1 ]; then
    k=$(expr $1 - 1)
    echo Il reste $k processus à crée
    ./gladiator.sh $k

  else
    echo Fin de chaîne
  fi

  trap "echo $$ : Morituri Te Salutant; exit 0" USR1

  while :
  do
	echo $$ ": Ave Caesar"
	sleep 5 &
    wait $!
  done

echo Processus $$ termine
exit 0