 Bonjour, aujourd'hui nous allons étudier les processus sous Unix.
 Les processus, je vous rappelle, c'est une entité active du système qui correspond à l'exécution d'un programme binaire.
 C'est un programme qu'on a chargé via le shell, ou sur lequel on a double-cliqué.
 À ce moment-là, le programme est chargé en mémoire et commence à s'exécuter.
 Chaque processus est indépendant et l'ensemble des processus sont en compétition pour accéder aux ressources de la machine.
 Les Unix fonctionnent en temps partagé, donc il y a une commutation pour passer d'un processus à un autre.
 Chaque processus est identifié de manière unique par un PID dans Unix.
 Il est caractérisé, c'est assez classique, par un contexte.
 Dans le contexte, je vous rappelle, il y a deux parties. Il y a le contexte mémoire, composé de trois zones, qu'on appelle les segments.
 Une zone de code dans laquelle on recopie le binaire du disque.
 Une zone de données dans laquelle on va retrouver les variables globales et l'allocation dynamique, les mallocs.
 Et une zone de PID dans laquelle on stocke en mémoire les variables locales.
 Il est caractérisé aussi par un contexte matériel, qui est l'ensemble des registres, qui permet de savoir où on se situe dans le code et où sont localisées les différentes zones.
 Ensuite, un processus est exécuté sur l'identité d'un utilisateur.
 Il y a ceci, un utilisateur, l'identifiant de l'utilisateur qui a lancé le programme.
 Il est caractérisé de manière unique par un UID.
 Par défaut, un processus s'exécute avec un certain UID, donc le nom de celui qui a lancé.
 Dans quelques cas particuliers, il peut s'exécuter sous un autre nom d'UID.
 Dans le cas particulier, où vous lancez des commandes qui ne vont pas s'exécuter sous le nom de l'utilisateur, mais sous le nom du propriétaire du fichier.
 Dans ces cas, par exemple, lorsque vous tapez la commande "password",
 la commande "password" a besoin de s'exécuter en tant que route, pour modifier ses passwords.
 À ce moment-là, il y a un bit spécifique. Si vous faites un "ls -l" de "password", vous allez voir qu'au lieu d'avoir un bit "x", qui veut dire que c'est un exécutable,
 vous avez un bit "s" qui est positionné, qui voudra dire qu'il devra s'exécuter sous le nom du propriétaire du fichier, mais pas sous le nom de l'appelant.
 Dans ce petit cas particulier, l'UID effectif est différent de l'UID réel du processus.
 De même, chaque processus est associé à une notion de groupe, et vous pouvez avoir des groupes réels ou effectifs.
 Enfin, chaque processus est associé à un répertoire courant.
 Juste pour revenir sur le contexte d'un processus, c'est un schéma qu'on avait déjà vu dans des cours précédents.
 Lorsque vous lancez un processus, l'exécutable qui est sur disque, le "a.route" qui est sur disque,
 que vous avez généré en faisant "gcc" par exemple, "gcc -o", lorsque vous le lancez,
 le système va vous allouer une zone mémoire dans laquelle il va recopier le binaire directement dans cette zone mémoire.
 Ensuite, il vous alloue deux autres zones mémoires, une zone mémoire qu'on appelle les données pour stocker les variables globales et le tas,
 là où on va stocker les mallocs, et enfin la pile dans laquelle on va stocker les variables locales et les appels de fonctions.
 La pile, dans la plupart des Unix, évolue d'une haute vers la basse.
 Elle augmente dynamiquement à chaque fois que vous définissez une nouvelle variable, on fait augmenter votre pile.
 Elle augmente et diminue à chaque retour de fonction.
 À chaque appel de fonction, votre pile va augmenter, et au retour de fonction, elle va diminuer.
 Toutes les variables manipulées par une fonction ne sont visibles que pendant la fonction,
 et comme on dépile, elles ne sont plus visibles à la fin de la fonction, elles deviennent invisibles.
 Donc, ces trois zones correspondent au contexte mémoire.
 Ensuite, vous avez le contexte matériel, qui est dépendant de la machine sur laquelle on s'exécute,
 mais on retrouve toujours un peu les mêmes informations, qui permettent de savoir où on en est dans l'exécution du programme.
 Notamment, vous avez le programme counter, qui indique la prochaine instruction exécutée,
 qu'on appelle par exemple un RIP dans le monde d'Intel, c'est le registre d'un pointeur d'instruction, en français.
 Vous avez aussi d'autres registres classiques, que l'on retrouve dans toutes les architectures,
 qui permettent de localiser le sommet de la pile, la base de la pile, etc.
 Donc, le contexte matériel permet de savoir caractériser l'évolution du programme en mémoire.
 Par exemple, pour illustrer ici, imaginons ce petit programme.
 C'est un processus avec un programme principal, qui manipule la globale compte,
 et qui appelle une fonction fou, avec un paramètre 10.
 Si je représente un instantané du contexte mémoire de ce processus,
 vous avez la zone de code dans lequel on retrouve l'exécutable.
 Pour simplifier, j'ai mis l'exécutable en C, mais bien sûr, ce n'est pas l'exécutable en C,
 c'est le code machine directement qui est ici.
 Ce sont les instructions de bas niveau qu'on retrouve ici.
 Toutes les instructions sont stockées ici.
 Vous avez le code, vous vous retrouvez dans la zone de données de la variable globale.
 Comme elle est de 5, cette variable globale est positionnée à 5.
 Et enfin, vous avez la pile.
 Ici, dans la pile, on a appelé la fonction fou,
 donc on a empilé sur la pile, lorsque vous appelez une fonction, vous empilez les arguments de la fonction,
 et l'adresse de retour, pour qu'ensuite, à la fin de la fonction fou,
 on puisse de nouveau ressauter à l'instruction qui suit, fou.
 L'argument est empilé, l'adresse de retour est empilée,
 et enfin, les variables locales manipulées par la fonction sont mises au sommet de la pile.
 Donc là, la pile est grossie de cette manière-là.
 À chaque appel de fonction, elle est grossie, et à chaque retour de fonction, on la fait rétrécir.
 Un processus peut être aussi dans différents états.
 Ça, c'est des choses qu'on a déjà vues, mais là, on va les spécifier pour les Unix.
 Il y a quelques états un peu différents par rapport à ce qu'on a vus dans les cours précédents.
 Donc, à la création d'un processus, c'est classique,
 lorsque vous créez un processus, il y a l'appel système "for" qu'on va voir par la suite,
 vous êtes créé à l'état "près".
 Donc, tous les états sont préfixés par "s", qui veut dire "status".
 Vous êtes à l'état "près".
 Ensuite, dans Unix, on ne distingue pas un état élu.
 Contrairement à ce qu'on a vu dans les autres cours, dans les Unix,
 on sait que parmi ceux qui sont à l'état "près", il y en a un qui est élu.
 Un ou plusieurs si on est en multicœur.
 Si on est en monocœur, il n'y en aura qu'un seul qui est élu.
 Donc, quand vous êtes à l'état "près", parmi ceux qui sont à l'état "près", vous pouvez vous bloquer.
 Donc là, il faut inverser les feuilles de blocage.
 Donc, lorsque vous êtes à l'état "près", si vous vous endormez,
 si vous vous bloquez, par exemple, en cas d'entrée/sortie, une saisie clavier,
 ou un entente de paquet sur le réseau,
 dans tous ces cas-là, vous basculez à l'état "sleep", l'état "bloqué",
 et vous serez réveillé dès que l'événement que vous avez attendu est arrivé.
 Donc, lorsque l'entrée/sortie est terminée, vous êtes rebasculé à l'état "près".
 Il y a un autre état aussi qu'on distingue de l'état "près" qui est l'état "stoppé".
 Donc, ici, c'est un état qui n'existait pas dans les tout premiers RUNIX,
 mais qui a été introduit assez rapidement, dans lequel un processus,
 même alors qu'il ne demande pas d'entrée/sortie, il ne demande rien, il ne se bloque pas,
 on a quand même la possibilité de le bloquer en lui envoyant un signal spécifique, qui est le signal "stop".
 Donc, lorsque vous lui envoyez le signal "stop",
 ce qui est généré par exemple lorsque vous faites un "Ctrl+Z" sur le "shell", sur le "bash",
 ça vous génère un signal "stop".
 À ce moment-là, votre processus, qui était prêt, qui était en train de s'exécuter, bascule à l'état "stoppé".
 Il sera re-injecté à l'état "près" que lorsque vous lui renverrez le signal "content",
 qui est l'inverse du signal "stop".
 Donc, vous le suspendez en lui envoyant un signal "stop"
 et vous pouvez le remettre à l'état "près" en lui envoyant le signal "content".
 Donc, on le distingue de l'état "bloqué",
 parce que là, le processus n'a rien demandé, il ne fait que du CPU,
 mais c'est de manière extérieure qu'on le bloque.
 Ensuite, un processus est à l'état "près" lorsqu'il se termine,
 lorsqu'il arrive soit sur la dernière instruction de son main ou lorsqu'il appelle la fonction "exit".
 On va revoir ça dans la suite.
 Il n'est pas directement évacué du système.
 Lorsque vous appelez "exit", aussi c'est une différence par rapport à ce qu'on a vu,
 le contexte mémoire va être détruit.
 Donc, les trois zones qu'on a vues, le code, la pile et les données vont bien être détruites de la mémoire,
 donc ça, il n'y a pas de souci.
 Par contre, on maintient un petit peu d'informations sur ce processus.
 Pourquoi ? Parce que lorsque vous êtes créé, celui qui vous a créé,
 il a peut-être besoin de savoir des informations sur "est-ce que vous êtes bien terminé ?"
 "Combien de temps c'est pu vous avez consommé ?"
 Donc, on maintient le processus, même s'il s'est terminé, à l'état "zombie", mort-vivant,
 tant que celui qui vous a créé, ce qu'on va appeler le père, n'a pas récupéré des informations sur vous.
 Donc, il pourra récupérer des informations sur vous en faisant les appels "wait", qu'on va voir aussi.
 Donc, tant que le père n'a pas fait "wait", tant que son père n'a pas fait "wait", on est maintenu à l'état "zombie".
 C'est un état dans lequel on maintient très très peu d'informations,
 mais suffisamment d'informations, on maintient notamment l'état de sortie du processus,
 "est-ce qu'il s'est bien terminé ?" et "combien de temps c'est pu lui avoir consommé ?"
 Et dès que ces informations sont récupérées par le père, à ce moment-là, il est évacué, il n'existe plus dans le système.
 Donc, le système maintient toutes les informations sur les processus dans une table.
 Donc, cette table, elle est présente en mémoire, elle est créée au démarrage du système d'exploitation,
 mais dans l'espace du système d'exploitation.
 Je vous rappelle, on distingue deux espaces, l'espace dans lequel il y a les variables du système d'exploitation,
 et l'espace pour vos processus avec les trois zones.
 Donc, dans l'espace du système d'exploitation, il y a une table, qui est une variable globale en fait,
 une table PROC, dans laquelle le système regroupe toutes les informations sur les processus.
 Vous n'avez pas accès à cette table lorsque vous êtes en mode utilisateur.
 C'est juste le système d'exploitation qui peut exploiter ces informations qui se trouvent dans cette table.
 Donc, dans cette information, pour chaque processus, on va stocker toutes les informations dont je vous ai parlé.
 L'ensemble des registres qui caractérisent l'UID, le PID, le JID, l'état du processus,
 sont à l'arrière, bloqués, stoppés.
 Toutes ces informations, pour chaque processus, sont maintenues par le système d'exploitation.
 Lorsque je bascule, par exemple, un processus P1 et un processus P2,
 il suffit que je sauvegarde les registres de P1 et que je restaure les registres de P2.
 Donc, le système a une copie de l'UID de tous les processus,
 et il peut facilement basculer d'un processus à l'autre.
 C'est ce qui se passe lorsqu'on fait une commutation.
 Ces structures s'appellent habituellement une structure PROC,
 mais c'est variable selon les UNIX. Dans Linux, ça ne s'appelle pas PROC, ça s'appelle TASC.
 Dans tous les UNIX, il y a une structure équivalente.
 Maintenant, on s'intéresse ici, dans ce cours-là,
 plus sur l'aspect "comment on programme ses processus".
 Un processus est caractérisé par un programme principal, un main.
 Ça, c'est des choses que vous connaissez bien.
 Il y a un certain nombre d'inclus d'un mètre lorsque vous manipulez un processus.
 Il y a notamment six types dans lesquels on stocke le PID.
 Un PID d'un processus, c'est un entier, qui est de type PIDT.
 PIDT, c'est un "SAM Type Def" de Inta.
 Ça se manipule exactement comme un entier.
 Dans les fonctions qu'on va voir par la suite,
 il faut mettre au moins ces deux includes, "unistd" et "lib",
 donc "unix" standard et la librairie standard,
 dans lesquelles il y a le prototype de la plupart des fonctions qu'on va voir par la suite.
 Lorsque vous définissez un processus, il faut définir un programme principal.
 Par défaut, lorsqu'un processus va être élu,
 on va exécuter la première instruction du programme principal.
 Le programme "printer" va être positionné à la première instruction du main,
 qui se trouve dans le main.
 Le main peut retourner un int.
 Lorsqu'un processus se termine,
 il suffit de faire un "return" dans le main.
 La valeur retournée a une certaine sémantique.
 On ne retourne pas n'importe quelle valeur.
 Si tout s'est bien passé, vous devez retourner 0.
 C'est ce qui est défini dans "SysType".
 Lorsque vous retournez 0, c'est que tout s'est bien passé.
 Si vous retournez autre chose que 0, c'est qu'il y a eu un problème.
 Il y a aussi un "just define exit failure", qui vaut 1.
 C'est important, il ne faut pas retourner 1 si tout s'est bien passé.
 Si tout s'est bien passé, vous retournez 0.
 Sinon, vous ne retournez pas autre chose que 0.
 C'est la valeur de retour.
 Ensuite, vous avez un certain nombre d'arguments à votre processus.
 Le nombre d'arguments est indiqué par le paramètre "arc_c", qui est un intérieur,
 qui compte le nombre d'arguments en comptant l'exécutable.
 Par exemple, si vous faites "ls f1", le nombre d'arguments sera 2,
 puisqu'on compte le nom de la commande "ls".
 C'est le nombre total d'arguments, l'exécutable compris.
 Ensuite, vous avez la liste des arguments,
 qui est un tableau de chaîne de caractère.
 Éventuellement, vous pouvez manipuler également les variables d'environnement.
 Dans nos exemples, on ne manipulera plus les variables d'environnement.
 Dans les arguments, dans ce tableau de chaîne de caractère,
 dans "arc_v0", vous avez le nom de l'exécutable,
 "arc_v1" correspond au vrai premier argument,
 "arc_v2" le deuxième argument, etc.
 Jusqu'à "arc_v2_arc_c-1", qui correspond au dernier argument,
 et enfin "arc_v2_arc_c_bomul".
 Ensuite, vous avez votre main,
 vous lancez un processus main.
 Ce processus peut lui-même, en C, créer un autre processus,
 en faisant un appel système qui s'appelle "forc".
 On peut créer ce qu'on appelle une arborescence de processus.
 Vous avez lancé un main, un processus classique,
 tel que vous le faites habituellement.
 Ce processus lui-même peut créer un autre processus,
 qui lui-même va créer un autre processus, et ainsi de suite.
 On fait l'analogie avec la famille.
 On dit que le processus qui crée, c'est le père.
 Donc, le P1 qui a créé un processus devient le père de P2.
 Si P2 crée un autre processus, il devient le père de P3.
 Et donc, le petit-fils de P1.
 C'est le cas pour tous les processus qui sont créés dans les Unix.
 Donc, on s'aboutit à une arborescence de processus.
 Au départ, vous avez le processus...
 Le seul qui n'a pas de père, c'est le processus 0.
 C'est le processus racine qui est créé au boot de la machine.
 On crée le processus 0, qui va créer le processus 1,
 et 1 va créer les autres processus.
 Donc, l'ancêtre commun à tous les processus, c'est le processus 1.
 Le 0 aussi, mais on va l'utiliser plus tard.
 Donc, le processus 1 va créer un processus qui accueille le login.
 Pour chaque terminaux, dès qu'on se logge, on lance login,
 qui va lancer "check", qui va lancer vos commandes.
 Donc, on peut retrouver une arborescence de processus.
 Comment on crée, depuis un main, un autre processus ?
 C'est via l'appel système FORK.
 FORK, qui veut dire fourche.
 On peut faire l'analogie avec une division cellulaire.
 C'est comme une cellule qui va se diviser en deux.
 Pareil, vous avez votre programme qui va se diviser.
 On peut faire une analogie avec la biologie.
 On va faire une division, ce qu'on appelle une fourche, un FORK.
 Une fois que vous avez créé votre processus fils,
 les deux vont être de nouveau en compétition pour accéder à la machine.
 Vous voyez que l'interface est minimaliste.
 C'est juste FORK, aucun paramètre,
 et ça vous retourne l'identifiant d'un processus, un PID, un entier.
 Ce qui est important à comprendre, ce qui n'est pas naturel,
 c'est qu'on fait une création par duplication.
 Le père se duplique en un fils.
 Cela veut dire que lorsque vous vous appelez FORK,
 on crée un processus fils, donc on lui crée un contexte mémoire,
 dans lequel on retrouve nos trois zones, le code, la pile et les données,
 et on va copier toutes les variables du père dans le fils.
 Toutes les variables locales sont copiées dans la pile du fils,
 toutes les variables globale et le tas sont recopiées dans le fils.
 La seule chose qui n'est pas copiée, c'est le code.
 Le code, c'est normal, car il est accessible uniquement en lecture.
 Ce n'est pas gênant d'être plusieurs à lire la même zone mémoire.
 Seul le code n'est pas copié, tout le reste est copié.
 L'impact de ça, c'est que si le processus, une fois que vous l'avez copié,
 chacun vit sa vie.
 C'est-à-dire que si le processus fils modifie une variable locale ou une variable globale,
 cette modification ne sera pas visible par le père,
 parce que la pile et les données ne sont pas partagées.
 De même, une fois qu'il a fait le FORK,
 après le FORK, s'il modifie une variable de sa pile ou de ses données,
 ces modifications ne seront pas visibles dans le fils.
 Donc, une fois que le FORK est fait, chacun vit sa vie,
 chacun modifie ses propres variables, qui restent dans son propre contexte.
 Le fils ne modifie pas les variables du père.
 De même, le père ne modifie pas les variables du fils.
 C'est important de comprendre ça.
 Donc, lorsque vous appelez FORK,
 on va voir un exemple juste après, pour mieux comprendre.
 Quand on appelle FORK, le père et le fils se retrouvent à l'instruction qui suit directement le FORK.
 Donc, père et fils se retrouvent juste à l'instruction qui suit le FORK.
 Ils ont le même code, et chacun une copie différente de la pile.
 Donc, ils ont les mêmes variables avec les mêmes noms, le même contenu, sauf que c'est une copie.
 Donc, il faut qu'ils se distinguent, est-ce que je suis le père ou le fils.
 Généralement, on veut que le père et le fils fassent des actions différentes.
 Si on a fait FORK, c'est qu'on veut que le fils fasse autre chose.
 Pour pouvoir distinguer si je suis l'original ou la copie,
 vous avez comme une division cellulaire, deux cellules avec le même état,
 et vous voulez qu'une des cellules fasse autre chose.
 Donc, on va tester pour pouvoir distinguer si vous êtes l'original, le père ou le fils.
 On distingue en fonction de la valeur de retour du FORK qui va être différente si vous êtes le père ou le fils.
 Donc, le FORK, il vous retourne un entier, un PID.
 La sémantique est la suivante.
 Si ça vous retourne 0, ça indique que vous êtes le fils.
 Donc, au fils, ça lui dit 0, ça ne veut pas dire que vous êtes le PID, le processus 0.
 Attention, 0 c'est réservé de toute façon.
 Ce n'est pas possible de créer un processus avec un PID 0.
 Si ça vous retourne 0, ça veut simplement dire que vous êtes le fils.
 Si ça vous retourne un PID qui est supérieur à 0,
 à ce moment-là, ça vous indique que vous êtes le père,
 et la valeur retournée, c'est directement au nom de vos fils.
 Donc, au fils, ça lui dit 0, tu es le fils,
 et au père, ça lui retourne le nom de son fils.
 Et en cas d'erreur, ça retourne -1.
 Une erreur est possible.
 En TME, ça va vous arriver.
 Typiquement, si vous vous mettez à boucler sur un FORK,
 vous allez créer des milliers de processus,
 et au bout d'un moment, vous allez saturer la table des processus qu'on a vu tout à l'heure.
 Lorsque la table des processus est pleine,
 le FORK vous retourne -1 pour dire qu'il y a trop de processus dans le système.
 Donc, il faut toujours tester si le FORK ne retombe pas -1.
 En plus de ces primitives,
 vous avez d'autres primitives qui permettent d'obtenir le PID.
 Notamment, le fils ne lui connaît pas son PID.
 Lorsque vous êtes créé, ça lui a simplement dit "tu es le fils" en lui retournant 0.
 Donc, si le fils veut connaître son PID,
 il peut faire l'appel GET_PID qui le revoit directement son PID.
 Si vous voulez connaître le PID de votre père,
 vous pouvez faire GET_PIPD, le P de parent.
 Un petit exemple.
 Imaginons que je vais créer un processus fils
 et chacun va faire un petit affichage.
 Au départ, le fils n'est pas créé.
 Vous avez votre main.
 J'ai représenté en bleu le père et en rouge le fils.
 Seul le père existe au départ.
 Il a une variable A qui vaut 10, qui est définie dans sa pile.
 Puis, il appelle FORK.
 Il stocke la valeur de retour de FORK dans P.
 Quand vous appelez FORK, ça va créer le processus fils.
 Le père et le fils vont tous les deux se retrouver dans leur espace,
 dans l'instruction qui suit le FORK.
 Le père et le fils vont arriver dans le IF.
 On va faire l'hypothèse que le FORK n'a pas échoué.
 S'il a échoué, si jamais P vaut -1,
 c'est que le FORK n'apparaît pas ici.
 À ce moment-là, PERA, c'est print error.
 Ça vous donne un petit dérègne de site d'erreur.
 C'est une bonne habitude de faire un PERA.
 Ça va vous dire pourquoi la table est pleine.
 La PERA vous affiche ce message, plus un diagnostic d'erreur.
 Là, vous voyez bien que je fais un return 1 et non pas 0,
 parce que mon programme a eu un problème.
 En cas d'erreur, je fais un print error,
 je retourne 1 pour dire qu'il y a eu un souci.
 Normalement, si tout se passe bien,
 le père et le fils se retrouvent ici.
 Ils vont faire le test.
 Le test P=0 va être vrai uniquement pour le fils.
 Je vous ai dit FORK retourne 0 uniquement au fils.
 Au père, c'est le retour de PID de son fils.
 Seul le fils va faire cette portion de code.
 Si cette condition va être vraie pour le fils,
 à ce moment-là, on voit que le fils incrémente sa variable A.
 Attention, il incrémente sa copie de la variable A.
 Toutes les variables ont été dupliquées dans le fils.
 A, pour lui, va valoir 11,
 puisqu'il a les 10, maintenant sa copie vaut 11.
 Puis, il affiche "je suis le fils", son PID,
 le PID du père et ainsi que la variable A.
 Il va afficher "je suis le fils",
 là, c'est son PID, mon père, c'est celui-là,
 et ma variable A, on voit bien qu'elle vaut 11 ici.
 Par contre, le père lui fait le test.
 Le test va être faux pour lui, donc il va exécuter cette partie-là du code.
 Le père va exécuter cette partie-là du code.
 Il affiche "je suis le père de",
 et là, il affiche la valeur qui lui a été retournée par le forc.
 Vous remarquez bien que c'est bien le même PID.
 Ça correspond bien au PID du fils qui a été créé,
 qui lui a été retournée par le forc.
 Par contre, quand il affiche la variable A,
 vous voyez bien que pour lui, la variable A vaut toujours 10.
 Si vous suivez l'exécution du père depuis le début,
 A vaut 10 à aucun moment.
 Si on suit l'exécution de le, le A n'a été modifié.
 Donc A vaut toujours la valeur initiale.
 La modification qui a été faite, ça a été fait dans l'espace du fils.
 Donc vous voyez, A vaut 10 pour le père, alors qu'il vaut 11 pour le fils.
 Donc ça, c'est comment on crée un processus.
 Maintenant, pour terminer, ça c'est une instruction que vous connaissez,
 qu'on a déjà vue, c'est la fonction exit.
 Donc exit, c'est une fonction de la librairie standard,
 il faut faire un include stdlib.h.
 Vous faites exit, ça ne retourne rien.
 Après exit, vous passez à l'état zombie.
 Et par contre, vous passez en paramètre une valeur
 qui pourra être récupérée par le père.
 Donc ça c'est une valeur pour indiquer que par défaut,
 on fait exit 0 pour dire que tout s'est bien passé,
 et exit autre chose que 0 pour dire qu'il y a eu un problème.
 Donc quand vous faites exit dans le main,
 c'est équivalent à faire return val dans le main.
 Par contre, vous pouvez appeler exit depuis n'importe quelle fonction.
 Donc exit val ou return val, c'est la même chose lorsque c'est dans le main.
 Et après, quand c'est au part du main, ça détruit le processus.
 Donc lorsque vous faites exit, c'est le grave d'état qu'on avait vu tout à l'heure,
 le processus est détruit en mémoire,
 ces trois zones vont être vraiment détruites de la mémoire.
 On libère la mémoire de sa pile et ses données,
 son code n'est pas forcément libéré puisqu'il peut être partagé par un autre processus,
 mais au moins c'est sûr que toutes ses variables sont détruites de la mémoire.
 Puis, il n'est pas complètement détruit, il est maintenu à l'état zombie.
 Il passe à l'état zombie,
 et donc le processus qui fait exit passe à l'état zombie,
 et il restera à l'état zombie tant que le père n'a pas pris connaissance de sa terminaison.
 Donc, on va voir maintenant.
 Le wait, c'est un petit outil de synchronisation
 qui permet à un père d'attendre la fin d'un fils.
 D'un seul fils, attention, puisqu'on a fait un exemple tout à l'heure
 dans lequel on créait un processus, mais on peut en créer plusieurs.
 On peut faire une boucle, c'est ce que vous ferez en TME,
 dans laquelle vous allez créer plusieurs processus.
 Donc, l'appel système wait,
 il faut mettre six includes pour mettre,
 donc il faut mettre six types et six waits dedans.
 Donc, ça retourne un entier, un PID,
 et ça passe en paramètre de retour aussi, un statut.
 Wait, ça retourne deux choses.
 Ça bloque le père tant qu'un de ses fils ne s'est pas terminé.
 Dès qu'un de ses fils est terminé, vous êtes débloqué.
 Vous attendez la fin d'un processus, d'un seul.
 Donc, si vous voulez attendre la fin de tous vos processus,
 il faut faire plusieurs waits.
 Il n'y a pas de notion de boucle dans le wait.
 Donc, la valeur de retour de wait, c'est le PID du processus qui s'est terminé.
 Ou, si jamais vous faites wait alors que vous n'avez jamais créé de processus,
 à ce moment-là, ça vous retournera -1.
 Si vous faites un wait alors que vous n'avez pas fait de forme,
 ça va vous retourner -1.
 Donc là, vous connaissez le PID du processus qui s'est terminé,
 mais vous pouvez également récupérer l'état dans lequel il s'est terminé,
 la valeur de son exit ou la valeur de son return dans le main.
 Donc, dans status, ça va vous stocker la valeur de son exit.
 C'est pour ça qu'on le met en init*, on passe par référence.
 C'est un paramètre qui est modifié par le wait.
 Donc, on sait lorsque vous voulez modifier un paramètre dans une fonction,
 vous êtes obligé de passer son adresse.
 C'est ce qu'on appelle un passage par référence.
 Donc ça, c'est l'état du processus, la valeur de son exit ou du return dans le main.
 Attention, cette variable ne peut pas être manipulée directement.
 Si vous faites un printf de cette variable, vous allez voir des valeurs un peu aberrantes.
 Pour manipuler cette variable, il y a deux macros qui sont définis dans SysWait.
 Il y a le if_exit_status qui permet vraiment de récupérer la valeur de retour.
 Et il y a aussi le macro qui vous indique s'il y a eu une fin normale.
 C'est le wait_if_exit_status.
 Ce sont des macros. Par défaut, les macros, on les met toujours en majuscule.
 C'est pour ça qu'on les met en majuscule.
 Le plus simple, c'est de voir un petit exemple.
 Dans ce petit exemple, on va créer un processus "Sys" et on va attendre directement la fin.
 Ici, j'appelle "fork", je récupère la valeur de retour de "fork" dans "P".
 Si je suis le fils, dans le cas du fils, "P" va être égal à 0.
 Dans les exemples suivants, je suppose que le "fork" n'a pas échoué.
 Je ne teste pas les -1.
 Mais dans vos codes à vous, il faudra bien tester les -1.
 Si je suis le fils, je fais exit 2.
 Sinon, je suis le père. Le père va attendre la fin d'un de ses fils.
 Dès qu'un de ses fils est terminé, il va être débloqué.
 On récupère l'identifiant de ce fils dans la variable "D"
 qui est le type "P" et "D" "T".
 Et dans la variable "état", la valeur de son exit.
 Là, j'appelle "if_exited".
 Cette fonction sera fausse si jamais le fils a eu un problème.
 Par exemple, il a fait une division par 0, ou des choses comme ça.
 Il a généré une vraie erreur pendant son exécution.
 Donc, s'il n'a pas eu de problème, ça va afficher l'état de sortie de mon fils.
 Ça affiche le pilier de mon fils que j'ai récupéré ici, le paramètre "D".
 Et directement son état de sortie, la valeur 2, en appliquant la macro ici.
 "where_exit_status".
 Vous remarquez ici que j'ai passé la variable "e", je l'ai allouée,
 dans laquelle je vais stocker son état, et je passe sa référence.
 Je passe son adresse en mettant "e" de "e".
 Ça évite de faire un malloc, vous allouez directement votre variable "e"
 et vous passez son adresse ici.
 Donc à la fin, vous voyez, vous allez avoir la fin du fils, le temps, à l'état 2.
 Donc on récupère l'état ici.
 Ensuite, là, je vous ai dit, pour illustrer le côté zombie,
 c'est ce qu'on vous demande à un moment en TME,
 comment créer un process zombie ?
 Parce que dans l'exemple précédent, ici, si je reviens en arrière,
 le processus, dès qu'il fait "exit", le père fait directement "wait".
 Donc il passe à peine à l'état zombie.
 Il passe à l'état zombie, mais on n'a pas le temps de le voir.
 Parce que dès qu'il fait "exit", le processus va être élu,
 il va faire le "wait", et du coup, il va être évacué du système.
 Ici, par exemple, vous voulez visualiser un zombie,
 vous voulez créer un zombie pendant environ 30 secondes.
 Donc comment faire ?
 Là, il suffit de faire pareil.
 Là, je crée un processus "fils".
 Le processus "fils" se détruit tout de suite.
 Dès qu'il fait "exit", il passe à l'état zombie.
 Mais je retarde le père.
 Je lui fais attendre 30 secondes en faisant un "sleep" 30.
 Donc il attend 30 secondes avant de faire son "wait".
 Si pendant ce temps-là, vous faites la commande "ps",
 là, vous allez voir qu'il y a un processus à l'état zombie.
 Dans Linux, par exemple, ils sont affichés comme des processus "defunt",
 qui viennent zombier pendant 30 secondes.
 Donc là, la question qu'on pourrait se poser,
 qu'est-ce qui se passe si, par exemple, mon processus fait "exit"
 et que mon père, lui, oublie de faire le "wait".
 Par exemple, ici, il ne fait rien du tout.
 Il fait "return 0".
 Donc dans ce cas-là, il ne faut pas que le processus zombie reste zombie indéfiniment.
 Si jamais le père n'est plus présent au moment où je fais l'"exit",
 ou plus tard,
 dès que mon père se détruit, alors sans qu'il ait fait d'"exit",
 à ce moment-là, on dit que mon processus va être adopté.
 Il devient orphelin, on dit qu'il est orphelin, il n'a plus de père, en fait.
 Et il est adopté par le processus "init",
 le processus numéro 1 qu'on a vu tout à l'heure.
 Donc si jamais mon processus n'a pas de père alors que j'ai fait "exit",
 à ce moment-là, c'est le processus "init" qui va faire le "wait" à la place de mon père.
 Donc les zombies ne vont pas rester dans le système.
 Il y a d'autres fonctions qui permettent aussi d'attendre un processus.
 Donc là, il y a la fonction "wait_pid".
 La sémantique est proche du "wait".
 Ça attend la fin d'un fils, mais d'un fils particulier.
 Donc on voit ici dans "wait_pid" qu'on passe en paramètre en plus.
 Il y a un paramètre supplémentaire qui est le "pid" du processus qu'on attend.
 Pareil, en récupérant l'état de sortie.
 Et quelque chose d'intéressant dans "wait_pid", c'est qu'on peut préciser un certain nombre d'options.
 On va revenir dessus.
 Donc le "pid", qu'est-ce que vous pouvez...
 Donc généralement, la plupart du temps, vous passez un "pid".
 Lorsque vous passez un "pid" qui est supérieur à 0,
 ça veut dire que vous vous attendez le processus de tel "pid".
 Si jamais vous passez 0, ça fait comme un "wait".
 Vous attendez n'importe quel processus dans le même groupe,
 c'est-à-dire qu'il est associé au même terminal que la plan.
 Donc n'importe quel processus fils qui est associé au terminal de la plan.
 Ou alors, lorsque vous passez -1, c'est n'importe quel processus fils.
 Et enfin, lorsque vous passez -2, -3,
 ça c'est des choses qu'on n'a pas le temps de voir,
 mais c'est pour pouvoir attendre des processus qui sont associés à un groupe particulier.
 Donc ça, on n'a pas le temps de le voir, je préfère ne pas le dire.
 Faites des man sur "wait_pid" pour avoir plus d'informations là-dessus.
 Donc la plupart du temps, ce qui est intéressant, c'est de le placer...
 soit de l'appeler en faisant un "wait_pid", soit en mettant 0.
 Et dans ce cas-là, on peut mettre une option aussi.
 Donc si vous mettez 0 ici, c'est qu'il n'y a pas d'option,
 c'est le comportement du "wait" normal.
 Par contre, une option intéressante, c'est une option non bloquante.
 Si vous mettez l'option qui est définie dans "sys_wait",
 l'option "no hang", ça indique que l'appel est non bloquant.
 Ça permet au père...
 C'est que le problème du "wait", si vous faites un "wait"
 et que votre fils n'est pas terminé, le père va rester bloqué.
 Il passe à l'état bloqué, il attend.
 Des fois, ce qu'on a envie, c'est que le père veuille juste tester
 "est-ce que mon fils est terminé ?"
 S'il ne s'est pas terminé, j'ai envie de faire autre chose.
 Donc "no hang", ça permet de faire un appel à "wait" sans être bloqué.
 Donc si vous mettez... on va voir un exemple tout de suite.
 Donc si l'option...
 Après, la valeur de retour dépend de l'option que vous avez mis.
 Par exemple, si vous mettez l'option "no hang",
 à ce moment-là, ça va vous retourner bloquant.
 Ça va vous retourner, pardon, 0.
 Si jamais aucun... ça ne vous retournera pas d'erreur.
 Ça vous a un retour de 0 si le processus ne s'est pas encore terminé.
 Donc si vous appelez "wait_pid" en mettant l'option "no hang",
 ça vous retourne 0, ça veut dire que le processus ne s'est pas encore terminé.
 À ce moment-là, le processus peut faire autre chose.
 Sinon, s'il s'est terminé, comme avant,
 ça vous retourne directement le "pid" du processus qui s'est terminé.
 Pour commencer, on va voir un petit exemple.
 Donc ici, imaginons que je vais tester simplement "est-ce que mon fils s'est terminé ?"
 Je crée un fils.
 Je récupère la valeur du "pid".
 Donc là ici, on voit que je fais les deux choses en même temps.
 Donc je crée un processus.
 Donc ça, c'est classique de mettre ça.
 Vous créez un processus "fils".
 Vous mettez le résultat du fork dans la variable "pid"
 et vous testez son retour.
 Donc là, si ça vaut 0, c'est que vous êtes le fils.
 Donc le fils, qu'est-ce qu'il fait ?
 Il s'endort avant de faire un "sleep".
 Donc le père, lui, va faire le "else" ici.
 Donc dans le "else", il fait "wait pid",
 mais comme le fils n'a pas encore fait son exit,
 il s'endort pendant une seconde,
 donc lui, ici, le "wait pid" va lui dire
 "ah, le fils ne s'est pas encore terminé".
 Donc là, je mets l'option "no hang" pour dire
 "je veux juste tester s'il s'est terminé".
 Donc là, je n'ai pas envie de me bloquer.
 Je veux juste tester, est-ce que mon fils s'est terminé ?
 Donc vous mettez le "pid" du fils qui vous intéresse,
 la valeur de retour, éventuellement,
 si la valeur de retour ne vous intéresse pas,
 vous pouvez mettre "nul" ici,
 et vous passez l'option "no hang".
 Donc si à ce moment-là, le "wait pid" vous retourne 0,
 ça ne veut pas dire que c'est une erreur.
 -1, ça veut dire qu'il n'y a pas de fils.
 0, ça veut dire simplement qu'il y a un fils.
 Le fils, il existe, ce fils-là existe,
 mais il n'est pas encore terminé.
 Et sinon, on attend de manière classique.
 S'il s'est terminé, à ce moment-là,
 on teste s'il n'a pas eu de problème,
 et vous affichez sa valeur de retour.
 Donc là, si vous lancez ce petit exemple,
 vous aurez un résultat de ce type-là.
 Au niveau du fils, ça va afficher directement son "pid".
 D'accord ?
 Voilà, ici, c'est cet affichage-là.
 Normalement, le fils aurait dû faire un affichage ici.
 Il y a un printf qui manque,
 dans lequel il affiche son "pid",
 et le père va afficher que le fils n'est pas terminé.
 Un autre "wait" qui est intéressant,
 dont vous allez avoir besoin, notamment en TME,
 c'est un "wait" dans lequel on ne récupère pas uniquement l'état de sortie,
 mais on peut récupérer également des informations
 sur le temps consommé par le fils.
 Donc ça s'appelle le "wait3", qui permet de faire ça.
 Pourquoi 3 ? Parce qu'il a 3 paramètres, tout simplement.
 Donc le "wait3", c'est la même sémantique qu'un "wait",
 on va attendre la fin d'un fils,
 donc ça retourne le "pid" du fils qui s'est terminé,
 avec son statut, comme avant.
 Les options, c'est les mêmes options que pour "waitpid",
 c'est-à-dire qu'on peut éventuellement dire un "wait" non bloquant.
 Si vous mettez 0, c'est qu'il n'y a pas d'option particulière,
 c'est le comportement par défaut.
 Et, là-dessus, ce qui nous intéresse, c'est le troisième paramètre,
 qui est la structure "eruse_age",
 que vous avez manipulée déjà dans le premier TME,
 quand vous demandez d'afficher l'occupation mémoire.
 C'est la même structure qui est définie dans 6 ressources.
 Pour voir le contenu de ces structures-là,
 vous faites un "manget_eruse_age".
 Dans "eruse_age", il y a des informations sur l'occupation mémoire du fils,
 mais il y a également des informations sur le temps CPU
 qui a été consommé par le fils.
 Comme ça, on peut récupérer des informations de statistiques sur le fils,
 combien de temps il a passé en mode utilisateur,
 et combien de temps CPU a passé en mode système.
 C'est ces deux champs-là, "ru", "resource_usage", "utime", "user_time", "s_time".
 Là, c'est un code, c'est un struct.
 Le temps, en général, dans les UNIX,
 est manipulé par des structures TimeVal,
 qui ont deux sous-champs, des secondes, TimeVal_secondes,
 et des microsecondes.
 Alors attention, c'est des microsecondes, pas des millisecondes.
 Microsecondes, c'est 10 moins 6.
 Ne faites pas l'erreur.
 Par exemple, si je veux afficher un petit exemple,
 je veux afficher le temps consommé par mon fils en mode "u",
 une fois qu'il s'est terminé.
 Comment ça se manipule ?
 Je crée un processus "fils" qui fait un certain traitement,
 il va faire une boucle ici, et lorsqu'il a terminé, il fait "exit".
 Le père, qu'est-ce qu'il fait ? Il fait "wait".
 On passe "null" parce que je ne m'intéresse pas à son état de sortie,
 je ne veux pas récupérer sa valeur de sortie.
 Pas d'option particulière, ça veut dire que je suis bloquant,
 j'ai le comportement "par défaut".
 Et j'ai défini une structure "error_usage" directement,
 une variable de type "error_usage" dans laquelle je passe la référence.
 C'est une variable qui est remplie par la fonction "wait_true",
 c'est pour ça qu'il faut que je passe la référence.
 Pour ça, dans "wait_true", vous avez un struct "error_usage*",
 parce que c'est une variable de sortie qui est modifiée par "wait_true".
 Là, c'est le même principe, je l'alloue directement en variable,
 et je passe son adresse.
 "wait_true" va me remplir ce champ-là.
 Une fois qu'il est rempli, dès que le processus est terminé,
 je vais être débloqué, et j'affiche le temps, le champ.
 Là, ce qui m'intéresse, c'est juste le temps consommé en mode "u".
 J'affiche le temps "u_time",
 là, je récupère le nombre de secondes consommées en mode "u",
 c'est "r.r_u_time.tvsec",
 auquel je rajoute le temps en microsecondes,
 c'est le temps en microsecondes depuis la dernière seconde.
 Pour avoir le temps total, il faut sommer les secondes
 plus les microsecondes.
 Là, il a passé 388 000 microsecondes depuis la dernière seconde.
 Pour avoir le calcul, il suffit de convertir les microsecondes
 en secondes en les multipliant par 10^-6.
 10^-6 en C, vous mettez 1e-6, ça vous fait 10^-6.
 Ici, si vous avez cet affichage-là, ça va vous faire ça.
 Voilà, on a vu toutes les principales primitives de synchronisation.
 Maintenant, il y a un dernier type de primitives qui va nous intéresser.
 Ce sont les primitives qu'on appelle de recouvrement, les primitives de exec.
 Souvent, lorsque vous voulez créer un processus,
 vous voulez que le processus "phys" fasse carrément complètement autre chose.
 C'est ce qui se passe dans le shell.
 Le shell crée les processus "phys" et les "phys" exécutent d'autres commandes.
 Ils exécutent "ls", "vi", etc.
 On veut donc que le processus "phys" crée un nouveau processus "phys"
 mais qui va exécuter un nouveau main.
 On veut donc écraser son contenu.
 C'est donc l'exec qui fait ça.
 L'exec ne crée pas de nouveau processus.
 Ça écrase le contenu d'un processus.
 C'est pour ça qu'on appelle ça de recouvrement.
 Le processus exec va exécuter un nouveau main avec une nouvelle pile et une nouvelle donnée.
 Ça réinitialise complètement, ça écrase toutes les variables,
 ça change le code et ça change la pile et les données.
 Mais ça ne crée pas de processus.
 Par exemple, si un processus "p" appelle une fonction "exec",
 on va voir comment...
 C'est une fonction de type "execl".
 Un nouvel exécutable va être chargé toujours dans l'espace mémoire du processus.
 On écrase son ancien code par le nouveau code
 et on réinitialise sa pile et ses données.
 Donc toutes les variables que vous manipuliez avant n'existent plus.
 Vous exécutez un nouveau code.
 Par exemple, vous exécutez le code de "ls".
 Donc le processus, par contre, vous n'avez pas créé de nouveau processus.
 Quand vous appelez "exec", vous gardez le même PID,
 votre pair reste le même, l'utilisateur reste le même, le JID reste le même,
 même si il y avait des fichiers ouverts, il reste ouvert.
 Si vous aviez des signaux masqués, il reste masqué.
 Mais le code et les variables sont complètement écrasés.
 Il y a différentes primitives "exec" qui font la même chose,
 c'est juste que l'interface est différente.
 Il y en a un peu plus que ça, mais les quatre de base, ce sont ce qu'on appelle les "execl".
 Il y a deux grands types de primitives, "execl_l" pour liste et "exec_v" pour vecteur.
 Après, vous avez deux versions de ces primitives.
 Dans les "execl", vous passez en paramètres le nom de l'exécutable et la liste des arguments.
 Dans le "exec_v", c'est pareil, vous avez le nom de l'exécutable et un tableau qui contient la liste des arguments.
 C'est juste l'interface qui change.
 Il y en a un, on les liste, et l'autre, c'est un vecteur qu'on passe en paramètres.
 Ces deux fonctions, on peut leur ajouter un "p" derrière, le "p" de "pass".
 Ça va faire la même chose, sauf que votre exécutable va être recherché dans votre variable "pass".
 Par exemple, si je zoom sur le "execl", je redis ce que j'ai déjà dit, mais il y a des petites subtilités.
 Le "execl", ça vous retourne un int qui vaut -1 en cas d'erreur.
 Par défaut, un "execl", ça ne retourne pas.
 Si tout se passe bien, vous ne faites pas l'instruction qui suit un "execl", puisque ça écrase tout.
 Votre code est écrasé.
 Normalement, un "execl", ça ne retourne pas.
 Mais si jamais il y a un problème, ça va vous retourner -1.
 Par exemple, si vous passez un "execl" qui n'existe pas, ça vous retourne -1.
 Ici, vous avez le nom de l'exécutable, et vous listez les arguments.
 Attention, il faut lister les arguments depuis arc 0, qui est le nom de l'exécutable.
 Le vrai premier argument est là.
 On répète le nom de l'exécutable, puis le premier argument.
 On les liste les uns après les autres.
 Le piège, c'est de bien remettre de nouveau le nom de l'exécutable,
 et à la fin, aussi bien terminer par "nu", pour dire qu'on a terminé.
 On est arrivé sur le dernier argument.
 Par exemple, si je veux créer un processus "fizz" qui exécute la commande "ls -l f1",
 je crée un processus "fizz", j'appelle "execl", dans lequel je mets directement le nom de l'exécutable.
 Il faut que je connaisse le chemin de l'exécutable.
 "ls" se trouve dans "usr -ls".
 De nouveau, vous énumérez les arguments en commençant par le arc 0.
 Vous répétez le nom de l'exécutable, le premier argument, le deuxième argument,
 et on met bien "nu".
 Pensez à mettre "nu", sinon vous allez faire une erreur.
 Là, ça va écraser tout ce code par le main de "ls".
 Normalement, si vous arrivez dans l'instruction qui suit le "exec",
 il n'y a même pas besoin de tester le -1.
 Si vous arrivez à l'instruction 3, c'est qu'il y a une erreur.
 Vous pouvez directement faire un "p" erreur, un "exit 1" pour déclarer un problème.
 Normalement, on n'exécute jamais,
 donc je ne veux pas avoir de traitement autre chose que des erreurs après un "execl".
 "execl", c'est comme un super gros "tool" dans lequel vous allez exécuter un nouveau mail.
 On exécute normalement, sauf en cas d'erreur, on n'exécute jamais les lignes 3 et 4.
 Vous pouvez, à la place d'execl, utiliser "execlp".
 C'est la même chose, sauf que le "ls",
 si vous ne connaissez pas à quel endroit se trouve "ls",
 vous allez le rechercher dans votre variable d'environnement "pass".
 Là, par exemple, si vous faites la même chose, vous faites "execlp",
 il va chercher "ls" dans votre "pass", il va le retrouver dans le "service".
 Ici, c'était avec uniquement deux arguments, il n'y avait pas le -l.
 Si on voit la même chose avec le "execv" pour "vecteur",
 ça fait la même chose, c'est juste l'interface qui change.
 Là, pareil, on a "execv" ou "execvp" si on va chercher dans le "pass",
 dans lequel on passe le nom de l'exécutable,
 et un tableau de chaîne de caractère dans lequel il y a la liste des arguments.
 Ici, si je reprends le même exemple que tout à l'heure,
 vous allez appeler "execv", dans lequel vous mettez le nom de l'exécutable,
 et vous passez un tableau de carré-toile avec la liste des arguments.
 Là, pareil, en commençant toujours par le "arg0",
 premier argument, deuxième argument, et vous terminez de séparer le nul.
 Vous l'appelez "execl", et si vous faites l'instruction qui suit l'execl,
 c'est qu'il y a un problème, forcément.
 Enfin, il y a une dernière fonction qui existe,
 qu'on déconseille souvent d'utiliser parce qu'elle est très lourde,
 mais que vous allez utiliser dans le premier TME,
 dans le TME de la semaine prochaine, c'est la fonction "system".
 La fonction "system" est une fonction assez lourde,
 qui permet de créer une commande directement,
 qui va lancer un shell, qui va créer un processus shell,
 qui va lui faire lancer votre commande.
 Par exemple, dans les premiers TME, on vous demande d'utiliser ça,
 parce qu'on n'a pas encore fait en TD les "forc" et "exec",
 mais rapidement, il faut éviter d'utiliser "system" parce que c'est très lourd.
 Par exemple, si je fais "system sleep 5",
 en C, j'appelle ça depuis C,
 ça va créer un processus "fizz",
 donc ça va faire un premier "forc" qui va faire un "execl" pour lancer un shell.
 Ce shell va faire un deuxième "forc" qui va faire un "execl" pour lancer le "sleep".
 Vous voyez, là ça crée deux processus et deux "execls",
 donc c'est extrêmement lourd.
 C'est une propriété qu'on déconseille d'utiliser,
 il vaut mieux utiliser directement des "forc" et "execl" pour être plus efficace.
 Je crois que c'est tout, on a vu les principales fonctions.
 Merci.
