100% ont trouvé ce document utile (1 vote)
239 vues56 pages

Chapter2 1

Transféré par

Oumaima Ziat
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd
100% ont trouvé ce document utile (1 vote)
239 vues56 pages

Chapter2 1

Transféré par

Oumaima Ziat
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd

Chapitre 1 :

Implémentation des processus

Plan
 Complément en c
 Concepts fondamentaux
 Création des processus
 Les tubes
 Les signaux

2
 Un premier programme en C
/* Un programme qui affiche bonjour. */
#include <stdio.h> //standard input/output library, ou stdio

int main()
{ printf("Bonjour.\n");
return 0; //termine l’exécution du main et retourne un résultat de 0 }
 Variables
o Toute variable a un type, qui spécifie la taille et l’interprétation de la mémoire
associée à la variable
#include <stdio.h>
int main()
{ int i; i = 1;
printf("La variable i vaut %d.\n", i);
i = 2;
printf("La variable i vaut maintenant %d.\n", i);
i = i + 1;
printf("La variable i vaut enfin %d.\n", i);
return 0;
}

 Sortie formatée
o L’instruction printf permet de formater et d’écrire des données sur la sortie
standard
o printf("Le carré de %d est %d.\n", i, i * i);

 Entrées
o L’instruction scanf permet de lire une valeur à partir de l’entrée standard

#include <stdio.h>
int main()
{
int i;
printf("Entrez un nombre: ");
scanf("%d", &i);
printf("Vous avez entré %d dont le carré est %d.\n", i, i * i);
return 0;
}

4
 Conditionnelles simples
o La conditionnelle if est une structure de contrôle. Elle consiste du mot clé if
suivi d’une condition entre parenthèses et de deux branches séparées par le
mot clé else. Si la condition est vraie, c’est la première branche qui est
exécutée ; sinon, c’est la deuxième.

#include <stdio.h>
int main()
{
int i;
printf("Entrez un nombre: ");
scanf("%d", &i);
if(i <= 5) {
printf("Inférieur ou égal à 5.\n");
} else {
printf("Pas inférieur ou égal à 5.\n");
}
return 0;
}

 Conditionnelles imbriquées
o Une conditionnelle peut aussi apparaître au sein d’une des deux branches
d’une autre conditionnelle.

#include <stdio.h>
int main()
{
int i;
printf("Entrez un nombre: ");
scanf("%d", &i);
if(i <= 5) {
printf("Inférieur ou égal à 5.\n");
} else {
printf("Pas inférieur ou égal à 5.\n");
}
return 0;
}

6
 Boucles définies
o Une boucle est une structure de contrôle qui sert à exécuter le même bloc de
code de multiples fois
o Une boucle définie, ou boucle for, est une boucle dont l’exécution est
contrôlée par un compteur de boucle dont la valeur varie entre deux valeurs
connues avant d’entrer dans la boucle
o for(e1 ; e2 ; e3 ) bloc
• l’expression e1, dite initialisation, est exécutée ;
• le test e2 est évalué; s’il est faux, on sort de la boucle, et l’exécution de la
boucle est terminée ;
• le corps de la boucle est exécuté ;
• l’expression e3 est exécutée ;
• on recommence à l’étape 2.
int main()
{ int i;
for(i = 1; i <= 10; i = i + 1)
{ printf("J'ai collé %d timbres.\n", i);
printf("Il m'en reste %d.\n", 10 - i);
}
}

 Quelques abréviations
o l’expression i++ est équivalente à i = i + 1 ;
o l’expression i-- est équivalente à i = i - 1 ;
o l’expression i += c est équivalente à i = i + c ;
o l’expression i -= c est équivalente à i = i - c.
 Terminaison prématurée à l’aide de return
o Il est parfois nécessaire d’interrompre l’exécution d’une boucle de façon prématurée,
#include<stdio.h>
int main()
{ int i; double x, somme;
somme = 0.0;
for(i = 0; i < 4; i = i + 1) {
scanf("%lf", &x);
if(x > -1.0E10 && x < 1.0E-10) {
printf("Division par zéro !\n"); return 1; //on utilise aussi Break
}
somme = somme + 1.0 / x;
}
printf("La somme des inverses est %lf.\n", somme);
return 0;
}

8
 Les directives
o Figurent sur les lignes commençant par #

o Les principales directives : #include qui permet d’inclure un fichier extérieur


• #include <nom_de_fichier> le fichier est recherché dans un répertoire
standard (/usr/include)
• #include "nom_de_fichier" le fichier est recherché dans le répertoire de
• travail puis, s’il n’y est pas, dans le répertoire standard

o Les principales directives : #define permet de définir une macro


o Macro simple : #define nom_macro [texte_de_remplacement], ex,
#define TAILLE 500
o Macro-fonction : #define nom(param_1, ..., param_n) texte, ex,
#define min(a,b) (((a) < (b)) ? (a) : (b))

 Les tableaux à une ou plusieurs dimensions


o Définition : type nom[expr_constante];
o Définition avec initialisation : type nom[expr] = {val0, val1, ..., valn};
o Déclaration sans définition : type nom[];
o Les index d’un tableau de n éléments varient entre 0 et n-1

#include <stdio.h>
#define MAXTAB 5
int min(int tab[],int nb)
{
int i, min=tab[0];
for (i=1; i<nb; i++)
if (tab[i] < min) min = tab[i];
return min;
}
int main()
{
int tab[MAXTAB] = { 106, 20, 34, 4, 15};
printf("Le minimum est : %d\n", min(tab,MAXTAB));
}

10
 Les structures
o L’accès aux champs d’une structure s’effectue directement pas l’opérateur
« . » : var1.a1 pour accéder au champ a1 de la variable var1

11

 Les chaînes de caractères


o La bibliothèque standard contient un assortiment très complet de fonctions
permettant de manipuler les chaînes de caractères comme des tableaux de
caractères
o il faut inclure string.h (parfois stdlib.h)
o ces fonctions utilisent le type size_t synonyme de unsigned
o dans les prototypes de fonction, const char * désignent les arguments de type
chaîne non modifiés par la fonction et char * ceux modifiés
o ces fonctions reconnaissent la fin d’une chaîne dès la rencontre du premier
caractère \0
o principales fonctions
• Strlen
• strcpy
• strcat
• strcmp

12
 Pointeurs
o un pointeur est un couple (adresse en mémoire, type de donnée)
o exemple : un pointeur sur un entier est donc l’adresse d’un entier dans la
mémoire
o déclaration d’une variable de type pointeur : type_pointé *ptr ; où ptr est une
variable qui contient l’adresse en mémoire d’un objet du type_pointé
o on acède à la valeur de l’objet pointé par ptr en utilisant l’opérateur
d’indirection * (*ptr)
o l’opérateur de prise d’adresse & permet d’accéder à l’adresse d’un objet

13

 Le principe des arguments de la ligne de commande


o Un programme C récupère les informations tapées sur la ligne commande du
Shell par l'intermédiaire des variables prédéfinies argc et argv.
o Ces variables doivent être déclarées en argument de la fonction main() qui
sera le point d'entrée initial du programme C

14
 Les arguments de la ligne de commande: syntaxe
o On peut écrire de nouvelles commandes qui peuvent être appelées avec des
paramètres
o La fonction main() s’écrit de la manière suivante :

• la valeur de argc est le nombre de chaîne de caractères de la ligne de


commande
• argv est un tableau de pointeurs sur des caractères pointant sur la
première chaîne de caractères de la ligne de commande : argv[0] pointe
sur le nom du programme, argv[1] sur le premier paramètre, …

o Exemple: si un fichier monprog.c permis de générer un exécutable monprog à


la compilation (cc monprog.c –o monprog) on peut invoquer le programme
monprog avec des arguments : ./monprog arg1 arg2 arg3
o La commande cp du bash prend deux arguments

15

 Les arguments de la ligne de commande


o la valeur de argc est le nombre de chaîne de caractères de la ligne de
commande
o argv est un tableau de pointeurs sur des caractères pointant sur la première
chaîne de caractères de la ligne de commande : argv[0] pointe sur le nom du
programme, argv[1] sur le premier paramètre, …

16
Concepts fondamentaux

17

 Processus Linux/Unix
o Un processus est une instance d'un programme en train de s’exécuter, une
tâche.
o Il possède un numéro unique sur le système pid
o Chaque processus appartient à un utilisateur et un groupe et à les droits qui
leur sont associés
o Sous shell, un processus est créé pour exécuter chacune des commandes
o Le shell est le processus père de toutes les commandes.

 Statut d’un processus


o Runing: le processus s’exécute
o Waiting: Attend quelque chose pour s’exécuter
o Ready: le processus a tout pour s’exécuter sauf le processeur
o Suspendu: Arrêté
o Zombie: état particulier

18
 Le multitâche sous Unix/Linux
o Unix est un système multitâches: il peut exécuter plusieurs progs à la fois
o Le shell crée un nouveau processus pour exécuter chaque commande
o Différence entre processus et programme :
• le programme est une description statique
• le processus est une activité dynamique (il a un début, un déroulement et
une fin, il a un état qui évolue au cours du temps)
o Par exemple, l’exécution d’un programme et la copie d’un fichier sur disque

 Exemple
o xclock: lancement d’un processus en 1ier plan (foreground )
o ctl+c: l’arrêt définitif d un processus
o xclock&: lancement d’un processus en 2ieme plan (background )
o ^z: un signal qui suspend l'exécution sans détruire le processus correspondant
o bg: pour afficher les processus en BG
o fg: pour afficher les processus en FG

19

 Le programme tourne au premier plan :


o ^Z le suspend ;
o ^C l'interrompt.
 Le programme est suspendu :
o fg le passe au premier plan ;
o bg le passe en arrière-plan.
 Le programme tourne en arrière-plan :
o si c'est le seul dans ce cas, fg le passe au premier plan ;
o sinon, c'est plus compliqué.
 Le programme ne tourne pas :
o il n'y a rien à faire...

o Travail (job) = processus lancé par une


commande au shell
o Seuls le travail en premier plan peut
recevoir des signaux du clavier. Les
o Autres sont manipulés par des commandes

20
o Exemple

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

int main() {
printf("processus %d, groupe %d\n", getpid(), getpgrp());
while(1) ;
}

21

o Exemple

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

int main() {
printf("processus %d, groupe %d\n", getpid(), getpgrp());
while(1) ;
}

22
23

 La commande ps:
o PID (process identificator) : c'est le numéro du processus.
o TT : indique le terminal dans lequel a été lancé le processus. Un point
d'interrogation signifie que le processus n'est attaché à aucun terminal (par
exemple les démons).
o STAT : indique l'état du processus :
• R : actif ( running)
• S : non activé depuis moins de 20 secondes (sleeping)
• I : non activé depuis plus de 20 secondes (idle)
• T : arrêté (suspendu)
• Z : zombie
o TIME : indique le temps machine utilisé par le programme (et non pas le
temps depuis lequel le processus a été lancé !).

24
 La commande ps: options
o a (all) : donne la liste de tous les processus, y compris ceux dont vous n'êtes
pas propriétaire.
o g (global, général...) : donne la liste de tous les processus dont vous êtes
propriétaire.
o u (user, utilisateur) : donne davantage d'informations (nom du propriétaire,
heure de lancement, pourcentage de mémoire occupée par le processus, etc.).
o x : affiche aussi les processus qui ne sont pas associés à un terminal.
o w : le tronque pas à 80 caractères (peut être utilisée plusieurs fois pour
tronquer plus loin)
o agux : est en fait souvent utilisé pour avoir des informations sur tout les
processus.

 Commande top
