Principes
|
figure 3.8 : Principe d'un pipe |
Un pipe fonctionne à sens unique, d'où son nom (tuyau). L'information disparaît après lecture. Il existe deux catégories de pipes :
- les pipes non nommés utilisables entre processus parents qui partagent le même environnement. Comme ces processus partagent le même envirtonnement ils peuvent se transmettre les caractéristiques des pipes sans difficulté.
- les pipes nommés employés pour les communications entre processus sans liens de parenté. Ils s'apparentent à des fichiers en mémoire.
Le fonctionnement est sensiblement le même. Ce sont des fichiers d'un type spécial à usage interne pour la communication entre processus. Le processus Q, lorsqu'il se met en attente de lecture d'une information que P doit lui transmettre à travers le pipe, se synchronise sur ce dernier. Il reçoit de plus un message qui peut être utilisé pour diriger ses actions.
Pipes non nommés
Pour comprendre leur fonctionnement, en voici un exemple réduit à sa plus simple expression. On crée un pipe par la fonction pipe() puis un processus fils par fork(). Ce fils hérite de tout l'environnement du père donc du pipe et de ses caractéristiques. close(p[0]) ferme le pipe en lecture qui ne peut donc plus servir qu'en écriture. Celle-ci se fait par la fonction write() qui comprend trois arguments :
- la structure d'écriture soit l'entrée p[1] du pipe
- la cahîne à transmettre
- le nombre de caractères de la chaine.
Notons que son usage est rigoureusement identique à son emploi lorsqu'on écrit dans un fichier.
Un deuxième fils est créé puis le pipe est fermé pour l'écriture puis employé pour lire les caractères envoyés par le premier fils de façon symétrique.
/* communication entre deux processus fils par pipe non nommé */
#include <stdio.h>
#include <sys/signal.h>
main()
{
int fils1, fils2, m, n, p[2], count;
char buf[6];
/* création du pipe */
pipe(p);
/* création premier fils écrivain */
if ((fils1=fork())== 0){
close (p[0]);
fprintf(stdout,"fils 1 ( %d ) : écrit dans le pipe \n",getpid());
count = write(p[1],"salut",5);
fprintf(stdout," %d caractères dans le buffer\n", count);
/* fermeture du pipe */
close(p[1]);
fprintf(stdout,"fils 1 : fin d'écriture dans le pipe\n");
exit(2);
}
/* création du deuxième fils lecteur */
else if ((fils2=fork())==0){
fprintf(stdout,"fils 2 ( %d ) : lit dans le pipe\n",getpid());
close (p[1]);
count = read(p[0],buf,5);
buf[5]='\0';
fprintf(stdout,"fils 2 : lu %d caractères : %s\n",count,buf);
exit(3);
}
else{
fprintf(stdout,"processus père %d\n",getpid());
close(p[0]);
close(p[1]);
fprintf(stdout, "wait fils : %d\n",wait(&m));
fprintf(stdout, "wait fils : %d\n",wait(&n));
fprintf(stdout, "fin du père\n");
exit(0);
}
}
On notera la similarité avec l'emploi d'écriture et de lecture par fichier pour faire communiquer deux programmes.
Pipes nommés
Les pipes nommés fonctionnent sur un principe sensiblement équivalent. Les différences essentielles sont les suivantes :
- les programmes de lecture et d'écriture dans le pipe sont séparés puisqu'ils correspondent au fonctionnement de deux processus indépendants.
- Le pipe est créé indépendemment des deux processus et avant leur emploi. Pour cela on emploie la commande :
mknod nom_du_pipe pi
qui est analogue à une création de fichier.
Le pipe est ensuite employé de façon très similaire à un fichier avec ouverture par open et close après usage. Nous donnons ci-dessous des exemples de codes simples en écriture et en lecture.
Ecriture
/* Ecriture non bloquante dans pipe nomme */
#include <stdio.h>
#include <sys/signal.h>
#include <fcntl.h>
main()
{
int d, i, n;
char buf[2000];
/* ouverture du pipe
Il a été créé par mknod mon_pipe pi
Ouverture :
O_RDWR lecture/écriture
O_NDELAY non bloquant
2 signifie droit d'écriture (convention Unix)
*/
d = open("mon_pipe",O_RDWR|O_NDELAY,2);
if (d < 0){
fprintf(stderr,"problème d'ouverture du pipe\n");
exit(1);
}
/* remplissage du pipe */
for (i=0; i<2000; i++){
buf[i]='n';
}
if ((n = write(d,buf,2000))>0)
fprintf(stdout,"Ecriture de %d caractères\n",n);
sleep(20);
exit(0);
}
Il y a peu d'explications à donner. L'usage est très similaire à celui d'un fichier.
Lecture
/* lecture bloquante dans pipe nomme mon_pipe */
#include <stdio.h>
#include <sys/signal.h>
#include <fcntl.h>
main()
{
int d,i,n;
char buf[2000];
/* open du pipe mon_pipe lecture bloquante
O_RDONLY lecture seulement 4 droit de lecture (convention Unix)
*/
d = open("mon_pipe",O_RDONLY,4);
if (d < 0){
fprintf(stderr,"Erreur ouverture du pipe\n");
exit(1);
}
fprintf(stdout,"pipe ouvert\n");
n = read(d,buf, 2000);
fprintf(stdout,"Lecture de %d caractères\n",n);
fprintf(stdout,"Les 20 premiers sont\n");
for (i=0;i<20;i++){
fprintf(stdout,"%c",buf[i]);
}
fprintf(stdout,"\n");
exit(0);
}
Le pipe est ouvert en lecture bloquante. Ceci signifie que le processus attendra l'arrivée de 2000 caractères avant de passer à l'instruction qui suit read.
Les pipes dans les shells de commande et sous Windows
|
Fig. 3.9 Fonctionnement d'un pipe sous Unix |
L'emploi des pipes permet de réaliser des filtres d'une extrème puissance dans les shells.
Ils permettent de connecter la sortie d'une commande (stdout), affichée normalement sur l'écran, à l'entrée d'une suivante (stdin) qui est usuellement affectée au clavier (fig. 3.9).
Par exemple, imaginons que l'on veuille afficher la liste des fichiers contenus dans un répertoire par la commande ls. Si leur nombre est trop grand on ne pourra pas voir l'ensemble dans la fenêtre sur l'écran. En enchaînant la commande avec "more" l'affichage se trouvera bloqué par cette seconde commande qui permettra d'examiner à loisir la réponse avant de passer à la suite. Le pipe est symbolisé par le caractère | et la commande s'affiche comme suit :
ls -l | more
Les commandes ne se limitent pas à celles reconnues par les shells. Ce peuvent être les programmes des utilisateurs. C'est une façon fort commode d'enchaîner des programmes, la sortie à l'écran d'un premier étant reprise comme si elle était fournie par le clavier au second. Il n'y a pas de limite théorique au nombre de programmes ou de commandes que l'on peut ainsi enchaîner.
Windows permet également de créer des pipes, dans l'interface ligne à ligne.
Copyright Yves Epelboin, université P.M. Curie, 2000, MAJ
30 janvier, 2006