Académique Documents
Professionnel Documents
Culture Documents
Chap 3
Chap 3
Processus
3.1 Processus
3.1.1 États d’un processus
Lorsqu’un processus s’exécute, il change d’état, comme on peut le voir
sur la figure ??. Il peut se trouver alors dans l’un des trois états principaux :
1
2 CHAPITRE 3. PROCESSUS
Interruption
Prêt Exécution
Élu
Bloqué
Nouveau Fin
Interruption
Admis Terminé
Prêt Exécution
Élu
Bloqué
(PCB).
relancer le processus courant dans le même état. Par exemple, il faut ab-
solument se rappeler à quelle instruction il est rendu. La figure ?? illustre
le changement de contexte entre processus. Le processsus en cours est in-
terrompu et un ordonnanceur est éventuellement appelé. L’ordonnanceur
s’exécute en mode kernel pour pouvoir manipuler les PCB. Le changement
de contexte sera expliqué plus en détail dans le Chapitre ??, Ordonnance-
ment de processus.
exécution
Charger état de PCB B
i n t main ( )
{
int return_value ;
/ r e t o u r n e 1 2 7 s i l e s h e l l ne peut pas s ’ e x e c u t e r
retourne 1 en c a s d ’ e r r e u r
autrement r e t o u r n e l e code de l a commande /
r e t u r n _ v a l u e = system ( " l s l /" ) ;
10 return return_value ;
}
Il crée un processus fils en lançant la commande :
ls -l /
dans un shell. Il faut retenir que system() n’est pas un appel système,
mais une fonction C. Ce qui rend l’utilisation de la fonction system()
moins performante qu’un appel système de création de processus. Ceci sera
discuté à la fin de la section.
#include <unistd.h>
int fork();
3.3. CRÉATION DE PROCESSUS 7
fork() est le seul moyen de créer des processus, par duplication d’un
processus existant1 . L’appel système fork() crée une copie exacte du pro-
cessus original, comme illustré à la figure ??. Mais maintenant il se pose un
problème, car les deux processus père et fils exécutent le même code. Com-
ment distinguer alors le processus père du processus fils ? Pour résoudre ce
pile pile
i 27 i 0
i= fork() i= fork()
co co
F IG . 3.5 – Processus père et son fils cloné. i=27 après l’appel fork() pour
le père et i=0 pour le fils. L’image mémoire est la même pour tous les deux,
notamment le compteur ordinal co.
1
Linux a introduit un autre appel système pour la création de contexte d’un processus :
clone(). C’est un appel système très puissant, avec un nombre d’arguments pour gérer la
mémoire par le programmeur. Malheureusement clone() ne fait pas partie de la norme
Posix, et ne sera pas étudié ici. Le lecteur intéressé peut consulter, par exemple, les articles
du Linux Journal, http ://www.linuxjournal.com
8 CHAPITRE 3. PROCESSUS
pid = 13
père
fils i = fork()
i=0 i=27
pid = 27 pid = 13
i n t main ( )
{
pid_t f i l s _ p i d ;
f i l s _ p i d=fork ( ) ;
i f ( f i l s _ p i d ==0)
10 p r i n t f ( " J e s u i s l e f i l s avec pid %d\n " , g e t p i d ( ) ) ;
else i f ( f i l s _ p i d > 0 )
p r i n t f ( " J e s u i s l e pere avec pid %d\n " , g e t p i d ( ) ) ;
else
p r i n t f ( " E r r e u r dans l a c r e a t i o n du f i l s \n " ) ;
}
L’exécution de fils.c montre les pid du père et du fils :
i n t main ( void )
{
int i , j , k,
n=5;
pid_t f i l s _ p i d ;
10 f o r ( i = 1 ; i <n ; i ++)
{
f i l s _ p i d = fork ( ) ;
i f ( f i l s _ p i d > 0 ) // c ’ e s t l e pere
break ;
}
Ce programme créera une chaîne de
processus :
Le problème ici est que dans tous les cas le processus père se termine
avant le processus fils. Comme tout processus doit avoir un parent, le pro-
cessus orphelin est donc "adopté" par le processus 1 (le processus init).
Ainsi, on peut remarquer qu’au moment où les processus affichent le nu-
méro de leur père, ils ont déjà éte adoptés par le processus init.
i n t main ( void )
{
pid_t p ;
int a = 20;
10
// é c r a t i o n d ’ un f i l s
switch ( p = f o r k ( ) )
{
case 1:
// l e f o r k a echoue
p e r r o r ( " l e f o r k a echoue ! " ) ;
break ;
case 0 :
// I l s ’ a g i t du p r o c e s s u s f i l s
20 p r i n t f ( " i c i p r o c e s s u s f i l s , l e PID %d . \ n " , g e t p i d ( ) ) ;
a += 10;
break ;
default :
// I l s ’ a g i t du p r o c e s s u s pere
p r i n t f ( " i c i p r o c e s s u s pere , l e PID %d . \ n " , g e t p i d ( ) ) ;
a += 100;
}
// l e s deux p r o c e s s u s e x e c u t e n t c e t t e i n s t r u c t i o n
p r i n t f ( " Fin du p r o c e s s u s %d avec a = %d . \ n " , g e t p i d ( ) , a ) ;
3.3. CRÉATION DE PROCESSUS 11
30 return 0 ;
}
Deux exécutions du programme tfork.cpp montrent que les processus
père et fils sont concurrents :
i n t main ( )
{
int i , n=5;
int childpid ;
10 f o r ( i = 1 ; i <n ; i ++)
{
i f ( ( c h i l d p i d = f o r k ( ) ) < = 0 ) break ;
p r i n t f ( " P r o c e s s u s %d avec pere %d , i=%d\n " , g e t p i d ( ) ,
getppid ( ) , i ) ;
}
return 0 ;
}
12 CHAPITRE 3. PROCESSUS
i n t main ( )
{
int i , n=5;
p i d _ t pid ;
10 f o r ( i = 1 ; i <n ; i ++)
{
i f ( ( pid= f o r k ( ) ) = = 1 )
break ;
i f ( pid = = 0 )
p r i n t f ( " P r o c e s s u s %d avec pere %d , i=%d\n " , g e t p i d ( ) ,
getppid ( ) , i ) ;
}
return 0 ;
20 }
La sortie de fork1.c est assez comprehensible. À chaque retour de l’appel
de fork(), on sort de la boucle si on est dans un processus fils (valeur de
retour égale à 0), ou si l’appel a échoué (valeur de retour négative). Si le
shell, qui le père du processus crée lors de l’exécution de fork1, a un pid
de 759, et le processsus lui-même un pid = 904 , on aura la sortie suivante :
pas le cas, puisqu’encore une fois des processus pères meurent avant que
leur fils ne puissent afficher leurs messages :
shell = 759
fork1 = 874
889
F IG . 3.7 – Arbre que nous obtiendrions avec fork2.c si les processus pères
ne mourraient pas avant que les fils aient pu afficher leurs messages.
14 CHAPITRE 3. PROCESSUS
#include <sys/wait.h>
int wait (int *status);
int waitpid(int pid, int *status, int options);
void exit(int return_code);
i n t main ( )
{
pid_t f i l s _ p i d ;
f i l s _ p i d =fork ( ) ;
i f ( f i l s _ p i d ==0)
10 p r i n t f ( " J e s u i s l e f i l s avec pid %d\n " , g e t p i d ( ) ) ;
else i f ( f i l s _ p i d > 0 ) {
p r i n t f ( " J e s u i s l e pere avec pid %d . \ n " , g e t p i d ( ) ) ;
p r i n t f ( " J ’ a t t e n d s que mon f i l s se termine\n " ) ;
wait (NULL) ;
}
else
p r i n t f ( " E r r e u r dans l a c r e a t i o n du f i l s \n " ) ;
20 exit (0);
}
16 CHAPITRE 3. PROCESSUS
i n t main ( )
{
int i , n=5;
i n t pid ;
10 f o r ( i = 1 ; i <n ; i ++)
{
i f ( ( pid= f o r k ( ) ) = = 1 )
break ;
i f ( pid = = 0 )
p r i n t f ( " P r o c e s s u s %d avec pere %d , i=%d\n " , g e t p i d ( ) ,
getppid ( ) , i ) ;
}
// Attendre l a f i n des f i l s
20 while ( wait (NULL) > = 0 ) ;
return 0 ;
}
void f i l s ( i n t i ) ;
i n t main ( )
{
3.3. CRÉATION DE PROCESSUS 17
int status ;
i f ( f o r k ( ) ) // c r e a t i o n du premier f i l s
10 {
i f ( f o r k ( ) = = 0 ) // c r e a t i o n du second f i l s
fils (2) ;
}
else f i l s ( 1 ) ;
i f ( wait (& s t a t u s ) > 0 )
p r i n t f ( " f i n du f i l s %d\n " , s t a t u s > > 8 ) ;
i f ( wait (& s t a t u s ) > 0 )
p r i n t f ( " f i n du f i l s %d\n " , s t a t u s > > 8 ) ;
return 0 ;
20 }
void f i l s ( i n t i )
{
sleep ( 2 ) ;
exit ( i ) ;
}
Deux exécutions du programme deuxfils-1.c :
void f i l s ( i n t i ) ;
i n t main ( )
{
int status ;
10 p i d _ t pid ;
18 CHAPITRE 3. PROCESSUS
// C r e a t i o n du premier f i l s
pid = f o r k ( ) ;
i f ( pid = = 1 ) p e r r o r ( " f o r k " ) ;
e l s e i f ( pid = = 0 ) f i l s ( 1 ) ;
// C r e a t i o n du second f i l s
pid = f o r k ( ) ;
i f ( pid = = 1 ) p e r r o r ( " f o r k " ) ;
20 e l s e i f ( pid = = 0 ) f i l s ( 2 ) ;
// A t t e n t e de t e r m i n a i s o n des deux f i l s
// I c i on a t t e n d l a f i n d ’ un t r o i s i e m e f i l s qui n ’ e x i s t e pas
// Une e r r e u r s e r a r e t o u r n e e
i f ( wait (& s t a t u s ) > 0 )
p r i n t f ( " f i n du f i l s %d\n " , WEXITSTATUS ( s t a t u s ) ) ;
e l s e p e r r o r ( " wait " ) ;
return 0 ;
}
40
// Routine pour l e s f i l s
void f i l s ( i n t i )
{
sleep ( 2 ) ;
exit ( i );
}
Exécution du programme deuxfils-2.c :
fork()
exit() zombie
F IG . 3.8 – Processus zombie : le fils meurt et le père n’a pas fait wait().
i n t main ( )
{
p i d _ t pid ;
20 CHAPITRE 3. PROCESSUS
fork() exit()
F IG . 3.9 – Processus orphelin : le père meurt avant le fils. init adopte l’en-
fant.
// p r o c e s s u s f i l s
10 pid = f o r k ( ) ;
i f ( pid > 0 )
{
// Pere : dormir 3 0 secondes
sleep ( 3 0 ) ;
}
else
{
// F i l s : q u i t t e r immediatement
exit ( 0 ) ;
20 }
return 0 ;
}
Exécution de zombie.c en tâche de fond : la commande ps permet de
constater son état zombie avant et après son disparition définitive :
leibnitz> gcc -o zombie zombie.c
leibnitz> zombie &
[1] 5324
leibnitz> ps -u jmtorres
PID TTY TIME CMD
2867 pts/0 00:00:00 tcsh
5324 pts/0 00:00:00 zombie
5325 pts/0 00:00:00 zombie <defunct>
5395 pts/0 00:00:00 ps
leibnitz>
3.3. CRÉATION DE PROCESSUS 21
execv execvp
execve
#include <unistd.h>
int execl(const char *path, const char *argv, ...);
int execv(const char *path, const char *argv[]);
int execle(const char *path, const char *argv,
const char *envp[]);
int execlp(const char *file, const char *argv, ...);
int execvp(const char *file, const char *argv[]);
22 CHAPITRE 3. PROCESSUS
char *arguments[4]
...
arguments[0]="/bin/ls";
arguments[1]="-l";
arguments[2]="/etc";
arguments[3]="NULL";
execv("/bin/ls", arguments);
...
pid_t f i l s _ p i d ;
// Dupliquer l e p r o c e s s u s
f i l s _ p i d = fork ( ) ;
if ( fils_pid ! = 0 ) {
// I l s ’ a g i t du pere
waitpid ( f i l s _ p i d ,NULL,NULL) ;
p r i n t f ( " Programme p r i n c i p a l termine \n " ) ;
return 0 ;
}
20 else
{
// E x e c u t e r programme avec l e s arguments a p a r t i r du PATH
execvp ( " l s " , a r g s ) ;
// Retourner en c a s d ’ e r r e u r
p e r r o r ( " E r r e u r dans execvp " ) ;
exit (1);
}
return 0 ;
30 }
Exécution de fork-exec.cpp :
i n t main ( void )
{
int p , child , status ;
p= f o r k ( ) ;
10 i f ( p == 1)
return 1;
i f ( p > 0 ) // i l s ’ a g i t du pere c a r p > 0
{
p r i n t f ( " I c i l e pere [%d ] , mon f i l s [ %d]\n " , g e t p i d ( ) , p ) ;
i f ( ( c h i l d =wait (& s t a t u s ) ) > 0 )
p r i n t f ( " I c i pere [%d ] , f i n du f i l s [%d]\n " , g e t p i d ( ) , c h i l d ) ;
// Dormir pendant une seconde
sleep ( 1 ) ;
p r i n t f ( " Le pere [%d ] se termine \n " , g e t p i d ( ) ) ;
20 }
else
{ // i l s ’ a g i t du f i l s
i f ( ( status=execl (
"/home/ada/ u s e r s / j m t o r r e s / i n f 3 6 0 0 / l o g i c i e l /a . out " ,
3.3. CRÉATION DE PROCESSUS 25
i n t main ( )
{
p r i n t f ( " i c i programme f i l s [%d ] \ n " , g e t p i d ( ) ) ;
return 0 ;
}
Exécution des programmes parent.cpp et fils.cpp. Remarque : Le fi-
chier exécutable de fils.cpp est a.out :
10 }
e l s e i f ( wait (NULL) > 0 )
p r i n t f ( " Le pere d e t e c t e l a f i n du f i l s \n " ) ;
return 0 ;
}
Exécution du programme texecvp.cpp :
3.4 Exercices
1. Dans le système Unix, est-ce que tout processus a un père ?
2. Que se passe-t-il lorsqu’un processus devient orphelin (mort de son
père) ?
3. Quand est-ce qu’un processus passe à l’état zombie ?
4. Pour lancer en parallèle plusieurs traitements d’une même applica-
tion, vous avez le choix entre les appels système pthread_create()
et fork(). Laquelle des deux possibilités choisir ? Pourquoi ?
5. Qu’affiche chacun des segments de programme suivants :
(a) for (i=1; i<=4; i++)
{
pid = fork();
if (pid != 0) printf("%d\n", pid);
}