o affiche les mêmes informations, mais de façon dynamique : elle indique en
fait par ordre décroissant le temps machine des processus, les plus
gourmands en premier.

25

 Tuer les procesus


o ^c; ^d; ^z
o Kill pid: tuer un processus
o Kill -9 pid: imposer l’arrêt immédiat du processus

 Vie et mort des processus:


o Début : création par un autre processus - par fork()
o Fin: auto-destruction (à la fin du programme) - par exit(); destruction par un
autre processus - par kill(); certains processus ne se terminent pas (“démons”,
réalisant des fonctions du système)
 Dans Unix/Linux
o Dans le langage de commande: un processus est créé pour l’exécution de
chaque commande. On peut créér des processus pour exécuter des commandes
en (pseudo)-parallèle :
• prog1 & prog2 & /* crée deux processus pour exécuter prog1 et prog2 */
• prog1 & prog1 & /* crée deux exécutions parallèles de prog1 */
o Au niveau des appels système: un processus est créé par une instruction
fork() (voir plus loin)

26
 Autres commandes
o ps : liste des processus et de leurs caractéristiques
o htop : liste dynamique des processus et de ce qu'ils consomment
o pgrep : récupération d'une liste de processus par expression régulière
o pidof : récupération du pid d'un processus recherché
o fuser : informations sur les file descriptor d'un processus
o lsof : idem
o pmap : afficher le mapping mémoire d'un processus
o strace : liste les appels système du processus
o ltrace : liste les appels de fonction de bibliothèques dynamiques du processus
o pstack : affiche la pile d'appel du processus
o gdb : pour tout savoir et même modifier l'action d'un processus.
o kill : envoyer un signal à un processus connaissant son pid
o killall : envoie un signal à tous les processus portant un certain nom
o pkill : envoie un signal aux processus matchant une expression régulière
o ctrl-z : envoie le signal STOP au processus en avant plan du shell en cours
o fg, bg : envoie le signal CONT à un processus stoppé du shell en cours

