La Communication Entre Processus
Les Tubes

©Najib TOUNSI
Novembre 2000


Sommaire

1. La Communication Entre Processus 2. Les Tubes Nommés (fifo)

1. La Communication Entre Processus

Les Signaux constituent un moyen de communication entre processus. Mais ils ne permettent pas d'échanger des données. Pour réaliser de telles échanges, il faut utiliser des fichiers. Deux processus UNIX peuvent communiquer par un fichier spécial appelé tube en terminologie UNIX (pipe). Un processus y met des données et un autre les prend. C'est bien un fichier (il correspond à un i_node), mais d'un genre spécial, car il n'existe dans aucun répertoire; <<invisible>>, il appartient au système qui le crée et le détruit après usage. Comme c'est un moyen de communication, il lui correspond en plus un mécanisme de synchronisation.

1.1. Caractéristiques des Tubes

Un tube est un moyen de transmission de données d'un processus à un autre (Fig-IV-1). Comme il est implanté par fichier, il sera désigné par des descripteurs et manipulé par les primitives read() et write(). Mais avec les particularités suivantes:
Fig-1 Principe de Fonctionnement d'un Tube en Mode FIFO. Un Lecteur et un Ecrivain

 Les processus qui lisent/écrivent dans un tube sont en général des processus concurrents et doivent être liés dans une même généalogie (e.g. Père/Fils). En effet, un tube, ou du moins ses descripteurs, fait partie des caractéristiques hérités par un processsus après un fork(). Pour que deux processus puissent donc communiquer des données par un tube, il faut qu'ils disposent tous les deux du tube, et pour cela descendre d'un même père (ou ancêtre commun) qui crée le tube. Ce dernier pouvant être lui-même l'un des processus communiquants. (Une coopération semblable mais entre processus quelconques est réalisable par un mécanisme de tubes nommés, qui sont des fichiers disques "réels". Voir plus bas [[section]]2.)
 

Fig-2 Un lecteur et deux »crivainsP1ensuiteP2

 On utilise un tube à l'aide de deux descripteurs entiers: un en écriture pour l'écrivain, et un en lecture pour le lecteur. La création de tels descripteurs (et donc du tube) se fait à l'aide de la primitive pipe(). La lecture et l'écriture se font avec les primitives read() et write().

1.2. Utilisation d'un Tube

1.2.1. Création d'un Tube: Primitive pipe()

La primitive
int pipe (int p[2]);
crée un tube et lui associe deux descripteurs rendus dans le tableau de deux entiers p. Par définition p[0] est le descripteur pour lire (sortie du tube) et p[1] celui pour écrire (entrée du tube). La valeur retour de pipe() est 0 en cas de succès, -1 sinon (trop de descripteurs actifs, ou de fichiers ouverts, etc...)

Exemple:

int p[2];

if ( pipe(p) == -1 )
fprintf(stderr, "Impossible ouvrir tube\n");
...

Comme déjà dit, la création d'un tube doit se faire avant le lancement des processus qui vont l'utiliser. Ce sera le cas par exemple dans une coopération père/fils ou fils/fils..., quand le processus père crée le tube avant de faire fork().

Remarque: Il n'y a pas la notion d'ouverture d'un tube (open()). Après sa création, un tube est directement utilisable. Par contre, la fermeture close() s'applique à un tube.

1.2.2. Lecture dans un Tube: Primitive read()

On peut lire dans un tube à l'aide de la primitive classique read(). On utilise le descripteur p[0] rendu par pipe(). Dans l'exemple qui suit
char buf[100];
int p[2];
...
read(p[0], buf, 20);
on a une demande de lecture de 20 caractères dans le tube p. Les caractères lus sont rendus disponibles dans la zone buf. Leur nombre est la valeur retour de read().

 Une précaution voudrait qu'un processus ferme systématiquement les descripteurs dont il n'a pas besoin: ici on a besoin de lire dans le tube p. On doit fermer le descripteur p[1].

