Vous êtes sur la page 1sur 28

Chapitre 3

Processus

L E concept de processus est le plus important dans un systme dex-


ploitation. Tout le logiciel dun ordinateur est organis en un certain
nombre de processus squentiels. En simplifiant, on peut considrer un
processus comme un programme qui sexcute et possde un compteur
ordinal, pour indiquer quelle instruction il est rendu, des registres, des
variables et une pile dexcution (nous verrons plus tard quun processus
peut contenir plusieurs thread, donc plusieurs piles dexcution). Son ex-
cution est, en gnral, une alternance de calculs effectus par le processeur
et de requtes dEntre/Sortie effectues par les priphriques.

3.1 Processus
3.1.1 tats dun processus
Lorsquun processus sexcute, il change dtat, comme on peut le voir
sur la figure ??. Il peut se trouver alors dans lun des trois tats principaux :

lu : en cours dexcution.
Prt : en attente du processeur.
Bloqu : en attente dun vnement.
Initialement, un processus est ltat prt. Il passe ltat excution
lorsque le processeur entame son excution. Un processus passe de ltat
excution ltat prt ou lorsquil est suspendu provisoirement pour per-
mettre lexcution dun autre processus. Il passe de ltat excution ltat
bloqu sil ne peut plus poursuivre son excution (demande dune E/S). Il
se met alors en attente dun vnement (fin de lE/S). Lorsque lvnement

1
2 CHAPITRE 3. PROCESSUS

Interruption

Prt Excution

lu

Fin dE/S En attente dE/S

Bloqu

F IG . 3.1 Les tats principa-ux dun processus.

survient, il redevient prt.


Le modle trois tats est complt par deux tats supplmentaires :
Nouveau et Fin, qui indiquent respectivement la cration dun processus
dans le systme et son terminaison, comme illustr la figure ??.

Nouveau Fin
Interruption
Admis Termin

Prt Excution

lu

Fin dE/S En attente dE/S

Bloqu

F IG . 3.2 Modle cinq tats dun processus.

Consulter le Chapitre ??, Ordonnancement des processus pour plus de


dtails sur les tats de processus en Unix/Linux.

3.1.2 Implmentation de processus


Pour grer les processus, le systme dexploitation sauvegarde plusieurs
informations dans des structures de donnes. Il existe une table pour conte-
nir les informations concernant tous les processus crs. Il y a une en-
tre par processus dans la table, appele le Bloc de Contrle de Processus
3.1. PROCESSUS 3

(PCB).

Bloc de Contrle de Processus


Chaque entre de la table PCB comporte des informations sur :
Le pid du processus.
Ltat du processus.
Son compteur ordinal (adresse de la prochaine instruction devant tre
excute par ce processus).
Son allocation mmoire.
Les fichiers ouverts.
Les valeurs contenues dans les registres du processeur.
Tout ce qui doit tre sauvegard lorsque lexcution dun processus
est suspendue.
Sur la figure ??, nous montrons les principales entres de la PCB.

Gestion des processus Gestion de la mmoire Gestion des fichiers


Registres Pointeur seg. code Rpertoire racine
Compteur ordinal (co) Pointeur seg. donnes Rpertoire de travail
Priorit Pointeur seg. pile fd
Pointeur de pile (sp) uid
tat du processus gid
Date de lancement
Temps UCT utilis
Temps UCT des fils
Signaux
pid
ppid

F IG . 3.3 Principales entres de la Table des processus dUnix. Les entres


co, tats de processus, pid et ppid seront tudies dans ce chapitre. Celles
des descripteurs de fichiers (fd), signaux, priorit, temps, gestion de m-
moire et rpertoires seront tudies ultrieurement.

Changement de contexte de processus


Une des principales raison pour justifier lexistence des blocs de contrle
des processus est que dans un systme multiprogramm on a souvent be-
soin de redonner le contrle du CPU un autre processus. Il faut donc m-
moriser toutes les informations ncessaires pour pouvoir ventuellement
4 CHAPITRE 3. PROCESSUS

relancer le processus courant dans le mme 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. Lordonnanceur
sexcute en mode kernel pour pouvoir manipuler les PCB. Le changement
de contexte sera expliqu plus en dtail dans le Chapitre ??, Ordonnance-
ment de processus.

