Vous êtes sur la page 1sur 14

Les commandes de manipulation des processus sous UNIX :

Les caractéristiques d'un processus


On a vu auparavant, qu'on pouvait à un moment donné avoir plusieurs processus en cours, à
un temps donné. Le système doit être capable de les identifier. Pour cela il attribue à chacun
d'entre eux, un numéro appelé PID (Process Identification).

Un processus peut lui même créer un autre processus, il devient donc un processus parent ou
père, et le nouveau processus, un processus enfant. Ce dernier est identifié par son PID, et le
processus père par son numéro de processus appelé PPID (Parent Process Identification).

Tous les processus sont ainsi identifiés par leur PID, mais aussi par le PPID du processus qui
la créé, car tous les processus ont été créés par un autre processus. Oui mais dans tout ça, c'est
qui a créé le premier processus ? Le seul qui ne suit pas cette règle est le premier processus
lancé sur le système le processus init qui n'a pas de père et qui a pour PID 1.

Visualiser les processus


On peut visualiser les processus qui tournent sur une machine avec la
commande: ps (options), les options les plus intéressantes sous HP-UX sont -e (affichage de
tous les processus) et -f (affichage détaillée). La commande ps -ef donne un truc du genre :
UID     PID  PPID  C STIME      TTY     TIME    COMMAND  
root    1    0     0 Dec 6      ?       1:02    init  
...  
jean    319 300    0 10:30:30   ?       0:02    /usr/dt/bin/dtsession  
olivier 321 319    0 10:30:34   ttyp1   0:02    csh  
olivier 324 321    0 10:32:12   ttyp1   0:00    ps –ef

La signification des différentes colonnes est la suivante:

 UID nom de l'utilisateur qui a lancé le process


 PID correspond au numéro du process
 PPID correspond au numéro du process parent
 C au facteur de priorité : plus la valeur est grande, plus le
processus est moins prioritaire
 STIME correspond à l'heure de lancement du processus
 TTY correspond au nom du terminal
 TIME correspond à la durée de traitement du processus
 COMMAND correspond au nom du processus.

Pour l'exemple donné, à partir d'un shell vous avez lancé la commande ps -ef, le premier
processus à pour PID 321, le deuxième 324. Vous noterez que le PPID du process " ps -ef "
est 321 qui correspond au shell, par conséquent le shell est le process parent, de la commande
qu'on vient de taper.

Certains processus sont permanents, c'est à dire qu'ils sont lancés au démarrage du système et
arrêtés uniquement à l'arrêt du système. On appelle ces processus des daemons, le terme
démon est une francisation, daemon sont des abréviations.
Pour voir les processus d'un seul utilisateur, vous pouvez taper :

ps -u olivier

D'un UNIX à l'autre la sortie peut changer. Sous LINUX par exemple ps -Al permet une
sortie assez riche, en faisant un man ps, vous aurez l'éventail de tous les paramètres possibles.

 Options les plus fréquentes :


o -u : Affiche les processus de l'utilisateur qui exécute la commande
o -au : Affiche les processus de tous les utilisateurs
o -aux : Affiche l'intégralité des processus du système. Équivalent à ps -A
o -faux : Affiche tous les processus du système en les regroupant par
enchaînement d'exécution.
 Exemples d'utilisation :
o ps -u 
Tous les processus de l'utilisateur courant
o ps -aux 
Tous les processus en cours

Commandes de gestion des processus

Changer la priorité d'un processus

Les processus tournent avec un certain degré de priorité, un processus plus prioritaire aura
tendance à s'accaparer plus souvent les ressources du système pour arriver le plus vite
possible au terme de son exécution. C'est le rôle du système d'exploitation de gérer ces
priorités.

Vous disposez de la commande nice pour modifier la priorité d'un processus. La syntaxe est la


suivante :

nice -valeur commande

Plus le nombre est grand, plus la priorité est faible. Par exemple une valeur de 0 donne, la
priorité la plus haute 20 donne la priorité la plus faible.

La fourchette de valeur dépend de l'UNIX qu'on utilise.

Par exemple :

nice -5 ps –ef

