2 Processus Unix : création, vie et mort

2.1 Création d'un processus Unix

 
 
La primitive FORK permet la création dynamique d'un nouveau processus qui s'exécute de manière concurrente avec le processus qui l'a créé. Tout processus Unix hormis le processus 0 est crée à l'aide de cette primitive. Le processus créateur (le père) par un appel à la primitive FORK crée un processus fils qui est une copie exacte de lui-même (code est données).
Définition : Primitive FORK
pid_t fork (void) : permet la création d'un processus Unix par duplication complète du créateur.
 
 
Lors de l'exécution de l'appel système Fork, si les ressource s noyaux sont disponibles, le système effectue les opérations suivantes : 
  • le système alloue une entrée à la table des processus pour le nouveau processus
  • le système alloue un PID unique au nouveau processus
  • le système duplique le contexte du processus parent : le code, les données, et la pile
  • le système retourne au processus père le PID du processus crée et au nouveau processus (le fils) la valeur 0.
 
 
Lors de cette création le processus fils hérite de tous les attributs de son père sauf : 
  • l'identificateur de son père
  • son propre identificateur
  • les temps d'exécution du nouveau processus sont à nuls.
Notamment, le fils hérite de tous les descripteurs de fichiers ouverts par son père et partage donc ces fichiers avec lui. 
 
 
A l'issue de l'exécution de la primitive Fork, chaque processus, le père et le fils, reprend son exécution après le Fork. Le code et les données étant strictement identiques, il est nécessaire de disposer d'un mécanisme pour différencier le com port ement des deux processus après le Fork. On utilise pour cela le code retour du Fork qui est différent chez le fils (toujours 0) et le père (PID du fils créé).
 
 
Dans l'exemple de la figure ci-dessous, le processus n°12222 est le processus père. Le processus 12224 est le processus fils crée par le processus de 12222 à l'issue de l'appel à la primitive Fork. Une fois la primitive Fork exécutée par le père, les deux processus (le père et le fils) reprennent leur exécution de manière concurrente. Le processus fils a pour retour de la primitive Fork la valeur 0. Il va donc exécuter la partie de l'alternative pour laquelle (if pid = = 0) est vrai. Le processus père au contraire a pour retour de la primitive Fork la valeur du PID du processus créé c'est-à-dire une valeur positive. Il va donc exécuter l'autre partie de l'alternative. Dans cet exemple, la primitive GETPID retourne à un processus la valeur de son PID.. La primitive GETPPID retourne au processus le PID de son père.
Exemple de création d'un processus avec la primitive Fork

2.2 Terminaison d'un processus Unix

 
 
Un appel à la primitive EXIT provoque la terminaison du processus effectuant l'appel avec un code retour valeur. Un processus qui se termine passe dans l'état zombi et reste dans cet état tant que son père n'a pas pris en compte sa terminaison. 
Définition : Primitive EXIT
void exit (int valeur) : provoque la terminaison du processus appelant
 
 
Lorsqu'un processus se termine, le système démantèle tout son contexte, sauf l'entrée de la table des processus le concernant. Le processus père, par un appel à la primitive WAIT, récupère la mort de son fils, cumule les statistiques de celui-ci avec les siennes et détruit l'entrée de la table des processus concernant son fils défunt. Le processus fils disparaît complètement. La communication entre le fils zombi et le père s'effectue par le biais d'un signal transmis du fils vers le père (signal SIGCHLD ou mort du fils). Si le père n'attend pas la mort de son fils par un appel à la primitive WAIT alors la table des processus  n'est pas libérée et il y a risque de saturation de cette table.
Définition : Primitive WAIT
pid_t wait (int *status) : attente de la mort du fils

Synchronisation père fils assurant une bonne libération des ressources du système à la mort du fils.
 
 
Un processus fils défunt reste zombi jusqu'à ce que son père ait pris connaissance de sa mort. Un processus fils orphelin, suite au décès de son père (le processus père s'est terminé avant son fils) est toujours adopté par le processus numéro 1 (INIT) et non par son grand-père.

2.3 Les primitives de recouvrement

 
 