Programme A Mode kernel Programme B


mode utilisateur mode utilisateur
excution

interruption Sauvegarder tat en PCB A

excution
Charger tat de PCB B

Sauvegarder tat en PCB B


interruption
excution Charger tat de PCB A

F IG . 3.4 Le changement de contexte.

Il est important de noter que le passage au mode kernel par un appel


systme nimplique pas ncessairement un changement de contexte. On
reste en gnral dans le mme processus, sauf quon a accs des donnes
et des instructions qui sont interdites en mode utilisateur.

3.1.3 Les dmons


Les dmons sont des processus particuliers. Un dmon sexcute tou-
jours en arrire-plan (background ). Ceci implique que son pre nattend
pas la fin de son excution. Les dmons ne sont associs aucun termi-
nal ou processus login dun utilisateur. Ils sont toujours lcoute et at-
tendent quun vnement se produise. Ils ralisent des tches de manire
priodique.
Les dmons dmarrent normalement au dbut du chargement du sys-
tme dexploitation et ne meurent pas. Les dmons ne travaillent pas : ils
lancent plutt dautres processus pour effectuer les tches. Comme exemples
de dmons on retrouve le voleur de pages de la mmoire virtuelle (voir
3.2. SERVICES POSIX POUR LA GESTION DE PROCESSUS 5

Chapitre ?? sur la gestion de la mmoire) ou le dmon des terminaux qui


lance getty, puis login linvite des utilisateurs.

3.1.4 Cration et terminaison de processus


Le systme dexploitation fournit un ensemble dappels systme qui
permettent la cration, la destruction, la communication et la synchronisa-
tion des processus. Les processus sont crs et dtruits dynamiquement. Un
processus peut crer un ou plusieurs processus fils qui, leur tour, peuvent
crer des processus fils sous une forme de structure arborescente. Le pro-
cessus crateur est appel processus pre.
Dans certains systmes, comme MS-DOS, lorsquun processus cre un
fils, lexcution du processus pre est suspendue jusqu la terminaison du
processus fils. Cest ce quon appelle lexcution squentielle. Par contre
dans les systmes du type Unix, le pre continue sexcuter en concur-
rence avec ses fils. Cest ce quon appelle excution asynchrone. Un pro-
cessus fils cr peut partager certaines ressources comme la mmoire ou
les fichiers avec son processus pre ou avoir ses propres ressources. Le pro-
cessus pre peut contrler lusage des ressources partages et peut avoir
une certaine autorit sur ses processus fils. galement, il peut suspendre
ou dtruire ses processus fils. Il peut galement se mettre en attente de la
fin de lexcution de ses fils. Lespace dadressage du processus fils est ob-
tenu par duplication de celui du pre. Il peut excuter le mme programme
que son pre ou charger un autre programme. Un processus se termine par
une demande darrt volontaire (appel systme exit()) ou par un arrt
forc provoqu par un autre processus (appel systme kill()). Lorsquun
processus se termine toutes les ressources systmes qui lui ont t alloues
sont libres par le systme dexploitation.

3.2 Services Posix pour la gestion de processus


La norme Posix dfinit un nombre relativement petit dappels systme
pour la gestion de processus :

pid_t fork() : Cration de processus fils.


int execl(), int execlp(), int execvp(), int execle(),
int execv() : Les services exec() permettent un processus dex-
cuter un programme (code) diffrent.
pid_t wait() : Attendre la terminaison dun processus.
6 CHAPITRE 3. PROCESSUS

void exit() : Finir lexcution dun processus.


pid_t getpid() : Retourne lidentifiant du processus.
pid_t getppid() : Retourne lidentifiant du processus pre.
En Linux, le type pid_t correspond normalement un long int.

3.3 Cration de processus


3.3.1 Cration de processus avec system()
Il y a une faon de crer un sous-processus en Unix/Linux, en utilisant
la commande system(), de la bibliothque standard de C <stdlib.h>.
Comme arguments elle reoit le nom de la commande (et peut-tre une liste
darguments) entre guillemets.
Listing 3.1 system.c

# i n c l u d e < s t d l i b . h>

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 cre un processus fils en lanant la commande :
ls -l /

dans un shell. Il faut retenir que system() nest pas un appel systme,
mais une fonction C. Ce qui rend lutilisation de la fonction system()
moins performante quun appel systme de cration de processus. Ceci sera
discut la fin de la section.

