Remarques 1

    Il est assez rare d'avoir l'occasion de déclarer un identificateur à l'intérieur de la condition d'un schéma alternatif. C'est le cas ici, il faut donc en profiter :
 
    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.

Remarques 2

    La fonction C system() doit être utilisée avec parcimonie. Il faut en effet savoir qu"elle provoque un traitement lourd : elle crée un processus fils qui est chargé d'exécuter la commande, puis elle est en attente jusqu'à la fin de l'exécution de ce processus. Elle doit donc être réservée à l'exécution de commandes qu'il serait difficile de réaliser autrement.

    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.

Remarques 3

    Rappel : l'ajout du caractère \n dans une chaîne de caractères provoque, lors de son affichage, un passage au début de la ligne suivante. Dans \n, le caractère \ est appelé caractère d'échappement. Il apparaît aussi dans la succession \\, qui est la seule façon de faire apparaître un back-slash dans une chaîne.


    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.

Remarques 4

    En principe, le résultat de la fonction open(), qui est le file descriptor du fichier ouvert, est récupéré. C'est en effet le seul moyen d'effectuer des opérations sur ce fichier ouvert. Il est extrêmement rare que la fonction open() soit utilisée comme une procédure, c'est-à-dire sans que la valeur de retour ne soit récupérée. C'est pourtant le cas ici où la seule chose qui nous intéresse est qu'un fichier soit ouvert. De façon générale, ce cas se présente lorsqu'on désire qu'un nouveau processus, que l'on s'apprête à lancer (par la fonction execve() étudiée ultérieurement) hérite de certains fichiers ouverts. Aucune opération supplémentaire n'étant à faire sur ces fichiers, il est inutile d'en récupérer les file descriptors.

    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.

Remarques 5

    Il apparaît qu'un seul constructeur est appelé, ce qui est normal, mais que l'objet est détruit deux fois, ce qui est peut-être plus surprenant. La raison est simple : le code et les données ayant été dupliqués par la fonction fork(), il y a bien deux objets à détruire, l'un dans chaque espace mémoire virtuel, et le code d'appel du destructeur est lui aussi dupliqué.

    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.

Remarques 6

    La macro standard WEXITSTATUS() fait la même chose que GETSTATUS(). Vous pouvez le vérifier en remplaçant l'une par l'autre.

Remarques 7

    0n pourrait montrer comme dans l'exercice précédent que le signal SIGCHLD est bien reçu par le processus père mais qu'il fait sortir de la fonction wait3() sans l'interrompre.


    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.

Remarques 8

    Lorsque les deux processus sont stoppés simultanément, et que le père est réveillé après son fils, il ne garde pas trace de l'arrêt de son fils.

Remarques 9

    Après peu d'essais en général, on constate que le père reste bloqué dans la seconde boucle dès que les fils dorment pendant la même durée. Cela est dû à un phénomène déjà rencontré pendant le TP consacré aux signaux : les signaux ne sont pas empilés. Donc, lorsque les 5 fils meurent simultanément, le premier SIGCHLD provoque le déroutement du père. Immédiatement le second signal arrive et le flag "signal pendant" est positionné, mais les trois autres signaux SIGCHLD sont perdus.

    En conséquence :

"Il ne faut jamais utiliser les traîtants de signaux pour compter des événements"

Remarques 10

    Dans cette version, quel que soit le délai, le père est bien informé par le système de la mort de chacun de ses cinq fils.

Remarques 11

    Dans le fichier Autopsie.cxx remarquer l'instruction qui permet d'isoler le bit 7 de l'octet de gauche de a variable status pour savoir si un fichier core a été créé :
 
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é.

Remarques 12

    Lorsque la commande suivante est tapée :
 
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

Remarques 13

    Remarquer que le résultat de l'appel de la fonction Open() est directement utilisé comme paramètre effectif de la fonction Fcntl(), sans être récupéré puisqu'il n'a aucune utilisé dans le programme.


    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