Initiation à la programmation
système
Chapitre :Processus
Les processus : caractéristiques
• Un processus est un programme en cours
d’exécution.
• Le système alloue une structure pour contrôler, et
mémoriser
– ses caractéristiques, par exemple
• son identité (pid),
• son état (prêt, mode kernel/user, endormi, zombi, créé,
swappé),
• ses propriétaires (réel, effectif),
• son répertoire courant, etc.
– les ressources qui lui ont été attribuées, par exemple
les fichiers ouverts ;
– ses demandes non encore satisfaites.
Les processus : organisation mémoire
• La mémoire d’un processus a schématiquement plusieurs
zones.
• Certaines (texte, statique) sont de taille fixée à la compilation.
• D’autres (pile, tas) ont une taille évoluant au cours de
l’exécution.
Arguments et environnement
• Au lancement d’un processus, une routine de
démarrage initialise ses arguments et
l’environnement, avant sa première instruction.
• L’environnement est une liste de chaînes de
caractères de la forme
VARIABLE=valeur. Par exemple, HOME=/Users/toto.
• Lorsqu’un processus P crée un processus F ,
l’environnement de P est transmis par défaut à F .
Arguments et environnement
• Un processus utilise son environnement pour adapter
son comportement.
– man utilise MANPATH pour chercher les fichiers des pages
de manuel.
– COLUMNS affecte l’affichage de plusieurs commandes
(shell, pagers, etc.)
• Un programme peut avoir accès à son environnement
en utilisant
– Les fonctions de bibliothèque clearenv, putenv, setenv,
unsetenv.
– la variable extern char **environ; cf. <unistd.h>.
– la forme de main mentionnée par C99 comme extension
non portable :
int main (int argc , char *argv[], char *envp []);
Les processus : schéma simplifié des
états
Identificateurs
• Chaque processus est identifié par un numéro
entier unique, son pid.
• Deux processus ne peuvent pas avoir le même
numéro.
• Chaque processus a un processus père : celui
qui a demandé sa création.
• Tout processus orphelin est adopté par le
processus init, de pid 1.
Identificateurs
• Un processus récupère son identificateur et celui de son père par
pid_t getpid (void);
pid_t getppid(void );
• Tout processus a un propriétaire réel et un propriétaire effectif.
uid_t getuid (void);
uid_t geteuid(void );
• Le propriétaire réel correspond normalement au login de l’utilisateur
ayant lancé le processus.
• Le propriétaire effectif sert pour les vérifications de permissions.
• Idem pour le groupe, getgid()/getegid().
• Ces fonctions réussissent toujours.
Propriétaires
• Ne pas confondre le propriétaire d’un processus et celui d’un
fichier.
• De plus, un processus a 2 propriétaires : réel et effectif. Celui
qui détermine les droits du processus est le propriétaire
effectif.
• Le propriétaire réel est fixé au login, hérité lors de la création
d’un fils.
• Le propriétaire effectif peut changer à l’exécution d’un
programme binaire, dont le fichier a le set-uid bit positionné.
• Dans ce cas, le propriétaire effectif prend la valeur du
propriétaire du fichier exécutable.
• Ceci permet de donner au processus des droits pendant la
durée d’exécution du programme.
Création de nouveaux processus
• On peut créer des processus avec l’appel
système fork().
– le processus créé reprend son exécution au même
point que le programme appelant.
• Un processus peut exécuter un programme
par l’un des appels système exec(v/l)[p/e].
Généalogie des processus
• La fonction fork (unistd.h) retourne -1 en cas
d’erreur ; 0 dans le processus fils ; le PID du fils
dans le processus père.
• Un processus est donc forcément créé par un autre
processus (notion père-fils). Le PPID (Parent PID) d'un
processus correspond au PID du processus qui l'a créé
(son père)
Redirections
• Les descripteurs sont dupliqués dans le fils, ils
référencent les mêmes entrées que ceux du
père.
• Ceci permet au père et au fils de
communiquer.
• On peut aussi rediriger l’entrée, la sortie ou la
sortie erreur du fils juste après l’appel à fork().
Recouvrement
• Un processus exécute un programme exécutable par l’une
des 6 fonctions
exec(l/v)(-/p/e).
• Le processus « repart à 0 » avec le texte d’un autre
programme.
• Il garde son pid, ppid, son propriétaire et groupe réel, son
répertoire courant, son umask, ses signaux pendants, +...
• Il change de propriétaire/groupe effectif si le setuid/setgid
bit du binaire qu’il exécute est positionné.
• Chaque descripteur a un flag close_on_exec. S’il est
positionné, le descripteur est fermé au travers d’exec.
– On positionne ce flag sur le descripteur d par
fcntl(d, F_SETFD, FD_CLOEXEC);
Les fonctions de recouvrement
• execl, execlp, execle, execv, execvp, execve.
• Les fonctions execl* spécifient les arguments comme une
liste.
• Les fonctions execv* spécifient les arguments comme un
vecteur.
• Dans les 2 cas, on spécifie :
– 1. le fichier binaire à exécuter,
– 2. les arguments, sous forme liste ou vecteur,
• Les fonctions exec(v/l)p permettent de chercher le binaire
dans les répertoires spécifiés par la variable
d’environnement PATH,
• Les fonctions exec(v/l)e permettent de passer en dernier
argument un environnement char *env[].
Terminaison
• Lorsqu’un processus se termine, il passe à l’état « zombi ».
• Si son père est 1, il est retiré de la liste des processus.
• Sinon, il reste zombi (et consomme des ressources
internes) jusqu’à ce que son père le libère.
• Le père le libère en récupérant des informations sur la
terminaison par
pid_t wait(int *status );
• L’appel wait retourne immédiatement si le processus n’a
pas de fils, ou a déjà un fils zombi.
• Sinon, l’appel est bloquant : mise en sommeil du processus
appelant jusqu’à ce qu’un fils se termine (ou réception
d’un signal).
• Au retour de l’appel wait, l’entier pointé par status
contient des informations sur la terminaison du fils.
Terminaison
• L’appel wait() ne permet pas de préciser quel
processus on attend, et l’appel est bloquant si le
processus appelant a des fils, tous non zombis.
• waitpid() permet de lever ces limitations
pid_t waitpid(pid_t pid , int *status, int option
– pid peut être :
• −1 : attente de n’importe quel processus,
• > 0 : attente du processus de pid pid.
• = 0, < 0 : vu plus tard.
– Option fréquente : WNOHANG : non bloquant.
– L’appel permet aussi de récupérer des informations
sur les fils stoppés (option WUNTRACED).
#include <sys/types.h> #include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
wait() suspend l'exécution du processus appelant
jusqu'à ce que l'un de ses fils se termine.
waitpid() suspend l'exécution du processus appelant
jusqu'à ce que le fils spécifié par son pid ait changé
d'état. Par défaut, waitpid() n'attend que les fils
terminés, mais ce comportement est modifiable
avec l'argument options
wait(&status) est équivalent à : waitpid(-1, &status, 0);
Terminaison
• Macros appliquées à l’entier status acquis par
wait/waitpid :
– WIFEXITED(status) : vrai si le processus s’est terminé
normalement,
– WEXITSTATUS(status) : le code retour du processus,
– WIFSIGNALED(status) : vrai si le processus a été
terminé par un signal,
– WTERMSIG(status) : le numéro du signal.
– ...
Terminaison
• Un processus se termine normalement en appelant
exit, _exit ou return dans le 1er cadre de la fonction
main.
• La fonction exit()
– 1. appelle les fonctions enregistrées par atexit,
– 2. vide les buffers de la bibliothèque standard.
– 3. continue comme _exit().
• La fonction _exit()
– 1. ferme les descripteurs,
– 2. termine le processus.
• Un processus peut se terminer anormalement, sur
réception d’un signal.
• Exemple :
execl("/bin/ls", "ls", "-l", "-a", NULL);