close(p[1]);
read(p[0], buf, 20);
Cela permet d'éviter des erreurs aboutissant parfois à des situations d'interblocage (deadlock): des processus communiquent, mais chacun attend que l'autre commence.

1.2.3. Ecriture dans un Tube: Primitive write()

On peut écrire dans un tube avec la primitive classique write(). L'écriture est faite en utilisant le descripteur p[1] cette fois-ci. La séquence qui suit:
char buf[100];
int p[2];
...
close p[0];
buf = "texte a ecrire ";
write(p[1], buf, 20);
est une demande d'écriture de 20 caractères dans le tube de descripteur ouvert p[1]. La séquence à écrire est prise dans la zone buf. La valeur retour de write() est le nombre d'octets ainsi écrits. Là aussi, on a fermé le descripteur p[0] de lecture. L'écriture dans un tube est atomique (tout est écrit ou rien n'est écrit) et n'interfère pas avec d'autres écrivains éventuels. Les 20 caractères écrits ici seront consécutifs dans le tube. En d'autres termes, sur la figure VI-2 ci-dessus, chaque séquence "tic" ou "tac" est écrite en bloc. Il n'y aura pas d'enchevêtrement comme "ti" puis "tac" et ensuite "c".

Remarque: La taille d'un tube est bien sûr limitée. Elle a pour valeur 4096 en général (constante PIPE_BUF de <limits.h>). S'il arrive que le nombre de caractères à écrire soit supérieur à cette limite, le message peut être écrit mais décomposé par le système en plusieurs lots. L'atomicité serait alors perdue.

1.2.4. Comportement des Opérations read()/write()

Ces primitives ont un comportement particulier dû à la relation producteur / consommateur qui lie les processus écrivains et lecteurs. Quand on fait
read(p[0], buf, 20);
on a:

 1) Si le tube désigné contient 20 caractères ou plus, alors 20 caractères seront lus et mis dans la zone désignée par la variable buf. S'il contient moins de 20 caractères, ils seront lus de la même façon. En tout cas, la valeur retour de read() est le nombre de caractères effectivement lus; 20 ou moins.

 2) Si le tube ne contient aucun caractères (tube vide), alors deux cas peuvent se produire:

 Il apparaît donc qu'une lecture dans un tube risque de faire attendre un processus jusqu'à ce qu'il y ait écriture dans ce tube, ou que tout écrivain disparaisse (fermeture des descripteurs en écritures). D'où, encore, la précaution, pour un lecteur de fermer tous les descripteurs en écriture.

Remarque: avec la primitive fcntl(), la lecture peut être rendue non-bloquante.

fcntl(p[0], F_SETFL, O_NONBLOCK);
Dans ce cas read() dans un tube vide (avec ecrivains potentiels) ne bloque pas et renvoie -1. Ce n'est pas considéré comme fin de fichier, mais erreur dans ce cas.

 Quand à la primitive

write(p[1], buf, 20);
elle se comporte comme suit:

 1) Dans le cas où il n'y a aucun lecteur sur le tube (tous les descripteurs en lecture sont fermés), c'est une erreur fatale: le processus écrivain se termine par le signal SIGPIPE. C'est normal car l'écriture est inutile s'il n'y a pas de lecteurs. C'est le cas par exemple du message "broken pipe" sous shell.

 2) Dans le cas où il y a encore des lecteurs sur ce tube alors,

 L'écriture dans un tube est donc plus délicate, car elle est abortive dans le cas aucun lecteur, et il faut veiller à ne pas dépasser la capacité du tube.

 Mais, avec les tubes, l'erreur à ne pas commettre est une situation d'interblocage illustrée, par exemple, par deux processus qui communiquent dans les deux sens à travers deux tubes p et q
 

PROCESSUS_1
read(p[0], ch, n);      
... 
write(q[1], ch, n);
PROCESSUS_2
read(q[0], ch, n);
 ...
 write(p[1], ch, n);