27

Création des processus

28
 Soit ex un fichier de commandes exécutable.
 1er cas : on tape $ ex, voici ce qui se passe :
1. le shell lit la commande ex
2. il se duplique au moyen de la fonction fork ; il existe alors un shell père et un
shell fils
3. grâce à la fonction exec, le shell fils recouvre son segment de texte par celui
de ex qui s'exécute.
4. le shell père récupère le pid du fils retourné par fork ()
5. à la fin de l'exécution de ex, le shell père reprend son exécution.

 La fonction wait lui permet de connaître son achèvement : wait (&etat) retourne le
pid du processus fils à son achèvement (etat : entier).

29

 2ème cas : on tape ex&, mais n'accède pas à l'entrée standard réservée au shell),
voici ce qui se passe :
o 1. à 4. comme ci-dessus
o 5. le shell père reçoit le pid du fils, émet un prompt, puis reprend son activité
sans attendre la fin du fils

o un processus lancé en arrière-plan ne réagit pas aux interruptions émises au


clavier

30
 La norme Posix (Portable Operating System Interface) définit un nombre
relativement petit d'appels système pour la gestion de processus :
o pid_t fork() : Création de processus fils.

o int execl(), int execlp(), int execvp(), int execle(), int execv() : Les services
exec() permettent à un processus d'exécuter un programme (code) différent.

o pid_t wait() : Attendre la terminaison d'un processus

o void exit() : Finir l'exécution d'un processus.

o pid_t getpid() : Retourne l'identifiant du processus.

o pid_t getppid() : Retourne l'identifiant du processus père.

o En Linux, le type pid_t correspond normalement à un long int.

31

 Il y a une façon de créer un sous-processus en Unix/Linux, en utilisant la


