Communication Processus
Communication Processus
Communication Processus
Les tubes
Les tubes sont un mécanisme de communication entre processus résidant sur une même machi
ne. On peut en distinguer 2 catégories :
o les tubes anonymes (volatiles) : Ils concernent des processus issus
de la même application
o les tubes nommés (persistants) : Ils concernent des processus
totalement indépendants
1
Lorsqu'elle réussit, cette fonction crée un nouveau tube au sein du noyau et remplit le tableau
passé en argument avec les descripteurs des deux extrémités Les descripteurs correspondent
respectivement à la sortie et à l'entrée du tube.
Le tube est entièrement sous le contrôle du noyau. Il réside en mémoire (et pas sur le disque),
et le processus reçoit les deux descripteurs correspondant à l'entrée et à la sortie du tube. Le
descripteur d'indice 0 est la sortie du tube, il est ouvert en lecture seule. Le descripteur 1
est l'entrée ouverte en écriture seule.
Nous observons en effet que les tubes sont des systèmes de communication unidirectionnels. Si
on désire obtenir une communication complète entre deux processus, il faut créer deux tubes et
les employer dans des sens opposés.
exemple_pipe_1.c
#include <stdio.h>
#include <unistd.h>
int
main (void)
{
int tube [2];
unsigned char buffer [256];
int i;
fprintf (stdout, "Création tube \n");
if (pipe (tube) != 0) {
perror ("pipe");
exit (1);
}
for (i = 0; 1 < 256; i++)
buffer [i] = i;
fprintf (stdout, "Écriture dans tube \n");
if (write (tube [1], buffer, 256) != 256) {
perror ("write");
exit (1);
}
fprintf (stdout, "Lecture depuis tube \n");
if (read (tube [0], buffer, 256) != 256) {
perror ("read");
exit (1);
}
fprintf (stdout, "Vérification...");
for (i = 0; i < 256; i ++)
if (buffer [i] != i) 1
fprintf (stdout, "Erreur : i=%d buffer [i]=%d \n",
i, buffer [i]);
exit (1);
}
fprintf (stdout, "0k \n");
return (0);
}
Utiliser un tube pour transférer des données au sein du même processus ne présente aucun
intérêt. Aussi nous allons utiliser ce mécanisme pour faire communiquer deux processus (ou
plus). Pour cela, nous devons invoquer l'appel-système fork( ) après avoir créé le tube. Si
2
celui-ci doit aller du processus père vers le fils, le père ferme son descripteur de sortie de tube,
et le fils son descripteur d'entrée
un processus écrivain
o un processus lecteur
Mécanisme de partage
un tube est créé par un processus père
o le processus père crée un processus fils
o le père et le fils partagent descripteurs d’accès au tube
o chacun va utiliser un « bout» du tube
o chacun détruit le descripteur inutile
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int
main (void)
{
int tube [2];
unsigned char buffer [256];
int i;
fprintf (stdout. "Création tube \n");
if (pipe (tube) != 0) {
perror ("pipe");
exit (1);
}
switch (fork( )) {
case -1 :
perror ("fork( )");
exit (1);
case 0 :
fprintf (stdout, "Fils : Fermeture entrée \n");
close (tube [1]);
fprintf (stdout, "Fils : Lecture tube \n");
3
if (read (tube [0], buffer, 256) != 256) {
perror ("read");
exit (1);
}
fprintf (stdout, "Fils : Vérification \n");
for (i = 0; i < 256; i++)
if (buffer [i] != i) {
fprintf (stdout, "Fils : Erreur \n");
exit (1);
}
fprintf (stdout, "Fils : Ok \n");
break;
default :
fprintf (stdout, "Père : Fermeture sortie \n");
close (tube [0]);
for (i = 0; i < 256; i++)
buffer [i] = i;
fprintf (stdout, "Père : Écriture dans tube \n");
if (write (tube [1], buffer, 256) != 256) {
perror ("write");
exit (1);
}
wait (NULL);
break;
}
return (0);
}
L'exécution confirme bien le fonctionnement du tube allant du processus père vers son fils.
$ ./exemple pipe 2
Création tube
Père : Fermeture sortie
Fils Fermeture entrée
Fils : Lecture tube
Père : Écriture dans tube
Fils : Vérification
Fils : Ok
NB
: ce qui ce passe après le fork() dépends du sens de
la communication entre le processus père et le processus fils.
Si les données doivent être envoyées du père vers le fils, le
père ferme son descripteur d’entré (lecture, fd[0]) et le fils
son descripteur de sortie (écriture, fd[1]) . Et inversement.
Les tubes obtenus par l'appel-système pipe( ) représentent donc un moyen de communication
simple mais très efficace entre des processus différents. Mais il faut nécessairement que les
interlocuteurs aient un ancêtre commun, le processus qui a créé le tube. Il n'est pas possible de
lancer des programmes indépendants — par exemple un serveur et des clients — et qu'ils
établissent un dialogue.
4
Lecture dans un tube
Primitive read()
ssize_t read(int fd, void *buf, size_t nbyte);
Demande de lecture d’au plus nbyte caractères.
- Si le tube n’est pas vide et contient tbyte caractères alors il est extrait
min(tbyte,nbyte) caractères qui sont écrit à l’adresse buf
Retour : min(tbyte,nbyte)
- Si le tube est vide si le nombre d’écrivains est nul il n’y aura jamais plus rien à lire
c’est la « fin de fichier ». retourne 0
- si le nombre d’écrivains est non nul processus bloqué jusqu’à écriture par un
écrivain
Réveil d’un processus bloqué en lecture sur un tube vide
- si un écrivain réalise une écriture : read() retourne une valeur non nulle
- si le dernier écrivain ferme son descripteur d’accès au tube
o explicitement par un close()
o automatiquement à sa terminaison
read() retourne 0
Bonne pratique
- Systématiquement fermer, au plus tôt, tous les descripteurs non utilisés
- garder un descripteur en écriture sur un tube conduit potentiellement a bloquer un
processus
5
Fermeture d’un tube
Primitive close()
int close(int fd);
Fermer tous les descripteurs non utilisés évite les situations de blocage
Fermeture d’un tube libère le descripteur
6
La suppression d'un tube nommé se fait avec l'appel-système unlink( ).
Dans le programme suivant nous allons utiliser plusieurs tubes nommés pour faire dialoguer
un processus serveur avec plusieurs clients. Le serveur crée un nœud dans le système de
fichier et y lit les requêtes des clients. Ce tube dispose d'un nom connu par tous les processus.
Pour répondre à la requête d'un client, le serveur doit pouvoir écrire dans un autre tube
nommé, spécifique au client. Les requêtes doivent avoir une taille inférieure à PIPE_BUF, afin
d'être sûr qu'elles ne seront pas mélangées dans le tube d'interrogation. Le serveur va
simplement renvoyer un anagramme de la chaîne de caractères transmise dans la requête du
client. Le principe retenu pour faire fonctionner l'ensemble est le suivant :
o Le serveur crée un nœud dans le système de fichiers, nommé
«anagramme.fifo». Il ouvre ensuite ce tube en lecture et en écriture, puis le
rouvre sous forme de flux.
o Un client essaye d'ouvrir en écriture seule le tube du serveur. S'il n'existe pas,
le processus client se termine. Sinon, le client crée un tube personnel, nommé
«anagramme.<pid>», afin d'être unique dans notre application.
o Le client envoie au serveur une requête constituée du nom du tube pour la
réponse, suivi d'un retour chariot et de la chaîne de caractères dont on désire un
anagramme, suivie d'un retour chariot. Le serveur peut alors lire grâce à
fgets( ) ces éléments et répondre dans le tube du client.
o Le client ouvre son tube en lecture seule, lit la réponse, l'affiche et se termine,
après avoir supprimé avec unlink( ) son tube de réponse.
o Si la chaîne de caractères reçue vaut « FIN», le serveur se termine également
en supprimant le tube d'interrogation.
Nous avons ouvert, dans le serveur, le tube d'interrogation en lecture et écriture. Ceci nous
évite de rester bloqués durant l'ouverture en attendant qu'un autre processus soit prêt à écrire,
mais on y trouve aussi un second avantage. Si nous ouvrons le tube en lecture seule, à chaque
fois que le client se termine, la lecture dans le serveur avec fgets() échoue car le noyau
envoie un FOF. En demandant un tube en lecture et écriture, nous évitons cette situation, car il
reste toujours au moins un descripteur ouvert sur la sortie.
exemple_serveur.c
7
if ((fd = open (nom_fifo, O_WRONLY)) >= 0) {
reponse = fdopen (fd, "w");
anagramme = strdup (chaine);
strfry (anagramme);
fprintf (reponse, "%s\n", anagramme);
fclose (reponse);
free (anagramme);
}
if ((strcasecmp (chaine, "FIN") == 0)
||(strcasecmp (nom_fifo, "FIN") == 0))
return (1);
return (0);
}
int
main (void)
{
FILE * fichier;
int fd;
char nom_fifo [128];
char chaine [128];
if (mkfifo (nom_noeud, 0644) != 0) {
fprintf (stderr, "Impossible de créer le noeud Fifo \n");
exit (1);
}
fd = open (nom_noeud, O_RDWR);
fichier = fdopen (fd, "r");
while (1) {
fgets (nom_fifo, 128, fichier);
if (nom_fifo [strlen (non_fifo) - 1] == '\n')
nom_fifo [strlen (nom_fifo) - 1] = '\0';
fgets (chaine, 128, fichier);
if (chaine [strlen (chaine) - 1] == '\n')
chaine [strlen (chaine) - 1] = '\0';
if (repondre (nom_fifo, chaine) != 0)
break;
}
unlink (nom_noeud):
return (0);
}
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
int
main (void)
{
FILE * question;
FILE * reponse;
int fd;
char nom_fifo [128];
8
char chaine [128];
fprintf (stdout, "Chaîne à traiter : ");
if (fgets (chaine, 128, stdin) NULL)
exit (0);
sprintf (nom_fifo, "anagramme.%u", getpid( ));
if (mkfifo (nom_fifo, 0644) != 0) {
fprintf (stderr, "Impossible de créer le noeud Fifo \n");
exit (1);
}
if ((fd = open ("anagramme.fifo", O_WRONLY)) < 0) {
fprintf (stderr, "Impossible d'ouvrir la Fifo \n");
exit (1);
}
question = fdopen (fd, "w");
fprintf (question, "%s\n%s", nomfifo, chaine):
fclose (question);
fd = open (nom_fifo, O_RDONLY);
reponse = fdopen (fd, "r");
if (fgets (chaine, 128, reponse) != NULL)
fprintf (stdout, "Réponse = %s\n", chaine);
else
perror ("fgets");
fclose (reponse);
unlink (nom_fifo);
return (0);
}