6.4 fork
fork duplique une vie.
C'est un clonage !
Une tâche qui invoque
fork crée par scissiparité une seconde tâche en tout point semblable à
elle-même, à un unique détail près !
fork retourne à la mère le numéro de la tâche fille engendrée mais ne
retourne que zéro à la fille qui ignore donc, véritable orpheline,
qui est sa mère !
Les deux tâches continuent alors leur exécution en séquence après
l'appel à
fork(),et en parallèle entre elles.
Pour rester dans le domaine féérique,
fork est analogue au coup de hache que donne l'apprenti sorcier
[Goethe] au balai récalcitrant.
Il ne fait qu'obtenir deux balais rigoureusement identiques et programmés
pour le même travail.
fork est le seul moyen de créer une tâche.
L'aïeule commune à toutes est
init créée ex nihilo au lancement du système.
Pour lancer une tâche sur un programme on utilise un
fork suivi d'un
exec.
Comme ce qui suit :
#define ERREUR_FORK -1
fille=fork(); /* fourcher ! */
/* Il y a maintenant deux tâches qui
exécutent ce qui suit */
if ( fille != ERREUR_FORK )
/* le clonage s'est bien déroulé ! */
if ( fille == 0 )
/* Seule la fille exécute ce qui suit */
execl("programme", ... arguments ...);
/* et elle continuera par la première
instruction de programme */
/* Par contre ici ne peut passer que la tâche mère
qui imprime le numéro de sa fille (en décimal) */
printf(" ma fille est : %d",fille);
... suite ...
Au niveau de l'interprète de commandes, existent
& et
exec qui mettent en jeu les mêmes mécanismes.
Donnons un exemple plus réel montrant l'énorme consommation de tâches
de
sh.
Soit le fichier exécutable
cycle contenant :
programme &
{ sleep $1
kill -9 $?
exec cycle `expr $1 + 10`
}
et soit
sh<1>
(tous les noms de tâches seront indicées par le numéro de tâche
associée afin que l'on puisse suivre ces mêmes tâches se
transmutant et différencier celles qui ont un même programme support.)
l'interprète de commandes auquel vous soumettez
cycle 10
sh<1>
voit que
cycle est un programme à lancer aussi fourche-t'il (créant ainsi
sh<2>),
sh<2>
analyse
cycle,
et
sh<1>
se met en attente de la fin de
sh<2>.
sh<2>
substitue textuellement
$1 en
10 dans tout
cycle puis remarquant la présence d'un
&,
fourche en
sh<3>
qui analyse
programme tandis que, sans attendre,
sh<2>
poursuit l'analyse du reste de
cycle.
sh<3>
n'étant qu'un simple programme à lancer, se transmue en
programme grâce à
exec.
Pendant ce temps
sh<2>
substitue à
$? le numéro de tâche de
sh<3>
(qui est aussi celui de
programme<3>
après l'exec),
constate que c'est une séquence d'appels, fourche donc, en conséquence et
en
sh<4>,
destiné à analyser
sleep 10
tandis que
sh<2>
diffère l'examen de
kill -9 sh<3>
exec cycle `expr 10 + 10`
en attendant la fin de
sh<4>.
sh<4>
execute
sleep,
attend 10 secondes puis meurt, relançant ainsi
sh<2>
qui poursuit son analyse, constate que c'est une séquence et fourche donc
derechef.
sh<5>
examine
kill -9sh<3>,
execute
kill, envoie donc le signal 9 (SIGKILL) à
programme<3>
et donc le tue radicalement,
sans autre forme de procès.
Son office achevé
sh<5>
meurt.
sh<2>,
qui n'attendait que cela, scrute la ligne
exec cycle `expr 10 + 10`
Cette commande nécessite de calculer
expr 10 + 10
aussi, encore une nouvelle fois,
sh<2>
fourche-t'il en
sh<6>
qui
executeexpr.
Ce dernier, après décodage de ses arguments, place 20 sur son flux sortant,
récupéré par
sh<2>
qui n'a plus à interpréter que
exec cycle 20
sh<2>
se transmue alors en
cycle qui est un texte exécutable à interpréter,
aussi se transmue-t'il à nouveau en
sh,
et tout recommence !!!
cycle permet tout bonnement
d'exécuter un programme pendant des durées de plus en plus longues.