Les tubes (pipes)
Communication entre processus sur une même machine : tubes anonymes (pipe + fork), tubes nommés (mkfifo), synchronisation FIFO, redirection avec dup2, gestion SIGPIPE.
📄 Ressources du cours
🎥 Vidéos recommandées
-
🇫🇷
12-caractères spéciaux linux "entre et sortie et pipe" #darija
Net4maroc · 16:52 · 21k vues — Ce vidéo contient de:12-caractères spéciaux linux "entre et sortie et pipe" #darijaVeuillez nous trouvez sur :Notre page Facebook: https://www.facebook.com/n
-
🇫🇷
Les tubes - Pipe : Comment faire communiquer deux processus ?
YND Workshop · 09:36 · 20k vues — AboutPressCopyrightContact usCreatorsAdvertiseDevelopersTermsPrivacyPolicy & SafetyHow YouTube worksTest new features · © 2024 Google LLC
-
🇬🇧
Programar C con FIFOs (IPC, named pipes) 1
WhileTrueThenDream · 12:20 · 14k vues — Programar (en C) con FIFOs también conocidos "Named Pipes" son un mecanismo de Comunicación entre procesos.
1. Pourquoi les tubes ?
Plusieurs processus tournent simultanément sur une même machine et veulent échanger de l'information. Les questions classiques : quels moyens de communication ? comment choisir ? comment être sûr que le bon processus reçoit la donnée ? comment hiérarchiser l'exécution ?
- Tube anonyme — pas de nom, créé par
pipe(), nécessite un lien de parenté. - Tube nommé — visible dans le système de fichiers, créé par
mkfifo(), accessible à tout processus connaissant son nom.
Un air de déjà vu — le pipe shell
La commande :
$ ps -a | wc -l
déclenche deux processus concurrents et un tube :
- Un tube est créé entre
ps -aetwc -l. - Les résultats de
ps -asont écrits dans ce tube. wc -llit dans le tube et compte les lignes.- Quand
ps -atermine et quewc -la fini de lire (tube vide, plus d'écrivain),wc -ldétecte la fin de fichier sur son entrée standard et se termine à son tour.
Le tube se comporte comme une file FIFO (premier entré, premier sorti) unidirectionnelle. Les lectures sont destructrices : une donnée lue par un processus disparaît du tube — un autre processus ne pourra plus la lire.
2. Caractéristiques générales d'un tube
Un tube est presque identique à un fichier ordinaire :
- Taille limitée — définie par la constante
PIPE_BUFdans<limits.h>. - Deux extrémités : une pour lire, une pour écrire.
- Au plus deux entrées dans la table des fichiers ouverts : une pour la lecture, une pour l'écriture.
- L'opération de lecture est destructive : une donnée ne peut être lue qu'une seule fois.
Synchronisation gérée par le système
Le système assure automatiquement la synchronisation entre lecteurs et écrivains :
- Écriture sans lecteur (nombre de descripteurs en lecture = 0) : l'écrivain reçoit le signal
SIGPIPE, comportement par défaut = fin du processus.write()renvoie-1avecerrno = EPIPE. - Lecture sans écrivain (nombre de descripteurs en écriture = 0) : le lecteur détecte une fin de fichier ;
read()renvoie 0.
3. Tubes anonymes
- Structure sans nom — aucune référence dans le système de fichiers.
- Communication entre deux processus, sur deux descripteurs (lecture et écriture).
- Pointeurs automatiques : lecture sur le 1er caractère non lu, écriture sur le 1er emplacement vide.
- Lien de parenté obligatoire : processus de même filiation ou ancêtre commun ayant créé le tube.
- Canal half-duplex : communication dans les deux sens possible, mais une seule direction à la fois (PA peut parler à PB et inversement, mais pas simultanément).
- Destruction automatique à la fin de l'utilisation (tous les descripteurs fermés).
Primitive pipe()
#include <unistd.h>
int pipe(int p[2]);
Crée un tube anonyme et renseigne le tableau p avec deux descripteurs :
| Descripteur | Rôle |
|---|---|
p[0] | descripteur en lecture |
p[1] | descripteur en écriture |
Retourne 0 si succès, -1 sinon. Les deux descripteurs sont alloués dans la table des fichiers ouverts du processus appelant.
Le rituel : pipe() → fork()
Pour qu'un tube anonyme serve à la communication entre deux processus, il faut :
- Le processus père crée le tube avec
pipe(). - Le père fork : le fils hérite automatiquement des descripteurs du tube.
- Chaque processus ferme le descripteur qu'il n'utilise pas (sinon EOF jamais détecté).
- Communication par
read()etwrite(). - Fermeture finale avec
close().
Schéma de communication unidirectionnelle (père → fils)
Supposons que le père écrive et que le fils lise. Les cinq étapes :
- Le processus père crée un tube.
- Il fait
fork()pour créer un fils. - Les deux processus possèdent chacun un descripteur en lecture et en écriture sur le tube.
- Le père ferme son descripteur en lecture (
close(fds[0])). Le fils ferme son descripteur en écriture (close(fds[1])). - Le père peut écrire dans le tube ; les données pourront être lues par le fils.
fds[1] ouvert alors qu'il ne s'en sert pas, il y aura toujours un écrivain potentiel actif : le fils ne détectera jamais la fin de fichier quand le père termine. Le programme se bloque. Fermer ce qu'on n'utilise pas est indispensable.
Lire et écrire — read() et write()
// Lecture dans le tube
int read(int desc, char* buf, int nb);
Lit nb octets depuis le descripteur desc, les place dans buf, retourne le nombre réellement lu. Sémantique :
- Tube non vide de taille t : extrait
min(t, nb)caractères. - Tube vide avec écrivains actifs → lecture bloquante.
- Tube vide sans écrivain → fin de fichier, renvoie 0.
// Écriture dans le tube
int write(int desc, char* buf, int nb);
- Nombre de lecteurs = 0 →
SIGPIPEenvoyé à l'écrivain, fin du processus par défaut, message shellBroken pipe. - Lecteurs présents → écriture bloquante jusqu'à ce que les
nboctets soient effectivement écrits.
Exemple complet — père écrit « Salut » au fils
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
const int Nbuff = 1000;
char buff[Nbuff];
int fds[2], pid, n, status;
pipe(fds); // 1. création du tube
if ((pid = fork()) > 0) { // PÈRE
close(fds[0]); // 2. père ferme la lecture
write(fds[1], "Salut", 5); // 3. écrit dans le tube
wait(&status); // 4. attend le fils
} else { // FILS
close(fds[1]); // 5. fils ferme l'écriture
n = read(fds[0], buff, Nbuff - 1);
buff[n] = '\0';
printf("%s\n", buff); // affiche "Salut"
exit(0);
}
return 0;
}
Communication bidirectionnelle — modèle question/réponse
Pour faire dialoguer A et B dans les deux sens, on utilise deux tubes : un par direction. Chaque processus ferme les descripteurs inutiles pour éviter blocage et lectures parasites.
Tenter d'utiliser un seul tube dans les deux sens pose des problèmes : si PA écrit puis lit, il risque de relire sa propre donnée avant que PB ne la prenne. Toujours deux tubes pour le bidirectionnel.
4. Redirection avec dup / dup2
On veut souvent connecter la sortie d'un tube à l'entrée standard d'un processus, ou son entrée à la sortie standard. Ainsi un programme qui lit habituellement stdin (comme wc -l) lit en réalité notre tube, sans modification.
int dup(int desc);— dupliquedescsur le plus petit descripteur libre.int dup2(int oldfd, int newfd);— fait pointernewfdsur le même objet queoldfd. Sinewfdétait ouvert, il est d'abord fermé. C'est la version utile pour rediriger.
0 = stdin, 1 = stdout, 2 = stderr.
Reproduire ls | wc -l en C
#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
int main() {
int p[2];
pipe(p);
if (fork() == 0) { // fils — exécutera wc -l
close(p[1]); // pas d'écriture pour le fils
dup2(p[0], 0); // stdin ← extrémité lecture du tube
close(p[0]); // on n'a plus besoin du desc original
execlp("wc", "wc", "-l", NULL);
} else { // père — exécutera ls
close(p[0]); // pas de lecture pour le père
dup2(p[1], 1); // stdout → extrémité écriture du tube
close(p[1]);
execlp("ls", "ls", NULL);
}
return 0;
}
Après dup2(p[1], 1), tout printf ou write(1, ...) du père écrit en réalité dans le tube. Idem pour wc qui lit son stdin — donc le tube.
5. Tubes nommés (FIFO)
ls -l, type p dans la colonne mode). Il est caractérisé par :
- Un i-node existant sur disque (mais pas de blocs de données — uniquement un buffer noyau).
- Pas besoin de lien de parenté — tout processus connaissant le nom et ayant les droits peut l'utiliser.
- Persiste sur disque jusqu'à un
unlinkexplicite.
Création — mkfifo
En shell :
$ mkfifo /tmp/mon_tube
$ ls -l /tmp/mon_tube
prw-r--r-- 1 user user 0 mai 17 22:30 /tmp/mon_tube
↑ le type 'p' indique un tube nommé
En C :
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char* nomfichier, mode_t mode);
nomfichier— chemin du tube à créer.mode— droits d'accès, commechmod(S_IRUSR | S_IWUSR | S_IXUSR… ou en octal0644).- Renvoie
0si succès,-1si échec (errnodétaille).
Ouverture — open()
int desc = open(nom_du_tube, mode);
Le mode est typiquement O_RDONLY ou O_WRONLY. Le processus doit avoir les droits correspondants.
- Une
open(O_RDONLY)bloque tant qu'il n'y a pas d'écrivain. - Une
open(O_WRONLY)bloque tant qu'il n'y a pas de lecteur.
O_NONBLOCK.
Lecture, écriture, fermeture
Identiques aux tubes anonymes : read, write, close. Suppression du fichier avec unlink ou en shell rm.
Exemple — écrivain et lecteur indépendants
Deux programmes séparés, lancés depuis deux terminaux différents.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main() {
mode_t mode = 0644;
int tub;
mkfifo("fictub", mode);
tub = open("fictub", O_WRONLY);
write(tub, "0123456789", 10);
close(tub);
exit(0);
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main() {
char buf[11];
int tub;
tub = open("fictub", O_RDONLY);
read(tub, buf, 10);
buf[10] = '\0';
printf("J'ai lu %s\n", buf);
unlink("fictub");
exit(0);
}
# Terminal 1 :
$ ./ecrivain # bloque sur open(O_WRONLY)
# Terminal 2 :
$ ls -l fictub # vérifie la création du tube nommé
$ ./lecteur # déclenche le rendez-vous → les deux progressent
J'ai lu 0123456789
6. Tube anonyme vs tube nommé
| Critère | Tube anonyme (pipe) | Tube nommé (mkfifo) |
|---|---|---|
| Nom dans le FS | Aucun | Oui, fichier de type p |
| Lien de parenté requis | Oui (créé puis hérité par fork) | Non — tout processus connaissant le nom |
| Visibilité | Privée à la lignée de processus | Globale (selon droits POSIX) |
| Persistance | Détruit à la fermeture du dernier descripteur | Persiste tant que pas unlink |
| Création | pipe(int[2]) | mkfifo(nom, mode) + open() |
| Usage typique | Pipeline shell, communication parent-enfant | Démons, services FIFO sur disque |
★ Réviser le chapitre
❓ Q/R éclair
Pourquoi un tube est-il qualifié de « FIFO unidirectionnel » ?
FIFO car les données sont lues dans l'ordre où elles ont été écrites (première écrite, première lue). Unidirectionnel car le canal a une seule direction à la fois (half-duplex) : il faut deux tubes pour un dialogue dans les deux sens.
Que se passe-t-il si on écrit dans un tube sans lecteur ?
Le système envoie le signal SIGPIPE à l'écrivain. Le comportement par défaut est la fin du processus. write() renvoie -1 avec errno = EPIPE. Le shell affiche « Broken pipe ».
Que renvoie read si le tube est vide et qu'il n'y a plus d'écrivain ?
Le lecteur détecte une fin de fichier : read renvoie 0. C'est ainsi qu'un programme comme wc -l sait quand s'arrêter.
Pourquoi faut-il fermer les descripteurs inutiles après fork ?
Si le fils garde ouvert le descripteur en écriture alors qu'il ne s'en sert pas, le système considère qu'il existe toujours un écrivain potentiel : le tube ne renverra jamais EOF, même quand le père termine. Le programme se bloque indéfiniment.
Quelle est la séquence pipe → fork → close → IO ?
- Père :
pipe(fds) - Père :
fork()(le fils hérite des deux descripteurs) - Chaque processus :
close()du descripteur qu'il n'utilise pas - Communication :
read()/write() - Fermeture finale :
close()
À quoi sert dup2(p[0], 0) ?
À rediriger le stdin (descripteur 0) vers l'extrémité de lecture du tube. Après cet appel, toute lecture depuis stdin (par exemple scanf ou read(0, ...)) lit en réalité dans le tube. Indispensable pour reproduire un pipeline shell en C.
Pourquoi un tube nommé n'a-t-il pas besoin de lien de parenté ?
Parce qu'il est référencé par un nom dans le système de fichiers. Tout processus connaissant ce nom (et ayant les bons droits) peut l'ouvrir avec open() — pas besoin d'avoir été créé par un ancêtre commun.
Pourquoi open() sur un tube nommé est-il bloquant ?
Pour permettre un rendez-vous. Une open(O_RDONLY) attend qu'un autre processus fasse open(O_WRONLY) (et vice-versa). Quand les deux se rencontrent, ils débloquent simultanément et peuvent communiquer. Pour éviter ce blocage : O_NONBLOCK.
Que retourne pipe() ? Comment sont organisés les descripteurs ?
Renvoie 0 en cas de succès, -1 sinon. Renseigne le tableau p avec deux descripteurs : p[0] en lecture, p[1] en écriture. Mnémotechnique : 0 comme stdin (lecture), 1 comme stdout (écriture).
Quel est le rôle de PIPE_BUF ?
Constante définie dans <limits.h> qui donne la taille du buffer du tube. Sur Linux typique : 4096 octets. Une écriture de taille inférieure ou égale à PIPE_BUF est garantie atomique.
🃏 Flashcards
pipe ?int pipe(int p[2]); · p[0] = lecture, p[1] = écriture. Retourne 0 si OK, -1 sinon. Inclure <unistd.h>.mkfifo ?int mkfifo(const char* nom, mode_t mode);. Crée un tube nommé. Inclure <sys/types.h> + <sys/stat.h>.pipe(). Nommé : nom dans le FS (type p), pas de parenté requise, créé par mkfifo().read sur tube vide ?write sans lecteur ?SIGPIPE envoyé au processus écrivain → fin du processus par défaut. write renvoie -1 avec errno = EPIPE.dup2(fd, 0) ?fd. Tout read(0, ...) ou scanf lit alors depuis fd. Si fd est p[0], on lit dans le tube.PIPE_BUF dans <limits.h>).open() bloque sur un tube nommé ?open(O_RDONLY) attend un écrivain ; open(O_WRONLY) attend un lecteur. Pour le contourner : O_NONBLOCK.ls | wc -l en C ?pipe(p) → fork(). Père : close(p[0]), dup2(p[1], 1), execlp("ls", ...). Fils : close(p[1]), dup2(p[0], 0), execlp("wc", "-l", ...).ls -l, le 1er caractère du mode est p (au lieu de - pour fichier régulier, d pour dossier).unlink(nom). En shell : rm nom. Le fichier disparaît même si des processus ont encore le tube ouvert (ils peuvent continuer à l'utiliser jusqu'à la fermeture).fork() sur un tube créé par le père ?fds[0] et fds[1]. Chaque processus a une copie indépendante dans sa table de descripteurs, mais pointant vers le même tube dans la table des fichiers ouverts du noyau.dup vs dup2 ?dup(fd) alloue le plus petit descripteur libre. dup2(old, new) force le numéro exact new (ferme d'abord new s'il était ouvert). Utiliser dup2 pour les redirections de stdin/stdout/stderr.✎ Quiz éclair
pipe(fds) en cas de succès ?pipe renvoie 0 si succès. Le tableau fds est rempli : fds[0] en lecture, fds[1] en écriture.fds[1] s'il ne fait que lire ?read sur tube vide reste bloquant. Fermer fds[1] dans le fils est indispensable.dup2(p[1], 1) ?printf ou write(1, ...) écrit dans le tube. C'est ainsi qu'on connecte la sortie d'un programme à un pipe.fork, donc impossible d'accéder au tube anonyme. Pour processus indépendants : tube nommé.SIGPIPE est envoyé à l'écrivain ; le comportement par défaut est de terminer le processus. On peut intercepter le signal et write renvoie alors -1 avec errno = EPIPE.O_NONBLOCK rend l'ouverture non-bloquante. Sans lui, open(O_RDONLY) attend un écrivain pour établir le rendez-vous.ls -l signale-t-il un tube nommé ?p pour les pipes nommés. - = fichier régulier, d = directory, l = lien symbolique, c = char device, b = block device.Score : 0 / 8 ·
📌 À retenir
- pipe(fds) →
fds[0]lecture,fds[1]écriture - mkfifo(nom, mode) → tube nommé visible dans le FS, type
p - Ordre canonique : pipe → fork → close inutiles → read/write → close
- Fermer ce qu'on n'utilise pas — sinon EOF jamais détecté
- read sur tube vide sans écrivain → renvoie 0
- write sans lecteur →
SIGPIPE,errno = EPIPE - dup2(fd, 0) redirige stdin, dup2(fd, 1) redirige stdout
- Half-duplex — un seul sens à la fois, deux tubes pour bidirectionnel
- Lien de parenté requis pour tubes anonymes (héritage via fork)
- open() bloquant par défaut sur tube nommé = mécanisme de rendez-vous
- unlink(nom) ou
rmpour supprimer un tube nommé - PIPE_BUF dans
<limits.h>= taille du buffer (atomicité garantie en dessous)