3.3.2 Cration de processus avec fork()


Dans le cas dUnix, lappel systme fork() est la seule vritable faon
qui permet de crer des processus fils :

#include <unistd.h>
int fork();
3.3. CRATION DE PROCESSUS 7

fork() est le seul moyen de crer des processus, par duplication dun
processus existant1 . Lappel systme fork() cre une copie exacte du pro-
cessus original, comme illustr la figure ??. Mais maintenant il se pose un
problme, car les deux processus pre et fils excutent le mme code. Com-
ment distinguer alors le processus pre du processus fils ? Pour rsoudre ce

pile pile

i 27 i 0

i= fork() i= fork()
co co

Processus pre Processus fils

F IG . 3.5 Processus pre et son fils clon. i=27 aprs lappel fork() pour
le pre et i=0 pour le fils. Limage mmoire est la mme pour tous les deux,
notamment le compteur ordinal co.

problme, on regarde la valeur de retour de fork(), qui peut tre :


0 pour le processus fils
Strictement positive pour le processus pre et qui correspond au pid
du processus fils
Ngative si la cration de processus a chou, sil ny a pas suffisam-
ment despace mmoire ou si bien le nombre maximal de crations
autorises est atteint.
Ce branchement est montr la figure ??. Le pre et le fils ont chacun leur
propre image mmoire prive. Aprs lappel systme fork(), la valeur de
pid reoit la valeur 0 dans le processus fils mais elle est gale lidentifiant
du processus fils dans le processus pre.
Observez attentivement les exemples suivants :

1
Linux a introduit un autre appel systme pour la cration de contexte dun processus :
clone(). Cest un appel systme trs puissant, avec un nombre darguments pour grer la
mmoire par le programmeur. Malheureusement clone() ne fait pas partie de la norme
Posix, et ne sera pas tudi ici. Le lecteur intress peut consulter, par exemple, les articles
du Linux Journal, http ://www.linuxjournal.com
8 CHAPITRE 3. PROCESSUS

pid = 13

pre

fils i = fork()

i=0 i=27
pid = 27 pid = 13

F IG . 3.6 Services Posix : fork().

 Exemple 1. Aprs lexcution de fils.c le pre et le fils montrent leur


pid :
Listing 3.2 fils.c

# i n c l u d e < u n i s t d . h>
# i n c l u d e < sys/t y p e s . h>

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 " ) ;
 } 
Lexcution de fils.c montre les pid du pre et du fils :

leibnitz> gcc -o fils fils.c


leibnitz> fils
Je suis le pere avec pid 4130
leibnitz> Je suis le fils avec pid 4131
3.3. CRATION DE PROCESSUS 9

Remarquez que la console a raffich linvite (leibnitz>) avant laffi-


chage du second message. Ceci sexplique par le fait que le processus pre,
qui a t lanc partir de la console, sest termin avant le processus fils, et
a donc redonn le contrle la console avant que le fils ne puisse afficher
son message.
 Exemple 2. tudiez en dtail le programme suivant.
Listing 3.3 chaine.c

# i n c l u d e < sys/ t y p e s . h>
# i n c l u d e < u n i s t d . h>

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 ;

p r i n t f ( " P r o c e s s u s %d avec pere %d\n " , g e t p i d ( ) , getppid ( ) ) ;


}

20 // Pour f a i r e perdre du temps au p r o c e s s u s


f o r ( j = 1 ; j < 1 0 0 0 0 0 ; j ++)
for (k = 1 ; k <1000; k++);

 } 
Ce programme crera une chane de  
processus :

leibnitz> gcc -o chaine chaine.c


leibnitz> chaine
Processus 23762 avec pere 23761
leibnitz> Processus 23763 avec pere 23762
Processus 23764 avec pere 23763
Processus 23765 avec pere 23764

Si lon omet les boucles finales du programme, on obtient un rsultat


inattendu :
10 CHAPITRE 3. PROCESSUS

leibnitz> gcc -o chaine chaine.c


leibnitz> chaine
leibnitz> Processus 23778 avec pere 1
Processus 23779 avec pere 1
Processus 23780 avec pere 1
Processus 23781 avec pere 1