Comme les tubes sont vides au départ, les deux lectures vont bloquer, car l'écriture ne vient qu'après. Cela peut être dû plus à une erreur de programmation qu'à une erreur d'analyse (il existe des méthodes formelles de description de processus coopérants pour synchroniser les échanges).

1.3. Exemples

Voici à présent trois exemples illustrant le mécanisme de tubes. Le premier est une communication père/fils, le second et le troisième présentent une communication entre trois fils: deux écrivains et un lecteur ensuite un écrivain et deux lecteurs. En tout cas, le tube est créé par le processus père.

Exemple 1:

 Le père envoi au fils un message dans un tube. Message envoyé en bloc et lu caractère par caractère puis imprimé.

#include <stdio.h>
char message[25] = "Cela provient d'un tube";
main()
{
    /*
       * communication PERE --> FILS par pipe
     */
    int p[2];
    int pipe(int[2]);
    if (pipe(p) == -1) {
        fprintf(stderr, "erreur ouverture pipe\n");
        exit(1);
    }
    if (fork() == 0) {          /* fils */
        char c;
        close(p[1]);
        while (read(p[0], &c, 1) != 0)
            printf("%c", c);
        close(p[0]);
        exit(0);
    } else {                    /* suite pere */
        close(p[0]);
        write(p[1], message, 24);
        close(p[1]);
        exit(0);
    }
}
Remarquer l'arrêt de read() sur valeur retour nulle (réalisé par close(p[1]); dans le processus père). Exécution
tounsi@shems 53>cc pipe1.c
tounsi@shems 54>a.out
Cela provient d'un tube
Exercice: Faire un programme où ce sont deux fils qui communiquent, l'un écrivain et l'autre lecteur.

 Voici maintenant le cas où un lecteur tente de lire 30 caractères dans un tube qui n'en contient que 24. Dans le même exemple, on a remplacé

while ( read(p[0], &c, 1) != 0)
printf("%c", c);
par
nb_lu = read(p[0], buf, 30); /* 30 > 24 */
printf("%d %s\n", nb_lu, buf);
pour imprimer le message lu dans le tube précédé de sa taille.
tounsi@emi 58>cc pipe11.c
tounsi@emi 59>a.out
24 Cela provient d'un tube
Le lecteur n'a lu que ce qui est disponible. 24 au lieu de 30 caractères.

Exemple 2:

 Considérons maintenant deux écrivains et un lecteur sur le même tube. Deux fils écrivent respectivement les séquences "ABC...Z" et "abc...z" par blocs de trois caractères, et un troisième fils lit dans le tube par blocs de 4 caractères.

#include <stdio.h>
 /* Cummunication par pipe
  * FILS1 et FILS2 Ecrivains
  * FILS3 lecteur, meme pipe
  */
void f1(), f2(), f3();          /* les 3 fils */
void (*tab_fonct[3]) () = {
    f1, f2, f3
};
int p[2];
char seq1[27] = "ABCDEFGHIJKILMOPQRSTUVWXYZ";
       /* ecrite par FILS1 */
char seq2[27] = "abcdefghijklmnopqrstuvwxyz";
       /* ecrite par FILS2 */
main()
{
    int i;
    int pipe();
    if (pipe(p) == -1) {
        fprintf(stderr, "erreur ouverture pipe\n");
        exit(1);
    }
    for (i = 0; i < 3; i++)
        switch (fork()) {
        case 0:
            (*tab_fonct[i]) ();
            exit(0);
        default:;
        }
    exit(0);
}

void f1()
{
    int i;
    close(p[0]);
    for (i = 0; i < 26; i += 3) {
        write(p[1], &seq1[i], 3);
        sleep(3);
    }
    close(p[1]);
}


void f2()
{
    int i;
    close(p[0]);
    for (i = 0; i < 26; i += 3) {
        write(p[1], &seq2[i], 3);
        sleep(4);
    }
    close(p[1]);
}