Il s'agit d'un ensemble de primitives (famille EXEC) permettant à un processus de charger en mémoire, un nouveau code exécutable. L'exécution d'une de ces primitives EXEC entraîne l'écrasement du code hérité au moment de la création ( primitive Fork ) par le code exécutable spécifié en paramètre de la primitive. Des données peuvent être passées au nouveau code exécutable via les arguments de la primitive EXEC.
Définition : Primitives EXEC
Ensemble de primitives (famille EXEC) permettant à un processus de charger en mémoire, un nouveau code exécutable :
  • int execl (const char *ref, const char *arg, ..., NULL) : ref est le chemin d'un exécutable à partir du répertoire courant, const char *arg, ..., NULL est la liste des arguments.
  • int execlp (const char *ref, const char arg, ..., NULL) : ref est le chemin d'un exécutable à partir de la variable d'environnement PATH, const char *arg, ..., NULL est la liste des arguments.
  • int execv (const char *ref, const char *arg[]) : ref est le chemin d'un exécutable à partir du répertoire courant, const char *arg [] est un tableau contenant les arguments.
  • int execvp (const char *ref, const char *arg[]) : ref est le chemin d'un exécutable à partir de la variable d'environnement PATH, const char *arg [] est un tableau contenant les arguments.
 
 
L'interface du programme principal d'un exécutable (le main) se présente toujours de la manière suivante : 
int main (int argc, char *argv[], char *arge[]);
  • argc est le nombre de composants de la commande
  • argv est un tableau de pointeurs de caractères donnant accès aux différentes composantes de la commande
  • arge est un tableau de pointeurs de caractères donnant accès à l'environnement du processus.
Exemple
calcul 3 4
 
 
on a argc = 3, argv[0]="calcul",argv[1]="3"
 
 
et argv[2] = "4"                
 
 
 
 
Calcul.c
 
 
main(argc,argv)
 
 
{
 
 
int somme;
 
 
if (argc <> 3) {printf("erreur"); exit();}
 
 
somme = atoi(argv[1]) + atoi(argv[2]); -- atoi : conversion caractère --> entier
 
 
exit();
 
 
}
 
 
 
 
La figure ci-dessous donne un exemple de l'utilisation d'une des primitives EXEC : la primitive EXECL. Dans l'exemple ci-dessous, le processus fils résultant de l'exécution du Fork va exécuter la partie de l'alternative pour laquelle PID == 0 est vrai. Dans cette alternative, le processus fils fait appel à la primitive EXECL en demandant l'exécution d'un programme exécutable qui a pour nom calcul et qui admet pour paramètre argv[0] = "calcul", argv [1] = "3"et argv[2] ="2". Il faut noter que les arguments sont transmis comme des chaînes de caractères. C'est pourquoi une conversion de caractère vers entier est nécessaire. Dans l'exemple donné, cette conversion est réalisée à l'aide de la primitive atoi.
Exemple de programmation avec une primitive de la famille Exec..

2.4 Architecture du système Unix

 
 
Tout le système UNIX repose sur ce concept arborescent de processus père et de processus fils créé par duplication du processus père puis par recouvrement par un code exécutable spécifique du code hérité du processus père. Le seul processus Unix qui n'est pas crée par un appel à la primitive Fork puis par un appel à une des primitives Exec est le premier processus de la machine c'est-à-dire le processus 0. Une fois créé, le processus 0 crée un deuxième processus, le processus 1 appelé également processus INIT. Le processus 0 devient alors le processus Swapper c'est-à-dire le processus responsable de swapper hors de la mémoire centrale les processus utilisateurs endormis depuis trop longtemps. Le processus INIT va créer à son tour un certain nombre de processus : d'une part les processus DEMONS responsable de fonctions système telles que la surveillance du réseau (inetd), la gestion de l'imprimante (lpd)..., d'autre part des processus GETTY chargés de la surveillance des terminaux. Le processus INIT trouve l'ensemble des processus à créer dans un fichier qui est fichier /etc/inittab. Lorsqu'un utilisateur vient pour utiliser la machine, il se logue sur un terminal. Le processus GETTY crée alors un nouveau processus, le processus LOGIN chargé de lire le login de l'utilisateur, son mot de passe et de vérifier dans le fichier /etc/passwd que l'utilisateur est autorisé à se connecter à la machine. Si l'utilisateur est autorisé à se connecter à la machine, alors le processus LOGIN va créer à son tour un nouveau processus le processus SHELL c'est-à-dire l'interpréteur de commande pour le nouvel utilisateur. Cet interpréteur de commandes va prendre en charge les commandes tapées par l'utilisateur et pour chacune d'elles il va crée un nouveau processus chargé d'exécuter la commande. Ce processus existera le temps de l'exécution du programme ou de la commande.
L'arborescence de processus Unix
Processus Processus Les processus Unix