Aller au contenu principal

Tubes et tubes nommés

Les tubes sont un mécanisme de communication qui permet de réaliser des communications entre processus sous forme d'un flot continu d'octets. Les tubes sont un des éléments de l'agrément d'utilisation d'UNIX : c'est ce mécanisme qui permet l'approche « filtre » de la conception sous UNIX. Mécanisme de communication lié au système de gestion de fichiers, les tubes, nommés ou non, sont des paires d'entrées de la table des fichiers ouverts, associées à une inode en mémoire gérée par un pilote spécifique. Une entrée est utilisée par les processus qui écrivent dans le tube, l'autre par les lecteurs du tube.

Les tubes ordinaires (pipe)​

Un tube est matérialisé par deux entrées de la table des ouvertures de fichiers : une de ces entrées est ouverte en écriture (l'entrée du tube), l'autre en lecture (la sortie du tube). Ces deux entrées de la table des fichiers ouverts comptent le nombre de descripteurs qui pointent sur elles. Ces valeurs peuvent être interprétées comme :

  • nombre de lecteurs = nombre de descripteurs associés à l'entrée ouverte en lecture. On ne peut pas écrire dans un tube sans lecteur : l'écriture provoque l'envoi du signal SIGPIPE à l'écrivain (qui le termine par défaut) et, si ce signal est ignoré ou capturé, write échoue avec errno = EPIPE.
  • nombre d'écrivains = nombre de descripteurs associés à l'entrée ouverte en écriture. La nullité de ce nombre définit le comportement de la primitive read lorsque le tube est vide.

Il est donc indispensable que chaque processus ferme les extrémités du tube dont il ne se sert pas : un lecteur qui garde ouvert un descripteur en écriture ne verra jamais la fin de fichier.

Création de tubes ordinaires​

Un processus ne peut utiliser que les tubes qu'il a créés lui-même par la primitive pipe ou dont il a hérité de son père, grâce à l'héritage des descripteurs à travers fork et exec.

#include <unistd.h>
int pipe(int p[2]);

pipe retourne 0 en cas de succès et -1 en cas d'échec. p[0] est le descripteur de lecture (sortie du tube) et p[1] le descripteur d'écriture (entrée du tube).

On ne peut pas manipuler les descripteurs de tubes avec les fonctions et primitives lseek (échec avec ESPIPE), tcsetattr et tcgetattr, car il n'y a pas de support positionnable ni de terminal associé au tube (tout est fait en mémoire).

Exemple classique : le père écrit, le fils lit.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>

int main(void)
{
int p[2];
if (pipe(p) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
pid_t pid = fork();
if (pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pid == 0) { /* fils : lecteur */
close(p[1]); /* ferme l'extrémité inutilisée */
char buf[128];
ssize_t n;
while ((n = read(p[0], buf, sizeof buf)) > 0)
write(STDOUT_FILENO, buf, (size_t)n);
close(p[0]);
_exit(EXIT_SUCCESS);
}
close(p[0]); /* père : écrivain */
const char *msg = "bonjour\n";
write(p[1], msg, strlen(msg));
close(p[1]); /* le fils verra la fin de fichier */
waitpid(pid, NULL, 0);
return EXIT_SUCCESS;
}

Lecture dans un tube​

On utilise l'appel système read.

ssize_t nb_lu;
nb_lu = read(p[0], buffer, TAILLE_READ);

Remarquer que la lecture se fait sur le descripteur p[0]. Comportement de l'appel :

Si le tube n'est pas vide et contient taille octets :

lecture de nb_lu = min(taille, TAILLE_READ) octets

Si le tube est vide :

  • si le nombre d'écrivains est nul :
c'est la fin de fichier et nb_lu vaut 0
  • si le nombre d'écrivains est non nul :
si lecture bloquante : le processus est endormi jusqu'à l'arrivée de données
si lecture non bloquante :
O_NONBLOCK : nb_lu = -1 et errno = EAGAIN
O_NDELAY (System V historique) : nb_lu = 0
(sous Linux, O_NDELAY est un synonyme de O_NONBLOCK)

Écriture dans un tube​

nb_ecrit = write(p[1], buf, n);

L'écriture est atomique (non entrelacée avec celles d'autres écrivains) si le nombre d'octets à écrire est inférieur ou égal à PIPE_BUF (au moins 512 octets selon POSIX, 4096 sous Linux). PIPE_BUF n'est pas la capacité du tube, qui est plus grande (65 536 octets par défaut sous Linux). Si le tube est plein, l'écriture bloque jusqu'à ce qu'un lecteur libère de la place (ou échoue avec EAGAIN en mode non bloquant).

Interblocage avec des tubes​

Si un même processus possède les deux accès à un tube, un accès en lecture et un accès en écriture, et essaie de lire sur le tube vide en mode bloquant, le processus est bloqué indéfiniment dans la primitive read : il est lui-même l'écrivain qui empêche la fin de fichier. Avec deux processus et deux tubes entre eux, un interblocage survient si les deux processus sont bloqués en lecture (ou tous les deux en écriture sur des tubes pleins), chacun en attente d'une action de l'autre.

Les tubes nommés​

Les tubes nommés (FIFO) sont des tubes qui existent dans le système de fichiers et peuvent être ouverts grâce à une référence. Il faut préalablement créer le tube nommé dans le système de fichiers, avec la fonction mkfifo (ou la primitive mknod), avant de pouvoir l'ouvrir avec la primitive open.

#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);

/* appel équivalent avec mknod : */
mknod(pathname, mode | S_IFIFO, 0);

Par défaut, l'ouverture d'un tube nommé en lecture bloque jusqu'à ce qu'un autre processus l'ouvre en écriture, et réciproquement.