Le problme ici est que dans tous les cas le processus pre 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 quau moment o les processus affichent le nu-
mro de leur pre, ils ont dj te adopts par le processus init.

 Exemple 3. Le programme tfork.cpp suivant crera deux processus


qui vont modifier la variable a :
Listing 3.4 tfork.cpp

# include < sys/t y p e s . h > // pour l e type p i d _ t
# include < u n i s t d . h > // pour f o r k
# include < s t d i o . h > // pour p e r r o r , p r i n t f

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. CRATION DE PROCESSUS 11

30 return 0 ;
} 
Deux excutions du programme tfork.cpp montrent que les processus
pre et fils sont concurrents :

leibnitz> gcc -o tfork tfork.cpp


leibnitz> tfork
ici processus pere, le pid 12339.
ici processus fils, le pid 12340.
Fin du Process 12340 avec a = 30.
Fin du Process 12339 avec a = 120.
leibnitz>
leibnitz> tfork
ici processus pere, le pid 15301.
Fin du Process 15301 avec a = 120.
ici processus fils, le pid 15302.
Fin du Process 15302 avec a = 30.
leibnitz>

 Exemple 4. Les exemples suivants meritent dtre etudis en dtail. Quelle


est la diffrence entre les programmes fork1.c et fork2.c suivants ?
Combien de fils engendreront-ils chacun ?
Listing 3.5 fork1.c

f # i n c l u d e < sys/ t y p e s . h>
# i n c l u d e < u n i s t d . h>
# i n c l u d e < s t d i o . h>

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

Listing 3.6 fork2.c



# i n c l u d e < sys/t y p e s . h>
# i n c l u d e < u n i s t d . h>
# i n c l u d e < s t d i o . h>

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 lappel
de fork(), on sort de la boucle si on est dans un processus fils (valeur de
retour gale 0), ou si lappel a chou (valeur de retour ngative). Si le
shell, qui le pre du processus cre lors de lexcution de fork1, a un pid
de 759, et le processsus lui-mme un pid = 904 , on aura la sortie suivante :

leibnitz> gcc -o fork2 fork2.c


leibnitz> fork2
Processus 904 avec pere 759, i=1
Processus 904 avec pere 759, i=2
Processus 904 avec pere 759, i=3
Processus 904 avec pere 759, i=4

Par contre, le fonctionnement de fork2.c est plus complexe. Si lappel


fork() nchoue jamais, on sait que le premier processus crera 4 nouveaux
processus fils. Le premier de ces fils continue dexcuter la boucle partir

de la valeur courante du compteur . Il crera donc lui-mme trois proces-
sus. Le second fils en crera deux, et ainsi de suite. Et tout cela se rpte
rcursivement avec chacun des fils. En supposant que le shell a toujours
un pid de 759, et que le processus associ fork2 a un pid de 874, nous
devrions obtenir larbre illustr la figure ??. Malheureusement, ce ne sera
3.3. CRATION DE PROCESSUS 13

pas le cas, puisquencore une fois des processus pres meurent avant que
leur fils ne puissent afficher leurs messages :

leibnitz> gcc -o fork2 fork2.c


leibnitz> fork2
Processus 23981 avec pere 23980, i=1
Processus 23982 avec pere 23980, i=2
Processus 23983 avec pere 23980, i=3
leibnitz> Processus 23984 avec pere 1, i=4
Processus 23985 avec pere 23981, i=2
Processus 23986 avec pere 23981, i=3
Processus 23987 avec pere 1, i=4
Processus 23988 avec pere 23982, i=3
Processus 23989 avec pere 1, i=4
Processus 23990 avec pere 1, i=4
Processus 23991 avec pere 23985, i=3
Processus 23992 avec pere 1, i=4
Processus 23993 avec pere 1, i=4
Processus 23994 avec pere 1, i=4
Processus 23995 avec pere 1, i=4

Nous verrons la prochaine section comment viter ce problme.

shell = 759

fork1 = 874

875 876 877 878

879 880 881 882 883 884

885 886 887 888

889

F IG . 3.7 Arbre que nous obtiendrions avec fork2.c si les processus pres
ne mourraient pas avant que les fils aient pu afficher leurs messages.
14 CHAPITRE 3. PROCESSUS

3.3.3 Les appels systme wait(), waitpid() et exit()