Généralement on utilise nice sur des commandes qui prennent du temps, sur des commandes
courantes l'effet de nice est imperceptible. On l'utilisera par exemple pour compiler un
programme.
nice -5 cc monprogramme.c

Envoyer un signale à un processus


Les signaux unix permettent d'envoyer un signal (une information) à un processus.

La liste des signaux est définie et certains signaux sont bien connus. Le signal 9 (kill) est sans
doute le plus célèbre car il indique au processus de se terminer immédiatement.

Enfin chaque processus peut définir le comportement à adopter lors de la réception de chaque
type de signal. Ainsi lorsque le processus java de la jvm reçoit le signal 3 (QUIT) le processus
ne s'arrête pas mais génère un thread dump.

Voici donc la liste des signaux disponibles sous Unix/Linux. On peut envoyer ces signaux
avec la commande:
kill -<signal> <process id>

où <signal> est soit le numéro du signal, soit son nom sans le prefix SIG. Par exemple pour
envoyer SIGQUIT on utilisera
kill -QUIT <process id>

ou

kill -3 <processId>

Numéro Nom Description


1 SIGHUP Instruction (HANG UP) - Fin de session
2 SIGINT Interruption
3 SIGQUIT Instruction (QUIT)
4 SIGILL Instruction illégale
5 SIGTRAP Trace trap
6 SIGABRT (ANSI) Instruction (ABORT)
6 SIGIOT (BSD) IOT Trap
7 SIGBUS Bus error
8 SIGFPE Floating-point exception - Exception arithmétique
9 SIGKILL Instruction (KILL) - termine le processus immédiatement
10 SIGUSR1 Signal utilisateur 1
11 SIGSEGV Violation de mémoire
12 SIGUSR2 Signal utilisateur 2
13 SIGPIPE Broken PIPE - Erreur PIPE sans lecteur
14 SIGALRM Alarme horloge
15 SIGTERM Signal de terminaison
16 SIGSTKFLT Stack Fault
17 SIGCHLD ou SIGCLD modification du statut d'un processus fils
18 SIGCONT Demande de reprise du processus
19 SIGSTOP Demande de suspension imbloquable
20 SIGTSTP Demande de suspension depuis le clavier
21 SIGTTIN lecture terminal en arrière-plan
22 SIGTTOU écriture terminal en arrière-plan
23 SIGURG évènement urgent sur socket
24 SIGXCPU temps maximum CPU écoulé
25 SIGXFSZ taille maximale de fichier atteinte
26 SIGVTALRM alarme horloge virtuelle
27 SIGPROF Profiling alarm clock
28 SIGWINCH changement de taille de fenêtre
29 SIGPOLL (System V) occurence d'un évènement attendu
Numéro Nom Description
29 SIGIO (BSD) I/O possible actuellement
30 SIGPWR Power failure restart
31 SIGSYS Erreur d'appel système
31 SIGUNUSED  

Vous disposez de la commande kill pour arrêter un processus, on doit aussi tuer un processus.


Si vous voulez arrêter un processus, vous devez connaître son PID (commande ps), puis vous
tapez :
kill -9 PID
Un utilisateur ne peut arrêter que les processus qui lui appartient (qu'il a lancé). Seul
l'administrateur système a le droit d'arrêter un processus ne lui appartenant pas.

Lancer en processus en tâche de fond


Pour lancer une commande quelconque, vous en saisissez le nom après le prompt du shell,
tant que la commande n'est pas terminée, vous n'avez plus la main au niveau du shell, vous ne
disposez plus du prompt. Si la commande prend un certain temps, votre shell ne vous donnera
pas la main tant que la commande n'est pas terminée, vous êtes obligé de lancer un autre shell,
pour taper une autre commande.

Vous disposez d'une technique simple qui permet de lancer une commande à partir d'un shell,
et de reprendre aussitôt la main. Il vous suffit de rajouter un & à la fin de commande. Celle-ci
se lancera en " tâche de fond ", et vous reviendrez directement au prompt du shell.

En tapant une commande en tâche de fond, vous aurez à l'affichage :

> ps ef &  
[321]  
>
A la suite de la saisie de la commande suivie d'un &, le shell vous donne immédiatement la
main, et affiche le numéro du PID du processus lancé.

En lançant une commande à partir du shell sans le & à la fin, et si celle-ci prend du temps à
vous rendre la main, vous pouvez faire en sorte qu'elle bascule en tâche de fond, pour que
vous repreniez la main.

>netscape
Vous voulez basculer netscape en tâche de fond tapez, CTRL+Z, il va afficher
311 stopped +
311 étant le PID du process netscape. Tapez ensuite bg (pour background), vous voyez
s'afficher
[311]
Ca y est votre processus netscape est en tâche de fond et le shell vous rend la main.
Manipulation des processus en C
La fonction  fork

Pour créer un nouveau processus à partir d'un programme, on utilise la fonction fork (qui est
un appel-système). Pour rappel, le processus d'origine est nommé processus père et le
nouveau processus créé processus fils, qui possède un nouveau PID. Les deux ont le même
code source, mais la valeur retournée par fork nous permet de savoir si l'on est dans le
processus père ou dans le processus fils. Ceci permet de faire deux choses différentes dans le
processus père et le processus fils (en utilisant une structure de condition, if ou switch).