void f3()
{
    char s[5];
    int nb_lu;
    close(p[1]);
    while ((nb_lu = read(p[0], s, 4)) > 0)
        printf("%s", s);
    close(p[0]);
    printf("\n");
}
Les appels sleep() entre chaque écriture des écrivains, sont là pour simuler d'autres traitements des processus.

 Exécution:

tounsi@shems 66>cc pipe3.c
tounsi@shems 67>a.out
ABCabcDEFdefGHIghiJKILMOjklPQRmnoSTUpqrVWXYZstuvwxyz
Points à noter: Exercice: Réaliser un tel protocole. Le lecteur tachant d'imprimer ce qu'il reçoit sur sa sortie standard ou sa sortie erreur selon l'écrivain envoyeur.

Indication: On passera les numéros pid en paramètres pour réaliser les tests chez le lecteur. On utilisera une chaîne de longueur 10, comme "12345 DEF" contenant le numéro du pid sur 6 cases, un blanc est les 3 caractères du message à transmettre. Les processus lisent et écrivent donc par chaînes de 10 caractères. On codera la chaîne par sprintf() et on la décodera par sscanf().

Exemple 3:

 Cette fois-ci, il y un fils écrivain et deux fils lecteurs. Ces derniers lisent selon leur rythme de progression et affichent ce qu'ils lisent. Soit :

char seq[27]="ABCDEFGHIJKLMNOPQRSTUVWXYZ";
Le processus écrivain écrit la séquence d'un seul trait
write(p[1], seq, 26);
et les processus lecteurs lisent l'un par 2 octets et l'autre par 3:
* PROCESSUS 1 */
while ((nb_lu = read(p[0], s, 2)) > 0) {
      fprintf(stderr, "%s ", s);
      sleep(1); /* autre traitement */
}

/* PROCESSUS 2 */
while ((nb_lu = read(p[0], s, 3)) > 0) {
      fprintf(stdout, "%s ", s);
      sleep(1);
}

Pour distinguer entre les résultats des deux processus, nous les avons écrits sur la sortie standard pour l'un et la sortie erreur pour l'autre. Exécution:
tounsi@shems 86>cc pipe4.c
tounsi@shems 87>(a.out >res)>&err
tounsi@shems 88>cat res
EFG HIJ MNO RST YZT
tounsi@shems 89>cat err
AB CD KL PQ UV WX
où on voit que les deux lecteurs passent dans un ordre quelconque, l'un lisant par 2 et l'autre par 3.
AB CD KL PQ UV WX
EFG HIJ MNO RST YZT
(le T de YZT est un reliquat du message RST précédent, la dernière lecture étant YZ)

1.4. Autres Opérations

1.4.1. Redirection de Fichiers Vers un Tube