Ces appels systme permettent au processus pre dattendre la fin dun
de ses processus fils et de rcuprer son status de fin. Ainsi, un processus
peut synchroniser son excution avec la fin de son processus fils en excu-
tant lappel systme wait(). La syntaxe de lappel systme est :
pid = wait(status);
o pid est lidentifiant du processus fils et status est ladresse dans les-
pace utilisateur dun entier qui contiendra le status de exit() du proces-
sus fils.-

#include <sys/wait.h>
int wait (int *status);
int waitpid(int pid, int *status, int options);
void exit(int return_code);

wait() : Permet un processus pre dattendre jusqu ce quun pro-


cessus fils termine. Il retourne lidentifiant du processus fils et son tat
de terminaison dans &status.
waitpid() : Permet un processus pre dattendre jusqu ce que
le processus fils numro pid termine. Il retourne lidentifiant du pro-
cessus fils et son tat de terminaison dans &status.
void exit() : Permet de finir volontairement lexcution dun pro-
cessus et donne son tat de terminaison. Il faut souligner quun pro-
cessus peut se terminer aussi par un arrt forc provoqu par un autre
processus avec lenvoi dun signal 2 du type kill(). Une description
dtaille de kill() et dautres appels systme se trouve dans lAn-
nexe ??.
Le processus appelant est mis en attente jusqu ce que lun de ses fils
termine. Quand cela se produit, il revient de la fonction. Si status est
diffrent de 0, alors 16 bits dinformation sont rangs dans les 16 bits de
poids faible de lentier point par status. Ces informations permettent de
savoir comment sest termin le processus selon les conventions suivantes :
Si le fils est stopp, les 8 bits de poids fort contiennent le numro
du signal qui a arrt le processus et les 8 bits de poids faible ont la
valeur octale 0177.
Si le fils sest termin avec un exit(), les 8 bits de poids faible de
status sont nuls et les 8 bits de poids fort contiennent les 8 bits de
2
Les signaux et dautres types de Communication Interprocessus seront tudis au Cha-
pitre ??.
3.3. CRATION DE PROCESSUS 15

poids faible du paramtre utilis par le processus fils lors de lappel


de exit().
Si le fils sest termin sur la rception dun signal, les 8 bits de poids
fort de status sont nuls et les 7 bits de poids faible contiennent le
numro du signal qui a caus la fin du processus.
Dans le fichier den-tte <sys/wait.h> sont definis diffrents macros
qui permettent danalyser la valeur de status afin de determiner la cause
de terminaison du processus. En particulier, le processus pre peut obtenir
la valeur des 8 bits de poids faible du paramtre qui reoit depuis exit()
de la part du fils en utilisant le macro :
WEXITSTATUS(status)
La dfinition de ce macro se trouve dans <sys/wait.h> :

#define WEXITSTATUS(s) (((s)>>8)&0xFF)

Evidemment, si le processus appelant wait() na pas de processus fils


vivants, une erreur est produite.
 Exemple 5. Rappelons-nous que le programme fils.c a le dfaut de
revenir au shell avant que le fils nait pu affiher son message. En utilisant
lappel wait, on peut facilement corriger ce problme :
Listing 3.7 wait.c

# i n c l u d e < u n i s t d . h>
# i n c l u d e < sys/ t y p e s . h>

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

 Exemple 6. Pour viter le problme des processus pres qui meurent


avant leurs fils dans le programme fork2.c, il suffit dajouter une boucle
sur lappel wait. Ds quil ne restera plus aucun fils, lappel retournera
aussitt une valeur ngative. On saura alors que lon peut sortir sans pro-
blme. Voici le programme modifi :
Listing 3.8 fork2b.c

# i n c l u d e < sys/t y p e s . h>
# i n c l u d e < u n i s t d . h>
# i n c l u d e < s t d i o . h>

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 ;
 } 

 Exemple 7. Cration de deux fils (version 1), et utilisation des 8 bits de


poids fort :
Listing 3.9 deuxfils-1.c

# i n c l u d e < sys/wait . h>
# i n c l u d e < s t d i o . h>
# i n c l u d e < u n i s t d . h>

