ESPACE D'ADDRESSAGE DES PROCESSUS :
Chaque processus possède un espace d'adressage, c'est à dire un espace d'adressage dans
lequel il peut lire et écrire.
Cet espace est subdivisé en trois parties :
[Link] de texte
[Link] de données
[Link] pile (La partie pile sert à sauvegarder)
ORDONNANCEMENT DES PROCESSUS :
Lorsqu’un processus est lancé, le système doit gérer la mémoire et l'allocation du processeur
lui étant accordé. Il fait appel à l'ordonnanceur(scheduler).
On dit d'un système qu'il est préemptif lorsqu'il peut arrêter à tout moment n'importe quelle
application et passer à la suivante (win7, Win XP, win11, GNU/Linux etc.…).
À contrario il est dit coopératif quand il permet à plusieurs applications de fonctionner et
d'occuper la mémoire, leur laissant le soin de gérer cette occupation (win95, millenium).
Dans les systèmes préemptif, l'OS garde le contrôle et ferme l'application à souhait dans le
coopératif, l'OS laisse les applications gérer.
PID : chaque processus peut être identifié par son numéro de processus appelé PID. Un
numéro de PID est unique. Lorsqu’on créer un processus, on utilise une fonction qui
duplique un processus existant (c'est le processus appelant). On distingue alors deux
processus par leur PID. Le processus appelant est appelé processus père et le nouveau
processus est le processus fils. Quand on s'occupe du processus fils, le PID du processus père
est noté PPID qui signifie parent PID.
ATTRIBUTION DU PID : par défaut le noyau attribut un PID avec une valeur inférieure à
32768. Le 32768ième processus créer reçoit la plus petite valeur de PID libérer par un
processus mort entre temps. Par ailleurs cette valeur minimale peut être modifié par
l'admin. les PID sont attribués de façon linéaire, si 17 est le PID le plus élevé, un processus
créer à l'instant aura le PID 18, le PID réutilise les PID des processus n'existant plus
uniquement quand la valeur de PID max est atteinte. Quand il n’y a pas de processus mort et
qu'on a atteint les 32768 qui sont lancé, si on lance un nouveau processus, ce dernier sera
lancé en boucle jusqu'à ce qu'un processus ne meure pour qu'il prenne sa place.
UID : les systèmes basés sur Unix sont axés côté utilisateurs par sécurité de nombreux
utilisateurs ont besoin de permissions ou habilitations pour exécuter plusieurs actions. Pour
cela chaque utilisateur a un ID user ID (UID). Nous pouvons ainsi distinguer les processus
entre eux par les users qui les a lancés.
Les types d’UID :
UID réel : correspond à l'UID de l'utilisateur qui a lancé le processus
UID effectif : correspond aux privilèges accordés à l'utilisateur.
UID sauvé : c'est la copie de l'ancien UID réel quand celui-ci a été modifié par un
processus
PARTICULARITE SUR LA GESTION DES PROCESSUS
Une des particularités de la gestion des processus consiste à séparer la création de processus
et l'exécution d'une image binaire. La plupart du temps, ces deux taches sont exécutées
ensemble, mais cette séparation permet d'avoir plusieurs processus pour un mm
programme. C’est ainsi que nous avons : fork et exec dans le système d'exploitation Unix
alors que les autres n'ont qu'un seul appel système
ORGANISATION DES PROCESSUS
Les processus sont organisés en hiérarchie, chaque processus doit être lancé par un autre,
c'est la notion de père et fils, la racine de cette hiérarchie est le programme initial.
LES ETATS DES PROCESSUS
Un processus peut avoir plusieurs états :
Exécution (R) pour running : le processus est en cours d'exécution.
Sommeil (S) pour sleeping : dans un multitâche coopératif, il rend la main ; dans un
multitâche préemptif quand il est interrompu au bout d'un certain temps.
Arrêt (T) pour stopped : le processus a été temporairement arrêté par un signal, il ne
s'exécute plus et ne réagira qu'à un signal de redémarrage.
Zombie (Z) : le processus s'est terminé mais son père n'a pas encore lu son code de
retour
IMPLEMENTATION DES PROCESSUS
Pour implémenter les processus, L'OS utilise un tableau de structures appelé table des
processus, cette dernière comprend une entrée par processus alloué dynamiquement,
correspondant au processus associé à ce programme : c'est le bloc de contrôle du processus
appelé (Process Control Blok)
CREATION D'UN NOUVEAU PROCESSUS
La programmation système nécessite l'inclusion des bibliothèques pour les fonctions d'appel
système, on utilise la bibliothèque unistd.h qu'on met en début de fichier : #include unistd.h
La fonction fork : pour créer un nouveau processus, on utilise la fonction d'appel système
fork. Le processus d'origine est nommé le processus père et le nouveau processus créer le
processus fils. Les deux ont le même code source, mais la valeur retournée par fork permet
des distinguer le père du fils. Ceci permet de faire deux choses différentes suivants la
structure condition if ou switch.
QUE FAIT FORK AU NIVEAU DE L’OS :
Lors de l'exécution de fork le noyau effectue les opérations suivantes :
Il alloue un bloc de contrôle dans la table des processus
Il copie les infos contenues dans le bloc de contrôle du père dans celui du fils sauf les
identifications (PID et PPID)
Il alloue un PID au processus fils
Il associe au processus fils un segment de texte dans son espace d'adressage. Le
segment de donnée et la pile ne lui seront attribués uniquement lorsque celui-ci
tentera de les modifier. Cette technique s'appelle (copy on write) et permet de
réduire le temps de création du processus
L’état du processus est mis à l'état exécution.
La fonction fork retourne une valeur de type pid_t. Il s'agit généralement d'un int déclaré
dans sys/types.h
La valeur renvoyée par fork est :
(-1) s'il y a erreur
(0) si on est dans le processus fils
Le PID du fils si on est dans le processus père. Cela permet au père de connaitre le
PID de son fils
Au cas où fork renvoie -1 et donc il y a erreur, le code de l'erreur est contenu dans la valeur
globale errno déclaré dans errno.h. Ce code correspond à deux constantes :
ENOMEM : le noyau n'a plus assez de mémoire disponible pour créer un nouveau
processus
EAGAIN : ce code peut être dû à deux raisons, soit il n'y a pas suffisamment de
ressources système pour créer le processus soit l'utilisateur a déjà trop de processus
en cours d'exécution. Que ce soit pour l'une ou l'autre raison vous pouvez rééditer
votre requête tant que fork renvoie EAGAIN.
Terminaison d'un processus :
Un programme peut se terminer de façon normale (volontaire) ou anormale (erreur).
Terminaison normale d'un processus :
On peut laisser le processus finir avec main avec l'instruction return suivi du code de retour
du programme. Une autre est de terminer le programme avec la fonction exit.
Cette dernière a pour avantage de quitter le programme quel que soit la fonction dans
laquelle on se trouve. Cela n'empêche pas pour autant de mettre un return à la fin du MAIN.
Au risque d'avoir un warning.
Terminaison anormale d'un processus :
Pour quitter de manière propre un programme lorsqu'un bug a été détecté, on utilise la
fonction abort. Il est difficile cependant de savoir à quel endroit du programme le bug a lieu.
Il lui est donc préféré la macro "assert" qui est défini comme suit :
Assert prend en argument une condition
Si cette condition est vraie, assert ne fait rien
Si elle est fausse, assert écrit un message contenant la condition concernée, puis
quitte le programme
Cela permet une bonne gestion des erreurs. Il est recommandé d'utiliser assert dans les cas
critiques, dans lesquels votre programme ne peut pas continuer si la condition est fausse.
Exemple :
EXECUTION DE ROUTINES DE TERMINAISON :
Il est possible d’exécuter automatiquement des fonctions pour une terminaison normale du
programme avec les instructions exit et return. On utilise pour cela des fonctions comme
atexit et onexit.
Exemple :
Exécution :
Exemple 2 :
On-exit :
#include<stdlib.h>
Int on-exit (void*(function) (int, void *), void*arg);
On-exit reçoit deux arguments :
Un pointeur sur la fonction à exécuter
L’argument à passer à la fonction
Il est plus commode d’utiliser atexit que on-exit.
SYNCHRONISATION ENTRE PERE ET FILS :
Lorsque le fils se termine avant le père, il devient un zombie. Pour permettre au processus fils en état
zombie de disparaitre complètement, on utilise la fonction wait déclarée dans la bibliothèque
sys/wait.h. Si on oublie de mette wait, le système explose.
PROGRAMME DE CREATION DU FILS AVEC wait :
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
pid_t create_process(void) {
pid_t pid;
do {
pid = fork();
} while ((pid == -1) && (errno == EAGAIN));
return pid;
}
void child_process(void) {
printf("Nous sommes dans le fils !\n"
"Le PID du fils est %d.\n"
"Le PPID du fils est %d.\n",
(int)getpid(), (int)getppid());
exit(EXIT_SUCCESS);
}
void father_process(int child_pid) {
printf("Nous sommes dans le père !\n"
"Le PID du fils est %d.\n"
"Le PID du père est %d.\n",
(int)child_pid, (int)getpid());
if (wait(NULL) == -1) {
perror("wait");
exit(EXIT_FAILURE);
}
}
int main(void) {
pid_t pid = create_process();
switch (pid) {
case -1:
perror("fork");
return EXIT_FAILURE;
case 0:
child_process();
break;
default:
father_process(pid);
break;
}
return EXIT_SUCCESS;
}
NB : Mettre autant de ‘wait’ qu’il y a de fils dans le programme.
Attendre la fin de n’importe quel processus !
Il existe une fonction qui permet de suspendre l’exécution d’un processus père jusqu’à ce qu’un fils
ne se termine. Il s’agit de la fonction waitpid qui accepte le pid du fils en paramètre.
La valeur du pid est interprétée comme suit :
Si pid > 0 le processus père est suspendu jusqu’à la fin d’un fils dont le PID est égale à la
valeur du pid
Si pid = 0 le processus père est suspendu jusqu’à la fin de n’importe lequel des fils
appartenant à son groupe
Si pid =-1 le processus père est suspendu jusqu’à la fin de n’importe quel fils
Si pid < -1 le processus père est suspendu jusqu’à la mort de n’importe quel fils dont le GID
est égale
On distingue deux constantes en 3e paramètre de waitpid :
WNOHANG : ne pas bloqué si aucun fils ne s’est terminé
WUNTRACED : recevoir les infos concernant les fils bloqués si on ne l’avait reçu.
Généralement on met 0 à la place du troisième paramètre.
NB : waitpid (-1, status, 0) = wait
LES MACROS STATUS
Un processus peut se résumer de la façon dont il se termine :
Normale (return ou exit)
Anormale (assert)
Le processus anormal devient zombie jusqu’à ce qu’un wait ne soit appelé ou que le père meure. Le
code retour du processus est stocké à l’emplacement pointé par status que nous pouvons sortir à
partir les macros.
Macro Description
WIFEXITED (status) Elle renvoie vrai si le statut provient d’un processus fils qui s’est
terminé en quittant le main avec return ou avec un appel à exit.
WEXITSTATUS (status) Elle renvoie le code de retour du processus fils passé à exit ou à return.
Cette macro est utilisable uniquement si vous avez utilisé WIFEXITED
avant, et que cette dernière a renvoyé vrai.
WIFSIGNALED (status) Elle renvoie vrai si le statut provient d’un processus fils qui s’est
terminé à cause d’un signal.
WTERMSIG (status) Elle renvoie la valeur du signal qui a provoqué la terminaison du
processus fils. Cette macro est utilisable uniquement si vous avez utilisé
WIFSIGNALED avant, et que cette dernière a renvoyé vrai.
Ces macros utilisent le status et non le pointeur sur le status.
Quelques applications :
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
int status;
pid_t create_process(void) {
pid_t pid;
do {
pid = fork();
} while ((pid == -1) && (errno == EAGAIN));
return pid;
void child_process(void) {
printf("Nous sommes dans le fils !\n");
exit(EXIT_SUCCESS);
void father_process(int child_pid) {
printf("Nous sommes dans le père !\n");
if (wait(&status) == -1) {
perror("wait");
exit(EXIT_FAILURE);
if (WIFEXITED(status)) {
printf("Terminaison normale du fils\n"
"Code de retour : %d\n", WEXITSTATUS(status));
if (WIFSIGNALED(status)) {
printf("Terminaison anormale du fils\n"
"Tué par le signal : %d\n", WTERMSIG(status));
int main(void) {
pid_t pid = create_process();
switch (pid) {
case -1:
perror("fork");
return EXIT_FAILURE;
case 0:
child_process();
break;
default:
father_process(pid);
break;
return EXIT_SUCCESS;
EXECUTER UN PROGRAMME
La fonction main d’un programme peut prendre des arguments en ligne de commandes. Pour
récupérer les arguments argc et argv.
Argc : est un entier de type int qui donne le nombre d’arguments passer en ligne de
commandes plus 1 (+1).
Argv : est un tableur de pointeurs ;
o Argv [0] : contient le nom du fichier exécutable du programme.
o Argv [1], argv [2], etc. contient des arguments passer en ligne de commandes.
Application : Ecrire un programme qui prend des arguments et les affiche.
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
int i;
if (argc <= 1) {
printf("Le programme n’a pas d’argument !\n");
} else {
for (i = 1; i < argc; i++) {
printf("Argument %d : %s\n", i, argv[i]);
return EXIT_SUCCESS;
La variable PATH : est une variable contenant une série de chemin vers des répertoires. Lorsqu’on
lance une commande, le système va chercher l’exécutable dans les chemins définis dans le PATH. Si
deux commandes portent le même nom, c’est la première trouvée qui sera exécuté.
L’environnement
Une application peut-être exécuter dans des contextes différents : terminaux, répertoires de travail,
etc... C’est pour cela qu’on a besoin d’accéder à l’environnement défini sous la forme de variable
d’environnement. Elles sont définies sous la forme NOM = VALEUR. On accède aux variables
d’environnement en déclarant la variable externe globale : ‘’environ’’ définie comme suit :
extern char **environ ;
Ce tableau contient des chaînes de caractères se terminant par NULL et lui-même se terminant par
un pointeur NULL.
Application : écrire un programme qui affiche toutes les variables d’environnement.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
extern char **environ;
int main(void) {
int i;
for (i = 0; environ[i] != NULL; i++) {
printf("%s\n", environ[i]);
}
return EXIT_SUCCESS;
VARIABLES D’ENVIRONNEMENT CLASSIQUES
HOME : Contient le répertoire personnel de l’utilisateur
PWD : Contient le répertoire courant.
TERM : Type de terminal utilisé.
SHELL : interpréteur de commandes par défaut
USER : nom de l’utilisateur connecté.
Créer, modifier, rechercher et supprimer une variable : Pour créer une variable, on utilise la fonction
Créer : int putenv (const char*string) ;
Modifier: int setenv (const char*name, const char*value, int overwrite)
Rechercher : char*getenv (const char*name) ;
Supprimer : void unsetenv (const char *name) ;
Exo : écrire un programme qui permet de rechercher les variables d’environnement passés en
paramètre de la fonction ‘’main’’
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
int i;
char *variable; // Déclaration de la variable
// Vérifier si l'utilisateur a passé des arguments
if (argc <= 1) {
fprintf(stderr, "Le programme n’a pas d’argument.\n");
return EXIT_FAILURE;
// Parcourir les arguments donnés après le nom du programme
for (i = 1; i < argc; i++) {
Variable = getenv(argv[i]); // Récupérer la variable d'environnement
if (variable == NULL) {
printf("%s : n’existe pas.\n", argv[i]);
} else {
printf("%s = %s\n", argv[i], variable);
return EXIT_SUCCESS;
LES FONCTIONS DE LA FAMILLE EXEC
Il existe 6 fonctions appartenant à la famille exec :
Execl (const char * path, const char * arg, …);
Execle(const char * file, const char * arg, …, char * const envp[]);
Execlp(const char * file, const char * arg, …);
Execv (const char * path, const char * const argv [], …);
Execve(const char * filename, char * const arg[], char * const envp[]);
Execvp(const char * file, char * const argv[]);
Parmi elles, seule la fonction execv est un appel système. Ces fonctions permettent de remplacer un
programme en cours d’exécution par un autre sans en changer le pid. Autrement dit on peut
remplacer le code source d’un programme par celui d’un autre en faisant appel à une fonction exec.
Explication :
Suffixe en v : les arguments sont passé en argument sous forme de tableau
Suffixe en l : les arguments sont passé en argument sous forme de liste
Suffixe en p : le fichier à exécuter est recherché à l’aide de la variable d’environnement PATH
Pas de suffixe en p : le fichier à exécuter est recherché relativement au répertoire de travail
du processus père.
Suffixe en e : un nouvel environnement est transmis au processus fils.
Pas de suffixe en e : l’environnement du nouveau processus est déterminé à partir de la
variable d’environnement externe.
Toutes ces fonctions -1 en cas d’erreur, mais errno peut correspondre à plusieurs constantes :
EACCESS : vous n’avez pas les permissions pour exécuter le programme.
E2BIG : la liste d’argument est trop grande.
ENDENT : le programme n’existe pas.
ETXTBSY : le programme a été ouvert en écriture par d’autre processus
ENOEXEC : le format du fichier exécutable est incorrect
ENOMEM : le système de possède pas assez de mémoire pour lancer le processus
ENOTDIR : le chemin d’accès défini est incorrect.
Exo : créer un programme qui prend en argument le chemin complet d’un répertoire et qui ouvre
l’analyseur d’utilisation du disque AUD.
Résolution :
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
Int main (int argc, char * argv[])
{char * arguments[] = {‘’baobab’’, argv[1],NULL} ;
If (execv(‘’/usr/bin/baobab’’, arguments) == -1){
Perror (‘’execv’’);
Return EXIT_FAILURE;
Return EXIT_SUCCESS;
La commande permettant de lancer ce programme s’appelle baobab. Elle prend en argument le
chemin du répertoire à analyser et se trouve dans le répertoire /usr/bin/baobab.
En exécutant ce programme, et si vous avez saisit un chemin valide, la AUD s’ouvre. Mais on ne peut
plus entrer de commande, et si l’on fait ctrl + C, l’AUD se ferme. Si on veut exécuter un code à la suite
de l’ouverture du programme, on est obligé de passer par les processus.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
Pid_t create_process(void){
Pid_t pid;
Do{ pid = fork();
} while ((pid == -1) && (EAGAIN));
Return pid;
}
Void son_process(char*arg[])
{ if (execv(“/usr/bin/baobab”, arg) == -1)
{perror (“execv”);
Exit(EXIT_FAILURE);
Void father_process (void) {
Printf (“\n si ce texte s’affiche, OK ! \n’’) ;
Int main (int argc, char*argv[])
{ char * arg [] = {‘’baobab’’, argv[1], NULL}
Pid_t pid = create_process;
Switch(pid){
Case -1:
Perror (“fork”);
Return EXIT_FAILURE;
Break;
Case 0:
Son_process (arg);
Break;
Default:
Father_process ();
Break;
Return EXIT_SUCCESS;
LES THREADS
Dans la plupart de SE chaque processus possède un espace d’adressage et un thread de contrôle
unique, c’est le thread principal, c’est ce dernier qui exécute le main. Le mot thread se traduit par fil
d’exécution. Toutes les fonctions relatives au thread sont dans le fichier <pthread.h> dans la
bibliothèque libthread.a.
MANIPULATION DES THREADS
Pour créer un thread, il faut déclarer une variable le représentant, celle-ci sera de type : pthread_t
qui est un typedef d’unsigned long int. Et on utilise la fonction : pthread_create.
Création d’un thread :
#include <pthread.h>
Int pthread_create (pthread * thread, pthread_attr_t * attr, void* (*start_routine)(void*), void*arg);
Ce prototype semble compliqué mais simple d’utilisation :
La fonction renvoie une valeur de type int : 0 si la création est réussie et une autre valeur s’il
y a erreur.
Le premier argument est un pointeur vers l’identifiant du thread
Le second argument désigne les attributs du thread. On peut choisir de mettre le thread en
état joignable (par défaut) ou détaché, et choisir sa politique d’ordonnancement (usuelle,
temps réel) nous mettons généralement NULL.
Le troisième argument est un pointeur vers la fonction à exécuter et contient le code à
exécuter par le thread.
Enfin le dernier à argument est l’argument à passer au thread.
Supprimer un thread :
Comme dans la vie, supprimer est plus facile que créer et on utilise la fonction pthread_exit.
#include <pthread.h>
Void pthread_exit (void*ret) ;
APPLICATION:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
void *thread_1(void *arg) {
printf("Nous sommes dans le thread.\n");
(void)arg;
pthread_exit(NULL);
}
int main(void) {
pthread_t threadA;
printf("Avant la création du thread.\n");
if (pthread_create(&threadA, NULL, thread_1, NULL) == -1) {
perror("pthread_create");
return EXIT_FAILURE;
printf("Après la création du thread.\n");
return EXIT_SUCCESS;
Dans la pratique tout comme avec les processus le n’attendra pas lui-même que le thread se termine
avant d’exécuter son code. Par conséquent il va falloir le faire attendre et dieu pthread créa la
fonction pthread_join.
#include <pthread.h>
Int pthread_join (pthread_t th, void ** thread_return);
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
void *thread_1(void *arg) {
printf("Nous sommes dans le thread.\n");
(void)arg;
pthread_exit(NULL);
int main(void) {
pthread_t thread1;
printf("Avant la création de thread.\n");
if (pthread_create(&thread1, NULL, thread_1, NULL)) {
perror("pthread_create");
return EXIT_FAILURE;
}
if (pthread_join(thread1, NULL)) {
perror("pthread_join");
return EXIT_FAILURE;
printf("Après la création du thread.\n");
return EXIT_SUCCESS;
LES EXCLUSIONS MUTUELLES
Dans la gestion des threads, les variables sont gérées dans la mémoire partagée, ce qui pose
problème, c’est quand un thread lit une variable pendant qu’un autre tente de la modifié. Il est donc
nécessaire d’adopter un mécanisme de synchronisation appelé : exclusion mutuelle ou mutex.
Concrètement, un mutex est une variable de type pthread_mutex_t qui sert de verrou. Ce verrou a
deux états : disponible et verrouillé. Le mutex se déclare dans une structure avec la donnée à
partager.
typedef struct data {
int var;
pthread_mutex_t mutex;
} data;
On manipule les mutex grâce à pthread.
INITIALISE UN MUTEX
On initialise un mutex avec la valeur de la constante PTHREAD_MUTEX_INITIALIZER déclaré dans
pthread.h.
Exemple :
#include <stdlib.h>
#include <pthread.h>
typedef struct data {
int var;
pthread_mutex_t mutex;
} data;
int main(void) {
data new_data;
new_data.mutex = PTHREAD_MUTEX_INITIALIZER;
return EXIT_SUCCESS;
VERROUILLER UN MUTEX
#include <pthread.h>
Int pthread_mutex_lock (pthread_mutex_t * mut);
DEVERROUILLER UN MUTEX
#include <pthread.h>
Int pthread_mutex_unlock (pthread_mutex_t * mut);
DETRUIRE UN MUTEX
#include <pthread.h>
Int pthread_mutex_destroy (pthread_mutex_t * mut);
Les conditions
Lorsqu’un thread doit patienter jusqu’à ce qu’un évènement ne survienne dans un autre thread, on
parle de condition. Quand un thread est en attente d’une condition, il reste bloqué, tant que celle-ci
n’est pas réalisé par un autre thread.
On déclare la condition en variable globale comme suit : pthread_cond_t nomCondition =
PTHREAD_COND_INITIALIZER ;
Pour atteindre une condition : int pthread_cond_wait (pthread_cond_t * nomCondition,
pthread_mutex_t * nomMutex) ;
Pour réveiller un thread en attente d’une condition : int pthread_cond_signal (pthread_cond_t *
nomCondition) ;
Application : écrire un code qui crée deux threads : un qui incrémente une variable compteur par un
nombre tiré au hasard entre 0 et 10, et l’autre qui affiche un message lorsque la variable compteur
dépasse 20.
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>
pthread_cond_t condition = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void *threadAlarme(void *arg);
void *threadCompteur(void *arg);
int main(void) {
pthread_t monThreadCompteur;
pthread_t monThreadAlarme;
pthread_create(&monThreadCompteur, NULL, threadCompteur, NULL);
pthread_create(&monThreadAlarme, NULL, threadAlarme, NULL);
pthread_join(monThreadCompteur, NULL);
pthread_join(monThreadAlarme, NULL);
return 0;
void *threadCompteur(void *arg) {
int compteur = 0, nombre = 0;
srand(time(NULL));
while (1) {
nombre = rand() % 11; // Correction de la plage [0,10]
compteur += nombre;
printf("\n%d, compteur", compteur);
if (compteur >= 20) {
pthread_mutex_lock(&mutex);
pthread_cond_signal(&condition);
pthread_mutex_unlock(&mutex);
compteur = 0;
sleep(1);
pthread_exit(NULL);
void *threadAlarme(void *arg) {
while (1) {
pthread_mutex_lock(&mutex);
pthread_cond_wait(&condition, &mutex);
printf("\n LE COMPTEUR A DEPASSE 20\n");
pthread_mutex_unlock(&mutex);
pthread_exit(NULL);
LES TUBES
Un tube peut être présenté comme un tuyau dans lequel circule les informations. Les tubes servent à
faire communiquer plusieurs processus entre eux. On distingue alors les 2 processus pour leurs
actions :
Un qui écrit dans le tube, c’est l’entrée du tube.
L’autre qui lie dans le tube appelé sortie du tube
Pour créer un tube on utilise la fonction int pipe (int descripteur [2]) ;
Elle renvoie une valeur de type int qui est de 0 si elle réussie ou une autre valeur dans le cas
contraire
Elle prend en argument un tableau de int comprenant :
*descripteur de 0 qui désigne la sortie du tube
*descripteur de 1 qui désigne l’entrée du tube
On crée le tube dans un seul processus, l’autre ne peut donc en connaitre l’entrée ou la sortie. D’où
la nécessité d’en créer en utilisant pipe avant fork (), le père et le fils auront les mêmes valeurs dans
le tableau descripteur et pourront communiquer.
Pour écrire dans un tube : ssize_t (int entreeTube, const void * elementAEcrire, size_t
nombreOctetAEcrire) ;
La fonction prend en paramètre l’entrée du tube, un pointeur générique vers la mémoire contenant
l’élément à écrire, ainsi que le nombre d’octet de cet élément.
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#define TAILLE_MESSAGE 256
int main(void) {
pid_t pid_fils;
int descripteurTube[2];
char messageEcrire[TAILLE_MESSAGE];
pipe(descripteurTube); // Initialisation du tube (pipe)
pid_fils = fork(); // Création du processus fils
if (pid_fils != 0) { // Si c'est le processus père
sprintf(messageEcrire, "Bonjour, fils. C’est le père");
write(descripteurTube[1], messageEcrire, TAILLE_MESSAGE); // Écriture dans le tube
return EXIT_SUCCESS;
LIRE : pour lire dans un tube : ssize_t read (int sortieTube, void * elementAlive, size_t
nombreOcterAlive) ;
La fonction prend en paramètre la sortie du tube. Un pointeur vers la mémoire contenant l’élément à
lire et le nombre d’octet de cet élément.
Elle renvoie une valeur de type ssize_t qui correspond au nombre d’octet effectivement lu. On
pourra ainsi comparer le troisième paramètre (le troisième octet à lire) à la valeur renvoyée pour
être sûr qu’il n’y a pas d’erreur.
Exemple de lire :
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#define TAILLE_MESSAGE 256
int main(void) {
pid_t pid_fils;
int descripteurTube[2];
char messageLire[TAILLE_MESSAGE];
pipe(descripteurTube); // Initialisation du tube (pipe)
pid_fils = fork();
if (pid_fils == 0) {
read (descripteurTube[0], messageLire, TAILLE_MESSAGE); // Écriture dans le tube
printf (‘’Message reçu = \ ‘’ %S \ “”, messageLire);
return EXIT_SUCCESS;
Fermer: pour fermer une entrée ou une sortie, on utilise la fonction close() : int close (int
descripteur) ; la valeur en paramètre correspond au descripteur soit de l’entrée ou de la sortie à
fermer.
Exemple de code pour fermer l’entrée du processus fils et la sortie du processus père :
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void) {
pid_t pid_fils;
int descripteurTube[2];
pipe(descripteurTube); // Création du tube
pid_fils = fork(); // Création du processus fils
if (pid_fils == 0) { // Si c'est le processus fils
close(descripteurTube[1]); // Fermeture de l'extrémité d'écriture du tube
} else { // Si c'est le processus père
close(descripteurTube[0]); // Fermeture de l'extrémité de lecture du tube
return EXIT_SUCCESS;
Pratique : écrire un programme qui crée deux processus : le père qui écrit ‘Bonjour, fils. Je suis le
père.’ Et le fils qui récupère le message pour l’afficher.
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#define TAILLE_MESSAGE 256
int main(void) {
pid_t pid_fils;
int descripteurTube[2];
unsigned char messageLire [TAILLE_MESSAGE], messageEcrire[TAILLE_MESSAGE];
printf (‘’création du Tube.\n’’) ;
if (pipe(descripteurTube) != 0 ){
fprintf( stderr, ‘’Erreur de création du tube.\n’’) ;
Return EXIT_FAILLURE;
pid_fils = fork();
if (pid_fils == -1) {
fprintf (stderr, ‘’erreur de création de processus. \n’’);
return 1;
If (pid_fils ==0)
{printf (‘’fermeture de l’entrée dans le fils. \n\n’’) ;
Close (descripteurTube [1];
Read(descripteurTube[0], messageLire, TAILLE_MESSAGE) ;
return EXIT_SUCCESS;