Il serait intéressant de pouvoir rediriger des fichiers vers un tube. Un processus qui lit ou écrit sur un flot peut substituer un tube à ce flot. Le cas des fichiers standards d'entrées/sorties est à ce propos caractéristique. Un tube shell (|) n'est qu'un redirection du fichier standard d'entrée (resp. de sortie) vers la sortie (resp. l'entrée) d'un tube. On va illustrer cela sur un exemple qui fera comme la commande shell "ls -l | wc -l".

 On a deux processus, le père redirige sa sortie standard (stdout) sur le tube et fait execl ("ls -l" ...) et le fils redirige son entrée standard (stdin) vers le tube et fait execl("wc -l"...). Pour cela, on va utiliser la primitive

int dup(int desc);
qui duplique son descripteur. Rappelons qu'un appel dup (d); rend un nouveau descripteur associé au même fichier que d (i.e. on accède au même fichier avec les deux descripteurs). Le nouveau descripteur rendu est la plus petite valeur de descripteur de fichier disponible chez le processus appelant. Il suffirait par exemple, avant un appel
dup(p[0]);
de fermer stdin par
close(STDIN_FILENO);
pour que STDIN_FILENO, qui est égale à 0, soit associé au même fichier que p[0] et ainsi toute lecture standard se fera dans le tube p.

Voici donc le programme complet pour réaliser "ls -l | wc -l":

#include <unistd.h>
#include <stdio.h>
main()
{
/* ls -l | wc -l */
    int p[2];
    int pipe(int[2]);
    pipe(p);
    if (fork() == 0) {
        close(STDIN_FILENO);    /* fermer stdin */
        dup(p[0]);              /* stdin devient entrée tube */
        close(p[1]);
        close(p[0]);
        execl("/usr/ucb/wc", "wc", "-l", NULL);
    } else {
        close(STDOUT_FILENO);   /* fermer stdout */
        dup(p[1]);              /* stdout devient sortie tube */
        close(p[1]);
        close(p[0]);
        execl("/bin/ls", "ls", "-l", NULL);
    }
}
Exécution:
tounsi@shems 97>cc pipeShell.c
tounsi@shems 98>a.out
7
tounsi@shems 99>ls -l | wc -l
7
On obtient le même résultat avec ce programme qu'avec la commande shell correspondante.

1.4.2. Fonctions de Haut Niveau

Parfois, on aimerait bien manipuler un tube avec les fonctions bibliothèques de haut niveau: fread(), fgetc(), fwrite(), fprinf(), etc. Pour cela, il faudrait que le tube soit désigné par un descripteur de type FILE* correspondant à un flot (stream). Cela est possible avec l'appel à la fonction:
#include <stdio.h>
FILE* fdopen(int desc, char* type_ouv);
qui convertit un descripteur entier en un descripteur flot, résultat de l'appel. type_ouv est le type d'ouverture, lecture ou écriture, désiré.

Dans l'exemple 1 ci-dessus le processus fils peut lire dans le tube avec le code suivant:

FILE *f;
f = fdopen(p[0], "r");
while((c=fgetc(f)) != EOF)
      putchar(c);
La lecture se fera dans le tube considéré comme un flot FILE* f. Ici, on a utilisé la fonction fgetc() pour lire caractère par caractère avec le test sur EOF. Les caractéristiques de fonctionnement du tube, en particulier la condition fin de fichier (plus d'écrivains) sont bien sûr les mêmes.

Il faut néanmoins faire attention à l'écriture dans un tube avec les fonctions de <stdio.h>. Ce qui y est écrit n'est pas rendu immédiatement disponible comme avec la primitive write(), mais transféré dans le tampon associé à f. Il faut que le tube soit plein pour que le système le transfert, sinon faire fflush(f).

Exercice: Dans le même exemple 1, faire en sorte que le processus écrivain utilse fprintf(f, "%s", message); pour envoyer son message dans le tube désigné cette fois-ci par le flot f.

1.4.3. Communication Avec le Shell

Il est parfois tout aussi intéressant pour un processus de communiquer avec le shell. La communication se fera par un tube selon un schéma qu'on peut illustrer par
processus | commande shell
 le processus envoie des donnée sur l'entrée standard d'une commande, ou bien par
commande shell | processus
le processus lit des données provenant de la sortie standard de la commande. Pour se faire, le processus crée un tube tout en lancant la commande. La fonction pour cela est popen().
#include <stdio.h>
FILE* popen( char* commande, char* type_ouv);
qui crée un tube entre le processus appelant et la commande commande à exécuter. La valeur retourne est un descripteur (de type flot) vers ce tube, ouvert selon le type d'ouverture type_ouv. Un tube ouvert par popen() doit être fermée par pclose().

 L'exemple qui suit lance la commande hostname et récupère son résultat (affiché ensuite).

/* Execution de la commande hostname et
   * recuperation du resultat */
main()
{
    FILE *f;
    char s[20];
    f = popen("hostname\0", "r");       /* Ouverture lecture */
    fscanf(f, "%s", s);
    printf("Ce processus s'execute sur %s\n", s);
    pclose(f);
}
Exécution:
tounsi@shems 97>cc popen.c
tounsi@shems 98>a.out
Ce processus s'execute sur shems

tounsi@shems 99>rlogin yasmina
tounsi@yasmina 1>cc popen.c
tounsi@yasmina 2>a.out
Ce processus s'execute sur yasmina

Dans l'exemple suivant, c'est le schéma inverse. Le processus envoie un texte à la commande wc, qui en compte les lignes, mots et caractères.
/* envoie de texte à la commande wc */
char s[30] = "5 mots et 23 caracteres";
main()
{
    FILE *f;
    f = popen("wc \0", "w");    /* Ouverture ecriture */
    fprintf(f, "%s", s);
    pclose(f);
}
Exécution:
tounsi@shems 45>cc popen1.c
tounsi@shems 46>a.out
0 5 23
Noter que la sortie standard de la commande wc lancée est bien l'ecran traditionnel.

2. Les Tubes Nommés (fifo)

Ce sont des tubes qui ont l'avantage de connecter des processus quelconques, sans lien de parenté particulier. C'est très utile quand on imagine un processus serveur permanent qui offre des services à tout processus demandeur. D'autant plus, que ces tubes subsistent aux processus qui les créent. Ces tubes sont appelés fifo dans la terminologie SYSTEM V et POSIX. Ils fonctionnent comme les autres tubes pour ce qui concerne la lecture/écriture, sauf qu'ils existent rééllement dans le système de fichier UNIX, et apparaissent comme les fichiers permanents. La commande ls -l par exemple, les affichent avec la lettre p, comme pipe, en position type de fichier.
prw-r--r-- 1 tounsi 0 Jun 26 17:11 canal
Un tube nommé est donc un fichier spécial permettant à des processus quelconques d'échanger des données en mode fifo comme dans un tube normal. En particulier, il est

En particulier, il est ouvrable avec la primitive

open()

 Par contre, il est créé avec la primitive

 mkfifo()

 prévue à cet effet. Les opérations de lecture/écriture sont read() et write() classiques.

2.1. Manipulation d'un Tube Nommé

2.1.1. Création

#include <sys/types.h>
int mkfifo(char *nom, mode_t mode);
Le paramètre nom indique le nom du tube à créer et le paramètre mode, ses droits d'accès, généralement 0644 (cf. mode dans primitive open() des fichiers).

mkfifo() rend 0 en cas de succès (tube créé) ou -1 en cas d'échec (fichier de même nom existe etc ...).

Exemple:

mkfifo("NotreTube", 0644);
Remarque:

 Un tube nommé peut être créé en interactif par la commande shell

mkfifo nom
qui crée un tube nommé de nom donné.

Exemple:

tounsi@shems 49>mkfifo canal
tounsi@shems 50>ls -l canal
prw-r--r-- 1 tounsi 0 Jun 26 17:11 canal
On va maintenant ouvrir le tube et lire son contenu (par la commande cat).
tounsi@shems 51>cat canal &
[2] 501
Comme le tube est encore vide, la commande - l'ouverture en fait - sera suspendue (d'où le & pour garder la main). Il faut qu'un écrivain ouvre le tube pour mettre quelque chose dedans. Ce sera le résultat d'un processus ls -l par exemple, qui aura pour effet immédiat de relancer le processus cat suspendue. D'où les lignes
tounsi@shems 52>ls -l >canal
total 20
prw-r--r-- 1 tounsi 0 Jun 26 17:11 canal
-rw-r--r-- 1 tounsi 1972 Apr 15 7:13 client.c
-rw-r--r-- 1 tounsi 2340 Apr 15 19:23 serveur.c
Noter que le canal est vidé (taille 0).

 Voici maintenant un exemple inverse: le processus écrivain doit attendre un éventuel lecteur.

tounsi@shems 63>ls -l >canal& ---> Ecriture dans canal
[1] 511

tounsi@shems 64>ls -l canal
prw-r--r-- 1 tounsi 0 Jun 26 17:11 canal ---> canal toujours vide

tounsi@shems 65>cat canal ---> Voici un lecteur
total 20
prw-r--r-- 1 tounsi 0 Jun 26 17:11 canal
-rw-r--r-- 1 tounsi 1972 Apr 15 7:13 client.c
-rw-r--r-- 1 tounsi 2340 Apr 15 19:23 serveur.c
[1] + Done ls -l > canal

2.1. Manipulation d'un Tube Nommé

La programmation avec les tubes nommés est semblable à celle avec les tubes ordinaires. La différence se situe au niveau création: on utilise mkfifo() -au lieu de pipe()- qui fixe aussi le mode de protection. En outre, comme c'est un fichier "réel", un tube nommé doit être ouvert par open() qui fixera le type d'ouverture.

2.1.1. Création et Ouverture

La création d'un fifo se fait à l'aide de la primitive mkfifo()
#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(char *nom, mode_t pmode);

Le paramètre nom indique le nom du tube fifo à créer et le paramètre pmode ses droits d'accès (généralement 0644 cf. mode fichiers ordinaires). mkfifo() rend 0 en cas de succès (tube créé) ou -1 en cas d'échec (fichier de même nom existe ou autre anomalie etc.).

Exemple:

if (mkfifo("canal", 0644) < 0)
     fprintf(stderr,"erreur mkfifo\n");
Une fois créé, un fifo peut être ouvert. L'appel
#include <fcntl>
int fd;
...
fd = open("canal", O_WRONLY);
ouvre le fifo canal en écriture pour retourner son descripteur. Cette ouverture va bloquer le processus jusqu'à ce qu'un autre processus ouvre le même fifo en lecture (si ce n'est déjà fait). Par rapport au tubes ordinaires donc, c'est l'ouverture qui bloque un processus, et non pas la tentative de lecture/écriture.

 L'ouverture non bloquante d'un fifo est possible. Elle doit se faire par conjonction bit à bit avec l'indicateur O_NONBLOCK (ou O_NDELAY) . Par exemple,

if (fd == open("canal", O_RDONLY|O_NONBLOCK)) < 0)
    fprintf(stderr,"erreur open sur fifo\n");
ouvre le fifo canal en lecture mais ne bloque pas le processus en attente d'un écrivain.

 En cas de succès, fd recoit le descripteur de fichier associé, sinon open() retourne -1. C'est le cas par exemple d'une ouverture en écriture non bloquante alors qu'il n'y a aucun processus detenant un descripteur en lecture (car sinon le prochain appel à write() est fatal). Par contre, l'ouverture en lecture non bloquante sur un fifo sans rédacteur, reussit et renvoie un descripteur. Un futur read() renverra fin de fichier.

 La primitive open() appliquée à des fifos présente une caractéristique nouvelle propre aux fifos. Elle peut servir à synchroniser les processus communiquants. L'ouverture normale d'un fifo est bloquante pour un processus jusqu'à ce qu'un autre processus communiquant réalise à son tour une ouverture du même fifo. Ce mécanisme de rendez-vous, permet à des processus de se rejoindre à un même point de leur déroulement.

2.1.2. Lecture et Ecriture

Les entrées/sorties sur un fifo, se passent comme pour les tubes ordinaires. read() et write() sont bloquantes ou non selon le mode d'ouverture éffectuée. Ce statut peut changer par appel à fcntl(). En général, un procsus serveur choisira une ouverture en écriture non bloquante pour pouvoir écrire des messages sans attendre. Un processus client, ouvrira un fifo en lecture bloquante pour attendre de lire un message.

2.2. Exemple de Programme

Voici deux programmes, serveur.c et client.c, qui communiquent par deux tubes nommés cli2serv et serv2cli.
 
 
Fig-3 . Deux Processus Communicant
Par Tubes nommés

Le programme client.c envoie une requête de type a + b, dans le tube cli2serv, et le programme serveur.c lui retourne la réponse dans le tube serv2cli. C'est le programme serveur.c qui crée les tubes (désignés par les variables QUESTION et REPONSE) et qui les supprime en fin d'exécution. Il doit donc être lancé avant le programme client qui suppose les tubes existants. Les deux programmes se synchronisents sur l'ouverture des tubes qui doit se faire dans le même ordre. L'arrêt de la communication se fait quand le programme client envoi le message bye; le serveur répond alors par ciao.

programme serveur.c

% cat serveur.c

/* Serveur: retourne resultat (requete a+b)
 * Cree les fifos cli2serv et serv2cli
 * LANCER LE SERVEUR D'ABORD (car il ecrase fifos)
 */

#include <sys/types.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#define QUESTION "cli2serv"
#define REPONSE "serv2cli"

void trait();             /* traitement du serveur */

main()
{
    int fdq, fdr;
    unlink(QUESTION);
    unlink(REPONSE);
/* Creation fifos */
    if (mkfifo(QUESTION, 0644) == -1 ||
        mkfifo(REPONSE, 0644) == -1) {
        perror("Impossible creer fifos");
        exit(2);
    }
/* Attente des ouvertures Clients */

    fdq = open(QUESTION, O_RDONLY);
    fdr = open(REPONSE, O_WRONLY);
    trait(fdr, fdq);
    close(fdq);
    close(fdr);
    unlink(QUESTION);
    unlink(REPONSE);
    exit(0);
}


void trait(fdr, fdq)
int fdr, fdq;
/* fdr et fdq descripteurs reponse/question */
{
    int opd1, opd2, res;
    char opr;
    char quest[11];
    char rep[11];

/* traitement serveur
   * envoi reponse a question
   * a + b venant de client.
   * arret question = "Ciao"
 */

    while (1) {
        read(fdq, quest, 10);
        sscanf(quest, "%d%1s%d", &opd1, &opr, &opd2);
        if (strcmp(quest, "Ciao") == 0) {
            strcpy(rep, "Bye");
            write(fdr, rep, 10);
            break;
        }
        res = opd1 + opd2;
        sprintf(rep, "%d", res);
        write(fdr, rep, 10);
    }
}

programme client.c

% cat client.c

/* Client: envoie expressions
   * Les fifos sont supposes crees
   * par le serveur et sont cli2serv
   * et serv2cli
 */

#include <sys/types.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#define QUESTION "cli2serv"
#define REPONSE "serv2cli"
void trait();                   /* traitement client */
main()
{
    int fdq, fdr;
    fdq = open(QUESTION, O_WRONLY);
    if (fdq == -1) {
        fprintf(stderr, "Impossible ouvrire fifo %s\n", QUESTION);
        fprintf(stderr, "Lancer serveur d\'abord?\n");
        exit(2);
    }
    fdr = open(REPONSE, O_RDONLY);
    if (fdr == -1) {
        fprintf(stderr, "Impossible ouvrire fifo %s\n", REPONSE);
        fprintf(stderr, "Lancer serveur d\'abord?\n");
        exit(2);
    }
    trait(fdr, fdq);
    close(fdq);
    close(fdr);
    exit(0);
}

void trait(fdr, fdq)
int fdr, fdq;
/* fdr et fdq descripteurs reponse/question */
{
    char rep[11];
    char quest[10];
/* traitement client
   * lecture expression a op b
   * dans stdin et ecriture reponse
   * dans stdout. Arret rep = "Bye"
 */
    while (1) {
        if (gets(quest) == NULL)
            exit(2);
        write(fdq, quest, 10);
        printf("Client -> %s \n", quest);
        read(fdr, rep, 10);
        printf("Serveur -> %s \n", rep);
        if (strcmp(rep, "Bye") == 0)
            break;
    }
}

Script d'exécution

% ls cli_serv serv_cli
cli_serv not found
serv_cli not found

% client
Impossible ouvrire fifo cli_serv
Lancer serveur d'abord?

% serveur &
[1] 21653

% client
2 +4
Client -> 2 +4
Serveur -> 6
3 + 7
Client -> 3 + 7
Serveur -> 10
Ciao
Client -> Ciao
Serveur -> Bye
[1] + Done serveur

% logout
   Good bye Tounsi, come again
   _______________________
   What sane person could live in this world and not be crazy?
                -- Ursula K. LeGuin
   _______________________