2 - Programmation Système
2 - Programmation Système
02/12/2021 [email protected] 2
Références
• Ressources Internet
• Livres: En particulier la référence suivante:
• Titre: Programmation système en C sous Linux
Signaux, processus, threads, IPC et sockets 2e
édition
• Auteur: Christophe Blaess
• Edition: Eyrolles
02/12/2021 [email protected] 3
Chapitre I. Introduction aux
processus
• Le système Unix/Linux est multi-tâche: Plusieurs
programmes peuvent s’exécuter simultanément sur le même
processeur.
• Puisque le processeur ne peut exécuter qu'une seule
instruction à la fois, le noyau va donc découper le temps en
tranches de quelques centaines de millisecondes (quantum
de temps) et attribuer chaque quantum à un programme le
processeur bascule entre les programmes.
• L'utilisateur voit ses programmes s'exécuter en même
temps.
• Pour l’utilisateur, tout se passe comme si on a une exécution
réellement en parallèle.
02/12/2021 [email protected] 4
2. Définitions
• Programme: c’est un fichier exécutable stocké sur
une mémoire de masse. Pour son exécution, il est
•
chargé en mémoire centrale.
• Processus (process en anglais), est un concept
central dans tous système d’exploitation:
– C’es un programme en cours d’exécution; c’est-à-dire,
un programme à l’état actif.
• C’est l’image de l’état du processeur et de la
mémoire pendant l’exécution du programme.
C’est donc l'état de la machine à un instant donné
02/12/2021 [email protected] 5
3. Contexte d’un processus
• Le contexte d’un processus (Bloc de Contrôle de
Processus : BCP) contient toute les ressources
nécessaires pour l’exécution d’un processus.
– Il regroupe le code exécutable, sa zone de données, sa pile
d’exécution, son compteur ordinal ainsi que toutes les
informations nécessaire à l’exécution du processus.
• L'opération qui consiste à sauvegarder le contexte d'un
processus et à copier le contexte d'un autre processus
dans le processeur s'appelle changement (ou
commutation) de contexte.
– La durée d'une commutation de contexte varie d'un
constructeur à un autre, elle reste toutefois très faible.
02/12/2021 [email protected] 6
Remarque:
• A chaque instant, le processeur ne traite qu’un
seul processus.
• Un processeur peut être partagé par plusieurs
processus.
– L’ordonnanceur (un algorithme d’ordonnancement)
permet de déterminer à quel moment arrêter de
travailler sur un processus pour passer à un autre.
• Les processus permettent d’effectuer plusieurs
activités en "même temps".
• Exemple :
– Compiler et en même temps imprimer un fichier.
02/12/2021 [email protected] 7
4. Relations entre processus
• Compétition
– Situation dans laquelle plusieurs processus doivent utiliser
simultanément une ressource à accès exclusif (ressource ne pouvant
être utilisée que par un seul processus à la fois)
– Exemples
• imprimante
• Coopération
– Situation dans laquelle plusieurs processus collaborent à une tâche
commune et doivent se synchroniser pour réaliser cette tâche.
– Exemples: soient p1 et p2 deux processus
• p1 produit un fichier, p2 imprime le fichier
• p1 met à jour un fichier, p2 consulte le fichier
• Synchronisation: La synchronisation se ramène au cas suivant : un
processus doit attendre qu’un autre processus ait terminé.
02/12/2021 [email protected] 8
5. États d’un processus
• A un instant donné, un processus peut être dans
l'un des états suivants :
– Actif (Elu, en Exécution): Le processus en cours
d’exécution sur un processeur (il n'y a donc qu'un seul
processus actif en même temps sur une machine mono-
processeur).
• On peut voir le processus en cours d’exécution en tapant la
commande « ps » sur une machine Unix.
– Un processus élu peut être arrêté, même s'il peut
poursuivre son exécution.
• Le passage de l'état actif à l'état prêt est déclenché par le
noyau lorsque la tranche de temps attribué au processus est
épuisée.
02/12/2021 [email protected] 9
5. États d’un processus
• prêt : Le processus est suspendu provisoirement pour
permettre l'exécution d'un autre processus.
– Le processus peut devenir actif dès que le processeur lui sera
attribué par le système (il ne lui manque que la ressource
processeur pour s’exécuter).
• bloqué (en attente): Le processus attend un événement
extérieur (une ressource) pour pouvoir continuer, par
exemple lire une donnée au clavier. Lorsque la ressource est
disponible, il passe à l'état "prêt".
• Un processus bloqué ne consomme pas de temps
processeur; il peut y en avoir beaucoup sans pénaliser les
performances du système.
• Terminé : le processus a terminé son exécution.
02/12/2021 [email protected] 10
5. États d’un processus
• La transition entre ces trois état est
matérialisée par le schéma suivant:
02/12/2021 [email protected] 11
6. Les identifiants d’un processus
• Chaque processus est identifié par un numéro
unique,
– le PID (Processus IDentification)
– et il appartient à un propriétaire identifié par UID
(User ID)
– et à un groupe identifié par GID (Group ID).
• Le PPID (Parent PID) d’un processus
correspond au PID du processus qui l’a créé.
02/12/2021 [email protected] 12
Les primitifs
• La fonction « int getpid() » renvoie le numéro
(PID) du processus en cours.
• La fonction « int getppid() » renvoie le numéro du
processus père (PPID). Chaque processus a un
père, celui qui l’a créé.
• La fonction« int getuid() » permet d’obtenir le
numéro d’utilisateur du processus en cours (UID).
• La fonction « int getgid() » renvoie le numéro du
groupe du processus en cours (GID).
02/12/2021 [email protected] 13
7. Création d’un processus
• Un processus est créé au moyen d’un appel système
– (« fork() » sous UNIX/Linux).
• Cette création est réalisée par un autre processus (processus
père).
• Au démarrage du système, le processus « init » est lancé et
les autres processus descendent directement ou
indirectement de lui .
• notion d’arborescence de descendance des processus (père-
fils).
– Cette arborescence admet un ancêtre unique et commun à tous:
le processus « init ».
– Pour avoir des informations sur les processus on utilise les
commandes shells: « ps » ou « pstree »
02/12/2021 [email protected] 14
L’appel système de « fork() »
• L’appel système « fork() » dans un processus permet
de créer un nouveau processus.
– Elle est déclarée dans <unistd.h>.
• Il faut inclure le fichier d’en-tête <unistd.h> ( # include
<unistd.h>) dans le programme qui appelle « fork() »
• Syntaxe
– pid_t fork(void);
– La fonction « fork() » renvoit un entier.
– « pid_t » est nouveau type qui est identique à un entier.
• Il est déclaré dans « /sys/types.h ») .
• Il est défini pour l’identificateur du processus.
• A la place de «pid_t» on peut utiliser « int ».
02/12/2021 [email protected] 15
L’appel système de « fork() »
• Le processus qui a invoqué la fonction « fork() »
est appelé le processus père tandis que le
processus créé est appelé le processus fils.
• Le père et le fils ne se distinguent que par la
•
valeur de retour de «fork()».
– Dans le processus père: la fonction « fork() » renvoie
le numéro du processus nouvellement créé (le
processus fils).
– Dans le processus fils: la fonction « fork() » renvoie 0.
– En cas d’échec, le processus fils n’est pas crée et la
fonction renvoie « -1 ».
02/12/2021 [email protected] 16
Utilisation classique sans gestion des
erreurs:
• include <unistd.h>
• include <stdio.h>
• …
• if (fork() != 0) {
/*Exécution du code correspondant au processus père */
• }
• else { /* if (fork() == 0) */
/*Exécution du code correspondant au processus fils */
• }
02/12/2021 [email protected] 17
• Exemple 1: Le processus père crée un fils, ensuite chaque processus
affiche son identifiant.
• #include <stdio.h>
• #include <unistd.h>
• int main() {
if(fork() !=0) {
printf("Je suis le pere: ");
printf(" Mon PID est %d \n",getpid()); }
else {
printf("Je suis le fils:");
printf(" Mon PID est %d\n",getpid());
}
}
• Exécution:
– % testfork
– Je suis le pere: Mon PID est 1320
– Je suis le fils: Mon PID est 1321
– %
• Attention: l’affichage peut apparaître dans l’ordre inverse.
02/12/2021 [email protected] 18
• Exemple 1: Le processus père crée un fils, ensuite chaque processus
affiche son identifiant.
#include <stdio.h>
#include <unistd.h>
int main() {
if(fork() !=0) {
printf("Je suis le pere: ");
printf(" Mon PID est %d \n",getpid()); }
else {
printf("Je suis le fils:");
printf(" Mon PID est %d\n",getpid());
}
}
• Exécution:
– % testfork
– Je suis le pere: Mon PID est 1320
– Je suis le fils: Mon PID est 1321
– %
• Attention: l’affichage peut apparaître dans l’ordre inverse.
02/12/2021 [email protected] 19
• Exemple 2: Le processus père crée un fils ensuite affiche son identifiant
ainsi que celui de son fils. Le fils affiche son identifiant ainsi que celui de
son père.
#include <stdio.h>
#include <unistd.h>
int main() {
pid_t pid=fork(); // appel de la fonction fork()
if (pid!=0) {
printf("Je suis le pere:");
printf(" mon PID est %d et le PID de mon fils est %d \n“, getpid(), pid); }
else {
printf("Je suis le fils.");
printf(" Mon pid est:%d et le PID de mon pere est %d \n ",getpid(), getppid());
}
}
• Exemple d’exécution:
– % testfork
– Je suis le pere: Mon PID est 1320 et le PID de mon fils est 1321 Je suis le fils:
Mon PID est 1321 et le PID de mon pere est 1320 %.
02/12/2021 [email protected] 20
• Après l’appel de la fonction « fork() », le
processus père continue l’exécution du même
programme.
• Le nouveau processus (le processus fils)
exécute le même code que le processus parent
et démarre à partir de l'endroit où « fork() » a
été appelé.
02/12/2021 [email protected] 21
• Exemple 3:
int i=8 ;
printf("i= %d\n",i); /* exécuté uniquement par le processus père */
int pid=fork();
/* A partir de ce point, le père et le fils exécutent le même
programme*/
printf(" Au revoir\n" );
• Exemple d’exécution
– i=8
– Au revoir
– Au revoir
• Remarque:
– i=8 est affiché par le processus père
– Au revoir est affiché par les processus père et fils.
02/12/2021 [email protected] 22
• Exemple 4:
int i=8 ;
printf("i= %d \n", i); /* exécuté uniquement par le processus père */
if (fork() != 0) { /*à partir de ce point, le fils commence son
exécution*/
printf("Je suis le père, mon PID est %d\n", getpid()); }
else {
printf("Je suis le fils, mon PID est %d\n", getpid());
}
printf(" Au revoir \n" ); /* exécuté par les deux processus père et
fils*/
}
• Exemple d’exécution i=8
– Je suis le fils, mon PID est 1271 Au revoir
– Je suis le père, mon PID est 1270 Au revoir
02/12/2021 [email protected] 23
• Le processus créé (le processus fils) est un
clone (copie conforme) du processus créateur
(père).
– Il hérite de son père le code, les données la pile et
tous les fichiers ouverts par le père.
– Mais attention: les variables ne sont pas partagées.
• Un seul programme, deux processus, deux
mémoires virtuelles, deux jeux de données
02/12/2021 [email protected] 24
• Exemple 5:
int i=8 ;
if (fork() != 0) {
printf("Je suis le père, mon PID est %d\n", getpid()); i+= 2;
} else {
printf("Je suis le fils, mon PID est %d\n", getpid()); i+=5;
}
printf("Pour PID = %d, i = %d\n", getpid(), i); /* exécuté par les deux
processus */
}
• Exemple d’exécution
02/12/2021 [email protected] 25
8. Appel système de « fork() » avec
gestion des erreurs
• 8.1. Rappel sur la variable « errno »
– Dans le cas où une fonction renvoie « -1 » c’est qu’il y
a eu une erreur, le code de cette erreur est placé dans la
variable globale « errno », déclarée dans le fichier«
errno.h ».
– Pour utiliser cette variable, il faut inclure le fichier
d'en-tête « errno.h » ( #include <errno.h>).
• 8.2. Rappel sur la fonction « perror() »
– La fonction « perror() » renvoie une chaine de
caractères décrivant l'erreur dont le code est stocké
dans la variable « errno ».
02/12/2021 [email protected] 26
Syntaxe de la fonction « perror() »
• void perror(const char *s);
– La chaine « s » sera placée avant la description de
l'erreur.
– La fonction affiche la chaine « s » suivie de deux
points, un espace blanc et en fin la description de
l'erreur et un retour à la ligne
• Attention: Il faut utiliser « perror() » directement
après l’erreur, en effet si une autre fonction est
exécutée mais ne réussit pas, alors c’est le code de
la dernière erreur qui sera placée dans « errno » et
le code d'erreur de la première fonction sera
écrasé.
02/12/2021 [email protected] 27
Utilisation classique de « fork() »
avec gestion des erreurs:
include <unistd.h>
include <stdio.h>
#include <errno.h>
if (fork() == - 1) {
/* code si échec \n */
printf ("Code de l'erreur pour la création: %d \n", errno); perror("Erreur de création ");
}
else
if (fork() != 0) {
/* Exécution du code correspondant au processus père */
}
else { /* if (fork() == 0) */
/* Exécution du code correspondant au processus fils */
}
}
02/12/2021 [email protected] 28
9. Primitives de recouvrement: les
primitives exec()
• La famille de primitives « exec() » permettent
le lancement de l’exécution d’un programme
externe provenant d’un fichier binaire.
• Il n’y a pas création d’un nouveau processus,
mais simplement changement de programme.
• Diverses variantes de la primitive « exec() »
existent et diffèrent selon le type et le nombre
de paramètres passés.
02/12/2021 [email protected] 29
Cas où les arguments sont passés
sous forme de liste
• La primitive «execl() »
– int execl(char *path, char *arg0, char *arg1,..., char
*argn, NULL)
– « path » indique le nom et le chemin absolu du
programme à exécuter (chemin/programme).
• Exemple: « path » pour exécuter la commande « ls » est:
"/bin/ls".
– « arg0 »: désigne le nom du programme à exécuter.
• Par exemple si le premier paramètre est "/bin/ls" alors « arg0
» vaut : "ls".
– « argi » : désigne le i-ième argument à passer au
programme à exécuter.
02/12/2021 [email protected] 30
• Exemple: lancer la commande « ls -l /tmp » à
partir d’un programme:
#include <stdio.h>
#include <unistd.h>
int main(void){
execl("/usr/bin/ls","ls","-l","/tmp", NULL) ;
perror("echec de execl \n");
}
02/12/2021 [email protected] 31
La primitive «execlp() »
• int execlp(char *fiche, char *arg0,char *arg1,...,char
*argn, NULL)
• « fiche » indique le nom du programme à exécuter.
– Si le programme à exécuter est situé dans un chemine de
recherche de l’environnement alors il n’est pas nécessaire
d’indiquer le chemin complet du programme
– Exemple: pour exécuter la commande « ls », « fiche » vaut
est "ls".
• « arg0 »: est le nom du programme à exécuter.
– En général identique au premier si aucun chemin explicite
n’a été donné.
– Par exemple si le premier paramètre est "ls", « arg0 » vaut
aussi: "ls".
02/12/2021 [email protected] 32
• Exemple: lancer la commande « ls -l /tmp » à
partir d’un programme.
#include <stdio.h>
#include <unistd.h>
int main(void){
execlp("ls","ls","-l","/tmp", NULL) ;
perror("echec de execlp \n");
}
02/12/2021 [email protected] 33
Cas où les arguments sont passés
sous forme de tableau
• Primitive « execv() »
– int execv(char *path, char *argv[])
• Primitive « execvp() »
– int execvp(char *fiche,char *argv[])
• « argv » : pointeur vers un tableau qui contient le nom et les
arguments du programme à exécuter. La dernière case du
tableau vaut « NULL ».
• « path » et « fiche» ont la même signification que dans les
primitives précédentes.
• Remarque:
– Il est important de noter que les caractères que le shell
expanse (comme ~) ne sont pas interprétés ici.
02/12/2021 [email protected] 34
10. La primitive « sleep() »
• Syntaxe
– int sleep(int sec )
– Le processus qui appelle la fonction « sleep() »,
est bloqué pendant « sec » secondes.
02/12/2021 [email protected] 35
• Exemple: Le programme suivant nommé «testfork.c »
bloque le processus père de 10 secondes (appel de «
sleep(10) ») et le processus fils de 5 secondes (appel de
« sleep(5) ») .
int main() {
if (fork() != 0) {
printf(" je suis le père, mon PID est %d\n", getpid());
sleep(10) /* le processus père est bloqué pendant 10 secondes */
} else {
printf(“ je suis le fils, mon PID est %d\n", getpid());
sleep(5) /* le processus fils est bloqué pendant 5 secondes */
}
printf(" PID %d : Terminé \n", getpid())
}
02/12/2021 [email protected] 36
• Compilation et exécution
• % gcc -o testfork testfork.c
• On lance « testfork » en backround, ensuite en lance la commande «
ps -f »
• je suis le fils, mon PID est 2436 je suis le père, mon PID est 2434
[2] 2434
• UID PID PPID TTY .... CMD
• etudiant 1816 1814 pts/0 .... bash
• etudiant 2434 1816 pts/0 ..... ./testfork
• etudiant 2435 1816 pts/0 ..... ps-f
• etudiant 2436 2434 pts/0 ..... ./testfork
• 2436 : Terminé
• 2434 : Terminé
• %
02/12/2021 [email protected] 37
11. La primitive «exit() »
• La primitive «exit() » permet de terminer le processus
qui l’appelle.
• Elle est déclarée dans le fichier « stdlib.h ».
• Pour utiliser cette primitive, il faut inclure le fichier
d'en-tête « stdlib.h » ( #include <stdlib.h>) .
• Syntaxe:
– void exit (int status)
– « status » est un code de fin qui permet d’indiquer au
processus père comment le processus fils a terminé (par
convention: status=0 indique une terminaison normale
sinon indique une erreur).
02/12/2021 [email protected] 38
• Exemple:
02/12/2021 [email protected] 39
Ch.II. Gestion des signaux sous Unix
• 1. Introduction
• Un signal (ou interruption logicielle) est un message très court qui
permet d’interrompre un processus pour exécuter une autre tâche.
• Il permet d’avertir (informer) un processus qu’un événement
(particulier, exceptionnel, important, …) c’est produit.
• Le processus récepteur du signal peut réagir à cet événement sans
être obligé de le tester. Il peut:
– Soit ignorer le signal.
– Soit laisser le système traiter le signal avec un comportement par
défaut.
– Soit le capturer, c’est-à-dire dérouter (changer ) provisoirement son
exécution vers une routine particulière qu’on nomme gestionnaire de
signaux (signal handler).
02/12/2021 [email protected] 40
2. Emetteurs d'un signal
• Un signal peut être envoyé par:
• Par le système d'exploitation (déroutement : événement
intérieur au processus généré par le hard (le matériel))
pour informer les processus utilisateurs d'un événement
anormal (une erreur ) qui vient de se produire durant
l’exécution d’un programme (erreur virgule flottante
(division par 0), violation mémoire, ….).
• Un processus utilisateur: pour se coordonner avec les
autres, par exemple pour gérer une exécution multi-
processus plus ou moins complexe (par exemple du
branch-and-bound).
02/12/2021 [email protected] 41
3. Caractéristiques des signaux
• Il existe un nombre « NSIG » (ou « _NSIG») signaux
différents. Ces constantes sont définies dans « signal.h ».
• Chaque signal est identifié par:
•
02/12/2021 [email protected] 42
3. Caractéristiques des signaux
• si le processus exécute un programme
utilisateur : traitement immédiat du signal
reçu,
• s'il se trouve dans une fonction du système (ou
system call) : le traitement du signal est différé
jusqu'à ce qu'il revienne en mode utilisateur,
c'est à dire lorsqu'il sort de cette fonction.
02/12/2021 [email protected] 43
4. Etats des signaux
• Un signal pendant (pending) est un signal en attente
d’être pris en compte.
• Un signal est délivré lorsqu'il est pris en compte par le
processus qui le reçoit.
• La prise en compte d’un signal entraîne l’exécution
d’une fonction spécifique appelée handler, c’est l’action
associée au signal.
• Elle peut être soit:
– La fonction prédéfinie dans le système (action par défaut).
– Une fonction définie par l’utilisateur pour personnaliser le
traitement du signal.
02/12/2021 [email protected] 44
Liste incomplète des signaux
• SIGABRT : terminaison anormale du processus.
• SIGALRM : alarme horloge: expiration de timer
• SIGFPE : erreur arithmétique
• SIGHUP : rupture de connexion
• SIGILL : instruction illégale
• SIGINT : interruption terminal
• SIGKILL : terminaison impérative. Ne peut être ignoré ou intercepter
• SIGPIPE : écriture dans un conduit sans lecteur disponible
• SIGQUIT : signal quitter du terminal
• SIGSEGV : accès mémoire invalide
• SIGTERM : signal 'terminer' du terminal
• SIGUSR1 : signal utilisateur 1
• SIGUSR2 : signal utilisateur 2
• SIGCHLD : processus fils stoppé ou terminé
• SIGCONT : continuer une exécution interrompue
• SIGSTOP : interrompre l'exécution. Ne peut être ignoré ou intercepter
• SIGTSTP : signal d'arrêt d'exécution généré par le terminal
• SIGTTIN : processus en arrière plan essayant de lire le terminal
• SIGTTOU : processus en arrière plan essayant d'écrire sur le terminal
• SIGBUS : erreur accès bus
• SIGPOLL : événement interrogeable
• SIGPROF : expiration de l'échéancier de profilage
• SIGSYS : appel système invalide
• SIGTRAP : point d'arrêt exécution pas à pas
• SIGURG : donnée disponible à un socket avec bande passante élevée
• SIGVTALRM : échéancier virtuel expiré
• SIGXCPU : quota de temps CPU dépassé
• SIGXFSZ : taille maximale de fichier dépassée
02/12/2021 [email protected] 45
5. Les différents traitements par
défaut des signaux
• A chaque type de signal est associé un gestionnaire du
signal (« handler ») par défaut appelé « SIG_DFL ».
• Les 5 traitements par défaut disponibles sont :
– terminaison normal du processus.
– terminaison anormal du processus
– signal ignoré (signal sans effet).
– stoppe le processus (le processus est suspendu).
– continuation d’un processus stoppé.
• Un processus peut ignorer un signal en lui associant le
handler «SIG_IGN ».
• « SIG_DFL » et « SIG_IGN » sont les deux seules
macros prédéfinies.
02/12/2021 [email protected] 46
6. Signaux particuliers
• Pour tous les signaux, l’utilisateur peut remplacer le
handler par défaut par un handler personnalisé qui sera
défini dans son programme, à l’exception de certains
signaux qui ont des statuts particuliers et qui ne peuvent
pas être interceptés, bloqués ou ignorés :
• « SIGKILL » permet de tuer un processus.
• « SIGSTOP » permet de stopper un processus (stopper
pour reprendre plus tard, pas arrêter).
• « SIGCONT » permet de faire reprendre l’exécution
d’un processus stoppé (après un « SIGSTOP »).
02/12/2021 [email protected] 47
7. Manipulation des signaux
• Un processus peut envoyer un signal à un autre processus ou à un
groupe de processus.
• Un processus qui reçoit le signal agit en fonction de l’action
(handler) associé au signal.
• On peut mettre, à la place des handlers définis par défaut, des
handlers particuliers permettant un traitement personnalisé.
• La manipulation des signaux peut se faire:
– Par la ligne de commande (frappe au clavier). Par exemple des
combinaisons des touches Ctrl-C, Ctrl-Z, Ctrl-Q.
– Dans un programme utilisateur, essentiellement par les deux primitives
principales :
• la fonction « kill() » pour envoyer des signaux.
• les fonctions « signal() » ou « sigaction() » pour mettre en place une action
personnalisée à un signal donné
02/12/2021 [email protected] 48
7.1. Envoi d'un signal par un
processus
• Les processus ayant le même propriétaire peuvent communiquer
entre eux en utilisant les signaux. La primitive « kill() » permet
d’envoyer un signal « sig » vers un ou plusieurs processus.
• Remarques
– Les processus endormis (états bloqués) sont réveillés par l'arrivé d'un
signal et passent à l’état prêt.
– Les signaux sont sans effet sur les processus zombis.
• Syntaxe
– int kill(pid_t pid, int sig) ;
• « sig » désigne le signal à envoyer (on donne le nom ou le numéro
du signal). Quand « sig » est égal à 0 aucun signal n’est envoyé,
mais la valeur de retour de la primitive « kill() » permet de tester
l’existence ou non du processus « pid » (si kill(pid,0) retourne 0 (pas
d’erreur) le processus de numéro « pid » existe).
02/12/2021 [email protected] 49
7.1. Envoi d'un signal par un
processus
• « pid » désigne le ou les processus destinataires (récepteurs du signal).
– Si pid > 0, le signal est envoyé au processus d’identité « pid ».
– Si pid=0, le signal est envoyé à tous les processus qui sont dans le même
groupe que le processus émetteur (processus qui a appelé la primitive « kill()
»).
• Remarque: Cette possibilité peut être utilisée avec la commande shell « %
kill -9 0 » pour tuer tous les processus en arrière-plan sans indiquer leurs
identificateurs de processus).
– Si pid=-1, le signal est envoyé à tous les processus (non-conforme POSIX).
– Si pid < -1, le signal est envoyé à tous les processus du groupe de numéro |pid|.
• La primitive «kill()» renvoie 0 si le signal est envoyé et -1 en cas d’échec.
• Remarque: « kill » signifie tuer en anglais, mais son rôle n’est pas
d’envoyer un signal pour tuer un processus.
02/12/2021 [email protected] 50
Exemple1: considérons le programme
« test1.c » suivant:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
void main(void) {
pid_t p;
if ((p=fork()) == 0) { /* processus fils qui boucle */
while (1);
exit(2);
}
/* processus père */
sleep(10);
printf("Fin du processus père %d : %d\n", getpid());
}
02/12/2021 [email protected] 51
Résultats d’exécution:
%./test1 & ps -f
UID PID PPID … CMD
etudiant 2763 2761 … bash
etudiant 3780 2763 …. ./test1
etudiant 3782 3780 … ./test1
% ps -f
UID PID PPID … CMD
etudiant 2763 2761 … bash
etudiant 3782 1 … ./test1
Remarque:
Le processus père a terminé son exécution mais le fils continue son exécution et devient orphelin (devient le fils du processus «init»).
02/12/2021 [email protected] 52
Exemple2: On modifie le programme précédent en envoyant un
signal au processus fils. Ce signal provoquera la terminaison du
processus fils
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
void main(void) {
pid_t p;
if ((p=fork()) == 0) { /* processus fils qui boucle */
while (1);
exit(2);
}
/* processus père */
sleep(10);
printf("Envoi de SIGUSR1 au fils %d\n", p);
kill(p, SIGUSR1);
printf("Fin du processus père %d\n", getpid());
}
02/12/2021 [email protected] 53
Résultats d’exécution:
%./test1 & ps -f
UID PID PPID … CMD
etudiant 2763 2761 … bash
etudiant 4031 2763 …. ./test1
etudiant 4033 4031 … ./test1
% Envoi de SIGUSR1 au fils 4033 Fin du processus père 4031
Après quelques secondes, le programme affiche les message prévus dans «printf()» et se termine.
% ps -f
UID PID PPID … CMD
etudiant 2763 2761 … bash
Remarque:
Le processus père a interrompu l’exécution de son fils par l’envoi du signal «SIGUSR1».
02/12/2021 [email protected] 54
Exemple3: Envoie d’un signal à tous les
processus
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
void main(void) { pid_t p1,p2;
if ((p1=fork()) == 0) { /* processus fils qui boucle */
while (1);
exit(2);
}
else {
if ((p2=fork()) == 0) { /* processus fils qui boucle */
while (1);
exit(1);
}
}
/* processus père */
sleep(10);
printf(« Envoi de SIGUSR1 aux fils %d et %d \n", p1,p2);
kill(0, SIGUSR1);
printf("Fin du processus père %d\n", getpid());
}
02/12/2021 [email protected] 55
Résultats d’exécution:
%./test1 & ps -f
UID PID PPID … CMD
etudiant 1635 1633 … bash
etudiant 2007 1635 …. ./test1
etudiant 2008 2007 … ./test1
etudiant 2009 2007 … ./test1
Après quelques secondes, le programme affiche les message prévu dans « printf() » et se termine.
% ps -f
UID PID PPID … CMD
etudiant 1635 1633 … bash
Remarque:
Le processus père a interrompu l’exécution de ses deux fils (envoi du signal «SIGUSR1»).
02/12/2021 [email protected] 56
Exemple4: Tester si un processus
spécifié existe
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
void main(void) {
…..
sleep(2);
if ((kill(p, 0)==0)
printf(" le processus %d existe d\n",p);
else
printf(" le processus %d n’existe pas d\n",p );
}
02/12/2021 [email protected] 57
7.2. Mise en place d'un handler
(traitement personnalisé)
• Les signaux, autres que « SIGKILL », «SIGCONT» et «SIGSTOP»,
peuvent avoir un handler spécifique installé par un processus.
• La primitive « signal() » peut être utilisée pour installer des handlers
personnalisés pour le traitement des signaux. Cette primitive fait
partie du standard de C.
• Syntaxe:
• #include<signal.h>
• signal (int sig, new_handler);
– Elle met en place le handler spécifié par « new_handler() » pour le
signal « sig ».
– La fonction « new_handler » est exécutée par le processus à la
délivrance (la réception) du signal. Elle reçoit le numéro du signal.
– A la fin de l'exécution de cette fonction, l'exécution du processus
reprend au point où elle a été suspendue.
02/12/2021 [email protected] 58
Exemple1: test.c
• Exemple1: test.c
#include <stdio.h>
#include <signal.h>
int main(void) {
for (;;) { }
return 0;
}
• Résultat d’exécution:
• %./test
• Le programme boucle.
• Si on appuie sur CTRL-C le programme s’arrête.
02/12/2021 [email protected] 59
Exemple2: On met en place un handler qui permet de terminer le
processus seulement lorsqu’on appuie deux fois sur «Ctr-C»
(signal «SIGINT»).
#include <stdio.h>
#include <signal.h>
void hand(int signum) {
printf(" Numéro du signal est %d \n", signum);
printf("Il faut appuyer sur Ctrl-C une 2ème fois pour terminer\n");
/* rétablir le handler par défaut du signal « SIGINT » en utilisant la
macro « SIG_DFL » */
signal(SIGINT, SIG_DFL);
}
int main(void) {
signal(SIGINT, hand); /* installation du nouveau handler */
for (;;) { } /* boucle infinie */
return 0;
}
02/12/2021 [email protected] 60
Résultat d’exécution:
• %./test
• Le programme boucle.
• Si on appuie sur CTRL-C, le programme affiche
• Numéro du signal est 2
• Il faut appuyer sur Ctrl-C une 2ème fois pour
terminer
• Si on appuie maintenant sur CTRL-C, le
programme s’arrête.
02/12/2021 [email protected] 61
Exemple3: On reprend l’exemple précédent (Exemple2 du
paragraphe 7.1.) et on modifie le handler par défaut du signal «
SIGUSR1 » pour que le fils l’ignore.
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
void main(void) {
pid_t p;
if ((p=fork()) == 0) { /* processus fils qui boucle */
signal(SIGUSR1, SIG_IGN); /* Installe le handler « SIG_IGN »
pour le signal « SIGUSR1 » */
while (1);
exit(2);
}
/* processus père */
sleep(10);
printf("Envoi de SIGUSR1 au fils %d\n", p);
kill(p, SIGUSR1);
printf("Fin du processus père %d\n", getpid());
}
02/12/2021 [email protected] 62
Résultat d’exécution
• Après quelques secondes, le programme
affiche le message prévu dans «printf()» et se
termine.
• Le PPID du processus fils devient le processus
« init » (PID=1) et ne se termine pas (boucle
infini) car le signal « SIGUSR1 » a été ignoré.
02/12/2021 [email protected] 63
Exemple4 (déroutement): Le système
détecte une erreur de calcul
#include <signal.h>
#include <stdio.h>
main() {
int a, b, resultat;
printf("Taper a : ");
scanf("%d", &a);
printf("Taper b : ");
scanf("%d", &b);
resultat = a/b;
printf("La division de a par b = %d\n", resultat);
}
• Résultat d’exécution
%./test Taper a: 12 Taper b: 0
Exception en point flottant
et le programme s’arrête.
02/12/2021 [email protected] 64
Exemple5 (déroutement): Mise en place
d’un handler pour le signal « SIGFPE ».
#include <signal.h>
#include <stdio.h>
void hand_sigfpe() {
printf("\n Erreur: division par 0 !\n");
exit(1);
}
main() {
int a, b, resultat;
signal(SIGFPE, hand_sigfpe);
printf("Taper a : ");
scanf("%d", &a);
printf("Taper b : ");
scanf("%d", &b);
resultat = a/b;
printf("La division de a par b = %d\n", resultat);
}
• Résultat d’exécution
%./test Taper a: 12 Taper b: 0
Erreur: division par 0!
02/12/2021 [email protected] 65
7.3. Structure « sigaction »
• La primitive « sigaction() » peut être utilisée pour installer un
handler personnalisé pour le traitement d’un signal. Elle prend
comme arguments des pointeurs vers des structures « sigaction »
définies comme suit:
struct sigaction {
void (*sa_handler)() ;
sigset_t sa_mask ;
int sa_flags ;
}
- Le champ « sa_handler » pointeur vers une fonction (un handler) qui
sera chargé de gérer le signal. La fonction peut être:
– « SIG_DFL » : correspond à l'action par défaut pour le signal.
– « SIG_IGN » : indique que le signal doit être ignoré.
– une fonction de type void qui sera exécutée par le processus à la
délivrance (la réception) du signal. Elle reçoit le numéro du signal.
02/12/2021 [email protected] 66
• Le champ « sa_mask » correspond à
l’ensemble de signaux supplémentaires, à
masquer (bloquer) pendant l’exécution du
handler, en plus des signaux déjà masqués.
• Le champ « sa_flags »: indique des options
liées à la gestion du signal
• Remarque:
• Le champ «sa_handler » est obligatoire.
02/12/2021 [email protected] 67
7.4. Primitive « sigaction() »
• La fonction « sigaction() » permet d’installer
un nouveau handler pour la gestion d’un
signal.
• Syntaxe:
#include <signal.h>
int sigaction(int sig, struct sigaction *action,
struct sigaction *action_ancien );
• « sig »: désigne le numéro du signal pour
lequel le nouveau handler est installé.
02/12/2021 [email protected] 68
• « action »: désigne un pointeur qui pointe sur la
structure « sigaction » à utiliser. La prise en
compte du signal entraine l’exécution du handler
spécifié dans le champ « sa_handler » de la
structure «action».
• Si la fonction n’est ni « SIG_DFl » ni
«SIG_IGN», le signal « sig » ainsi que ceux
contenus dans la liste « sa_mask » de la structure
« action » seront masqués pendant le traitement.
• « action_ancien » contient l’ancien comportement
du signal, on peut le positionner NULL.
02/12/2021 [email protected] 69
• Remarque:
– Lorsqu’un handler est installé, il restera valide
jusqu’à l’installation d’un nouveau handler.
• Exemple: Le programme suivant («
test_sigaction.c ») masque les signaux
«SIGINT » et « SIGQUIT » pendant 30
secondes et ensuite rétablit les traitements par
défaut.
02/12/2021 [email protected] 70
#include <stdio.h>
#include <signal.h>
struct sigaction action;
int main(void) {
action.sa_handler=SIG_IGN ;
sigaction(SIGINT,&action, NULL);
sigaction(SIGQUIT,&action, NULL);
printf("les signaux SIGINT et SIGQUIT sont ignorés \n");
sleep(30);
printf("Rétablissement des signaux \n");
action.sa_handler=SIG_DFL ;
sigaction(SIGINT,&action, NULL);
sigaction(SIGQUIT,&action, NULL);
while(1);
}
02/12/2021 [email protected] 71
Résultat d’exécution
• % ./test_sigaction
• les signaux SIGINT et SIGQUIT sont ignorés
• on appuie sur CTRL-C out CTRL –Q, il ne se
passe rien. Après
• 30 secondes on a l’affichage suivant:
• Rétablissement des signaux
• Maintenant, si on appuie sur CTRL-C ou
CTRL-Q, le
• programme s’arrête.
02/12/2021 [email protected] 72
8. Opérations sur les ensembles de
signaux
• Le type « sigset_t » désigne un ensemble de signaux. On peut
manipuler les ensemble de signaux à l’aide des fonctions suivantes:
• int sigemptyset (sigset_t * ens);
– Initialise l’ensemble de signaux « ens » à l’ensemble vide.
• int sigfillset (sigset_t * ens);
– Remplit l’ensemble « ens » avec tous les signaux.
• int sigaddset (sigset_t * ens, int sig);
– Ajoute le signal « sig » à l’ensemble « ens ».
• int sigdelset (sigset_t * ens, int sig);
– Retire le signal « sig » de l’ensemble « ens ».
• int sigismember (const sigset_t * ens, int sig);
– Retourne vrai si le signal « sig » appartient à l’ensemble « ens ».
02/12/2021 [email protected] 73
9. Masquage des signaux
La primitie « sigprocmask() » permet de masquer ou de démasquer un
ensemble de signaux. La valeur de retour de la fonction est 0 ou -1
selon qu'elle est ou non bien déroulée
• Syntaxe:
• #include <signal.h>
• int sigprocmask(int opt, const sigset_t *ens, sigset_t *ens_ancien);
• « ens » pointeur sur le nouveau ensemble des signaux à masquer.
• « ens_ancien »: pointeur sur le masque antérieur. Il est récupéré au
retour de la fonction.
• « opt » précise ce que on fait avec ces ensembles. Les valeurs de «
opt » sont des constantes symboliques définies dans « signal.h ».
• Si le troisième argument « ens_ancien » est un pointeur non NULL,
alors la fonction installe un masque à partir des ensembles pointé par
« ens » et « ens_ancien ».
02/12/2021 [email protected] 74
Valeur du paramètre opt :
• - « SIG_SETMASK »
– Les seuls signaux masqués seront ceux de l’ensemble
«ens».
– Nouveau masque = « ens ».
• - « SIG_BLOCK »:
– Les signaux masqués seront ceux des ensembles « ens
» et «ens_ancien».
– Nouveau masque= « ens » U « ens_ancien »
• -« SIG_UNBLOCK »
– Démasquage des signaux de l’ensemble « ens ».
Nouveau masque: « ens_ancien » – « ens »
02/12/2021 [email protected] 75
9. Liste des signaux pendant
• Grâce à la fonction « sigpending() » on peut voir la liste des
signaux, en attente d’être pris en compte.
• Le code de retour de la fonction est 0 si elle est bien
déroulée ou -1 sinon.
• Syntaxe:
• #include <signal.h>
• int sigpending(sigset_t *ens) ;
– Retourne dans « ens » la liste des signaux bloqués (en attente
d’être pris en compte).
• Exemple: Le programme suivant (test_pending.c) suivant
montre le masquage et le démasquage d’un ensemble de
signaux et comment connaître les signaux pendants.
02/12/2021 [email protected] 76
#include <stdio.h>
#include <signal.h>
#include<stdlib.h>
sigset_t ens1, ens2; int sig;
main() {
//construction de l’ensemble ens1={SIGINT, SIGQUIT, SIGUSR1}
sigemptyset(&ens1);
sigaddset(&ens1,SIGINT); // ajoute le signal SIGINT à ens1
sigaddset(&ens1,SIGQUIT); // ajoute le signal SIGQUIT à ens1
sigaddset(&ens1,SIGUSR1); // ajoute le signal SIGUSR1 à ens1
printf(" Numéros des signaux %d %d %d\n",SIGINT,SIGUSR1,SIGQUIT);
// Installation du masque ens1
sigprocmask(SIG_SETMASK, &ens1, NULL);
printf("Masquage mis en place.\n");
02/12/2021 [email protected] 77
sleep(15);
/* affichage des signaux pendants: signaux envoyés mais non délivrés car masqués*/
sigpending(&ens2);
printf("Signaux pendants:\n");
for(sig=1; sig<NSIG; sig++)
if(sigismember(&ens2, sig))
printf("%d \n",sig);
3èmesleep(15);
test: on lance l’exécution et ensuite on tappe Ctrl-C et Ctrl-Quit
/* Suppression du masquage des signaux */
sigemptyset(&ens1);
%./test_pending
printf("Déblocage des signaux.\n");
Numéro des signaux 2 10 3 Masquage mis en
sigprocmask(SIG_SETMASK, &ens1, (sigset_t *)0);
placesleep(15);
^C ^\printf("Fin
Signaux normale
pendant:
du 2processus
3 \n");
exit(0);
Déblocage
} des signaux Résultat d’exécution
% 1er test: on lance l’exécution et on attend la fin du programme
02/12/2021 [email protected] 78
10. La fonction « pause() »
• La primitive « pause() » bloque (endort: attente
passive) le processus appelant jusqu'à l'arrivée d'un
signal quelconque. Elle est déclarée dans <unistd.h>.
• Syntaxe:
• #include<unistd.h> int pause (void);
• A la prise en compte d'un signal, le processus peut :
– se terminer car le « handler » associé est « SIG_DFL » ;
– exécuter le « handler » correspondant au signal intercepté.
• Remarque: « pause() » ne permet pas d'attendre un
signal de type donné ni de connaître le nom du signal
qui l'a réveillé.
02/12/2021 [email protected] 79
Exemple d'attente
int nb_req=0;
int pid_fils, pid_pere;
02/12/2021 [email protected] 80
11. La fonction « raise () »
• Syntaxe:
• int raise (int sig)
• La fonction « int raise (int sig) » envoie le
signal de numéro « sig » au processus courant
(au processus qui appelle « raise() »). raise(sig)
est équivalent à kill(getpid(),sig).
02/12/2021 [email protected] 81
12. La fonction « alarm() »
• L’appel système « alarm() » permet à un processus de gérer des
temporisation(timeout) avant la routine concernée. Le processus est
avertit de la fin d’une temporisation par un signal « SIGLRM ».
• Syntaxe:
• #include<signal.h>
• unsigned int alarm(unsigned int sec)
• Pour annuler une temporisation avant qu’elle n’arrive à son terme,
on fait « alarm(0) » c’est-à-dire si la routine se termine normalement
avant le délai maximal, on annule la temporisation avec alarm(0).
• Le traitement par défaut du signal est la terminaison du processus.
Pour modifier le comportement on installe un nouveau handler.
02/12/2021 [email protected] 82
Exemple
• Le programme « test_alarm.c » suivant lit deux
entiers. Ensuite demande de lire le résultat qui
la compare avec le produit. Si le temps de
réponse est plus grand que 3 secondes le
programme se termine avec un message
d’alarme, sinon il termine normalement.
02/12/2021 [email protected] 83
#include<stdio.h>
#include<signal.h>
main() {
int a, b, resultat;
printf("Entrer deux entiers ");
scanf("%d%d",&a,&b);
printf(" Donner le résultat de a*b : ");
alarm(3);
scanf("%d",&resultat);
alarm(0); // annule la temporisation
if(resultat == (a*b))
printf(" Résultats juste \n ");
else
printf(" Résultat faut \n ");
printf(" Fin normale du programme ");
}
02/12/2021 [email protected] 84
• Résultat d’exécution 1 (on saisit le résulttat de a*b avant le
délais)
• %.test_alarm
• Entrer deux entier 12 12 Donner le résultat de a*b : 123 Résultat
faut
• Fin normale du programme
• Résultat d’exécution 2 (on met plus de temps pour saisir le
résultat)
• Si on ne tape rien, au bout de 3 seconde on a le message « Minuterie
d’alerte » sera affiché.
• %.test_alarm
• Entrer deux entier 12 12 Donner le résultat de a*b Minuterie d’alerte
• %
02/12/2021 [email protected] 85
Exemple: on installe un handler
#include<stdio.h>
#include<signal.h>
void hand(int signum) {
printf(" délais dépassé. Num SIGLRM = %d \n" , signum);
exit(1);
}
main() {
int a, b, resultat;
signal(SIGLRM,hand);
printf("Entrer deux entiers ");
scanf("%d%d",&a,&b);
printf(" Donner le résultat de a*b : ");
alarm(3);
scanf("%d",&resultat);
alarm(0); // annule la temporisation
if(resultat == (a*b))
printf(" Résultats juste \n ");
else
printf(" Résultat faut \n ");
printf(" Fin normale du programme ");
}
02/12/2021 [email protected] 86
• Résultat d’exécution 2 (on met plus de
temps pour saisir le résultat)
• %.test_alarm
• Entrer deux entier 12 12 Donner le résultat de
a*b
• délais dépassé. Num SIGLARM = 14 %
02/12/2021 [email protected] 87
13. Signal SIGHUP
• SIGHUP =Signal Hang UP (déconnexion de
l’utilisateur): ce signal est envoyé
automatiquement au processus si on ferme le
terminal auquel il est attaché.
02/12/2021 [email protected] 88
14. Libellé des signaux
• Il existe une table globale de chaînes de caractères
contenant les libellés des signaux :
• char * sys_siglist [numero_signal].
• L’exemple suivant permet d’afficher ces libellés.
#include <stdio.h>
#include <signal.h>
void main () {
int i;
printf("Liste des libellés : \n");
for (i =1; i < NSIG; i ++)
printf (“signal %d : %s\n ", i, sys_siglist[i]);
}
02/12/2021 [email protected] 89
Liste des libellés
• signal 1 : Hangup
• signal 2 : Interrupt
• signal 3 : Quit
• signal 4 : Illegal instruction
• signal 5 : Trace/breakpoint trap
• signal 6 : Aborted
• signal 7 : Bus error
• signal 8 : Floating point exception
• signal 9 : Killed
• signal 10 : User defined signal 1
• signal 11 : Segmentation fault
• signal 12 : User defined signal 2
• signal 13 : Broken pipe
• signal 14 : Alarm clock
• signal 15 : Terminated
• signal 16 : Stack fault
• signal 17 : Child exited
• signal 18 : Continued
02/12/2021 [email protected] 90
Chapitre III. Communication classique
entre processus
• I. Synchronisation
• 1. Introduction
• Après création d’un processus par l’appel système « fork()
», le processus créé et son père s’exécutent de façon
concurrente.
• Lorsqu'un processus se termine, il envoie le signal «
SIGCHLD » (SIGnal CHiLD) à son père et passe dans l’état
zombi tant que son père n’a pas pris connaissance de sa
terminaison, c'est à dire que la mémoire est libérée (le fils
n’a plus son code ni ses données en mémoire) mais que le
processus existe toujours dans la table des processus. Une
fois le père lit le code retour du fils, à ce moment il termine
et disparaît complètement du système.
02/12/2021 [email protected] 91
• Dans le cas ou le père meurt avant la
terminaison de son fils, alors le processus fils
sera rattaché au processus « init » ( l’ID du
processus « init » est 1).
• Afin de synchroniser le père avec ses fils (le
processus père attende la terminaison de ses
fils avant de se terminer), on utilise les
fonctions de synchronisation « wait() » et «
waitpid() » qui permettent au processus père
de rester bloqué en attente de la réception d'un
signal « SIGCHLD ».
02/12/2021 [email protected] 92
2. Les processus zombi
• Quand un processus se termine (soit en sortant du
« main() » soit par un appel à « exit() »), il délivre
un code retour. Par exemple « exit(1) » donne un
code retour égal à 1.
• Tout processus qui se termine passe dans l’état «
zombi » tant que son père n’a pas pris
connaissance de sa terminaison.
• Une fois le père ait connaissance de l’état de son
fils, à ce moment le processus fils se termine et
disparaît complètement du système
• Exemple:
02/12/2021 [email protected] 93
• Le temps pendant lequel le processus fils est
en état zombi, le fils n’a plus son code ni ses
données en mémoire; seules les informations
utiles pour le père sont conservées.
02/12/2021 [email protected] 94
Exemple: Soit le fichier test_zombi.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
main() {
if (fork() == 0) { /* fils */
printf("je suis le fils, mon PID est %d\n", getpid());
sleep(1);
} else { /* père */
printf("je suis le père, mon PID est %d\n", getpid());
sleep(30);
}
}
02/12/2021 [email protected] 95
Exemple d'exécution
• %./tes-zombi & ps g
• je suis le fils, mon PID est 3748 je suis le père, mon PID est 3746
• PID TTY STAT .... CMD
• 1716 pts/0 Ss .... bash
• 3746 pts/0 S ..... ./test-zombi
• 3747 pts/0 R+ ..... ./ps g
• 3748 pts/0 S ..... ./test-zombi
• % ps g STAT .... CMD
• PID TTY
• 1716 pts/0 Ss .... bash
• 3746 pts/0 S ..... ./test-zombi
• 3748 pts/0 Z ..... [test_zombi] <defunct>
• 3749 pts/0 R+ ..... ps g
02/12/2021 [email protected] 96
• Dans ce cas le fils termine et attend que le père
soit réveillé car le père est dans un état bloqué
(sleep(30) pour avoir connaissance de son état.
• Pour que le fils se termine et disparait du
système, il faut que le processus père lise l’état
(le statut) de son fils.
02/12/2021 [email protected] 97
3. Les processus orphelins
• La terminaison d'un processus parent ne termine pas ses processus fils.
Dans ce cas les processus fils deviennent orphelins et sont adoptés par le
processus initial (PID 1), c'est-à-dire que " init " devient leur père.
• Exemple: Soit le fichier " test_orp.c "
#include <stdio.h>
# include <stdlib.h> #include <unistd.h> main() {
if (fork() == 0) { /* fils */
printf("je suis le fils, mon PID est %d\n", getpid());
sleep(30);
exit(0);
} else { /* père */
printf("je suis le père, mon PID est %d\n", getpid()); exit(0);
}
}
02/12/2021 [email protected] 98
• Après compilation on lance " test_orp " en
background, ensuite en lance la commande " ps -f
"
• % ./test_orp & ps -f
• Je suis le fils, mon PID est 3790
• Je suis le père, mon PID est 3788
• [2] 3788
• UID PID PPID TTY .... CMD
• etudiant 1969 1750 pts/0 .... bash
• etudiant 3789 1969 pts/0 ..... ps -f
• etudiant 3790 1 pts/0 ..... ./test_orp
• %
02/12/2021 [email protected] 99
4. Synchronisation père et fils: La
primitive « wait() »
• La fonction wait() est déclarée dans <sys/wait.h>.
• Syntaxe:
– # include<sys/wait.h> pid_t wait (int * status);
• L'appel de la fonction « wait() » bloque le processus qui
l’appelle en attente de la terminaison de l’un de ses
processus fils.
• Si le processus qui appelle « wait() » n'a pas de fils, alors «
wait() » retourne -1.
• Sinon « wait() » retourne le PID du processus fils qui vient
de se terminer.
• Si le processus appelant admet au moins un fils en attente à
l’état zombie, alors « wait() » revient immédiatement et
renvoie le PID de l’un de ces fils zombis.
main(){
int i,n=1,m=2; pthread_t thread[N];
for (i=0; i<N; i++)
pthread_create(&thread[i], NULL, fonction, (void *)i);
printf("Dans main: n = %d m = %d \n",n,m); sleep(10);
include <pthread.h>
include <stdio.h>
void* affiche_a (void *v) { int *cp=(int *)v;
int i;
for (i=1;i<=*cp;i++)
fputc ('a', stderr );
return 0 ;
}
void* affiche_b (void *v) { int *cp=(int *)v;
int i;
for (i=1; i<=*cp; i++)
fputc ('b', stderr );
return 0 ;
}
Thread 1 Thread 2
- Méthode très peu économique: « thread2 » occupe le processeur pour une boucle vide.