if (const ::pid_t PidFils = Fork ()) |
Lorsque le fils commence par dormir pendant 1 seconde, cela laisse au père le temps de se terminer. Lorsque le fils se réveille, il est devenu un démon, et son père est alors le processus init de pid = 1.
Le fonctionnement de la fonction sera émulé dans l' exo_17.
Il est demandé de faire dormir le démon une seconde pour laisser le temps au processus père de mourir, sinon, ce ne serait pas un démon (rattaché au processus init de pid = 1. Bien entendu, si on ne travaille pas en temps réel, on ne peut jamais garantir qu'un événement arrive dans un délai donné, quel qu'il soit. Cependant, la probabilité que le père soit terminé au bout d'une seconde est très proche de 1.
Le signal Ctrl C, envoyé au clavier, tue le père. Le fils, qui existe encore (vérifié par ps lx) ne reçoit plus les signaux en provenance du clavier.
Le signal Ctrl \, envoyé au clavier, tue le fils. On remarque aussi que le père reçoit deux signaux : le signal 3, et le signal 17, qui correspond à SIGCHLD, et qui lui est transmis par le système lors de la mort de son fils.
Cet exercice montre qu'une saine programmation exige que tous les fichiers soient refermés explicitement dès qu'ils ne sont plus utilisés, si on ne veut pas qu'ils soient hérités implicitement ouverts par un autre processus.
Ce qui paraît donc finalement normal après réflexion peut se révéler extrêmement gênant, et source de nombreuses erreurs. Considérons en effet que le constructeur crée et associe à l'objet une ressource externe du système, par exemple un ensemble de sémaphores, une mémoire partagée ou une file de messages. Contrairement à un fichier qui peut être "effacé" par unlink() dans un processus alors qu'il continue à être accessible par un autre, un ensemble de sémaphores par exemple ne peut être détruit plusieurs fois. Le second processus qui tentera de détruire la ressource provoquera alors une erreur de la fonction système utilisée.
Nous proposerons des solutions à ce problème en temps utile.
Si le processus père déroutait d'autres signaux, il serait nécessaire de placer l'appel de la fonction wait3() dans un bloc try-catch, puis de la relancer au cas où elle serait interrompue, comme le montre le corrigé, à titre purement indicatif.
En conséquence :
if (Status & 0x80) |
Deux versions de corrigé sont données. Dans la première, le père ignore le signal SIGCHLD. De ce fait, le fils farceur ne fait rire que lui ... Le père se termine lorsqu'il est sorti 3 fois de la fonction wait() lui renvoie 0, ce qui signifie qu'il n'a plus de fils vivant.
La seconde version est plus subtile et illustre le fonctionnement caché du système : le signal SIGCHLD est détourné dans le père, donc celui-ci reçoit bien quatre fois ce signal, un de chacun des deux premiers fils et deux du troisième fils. On remarque cependant que la fonction wait() n'est interrompue qu'une seule fois par le signal, lorsque SIGCHLD est directement émis par le fils. En revanche, lorsqu'un fils meurt, c'est le système qui délivre SIGCHLD, et la fonction wait() s'exécute complètement sans erreur, le paramètre status étant alors correctement initialisé.
exo_15a arg1 e* arg2|cat>toto |
on peut s'attendre à avoir l'affichage suivant :
Nombre d'arguments : 4
Ligne de commande : exo_15a arg1 e* arg2|ca>toto |
En réalité, rien ne s'affiche.
En relançant la même commande par l'historique des commandes (!! ou !e ou la flèche montante), la commande se réaffiche ainsi :
exo_15a arg1 e* arg2 | cat > toto |
Le shell a analysé la ligne, l'a transformée et l'a interprétée : le résultat de exo_15a a été redirigé vers la commande cat qui l'a elle même redirigé vers le fichier toto, dont le contenu est par exemple le suivant :
Nombre d'arguments : 6
Ligne de commande : exo_15a arg1 exo_01.cxx exo_02.cxx exo_03.cxx arg2 |
qui montre que le paramètre e* a été développé en "tous les fichiers commençant par e".
Pour obtenir ce qui était attendu initialement, il faut faire précéder chaque caractère "bizarre" (on dirait plutô "spécial" !) du caractère d'échappement \ :
exo_15a arg1 e\* arg2\|cat\>toto |
Puisque tout retour d'une fonction de la famille exec...() est une erreur, il n'est pas nécessaire de tester la valeur de retour : une exception peut directement être levée. Pour éviter ce travail au développeur, il serait préférable de faire un wrapper comme cela a été fait pour les autres fonctions système. Cependant, deux raisons nous ont poussé à ne pas le faire :
© D. Mathieu
mathieu@romarin.univ-aix.fr
I.U.T.d'Aix en Provence - Département Informatique
Créé le 10/09/2001 - Dernière mise à jour : 21/09/2001