5. fork, exec, wait et trace d'exécution
5.1 fork()
fork() crée un processus fils à partir du processus courant. Après succès, deux processus reprennent à l'instruction suivant l'appel, mais chacun possède sa copie logique de la variable de retour :
Code (c) · sélectionner pour copier
pid_t pid = fork();
if (pid < 0) {
/* échec : aucun fils */
} else if (pid == 0) {
/* fils */
} else {
/* père : pid contient le PID du fils */
}
Le noyau doit vérifier les ressources, choisir un PID inutilisé, créer une entrée dans ses tables et préparer le contexte du fils. Une valeur négative indique l'échec; 0 identifie la branche du fils; une valeur positive donne au père le PID de son fils.
Une variable nommée pid n'est pas automatiquement le PID du processus courant. Après fork, la variable de retour vaut 0 dans le fils et le PID du fils dans le père; getpid() retourne, lui, le PID du processus qui l'appelle.
5.2 Trace représentative
Supposons un père de PID 34 et un fils de PID 35. Ces valeurs sont uniquement illustratives.
| Processus | pid = fork() |
getpid() |
Branche |
|---|---|---|---|
| Père | 35 | 34 | pid > 0 |
| Fils | 0 | 35 | pid == 0 |
Si l'ordonnanceur exécute d'abord le fils, celui-ci peut afficher son message puis appeler sleep. Le sommeil le bloque et permet au père de s'exécuter. Le père affiche son message puis appelle wait; si le fils dort encore, le père se bloque. Quand le fils se termine, le père redevient prêt, reprend après wait puis se termine.
Un autre lancement peut faire exécuter le père avant le fils. La valeur de retour de fork est déterministe par branche, mais l'ordre des affichages ne l'est pas. Une seule observation n'établit jamais une garantie d'ordonnancement.
Avec plusieurs fork, il faut suivre chaque processus qui atteint chaque appel. Sans branchement empêchant certains processus de poursuivre, chacun des processus existants peut créer un nouveau fils.
5.3 exec*()
La famille exec remplace l'image mémoire du processus courant par un autre programme. Elle ne crée pas de processus supplémentaire et conserve le PID. Le code, les données, le tas et la pile sont remplacés.
execl*reçoit une liste d'arguments;execv*reçoit un tableau;- une forme contenant
pcherche l'exécutable dansPATH; - certaines variantes reçoivent explicitement l'environnement.
Un exec réussi ne revient pas au code précédent. Un retour indique donc normalement une erreur. Le patron d'un shell est : créer un fils avec fork, configurer ses descripteurs et son environnement, puis appeler exec dans le fils.
5.4 wait() et waitpid()
wait() bloque le père jusqu'à la terminaison d'un fils, puis retourne son PID et permet d'examiner son statut. Une boucle est nécessaire pour récolter plusieurs enfants. waitpid() permet de viser un fils déterminé et d'ajouter des options.
L'attente remplit deux rôles :
- imposer une relation causale lorsque le père ne doit continuer qu'après le fils;
- autoriser le noyau à supprimer l'information minimale conservée sur un fils terminé.
Elle ne doit pas être remplacée par un délai arbitraire : sleep() suppose une durée; wait() exprime la vraie dépendance.
5.5 Modèle Windows
CreateProcess crée un processus indépendant et son thread principal, puis fournit leurs identifiants et handles dans une structure de résultat. L'appelant fournit directement la ligne de commande, l'environnement, les attributs, les drapeaux et le répertoire courant. Il doit fermer les handles reçus.
Unix sépare habituellement duplication (fork) et remplacement (exec); Windows réunit création et choix du programme dans une interface plus paramétrée. Les capacités générales sont semblables, mais les contrats d'API diffèrent.