La fonction fork retourne une valeur de type pid_t. Il s'agit généralement d'un int ; il et


déclaré dans <sys/types.h>. 
La valeur renvoyée par fork est de :

 -1 si il y a eu une erreur ;


 0 si on est dans le processus fils ;
 Le PID du fils si on est dans le processus père. Cela permet ainsi au père de connaître
le PID de son fils.
#include <unistd.h>
#include <sys/types.h>
int main(void)
{
pid_t=fork();

switch (pid_t) {
/* Si on a une erreur irrémédiable (ENOMEM dans notre cas) */
case -1:
perror("fork");
return EXIT_FAILURE;
break;
/* Si on est dans le fils */
case 0:
child_process();
break;
/* Si on est dans le père */
default:
father_process(pid);
break;
}

return EXIT_SUCCESS;
}
La fonction  getpid() et getppid() :

La fonction getpid retourne le PID du processus appelant.

La fonction getppid retourne le PPID du processus appelant.

#include <unistd.h>
#include <sys/types.h>
pid_t=getpid();

La fonction  exit() :

La fonction exit termine processus appelant. Exit(0) pour une fin normale, et exit(-1) en cas
d’erreur.

#include <stdlib.h>
Exit(0)

La fonction  wait():

Lorsque l'on appelle cette fonction, cette dernière bloque le processus à partir duquel elle a été
appelée jusqu'à ce qu'un de ses fils se termine. Elle renvoie alors le PID de ce dernier. 
En cas d'erreur, la fonction renvoie la valeur -1.

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *status);

La fonction  sleep():

Endormir le processus un certain nombre de secondes données en paramètre.

Sleep(x)

Exécution d'une commande par system :

La fonction  system  permet de démarrer un processeur de commande fournit par


l'environnement hôte et de lui passer une chaîne de caractère string contenant la commande à
exécuter.  system  retourne 0 si tout s'est bien passé.

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
system("clear");
return EXIT_SUCCESS;
}
La fonction execlp( )

La fonction execlp() illustre le mécanisme complémentaire à celui de la création de processus


implanté via la fonction fork(), dont dispose UNIX. Le principe que nous allons voir ici
revient à modifier le code du programme à exécuter pendant l'exécution du code en cours.

int execlp(const char *file, const char *arg, ...);

execlp(“path”, “executable”, “arg1”, “arg2”,…, NULL);

path est le chemin de la commande, et le NULL est obligatoire à la fin.

#include stdio.h
#include unistd.h
int main ( )
{ printf (” preambule du bonjour\n ”);
execlp (”./bonjour”,(char *) 0);
printf (” postambule du bonjour\n ”); }

Les tubes

Définition  : Un tube (en anglais pipe) peut être représenté comme un tuyau dans lequel
circulent des informations.

Utilité  : Les tubes servent à faire communiquer plusieurs processus entre eux.

Vocabulaire : On distingue alors les deux processus par leur action :

 Un écrit des informations dans le tube. Celui-ci est appelé entrée du tube ;


 L'autre lit les informations dans le tube. Il est nommé sortie du tube.
