Les pipes
Les techniques que nous avons étudiées jusqu'ici sont essentiellement destinées à la synchronisation des processus, les quantités d'information transmises étant nulles ou très faibles (la valeur du compteur d'un sémaphore par exemple).
Une exception cependant : les moniteurs, qui peuvent avoir des variables locales importantes.
Les pipes et la mémoire partagée sont les moyens universels de communiquer entre processus sur la même machine, auxquels il faut ajouter des techniques spécifiques à chaque système d'exploitation.
Définition d'un pipe
Un pipe est une file gérée par le système d'exploitation, implémentée en mémoire partagée, dans laquelle les processus peuvent lire et écrire, selon l'ordre FIFO, un peu comme dans un buffer circulaire.
Il peut aussi être décrit comme un tuyau à deux extrémités.
Un pipe supporte les opérations d'E/S classiques (read, write).
Selon la norme Posix, les pipes sont half-duplex, ce qui signifie que ce ne sont pas les mêmes processus qui lisent ou qui écrivent dans un pipe donné.
Il est donc plus fréquent que deux processus communiquent par deux pipes, chacun ayant un sens unique.
Certains systèmes comme Unix System V, les implémentent en full-duplex : deux processus peuvent échanger des informations sur le même pipe.
Le processus qui crée un pipe est considéré comme serveur du pipe.
Le processus qui se connecte sur un pipe existant est un client du pipe.
La taille du pipe (en octets) est soit imposée (Unix), soit fixée par l'utilisateur (Win32).
Sauf dispositions particulières explicites, les opérations d'E/S sur les pipes sont bloquantes par défaut : une lecture dans un pipe est bloquante si le pipe est vide, une écriture est bloquante si le pipe est plein.
Les pipes sont souvent utilisés pour rediriger les E/S standard des processus (clavier/écran).
Par leur nature, les opérations sur les pipes sont en général classées dans les opérations sur les fichiers.
Il existe généralement deux types de pipes : les pipes anonymes et les pipes nommés.
Pipe anonyme ou pipe
non nommé
Un pipe anonyme est un pipe créé par un processus et qui n'apparaît pas dans les ressources générales du système d'exploitation.
Il est seulement connu du processus qui l'a créé et éventuellement de ses processus fils, par l'intermédiaire d'un entier appelé descripteur de fichier (file descriptor) en Unix ou poignée (handle [1]) en Win32.
Dans les deux S.E. la création d'un pipe renvoie en fait deux file descriptors ou handles, désignant le pipe comme support des opérations de lecture pour le premier et d'écriture pour le second.
L'utilisation des pipes anonymes est donc réservé à des processus affiliés.
Un pipe anonyme disparaît lorsque tous les descripteurs de fichiers ou handle ont été fermés ou lorsque plus aucun des processus qui l'utilisaient n'existe.
Pipes nommés
Contrairement aux pipes anonymes, les pipes nommés sont utilisables pour faire communiquer des processus non affiliés : le processus serveur de pipe crée le pipe en lui donnant un nom, géré par le S.E., qui est accessible par tout processus, comme un nom de fichier.
Le comportement et les opérations sur un pipe nommé sont en tout point identiques à ceux d'un pipe anonyme.
La seule différence est que le pipe nommé continue d'exister tant qu'il n'est pas explicitement détruit par un processus.
Pipes de Win32
Les pipes de Win32 sont en tout point conformes à la définition générale donnée ci-dessus.
Fonctions de bas niveau _pipe()
#include <io.h>
#include <fcntl.h> // pour les constantes _O_BINARY et _O_TEXT
#include <errno.h>
int _pipe (int phandles [2], // handles de lecture et
d'écriture
unsigned
int psize, // Nombre d'octets à réserver en mémoire
int
textmode // mode du pipe
); |
Le paramètre textmode indique le mode de traduction du pipe.
Il peut prendre deux valeurs :
-
_O_TEXT : dans ce mode, les opérations d'E/S effectuent une traduction des caractères lus/écrits.
En entrée, CTRL+Z est interprété comme un caractère fin-de-fichier.
En entrée, la combinaison Retour chariot – Nouvelle ligne (CR-LF : Carriage Return–LineFeed) est traduite en un simple Retour chariot, et la traduction inverse est effectuée en sortie.
D'autres conversions sont effectuées selon le jeu de caractères utilisés (Unicode, SMBC (Sequence of MultiByte Characters)
-
_O_BINARY : aucune traduction n'est effectuée sur les données.
L'utilisation en mode multitâches (multithreaded programs) nécessite quelques précautions d'emploi (voir manuel d'utilisation de la fonction).
Les opérations d'E/S doivent être réalisées au moyen des fonctions _read() et _write().
Les fonctions _dup() et _dup2() peuvent aussi être utilisées pour effectuer les redirections.
Fonctions CreatePipe() et CreateNamedPipe() de Visual C++
Les fonctions CreatePipe() et CreateNamedPipe() permettent de créer un pipe anonyme ou nommé:
BOOL CreatePipe (
PHANDLE hReadPipe,
// address of variable for read handle
PHANDLE hWritePipe,
// address of variable for write handle
LPSECURITY_ATTRIBUTES lpPipeAttributes, // pointer
to security attributes
DWORD nSize
// number of bytes reserved for pipe
);
HANDLE CreateNamedPipe (
LPCTSTR lpName,
// pointer to pipe name
DWORD dwOpenMode,
// pipe open mode
DWORD dwPipeMode,
// pipe-specific modes
DWORD nMaxInstances,
// maximum number of instances
DWORD nOutBufferSize,
// output buffer size, in bytes
DWORD nInBufferSize,
// input buffer size, in bytes
DWORD nDefaultTimeOut,
// time-out time, in milliseconds
LPSECURITY_ATTRIBUTES lpSecurityAttributes // pointer
to security attributes
// structure
); |
Les identificateurs des paramètres sont suffisamment explicites.
Les opérations d'E/S doivent être réalisées au moyen des fonctions ReadFile() et WriteFile().
Pipes d'Unix
Les pipes d'Unix sont en tout point conformes à la définition générale donnée ci-dessus.
Un complément et des précisions sont données dans le complément de cours consacré aux
supports de Linux au modèle Producteurs/Consommateurs.
Ils sont créés par la fonction
pipe()
pour les pipes anonymes, et
mkfifo()
pour les pipes nommés.
La plupart des fonctions applicables aux fichiers le sont aussi aux pipes :
read(),
write(),
close()
etc. à l'exception de
lseek().
Par défaut les opérations d'E/S sont bloquantes.
[1]
Nous utiliserons désormais le terme anglais handle, d'un usage beaucoup plus répandu.
Dernière mise à jour : 10/10/2001