void f i l s ( i n t i ) ;
i n t main ( )
{
3.3. CRATION 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 excutions du programme deuxfils-1.c :

leibnitz> gcc -o deuxfils-1 deuxfils-1.c


leibnitz> deuxfils-1
fin du fils 2
fin du fils 1
leibnitz> deuxfils-1
fin du fils 1
fin du fils 2
leibnitz>

 Exemple 8. Cration de deux fils (version 2), avec la simulation dattente


dun troisime fils inexistant et lutilisation du macro WEXITSTATUS :
Listing 3.10 deuxfils-2.c

# include < sys/wait . h>
# include < s t d i o . h>
# include < s t d l i b . h>
# include < u n i s t d . h>

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 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 " ) ;

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 ) ) ;
30 e l s e p e r r o r ( " wait " ) ;

// 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 );
 } 
Excution du programme deuxfils-2.c :

leibnitz> gcc -o deuxfils-2 deuxfils-2.c


leibnitz> deuxfils-2
fin du fils 2
fin du fils 1
wait: No child processes
leibnitz>
3.3. CRATION DE PROCESSUS 19

3.3.4 Processus zombie


Lexcution asynchrone entre processus parent et fils a certaines cons-
quences. Souvent, le fils dun processus se termine, mais son pre ne lat-
tend pas. Le processus fils devient alors un processus zombie, comme illus-
tr la figure ??. Le processus fils existe toujours dans la table des proces-
sus, mais il nutilise plus les ressources du kernel.

init init init init

processus A processus A processus A processus A

fork()

processus B processus B processus B

exit() zombie

F IG . 3.8 Processus zombie : le fils meurt et le pre na pas fait wait().

Lexcution de lappel systme wait() ou waitpid() par le proces-


sus pre limine le fils de la table des processus. Il peut y avoir aussi des
terminaisons prmatures, o le processus parent se termine ses processus
fils (figure ??). Dans cette situation, ses processus fils sont adopts par le
processus init, dont le pid = 1.
 Exemple 9. Engendrer des zombies est relativement facile. Le programme
zombie.c montre la cration dun processus fils zombie pendant 30 se-
condes :
Listing 3.11 zombie.c

# include < u n i s t d . h > //pour s l e e p
# include < s t d l i b . h>
# include < sys/ t y p e s . h>

i n t main ( )
{
p i d _ t pid ;
20 CHAPITRE 3. PROCESSUS

init init init init

processus A processus A processus A wait()

fork() exit()

processus B processus B processus B

F IG . 3.9 Processus orphelin : le pre meurt avant le fils. init adopte len-
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 ;
} 
Excution de zombie.c en tche de fond : la commande ps permet de
constater son tat zombie avant et aprs son disparition dfinitive :
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. CRATION DE PROCESSUS 21

[1] Done zombie


leibnitz>
leibnitz> ps -u jmtorres
PID TTY TIME CMD
2867 pts/0 00:00:00 tcsh
5396 pts/0 00:00:00 ps
leibnitz>

3.3.5 La famille des appels systme exec


Un processus fils cr peut remplacer son code de programme par un
autre programme. Le systme Unix offre une famille dappels systme exec
qui permettent de changer limage dun processus (figure ??). Tous les ap-
pels systme exec remplacent le processus courant par un nouveau pro-
cessus construit partir dun fichier ordinaire excutable. Les segments de
texte et de donnes du processus sont remplacs par ceux du fichier excu-
table.

execl execle execlp

execv execvp

execve

F IG . 3.10 Services Posix : exec.

#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

execl() : permet de passer un nombre fix de paramtres au nou-


veau programme.
execv() : permet de passer un nombre libre de paramtres au nou-
veau programme.
execle() : mme fonctionnement quexecl() avec en plus, un ar-
gument envp qui reprsente un pointeur sur un tableau de pointeurs
sur des chanes de caractres dfinissant lenvironnement.
exelp() : Interface et action identiques celles dexecl(), mais la
diffrence vient du fait que si le nom du fichier nest pas un nom
complet par rapport la racine le systme utilisera le chemin
de recherche des commandes les chemins indiqus par la variable
PATH pour trouver dans quel rpertoire se trouve le programme.
exevp() : Interface et action identiques celles dexecv(), mais
la diffrence vient du fait que si le nom de fichier nest pas un nom
complet, la commande utilise les rpertoires spcifis dans PATH.
La convention Unix veut que chaque chane ait la forme nom=valeur.
Ainsi, les arguments pour execv() peuvent tre passs, par exemple :