Le processus qui écrit ne peut pas lire des informations, et inversement. Il faut donc créer
deux tubes si on veut que les processus établissent réellement un dialogue.

Manipuler les tubes :

1- Créer

Pour commencer, il nous faudrait créer le tube. Pour cela, on utilise la fonction :
int pipe(int descripteur[2]);

Voici ses caractéristiques :


 Elle renvoie une valeur de type int, qui est de 0 si elle réussit, ou une autre valeur
dans le cas contraire.
 Elle prend en argument un tableau de int, comprenant :
o descripteur[0]: désigne la sortie du tube ;
o descripteur[1]: désigne l'entrée du tube.

Mais là, nous devons faire face à notre premier problème. En effet, on ne crée le tube que
dans un seul processus. L'autre ne peut donc pas en connaître l'entrée ou la sortie !
En conséquence, il faut utiliser pipe avant d'utiliser fork. Ensuite, le père et le fils auront
les mêmes valeurs dans leur tableau descripteur, et pourront donc communiquer.
2- Écrire

Pour écrire dans un tube :


ssize_t write(int entreeTube, const void *elementAEcrire, size_t nombreOctetsAEcrire);

La fonction prend en paramètre l'entrée du tube (on lui enverra descripteur[1]),


un pointeur générique vers la mémoire contenant l'élément à écrire ainsi que le nombre
d'octets de cet élément. Elle renvoie une valeur de type ssize_t correspondant au nombre
d'octets effectivement écrits.

Exemple : Pour écrire le message "Bonjour" dans un tube, depuis le père :

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

#define TAILLE_MESSAGE 256 /* Correspond à la taille de la chaîne à écrire */

int main(void)
{
pid_t pid_fils;
int descripteurTube[2];

char messageEcrire[TAILLE_MESSAGE];

pipe(descripteurTube);

pid_fils = fork();

if (pid_fils != 0) /* Processus père */


{
sprintf(messageEcrire, "Bonjour, fils. Je suis ton père !"); /* La fonction sprintf
permet de remplir une chaîne de caractère avec un texte donné */
write(descripteurTube[1], messageEcrire, TAILLE_MESSAGE);
}

return EXIT_SUCCESS;
}

3- Lire

Et pour lire dans un tube :


ssize_t read(int sortieTube, void *elementALire, size_t nombreOctetsALire);

La fonction prend en paramètre la sortie du tube (ce qui correspond à... descripteur[0]),


un pointeur vers la mémoire contenant l'élément à lire et le nombre d'octets de cet
élément. Elle renvoie une valeur de type ssize_t qui correspond au nombre d'octets
effectivement lus. On pourra ainsi comparer le troisième paramètre (nombreOctetsALire) à
la valeur renvoyée pour vérifier qu'il n'y a pas eu d'erreurs.

Exemple : Pour lire un message envoyé par le père à un fils :


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

#define TAILLE_MESSAGE 256 /* Correspond à la taille de la chaîne à lire */

int main(void)
{
pid_t pid_fils;
int descripteurTube[2];
char messageLire[TAILLE_MESSAGE];

pipe(descripteurTube);

pid_fils = fork();

if(pid_fils == 0) /* Processus fils */


{
read(descripteurTube[0], messageLire, TAILLE_MESSAGE);
printf("Message reçu = \"%s\"", messageLire);
}

return EXIT_SUCCESS;
}

4- Fermer

Lorsque nous utilisons un tube pour faire communiquer deux processus, il est important de
fermer l'entrée du tube qui lit et la sortie du tube qui écrit. 
En effet, il faut que le noyau voie qu'il n'y a plus de processus disposant d'un descripteur sur
l'entrée du tube. Ainsi, dès qu'un processus tentera de lire à nouveau, il lui enverra EOF (fin
de fichier).

Pour fermer une entrée ou une sortie :


int close(int descripteur);

La valeur prise en paramètre correspond au descripteur de l'entrée ou de la sortie à fermer.