commande system(), de la bibliothèque standard de C <stdlib.h>.
o Comme arguments elle reçoit le nom de la commande (et peut-être une liste
d'arguments) entre guillemets.
o Dans un shell. Il faut retenir que system() n'est pas un appel système, mais
une fonction C.
o Ce qui rend l'utilisation de la fonction system() moins performante qu'un
appel système de création de processus !!!
#include <stdlib.h>

int main( )
{ int return_value ;
/* retourne 127 si le shell ne peut pas s'exécuter, retourne -1 en cas d'erreur, autrement retourne
le code de la commande */
return_value = system("ls -l") ; return return_value ;
}

32
 On peut lancer un programme à l’intérieur d’un autre programme et ainsi créer un
nouveau processus en utilisant la bibliothèque system.
#include <stdlib.h>
Int system (const char *string)

 Exemple: un programme qui exécute ps.

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

int main ()
{
printf ( "executer ps avec system\n") ;
system ("ps ax");
printf ("c'est fait\n");
exit(0);
}

33

 Création d’un processus


o fork() crée un processus fils, une copie exacte du processus original
o Ce processus possède sa propre image mémoire (modifier une variable dans
une des processus ne la modifie pas dans l’autre)

Problème !!! les deux processus père et fils exécutent le


même code. Comment distinguer alors le processus père du
processus fils ?
Solution: on regarde la valeur de retour de fork()

34
 La primitive Fork()

35

 Création d’un processus


o Les deux processus père et fils exécutent le même code. Comment distinguer
alors le processus père du processus fils?
o Pour résoudre ce problème, on regarde la valeur de retour de fork(), qui peut
être :
• 0 pour le processus fils
• Strictement positive pour le processus père et qui correspond au pid du
processus fils
• Négative si la création de processus a échoué, s'il n'y a pas suffisamment
d'espace mémoire ou si bien le nombre maximal de créations autorisées
est atteint.

 Identifiant de processus
o Chaque processus possède un identifiant, son pid
o La fonction getpid retourne le pid du processus actif
o La fonction getppid retourne le pid du processus père

36
 Création d’un processus
o Après l'appel système fork(), la valeur de pid reçoit la valeur 0 dans le
processus fils mais elle est égale à l'identifiant du processus fils dans le
processus père
o Le processus créé (fils) est un clone (copie conforme) du processus créateur
(père). Le père et le fils ne se distinguent que par le résultat rendu par fork()
• pour le père : le numéro du fils (ou –1 si création impossible), pour le fils
:0

37

• Le processus père exécutera le bloc 1.


• Puis il créera un fils identique par fork et exécutera le bloc3 puisque fork lui
retournera une valeur non nulle.
• Le processus fils n'exécutera pas le bloc1 car à sa création, son compteur ordinal
pointera sur la ligne contenant fork. Comme fork lui retourne 0, il exécutera le seul
bloc2.

38
 Exemple
#include<stdio.h>
#include<sys/times.h>
Int main()
{
int pid;
Char quisuisje=‘’le pere’’;
Pid=fork();
If (pid==0)
{ Quisuisje=‘’le fils’’;
Printf(‘’ je suis le %s ’’, quisuisje );
}
else
{
Printf(‘’ je suis le %s ’’, quisuisje );
Wait(NULL);
}
Return 0;
}

39

 Création de processus

40
 Création de processus

41

 Création de processus

42
 Création de processus

43

 Création de processus

44
 Création de processus

45

 Création d’un processus


o Après l'appel système fork(), la valeur de pid reçoit la valeur 0 dans le
processus fils mais elle est égale à l'identifiant du processus fils dans le
processus père
o Le processus créé (fils) est un clone (copie conforme) du processus créateur
(père). Le père et le fils ne se distinguent que par le résultat rendu par fork()
• Le code de retour de fork vaut 0 dans le fils; Dans le père, c’est
l’identifiant du processus (pid) fils; En cas d’erreur, ce code de retour
vaut -1
1programme, 2 processus
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
int main ()
{ if (fork() != 0) { printf("je suis le père, mon PID est %d\n", getpid());
} else {
printf("je suis le fils, mon PID est %d\n", getpid()); /* en général exec (exécution d’un
nouveau programme) */
}
}

46
 Création d’un processus: 1 programme, 2 processus, donc 2 mémoires virtuelles, 2
jeux de données
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
int main ()
{ int i ;
if (fork() != 0) {
printf("je suis le père, mon PID est %d\n", getpid());
i = 3;
} else {
printf("je suis le fils, mon PID est %d\n", getpid());
i = 5;
}
printf("pour %d, i = %d\n", getpid(), i);
}

47

 Création d’un processus


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
int main ()
{ int i ;
if (fork() != 0) {
printf("je suis le père, mon PID est %d\n", getpid());
sleep(10) /* blocage pendant 10 seconds*/; exit(0);
} else {
printf("je suis le fils, mon PID est %d\n", getpid());
sleep(10) /* blocage pendant 10 seconds*/; exit(0);
}
}

48
 Création d’un processus

49

 Synchronisation entre un processus père et ses fils


o Le fils termine son exécution par exit(statut), où statut est un code de fin (par
convention : 0 si normal, sinon code indiquant une erreur)
o Un processus père peut attendre la fin de l’exécution d’un fils par la primitive
: pid_t wait(int *ptrStatut). La variable (facultative) ptrStatut recueille le
statut, wait renvoie le PID du fils.
o On peut aussi utiliser pid_t waitpid(pid_t pid, int *ptrStatut) pour attendre
la fin de l’exécution d’un fils spécifié pid
 Envoyer un signal à un autre processus
o Sera vu plus tard en détail. Pour le moment, on peut utiliser kill pid au niveau
du langage de commande, pour tuer un processus spécifié pid
 Faire attendre un processus
o sleep(n) : se bloquer pendant n secondes
o pause: se bloquer jusqu´à la réception d’un signal envoyé par un autre
processus

50
 Le système Linux repose sur ce concept arborescent

51

 Quand un processus se termine, il délivre un code de retour (paramètre de la


primitive exit()). Par exemple exit(1) renvoie le code de retour 1.

 Un processus père peut attendre la fin d’un ou plusieurs fils en utilisant wait() ou
waitpid(). Tant que son père n’a pas pris connaissance de sa terminaison par l’une
de ces primitives, un processus terminé reste dans un état dit zombi.
• Un processus zombi ne peut plus s’exécuter, mais consomme encore des
ressources. Il faut éviter de conserver des processus dans cet état.

52
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>

int main ()
{
if (fork() != 0) {
printf("je suis le père, mon PID est %d\n", getpid());
while (1) ; /* boucle sans fin sans attendre le fils */
} else {
printf("je suis le fils, mon PID est %d\n", getpid());
sleep(2) /* blocage pendant 10 seconds*/;
printf("fin du fils\n");
exit(0);
}
}

53

Zombie

54
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main ()
{ pid_t fils; int statut;
if (fork() != 0) {
printf("je suis le père, mon PID est %d\n", getpid());
fils = wait(&statut);
if (WIFEXITED(statut)) {
printf("%d : mon fils %d s'est terminé avec le code %d\n", getpid(), fils,
WEXITSTATUS(statut)); }
exit(0);
} else {
printf("je suis le fils, mon PID est %d\n", getpid());
sleep(2) /* blocage pendant 10 seconds*/;
printf("fin du fils\n");
exit(1);
}
}

55

56
 La primitive exec :
o sert à faire exécuter un nouveau programme par un processus
o elle est souvent utilisée immédiatement après la création d’un processus
o son effet est de “recouvrir” la mémoire virtuelle du processus par le nouveau
programme et de lancer celui-ci en lui passant des paramètres spécifiés dans la
commande.
 Diverses variantes d’exec existent selon le mode de passage des paramètres
(tableau, liste, passage de variables d’environnement).

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main ()
{
if (fork() == 0) { execl("bin/ls", "ls", "-a", NULL); }
else {wait(NULL);}
exit(0);
}

57

 Exemple 1

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

int main()
{ pid_t v;
v=fork();
if(v==0) printf("Je suis le fils avec pid %d\n" , getpid () ) ;
else if (v > 0)
printf ("Je suis le pere avec pid %d\n" , getpid () ) ;
else
printf ("Erreur dans la creation du fils \n" ) ;
}

58
 Exemple 2

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

int main ( void )


{
int i , j , k , n=5;
pid_t fils_pid ;
for (i=1;i<n;i++)
{
fils_pid=fork();
if (fils_pid>0) // c'est le pere
break ;
printf ( " Processus %d avec pere %d\n",getpid (), getppid ()) ;
}

59

 Exemple 2
o Après l'exécution de « exemple2.c» on obtient le résultat suivant

o Ce programme créera une chaîne de (n-1) processus


o Le problème ici est que dans quelques cas le processus père se termine avant
le processus fils
o Comme tout processus doit avoir un parent, le processus orphelin est donc
"adopté" par le processus dont le pid est 1 (le processus init).
o Ainsi, on peut remarquer qu'au moment où les processus affichent le numéro
de leur père, ils ont déjà été adoptés par le processus init

60
 Exemple 2
o Pour faire perdre du temps au processus on ajoute les deux lignes:
for ( j = 1 ; j <100000 ; j ++)
for ( k = 1 ; k <100000 ; k++) ;

61

 Exemple 3: le programme exemple3.cc suivant créera deux processus qui vont


modifier la variable a
#include <sys/types.h> //type pid_t
#include <unistd.h> // pour fork
#include <stdio.h> // pour perror, printf
int main (void)
{ pid_t p ; int a = 20 ;
// Création d'un fils
switch ( p = fork ( ) )
{ case -1 : // le fork a echoue
perror ( " le fork a echoue ! " ) ; break ;
case 0 : // Il s'agit du processus fils
printf ("ici processus fils, le PID %d. \n" , getpid () ) ;
a += 10 ; break ;
default : // Il s'agit du processus pere
printf ("ici processus pere, le PID %d.\n" , getpid ()) ;
a += 100 ;
} // les deux processus exécutent cette instruction
printf ("Fin du processus %d avec a= %d. \n" , getpid ( ) , a ) ;
return 0 ;
}

62
 Exemple 3
o Deux exécutions du programme montrent que les processus père et fils sont
concurrents :
station > cc exemple3.cc exemple3
station > ./exmple3
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.

station > ./fork3


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.
station >

63

 Exemple 4: Quelle est la différence entre les programmes exemple4-1 et exemple4-


2 suivants ? Combien de fils engendreront-ils chacun ?
int main( )
{ int i , n=5; int childpid ;
for (i = 1 ; i<n ; i++)
{ if ( ( childpid=fork () ) <= 0 ) break ;
printf( " Processus %d avec pere %d , i=%d\n" , getpid( ) , getppid( ) , i) ;
}
return 0 ;
}

int main ( )
{ int i , n=5; pid_t pid ;
for ( i = 1 ; i <n ; i ++)
{ if ( ( pid=fork ( ) ) == -1) break ;
if (pid == 0 )
printf (" Processus %d avec pere %d , i=%d\n" , getpid ( ) , getppid ( ) , i ) ;
}
return 0;
}

64
 Exemple 4

o La sortie de exemple4-1 : À 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).
o Le shell, qui est le père du processus crée lors de l'exécution de fork, a un pid
de 29257, et le processus lui-même a un pid = 10581.

65

 Exemple 4

o Si l'appel à fork() n'échoue jamais, on sait que le premier processus créera 4


nouveaux processus fils.
o Le premier de ces fils continue d'exécuter la boucle à partir de la valeur
courante du compteur i. Il créera donc lui-même trois processus.
o Le second fils en créera deux, et ainsi de suite.
o Et tout cela se répète récursivement avec chacun des fils.

66
 Exemple 4
o En supposant que le shell a toujours un pid de 759, et que le processus associé
à fork a un pid de 874, on devrait obtenir l'arbre suivante:

67

 Exemple 4
o Malheureusement, ce ne sera pas le cas, puisque encore une fois des
processus pères meurent avant que leur fils ne puissent afficher leurs
messages :

68
 void exit (int etat)
o La fonction provoque la terminaison du processus avec le code de retour etat
(0 = bonne fin). Si le père est un shell, il récupère etat dans la variable $?

o A l'exécution de exit, tous les fils du processus sont rattachés au processus de


pid 1.

o exit réalise la libération des ressources allouées au processus et notamment


ferme tous les fichiers ouverts.

o Si le père est en attente sur wait, il est réveillé et reçoit le code de retour du
fils.

o Il faut souligner qu'un processus peut se terminer aussi par un arrêt forcé
provoqué par un autre processus avec l'envoi d'un signal du type kill().

69

 Code de retour de fork:


o Le code de retour de fork vaut 0 dans le fils
o Dans le père, c’est l’identifiant du processus (pid) fils
o En cas d’erreur, ce code de retour vaut -1

70
 Appel système: pid =wait (status)
o pid est l'identifiant du processus fils et status est l'adresse dans l'espace
utilisateur d'un entier qui contiendra le status de exit() du processus fils.
o Ces appels système permettent au processus père d'attendre la fin d'un de ses
processus fils et de récupérer son status de fin.
o Ainsi, un processus peut synchroniser son exécution avec la fin de son
processus fils en exécutant l'appel système wait().

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

o wait() : permet à un processus père d'attendre jusqu'à ce qu'un processus fils


termine. Il retourne l'identifiant du processus fils et son état de terminaison
dans &status.
o waitpid() : permet à un processus père d'attendre jusqu'à ce que le processus
fils numéro pid termine. Il retourne l'identifiant du processus fils et son état de
terminaison dans &status.

71

 Le processus appelant est mis en attente jusqu'à ce que l'un de ses fils termine.
Quand cela se produit, il revient de la fonction. Si status est différent de 0, alors 16
bits d'information sont rangés dans les 16 bits de poids faible de l'entier pointé par
status.

 Ces informations permettent de savoir comment s'est terminé le processus selon les
conventions suivantes :

o Si le fils est stoppé, les 8 bits de poids fort contiennent le numéro du signal
qui a arrêté le processus et les 8 bits de poids faible ont la valeur octale 0177.

o Si le fils s'est 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 poids faible du
paramètre utilisé par le processus fils lors de l'appel de exit().

o Si le fils s'est terminé sur la réception d'un signal, les 8 bits de poids fort de
status sont nuls et les 7 bits de poids faible contiennent le numéro du signal
qui a causé la fin du processus.

72
 Exemple 5

int main()
{
pid_t v;
v=fork();
if(v==0)
printf("Je suis le fils avec pid %d\n" , getpid () ) ;
else if (v > 0) {
printf("Je suis le pere avec pid %d\n" , getpid () ) ;
printf("J'attends que mon fils se termine\n" ) ;
wait(NULL) ;
}
else
printf ("Erreur dans la creation du fils \n" ) ;
exit(0);
}

73

 Exemple 6

int main ( )
{ int i , n=5; pid_t pid ;
for ( i = 1 ; i <n ; i ++)
{ if ( ( pid=fork ( ) ) == -1) break ;
if (pid == 0 ) printf (" Processus %d avec pere %d , i=%d\n" , getpid ( ) , getppid ( ) , i ) ;
}
// attendre la fin des fils
while (wait (NULL) >= 0 ) ;
return 0;
}

74
 Le mécanisme exit+wait permet
o au processus fils de retourner une information codée sur 8 bits au processus
père
o au processus père d’attendre la terminaison du fils

o mais il ne permet pas aux processus d’échanger des informations plus


complexes
o Solution: mécanisme de communication par tube permet
• d’échanger des informations (éventuellement plus complexes) entre
processus
• de se synchroniser par un mécanisme de lecture/écriture

75

Les tubes

76
 Définition d’un tube
o moyen de transmettre un flot d'octets entre des processus

o représenté par une paire de descripteurs associés à une inode présente


uniquement en mémoire et gérée de façon spéciale

o unidirectionnel: un côté du tube sert à l'écriture, le second pour la lecture

o la lecture est destructive !

o tous les détails dans man 7 pipe

o ordre des octets conservés: FIFO

o capacité finie, qui dépend du système


• lecteur=processus possédant un descripteur sur le côté lecture
• écrivain=processus possédant un descripteur sur le côté écriture

77

 Utilisation des tubes

o Plusieurs processus qui s’exécutent en parallèle peuvent communiquer entre


eux

o Il est possible de réaliser cette communication par l’intermédiaire de tubes


(pipes)

o Le système est alors chargé d’assurer la synchronisation de l’ensemble des


processus crées
o Exemple: commande1 | commande2 | ...| commanden

78
 Exemple d’un tube
o Un tube est un flux de données permettant l'échange unidirectionnel de
données entre deux processus s'exécutant sur une même machine

o Utilisation simple en langage shell (opérateur '|')


o who | wc –l » crée deux processus qui s'exécutent en parallèle et qui sont
reliés par un tube de communication pipe
o Le premier processus réalise la commande who, alors que le second exécute la
commande wc -l.
o Le processus réalisant la commande who ajoute dans le tube une ligne
d'information par utilisateur du système.
o Le processus réalisant la commande wc -l récupère ces lignes d'information
pour en calculer le nombre total

79

 Primitives C pour la gestion des tubes :


o pipe() : création d'un tube (renvoie une paire de descripteurs du fichier)
o write() : écriture dans un tube
o read() : lecture du contenu d'un tube
o close() : suppression d'un tube
 Un tube de communication sans nom est créé par l'appel système pipe, auquel on
passe un tableau de deux entiers : int pipe(int descripteur[2])
 Au retour de l'appel système pipe(), un tube aura été créé, et les deux positions du
tableau passé en paramètre contiennent deux descripteurs de fichiers
 On peut considérer un descripteur comme une valeur entière que le système
d'exploitation utilise pour accéder à un fichier
 Dans le cas du tube, on a besoin de deux descripteurs : le descripteur pour les
lectures du tube et le descripteur pour les écritures dans le tube
 Le descripteur de l'accès en lecture se retrouvera à la position 0 du tableau passé en
paramètre, et le descripteur de l'accès en écriture se retrouvera à la position 1
 Seul le processus créateur du tube et ses descendants (ses fils) peuvent accéder au
tube
 Si le système ne peut pas créer de tube pour manque d'espace, l'appel système
pipe() retourne la valeur -1, sinon il retourne la valeur 0

80
 Création d’un tube: syntaxe

o crée deux descripteurs:


• filedes[0] pour la lecture
• filedes[1] pour l'écriture
• retourne 0 en cas de réussite, -1 en cas d’ échec

81

 Lecture : syntaxe

o lit count octets depuis le descripteur fd et les range dans le buffer pointé par
buf
o renvoie le nb d’octets effectivement lus (0 en fin de fichier) et avance la tête
de lecture de ce nombre
o renvoie -1 en cas d’échec

82
 Lecture : fonctionnement
o Si le nb d’écrivains est nul alors la fonction retourne 0
o Si la lecture est bloquante (par défaut) et que le tube est vide, le processus est
mis en attente jusqu’à ce qu’une écriture se produise dans le tube
o L’indicateur O_NONBLOCK peut etre utilisé pour rendre la lecture non
bloquante (dans ce cas -1 est renvoyé si le tube est vide)
o Si l’indicateur O_NDELAY est positionné et que le tube est vide, alors la
fonction retourne 0
 Ecriture : syntaxe

o écrit count octets dans le tube désigné par le descripteur fd depuis le buffer
pointé par buf
o renvoie le nombre d’octets effectivement emis
o retourne 0 si aucune écriture n’est réalisée
o renvoie -1 en cas d´échec

83

 Ecriture: fonctionnement
o Si le nombre de lecteurs est nul, un signal est envoyé au processus écrivain
qui se termine
o Si l’écriture est bloquante et que le tube est plein, le processus est bloqué en
attendant qu’il se vide
o Si l’écriture est non bloquante, le processus écrit les caractères qu’il peut
écrire (maximum PIPE_BUF) et retourne une valeur correspondante

 Fermeture
o La primitive close() doit être utilisée sur le descripteur qu’on désire fermer
• close(filedesc[0]) ferme le tube en lecture
• close(filedesc[1]) ferme le tube en écriture
o Une fois fermé, on ne peut plus récupérer le descripteur

84
 Fonctionnement de la procédure écriture-lecture
o Le processus père crée un tube de communication sans nom en utilisant l'appel
système pipe()
o Le processus père crée un ou plusieurs fils en utilisant l'appel système fork()
o Le processus écrivain ferme l'accès en lecture du tube
o De même, le processus lecteur ferme l'accès en écriture du tube
o Les processus communiquent en utilisant les appels système write() et read()
o Chaque processus ferme son accès au tube lorsqu'il veut mettre fin à la
communication via le tube

85

 Pipe – père et fils: fonctionnement


o le fils hérite les descripteurs ouverts dans le père: accès au pipe–
communication

86
 Père parle au fils
o le père ferme fd[0] et écrit sur fd[1]
o le fils ferme fd[1] et lit depuis fd[0]
#include<sys/types.h>
#include<unistd.h>
#include <stdio.h>

int main() {
const int Nbuff=1000; char buff[Nbuff]; int pF ,
dFic[2];
pipe(dFic);
if ((pF = fork()) > 0) { // pere
close(dFic[0]); write(dFic[1],"Salut",5);
}
else if (pF == 0) { // fils
close(dFic[1]); int n = read(dFic[0] , buff, Nbuff-1);
buff[n] = '\0'; printf("buffer=%s\n",buff);
}
return 0;
}

87

 Fils parle au père


o le fils ferme fd[0] et écrit sur fd[1]
o le père ferme fd[1] et lit depuis fd[0]
#include<sys/types.h>
#include<unistd.h>
#include <stdio.h>

int main() {
const int Nbuff=1000; char buff[Nbuff]; int pF ,
dFic[2];
pipe(dFic);
if ((pF = fork()) > 0) { // pere
close(dFic[1]); int n = read(dFic[0] , buff, Nbuff-1);
buff[n] = '\0'; printf("buffer=%s\n",buff);
}
else if (pF == 0) { // fils
close(dFic[0]); write(dFic[1],"Salut",5);
}
return 0;
}

88
 Exemple: le processus père crée un tube de communication pour communiquer
avec son
#define R 0 processus fils.
#define W 1
int main()
{ int fd[2]; char message [100]; // pour recup. un msg
int nboctets ; char* phrase = "message envoye au pere par le fils ";
pipe(fd); // creation d' un tube sans nom
if (fork()==0) // création d' un processus fils
{ close (fd[R]) ; // Le fils ferme le descripteur non utilise de lecture
write (fd[W], phrase, strlen(phrase) + 1 ) ; // depot dans le tube du message
close (fd[W]) ; // fermeture du descripteur d ' ecriture
}
else
{ close(fd[W]) ; // Le pere ferme le descripteur non utilise d' écriture
// extraction du message du tube
nboctets = read(fd[R], message, 100);
printf ( "Lecture %d octets : %s\n", nboctets, message);
close (fd[R]) ; // fermeture du descripteur de lecture
}
return 0 ;
}

89

 Exercice
o On veut créer deux processus qui échangent des entiers de la manière suivante
o Le père crée un tube
o Le père crée un fils
o Le fils lit une suite d’entiers au clavier terminée par Ctrl-D
o Il en fait la somme
o Il l’écrit dans le tube au père
o Le père lit la somme dans le tube puis l’affiche

90
 On souhaite maintenant que le fils envoie tous les entiers lus au père, c’est le père
qui est chargé de faire la somme avant de l’afficher

91

 Expliquez la figure suivante

92
 Comment créer un pipe entre deux processus fils: ls –al | tr a-z A-Z. Donner le code
des fonctions sunsource() et rundest()

93

 Comment créer un ring de processus:


o Complétez ce code de manière à implémenter cette architecture de
communication des N processus créés.
o L’entrée standard et la sortie standard de chaque processus Pi sont redirigées
vers les tubes appropriés.
o Par exemple, pour le processus P0, l’entrée et la sortie standards deviennent
respectivement les tubes tub3 et tub0.

94
 Modifier le programme du ring pour mettre en ouvre la commande suivante:
ls -al / | tr a-z A-Z

95

 Modifier le programme du ring pour mettre en ouvre la commande suivante:


ls -al / | tr a-z A-Z

 Donner un programme pour mettre en ouvre les commandes suivantes:


• ps | grep emacs| wc –l
• sort telephone.txt|grep Dupond |uniq -u|cut -f 3

Durand Emilie 0381818585


Terieur Alex 0478858689
Tinrieur Georges 0563868985
Dupond Albert 04961868957
Dupont Emilie 02971457895
Dupond Albertine 0131986258
Bouvier Jacques 0381698759
Zeblues Agathe 0685987456
Dupond Agnès 0687598614
Dumont Patrick 04661645987
Dupond Elisabeth 0654896325
Houtand Laurent 0658769458

96
97

Les signaux

98
 Un signal est l’équivalent logiciel des interruptions matérielles

 Exemple : un utilisateur demande à un éditeur de charger un très gros fichier, il


peut réaliser son erreur et demander a interrompre le chargement

 Technique habituelle : frappe de touches particulières (Ctrl-C, DEL ...) qui envoie
un signal au processus éditeur ; ce dernier récupère le signal et interrompt le
chargement

 La prise en compte du signal déclenche l’exécution d’une fonction particulière


définie par le système ou l’utilisateur

 Les signaux sont identifiés par des nombres entiers : 1 ... NSIG

 Des constantes sont définies dans signal.h

99

 A chaque signal est associé un comportement par défaut :


o exit : terminaison du processus
o ignore : le signal est ignoré
o stop : suspension du processus
o continue : reprendre l'exécution si le processus a été suspendu

100
101

 On peut obtenir la liste des signaux par la commande kill -l

102
 Rappel:
o 2 façons d'envoyer un signal à un processus :
• En ligne de commande :

• Un caractère de contrôle :

• Primitives C de gestion des signaux :


1. kill() : envoie un signal vers un processus
2. sigaction() : spécifie une action associée à un signal

103

 Syntaxe: kill -SIGNAL PID


o envoie le signal SIGNAL au processus de pid PID
o kill -KILL 7657 ou kill -9 7657 envoie le signal KILL (tue) au pro-
o cessus de pid 7657
o kill -STOP 7765 envoie le signal STOP (suspend) au processus de pid 7765
o kill -CONT 7765 envoie le signal CONT (reprend l’exécution) au processus
de pid 7765
 Syntaxe d’nvoi d’un signal sig au processus pid

o pid peut prendre les valeurs suivantes :


• > 0 le signal est envoyé au processus de pid pid
• = 0 le signal est envoyé à tous les processus du même groupe que le
processus émetteur
• < −1 tous les processus du même groupe que le processus de pid pid
o sig peut prendre les valeurs suivantes:
• = 0 teste l’existence du processus
• 1…NSIG envoie le signal spécifié

104
 Exemple 1
#include <unistd.h>
#include <stdio.h>
#include<signal.h>
//#include <stdlib.h>
#include <sys/wait.h>
int main() {
int pid,statut;
if ((pid=fork())==0)
while(1) sleep(1);
else {
sleep(10); statut = kill(pid,0);
if (statut == -1) printf("Fils %d inexistant \n",pid);
else {
printf("Signal SIGKILL envoye a %d\n",pid);
kill(pid,SIGKILL);
pid = waitpid(pid,&statut,WUNTRACED);
printf("Le fils %d s'est termine\n",pid);
}
}
}

105

 Prise en compte des signaux: principe


o A chaque signal est associé un comportement (handler) par défaut
o Le comportement de certains signaux (SIGKILL, SIGCONT et SIGSTOP) ne
peut pas être modifié
o Pour modifier le comportement des autres signaux, il faut leur associer un
nouvel handler
o Lors de la prise en compte du signal, la fonction handler est exécutée ; le
processus reprend ensuite au retour de la fonction
 Définition d’un comportement : syntaxe

o Cet appel système sert a modifier l’action effectuée à la réception d’un signal
o La fonction retourne 0 normalement, -1 en cas de problème
o signum désigne le signal concerné
o newact est le nouveau comportement
o oldact permet de sauvegarder l’ancien comportement

106
 La structure de comportement sigaction

o sa_handler indique l’action a effectuer (SIG DFL par défaut, SIG IGN pour
ignorer ou un pointeur vers une fonction définissant le comportement)
o sa_mask fournit un masque de signaux a bloquer
o sa_flags spécifie un ensemble d’attributs qui modifie le comportement du
gestionnaire de signaux

107

 Exemple 2

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
int cpt=0;
void comportement(int sig) {
if (sig == SIGUSR1) cpt ++;
else if (sig == SIGINT) {
printf ("nombre signaux recus : %d\n",cpt+1);
exit(0);
}
}

int main() {
struct sigaction action;
action.sa_handler=comportement;
sigaction(SIGUSR1,&action,NULL);
sigaction(SIGINT,&action,NULL);
while (1);
}

108
 Exemple 2
• Terminal1

• Terminal 2

109

 Exemple 3

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

void sighandler(int signum)


{ printf ("Signal SIGTERM intercepte \n") ; }

int main ( void )


{ char buffer [256] ;
if(signal(SIGTERM,&sighandler) == SIG_ERR)
{ printf("Ne peut pas manipuler le signal \n" ) ; return 1 ; }
while ( 1 )
{ fgets(buffer, sizeof(buffer), stdin) ; printf ("Input : %s ", buffer) ; }
return 0 ;
}

• Terminal1: ./Ex3
Salut
Input: salut
• Terminal 2: kill -SIGKILL npid

110
 Introduction aux systèmes et aux réseaux (S. Krakowiak, Grenoble), Source de
nombreux transparents présentés ici.
http://sardes.inrialpes.fr/~krakowia/Enseignement/L3/SR-L3.html
 Cours du Mr J. Chroboczek, 2013
 Cours de Y. PAGNOTTE, Systèmes d'exploitation et programmation système
 Luigi Logrippo, Ordonnancement Processus, slides. http://w3.uqo.ca/luigi/
 http://perso.univ-rennes1.fr/pascal.gentil/docs/unix/unix.fichiers.html#cat1
 http://upsilon.cc/~zack/teaching/1314/progsyst/#index1h2
 Karim Sehaba, Systèmes et Architectures, http://perso.univ-lr.fr/ksehaba/
 Vincent Granet, Introduction à Linux, Polytech’Nice-Sophia
 Sylvain CHERRIER, Cours Linux,
 A. Queudet, Cours Systèmes d'exploitation, Univ. Nantes
 C. F Zucke, cours Systèmes d'exploitation
 Gestion des processus, Telecom-ParisTech BCI Informatique

111

Vous aimerez peut-être aussi