char *arguments[4]
...
arguments[0]="/bin/ls";
arguments[1]="-l";
arguments[2]="/etc";
arguments[3]="NULL";
execv("/bin/ls", arguments);
...

Aprs lexcution dun appel systme de la famille exec(), limage m-


moire dun processus est crase par la nouvelle image mmoire dun pro-
gramme excutable, mais le pid ne change pas. Le contenu du contexte
utilisateur qui existait avant lappel exec() nest plus accessible.
 Exemple 10. Le programme fork-exec.c montre lutilisation des ap-
pels systme fork() et execvp() :
Listing 3.12 fork-exec.c

# include < u n i s t d . h > //pour s l e e p
# include < s t d i o . h>
# include < sys/t y p e s . h>

i n t main ( i n t a r g c , char  argv [ ] )


{
3.3. CRATION DE PROCESSUS 23

pid_t f i l s _ p i d ;

// L i s t e d arguments pour l a comande " l s "


10  
char a r g s [ ] = { " l s " , " l " , argv [ 1 ] , NULL} ;

// 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 } 
Excution de fork-exec.cpp :

leibnitz> gcc -o fork-exec fork-exec.cpp


leibnitz> fork-exec /
total 113
drwxr-xr-x 2 root bin 4096 Aug 22 12:00 bin
drwxr-xr-x 2 root root 4096 Jan 20 15:46 boot
drwxr-xr-x 2 root root 4096 Aug 22 16:43 cdrom
drwxr-xr-x 14 root root 40960 Jan 20 16:10 dev
drwxr-xr-x 33 root root 4096 Jan 22 15:55 etc
drwx------ 2 root root 16384 Aug 22 11:18 lost+found
drwxr-xr-x 5 root root 4096 Mar 16 2002 mnt
...
drwxrwxrwt 8 root root 4096 Jan 22 16:44 tmp
drwxr-xr-x 20 root root 4096 Jan 20 16:04 usr
drwxr-xr-x 17 root root 4096 Aug 22 16:43 var
Programme principal termine
leibnitz>
24 CHAPITRE 3. PROCESSUS

3.3.6 Commentaire sur system()


Lexcution de la fonction de bibliothque system() est essentielle-
ment un fork() + exec() + wait(). Et elle est peut tre encore pire que
cela, car elle invoque un shell pour excuter des commandes, ce qui est trs
coteux en ressources, car le shell doit charger des scripts et des initialisa-
tions. Bien que system() puisse savrer utile pour des petits programmes
qui sexcutent trs occasionnellement, on la dconseille fortement pour
dautres applications. Par exemple, le seul appel :
system("ls -l /usr")
est un gaspillage incroyable de ressources. Dabord on effectue un fork(),
puis un exec() pour le shell. Puis le shell sinitialise et effectue un autre
fork() et un exec() pour lancer ls. Il est prfrable dutiliser des appels
systme pour la lecture des rpertoires (voir par exemple le Chapitre ??
Systme de fichiers, Section ?? Services Posix sur les rpertoires.) qui sont
moins coteux que la fonction system().
 Exemple 11. Les programmes parent.cpp et fils.cpp montrent la
cration des processus avec la combinaison fork() et execl() :
Listing 3.13 parent.cpp

# include < u n i s t d . h > // pour f o r k e t e x e c l
# include < sys/wait . h > // pour wait
# include < s t d i o . h> // pour p r i n t f

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. CRATION DE PROCESSUS 25

" a . out " ,


NULL))== 1) 
p r i n t f ( " d e s o l e l e programme n e x i s t e pas : % d\n " , s t a t u s ) ;
else
p r i n t f ( " c e t t e i n s t r u c t i o n n e s t j a m a i s e x e c u t e e \n " ) ;
30 }
return 0 ;
} 
Listing 3.14 fils.cpp

# i n c l u d e < s t d i o . h>
# i n c l u d e < u n i s t d . h>

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 ;
 } 
Excution des programmes parent.cpp et fils.cpp. Remarque : Le fi-
chier excutable de fils.cpp est a.out :

leibnitz> gcc fils.cpp