Exemple : Pour fermer l'entrée du processus fils et la sortie du processus père :


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void)
{
pid_t pid_fils;
int descripteurTube[2];

pipe(descripteurTube);

pid_fils = fork();

if(pid_fils == 0) /* Processus fils */


close(descripteurTube[1]);

else /* Processus père */


close(descripteurTube[0]);

return EXIT_SUCCESS;
}
Exercice pratique : Écrivez un programme qui crée deux processus : le père écrit le message «
Bonjour, fils. Je suis ton père ! ». Le fils le récupère, puis l'affiche.

Correction:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

#define TAILLE_MESSAGE 256 /* Correspond à la taille de la chaîne à lire et à écrire */

int main(void)
{
pid_t pid_fils;
int descripteurTube[2];

unsigned char messageLire[TAILLE_MESSAGE], messageEcrire[TAILLE_MESSAGE];

printf("Création du tube.\n");

if(pipe(descripteurTube) != 0)
{
fprintf(stderr, "Erreur de création du tube.\n");
return EXIT_FAILURE;
}

pid_fils = fork();

if(pid_fils == -1)
{
fprintf(stderr, "Erreur de création du processus.\n");
return 1;
}

if(pid_fils == 0)
{
printf("Fermeture de l'entrée dans le fils.\n\n");
close(descripteurTube[1]);

read(descripteurTube[0], messageLire, TAILLE_MESSAGE);


printf("Nous sommes dans le fils (pid = %d).\nIl a reçu le message suivant du
père : \"%s\".\n\n\n", getpid(), messageLire);
}

else
{
printf("\nFermeture de la sortie dans le père.\n");
close(descripteurTube[0]);

sprintf(messageEcrire, "Bonjour, fils. Je suis ton père !");

printf("Nous sommes dans le père (pid = %d).\nIl envoie le message suivant au


fils : \"%s\".\n\n\n", getpid(), messageEcrire);

write(descripteurTube[1], messageEcrire, TAILLE_MESSAGE);

wait(NULL);
}

return 0;
}
Résultat :

Création du tube.

Fermeture de la sortie dans le père.
Fermeture de l'entrée dans le fils.

Nous sommes dans le père (pid = 2584).
Il envoie le message suivant au fils : "Bonjour, fils. Je suis ton père !".

Nous sommes dans le fils (pid = 2585).
Il a reçu le message suivant du père : "Bonjour, fils. Je suis ton père !".
Notion complémentaire sur les tubes : Tubes nommés

Cependant, un problème se pose avec l'utilisation des tubes classiques. En effet, il faut
obligatoirement que les processus connaissent le processus qui a créé le tube. Avec les tubes
tout simples, il n'est pas possible de lancer des programmes indépendants, puis qu'ils
établissent un dialogue.

C'est pourquoi on a créé une "extension" des tubes, appelée "tube nommé" (named pipe). Son
concept est assez simple : comme son nom l'indique, le tube dispose d'un nom dans le système
de fichier. Il suffit qu'un processus l'appelle par son nom, et il accourt pour laisser le
processus lire ou écrire en son intérieur.

1- Créer

Pour commencer, il faut créer le tube. Concrètement, nous allons créer un fichier.

Pour créer un tube nommé, on utilise la fonction mkfifo, dont voici le prototype :
int mkfifo (const char* nom, mode_t mode);
Le premier argument est le nom du tube nommé. On donne généralement l'extension « .fifo »
au nom du tube nommé. Voici un exemple de nom pour un tube : « essai.fifo ».

Le deuxième paramètre concerne les droits d'accès du tube. Deux solutions :

 La première, c'est de lire la documentation du fichiersys/stat.h. dans la section « File


mode bits ». Vous y trouverez des constantes correspondant aux droits d'accès
(S_IRUSR, S_IWUSR...). Vous pouvez combiner ces constantes avec le symbole «|».
 Deuxième solution: fabriquer des valeurs des droits tout seuls. Les valeurs des droits