leibnitz> gcc -o parent.cpp
leibnitz> parent
Ici le pere 19909, mon fils 19910
ici programme fils 19910
Ici pere 19909, fin du fils 19910
Le pere 19909 se termine
leibnitz>

 Exemple 12. Utilisation dexecvp() :


Listing 3.15 texecvp.c

# include < u n i s t d . h>
# include < s t d i o . h>
# include < sys/wait . h>

i n t main ( i n t a r g c , char  argv [ ] )


{
i f ( fork ( ) = = 0 ) {
execvp ( argv [ 1 ] , & argv [ 1 ] ) ;
f p r i n t f ( s t d e r r , " on ne peut e x e c u t e r % s\n " , argv [ 1 ] ) ;
26 CHAPITRE 3. PROCESSUS

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 ;
} 
Excution du programme texecvp.cpp :

leibnitz> gcc -o texecvp texecvp.cpp


leibnitz> texecvp date
Wed Sep 4 15:42:06 EDT 2002
Le pere detecte la fin du fils
leibnitz> texecvp ugg+kju
on ne peut pas executer ugg+kju
Le pere detecte la fin du fils
leibnitz> gcc -o fils fils.cpp
leibnitz> fils
ici programme fils [20498]
leibnitz> texecvp fils
ici programme fils [20500]
Le pere detecte la fin du fils
leibnitz>
3.4. EXERCICES 27

3.4 Exercices
1. Dans le systme Unix, est-ce que tout processus a un pre ?
2. Que se passe-t-il lorsquun processus devient orphelin (mort de son
pre) ?
3. Quand est-ce quun processus passe ltat zombie ?
4. Pour lancer en parallle plusieurs traitements dune mme applica-
tion, vous avez le choix entre les appels systme pthread_create()
et fork(). Laquelle des deux possibilits choisir ? Pourquoi ?
5. Quaffiche chacun des segments de programme suivants :
(a) for (i=1; i<=4; i++)
{
pid = fork();
if (pid != 0) printf("%d\n", pid);
}

(b) for (i=1; i<=4; i++)


{
pid = fork();
if (pid == 0) break;
else printf("%d\n", pid);
}

(c) for (i=0; i<nb; i++)


{
p = fork();
if (p < 0) exit(1);
execlp("prog", "prog",NULL);
}
wait(&status);

(d) for (i=1; i<=nb; i++)


{
p1 = fork();
p2 = fork();
if (p1 < 0) exit(1);
if (p2 < 0) exit(1);
execlp("prog1", "prog1",NULL);
execlp("progc", "prog",NULL);
}
wait(&status);
28 CHAPITRE 3. PROCESSUS

(e) Quel est le nombre de processus crs, dans chaque cas ?


6. crire un programme qui lit lcran le nombre de fils crer, puis
les cre lun la suite de lautre. Chaque fils affiche lcran son pid
(getpid()) et celui de son pre (getppid()). Le processus crateur
doit attendre la fin de ses fils. Lorsquil dtecte la fin dun fils, il affiche
le pid du fils qui vient de se terminer.
7. crire un programme qui lance en crant un processus le progra-
mme prog2.cpp, puis se met en attente de la fin dexcution du
programme.
8. Expliquez un exemple dexcution de programme dans lequel il existe
de lincohrence entre les trois niveaux de mmoire, i.e. registres, m-
moire principale, et mmoire secondaire.
9. Lorsquun nouveau processus passe en tat dexcution, tous les re-
gistres de lUCT doivent tre initialiss ou restaurs avec les valeurs
au moment de linterruption du processus. Expliquez la raison pour
laquelle le registre compteur ordinal (i.e. program counter ) est le der-
nier tre initialis.
10. Quelle diffrence existe-il entre un programme excutable et un pro-
cessus ?
11. Est-ce quun processus peut excuter plusieurs programmes, et est-ce
quun programme peut tre excut par plusieurs processus la fois ?
Expliquez.
12. Considrez un systme ayant les caractristiques suivantes :
(a) Systme dexploitation multiprogramm.
(b) Un seul UCT.
(c) Nombre limit de ressources.
(d) Un processus peut quitter volontairement ltat dexcution pour
revenir ltat prt.
(e) Le systme peut bloquer tout moment un processus en tat
dexcution sil a besoin des ressources utilises par ce proce-
ssus.
Dessinez le diagramme dtat de ce systme.