comprennent quatre chiffres, sont sous mode octal : elles commencent donc par un
zéro. Le premier chiffre correspond au propriétaire (vous en l'occurrence), le
deuxième correspond au groupe du propriétaire et le troisième correspond à tous les
autres. Une valeur est attribuée à chaque permission : 1 pour l'exécution, 2 pour
l'écriture et 4 pour la lecture. On fait une somme quand on veut combiner plusieurs
permissions. Exemple : Pour attribuer toutes les permissions à vous, seule la lecture
pour le groupe, et aucune pour les autres, la valeur correspondante est 0720 (Premier
chiffre = 0 (obligatoire) ; Deuxième chiffre = 1 (Exécution) + 2 (Ecriture) + 4
(Lecture) ; Troisième chiffre = 0 (aucune permission)).
Enfin, la fonction renvoie 0 si elle réussit, ou -1 en cas d'erreur. Vous pouvez aussi consulter
la variable errno, qui peut contenir :

 EACCES : le programme n'a pas les droits suffisants pour accéder au chemin de
création du tube nommé ;
 EEXIST : le tube nommé existe déjà ;
 ENAMETOOLONG : dépassement de la limitation en taille du nom de fichier ;
 ENOENT : le chemin du tube nommé n'existe pas ;
 ENOSPC : il n'y a plus assez de place sur le système de fichiers.
Exercice : Créez un tube nommé que vous appellerez « essai », dont vous vous aurez toutes
les permissions, dont le groupe aura les permissions de lecture et d'écriture et dont les autres
n'auront aucun droits.

Deux solutions de possibles, pour chacune présentées pour les droits d'accès :
1)

if (mkfifo(("essai.fifo"), S_IRWXU | S_IRGRP | S_IWGRP) == -1)


{
fprintf(stderr, "Erreur de création du tube");
exit(EXIT_FAILURE);
}
2)
if (mkfifo(("essai.fifo"), 0760) == -1)
{
fprintf(stderr, "Erreur de création du tube");
exit(EXIT_FAILURE);
}

2- Ouvrir

Ensuite, il faut ouvrir l'entrée/la sortie du tube avec la fonction open:


int open (const char* cheminFichier, int options);

La fonction renvoie une valeur de type int que l'on attribue à l'extrémité du tube en
question. 
Le premier argument est le nom du fichier (on mettra le nom du tube nommé, bien
évidemment).
Le second argument indique si c'est l'entrée ou la sortie du tube. Il existe deux constantes pour
cela, déclarées dans fcntl.h:

 O_WRONLY: pour l'entrée ;


 O_RDONLY: pour la sortie.

Exemple : Pour ouvrir l'entrée d'un tube « essai.fifo » :


descripteur[1] = open("essai.fifo", O_WRONLY);

Ensuite, vous pouvez écrire et lire avec write et read comme si c'était des tubes classiques.

Exercice : Écrivez deux programmes indépendants : un écrit un message dans un tube nommé,
et l'autre le lit, puis l'affiche. Exécutez ces deux programmes en même temps.

Correction :

Ecrivain.c :
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define TAILLE_MESSAGE 256

int main(void)
{
int entreeTube;
char nomTube[] = "essai.fifo";

char chaineAEcrire[TAILLE_MESSAGE] = "Bonjour";

if(mkfifo(nomTube, 0644) != 0)
{
fprintf(stderr, "Impossible de créer le tube nommé.\n");
exit(EXIT_FAILURE);
}

if((entreeTube = open(nomTube, O_WRONLY)) == -1)


{
fprintf(stderr, "Impossible d'ouvrir l'entrée du tube nommé.\n");
exit(EXIT_FAILURE);
}

write(entreeTube, chaineAEcrire, TAILLE_MESSAGE);

return EXIT_SUCCESS;
}

Lecteur.c :
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define TAILLE_MESSAGE 256

int main(void)
{
int sortieTube;
char nomTube[] = "essai.fifo";

char chaineALire[TAILLE_MESSAGE];

if((sortieTube = open ("essai.fifo", O_RDONLY)) == -1)


{
fprintf(stderr, "Impossible d'ouvrir la sortie du tube nommé.\n");
exit(EXIT_FAILURE);
}

read(sortieTube, chaineALire, TAILLE_MESSAGE);


printf("%s", chaineALire);

return EXIT_SUCCESS;
}

Résultat :

Premier terminal :

lucas@lucas-Deskstop:~/Documents$ ./ecrivain
_
Deuxième terminal :

lucas@lucas-Deskstop:~/Documents$ ./lecteur
Bonjour