C pipe
Autres actions
Introduction
Un tube est un canal par lequel les informations circulent de manière uni-directionnelle. Un processus écrit dans l'entrée du tube et un autre processus lit les informations en sortie.
Manipulation des tubes
Création
La première étape est la création d'un tube:
#include <unistd.h> int tube[2]; int pipe(int tube[2]);
- tube[0] → contiendra le fichier descripteur de l'extrémité de lecture
- tube[1] → contiendra le fichier descripteur de l'extrémité d'écriture
- Le retour sera :
- '0' si tout s'est bien passé
- '1' si une erreur survient et errno est positionné
Écriture
Pour écrire dans un tube :
#include <unistd.h> ssize_t write(int tube[1], const void *message, size_t longueur);
- tube[1] → le fichier descripteur de l'extrémité d'écriture
- message → le message à écrire
- longueur → la longueur du message
- ssize_t → le nombre d'octets écrits
Lecture
Pour lire dans un tube :
#include <unistd.h> ssize_t read(int tube[0], void *message, size_t longueur);
- tube[0] → le fichier descripteur de l'extrémité de lecture
- message → un tableau de caractères qui contiendra le message à lire
- longueur → la longueur du message à lire
- ssize_t → le nombre d'octets lus
fermeture
Un fois la lecture terminée on ferme le tube:
int close(int descripteur);
- descripteur : une extrémité du tube
Utilisation dans un contexte d'exécution parallélisé
Utilisation uni-directionnelle
Ci-dessous un exemple qui permet au père de communiquer avec ses fils:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
// Nombre total de thread
#define NB_FORK 2
// Taille du message
#define LENGTH_MSG 30
// Tableau contenant le message
char message[LENGTH_MSG] = "";
// Fonction exécutée dans le fork
void job(int * tube) {
int tid = getpid();
// timer pour attendre maximum 5 secondes
int i = 5;
while (i > 0) {
// lecture dans le tube
if (read(*tube, message, LENGTH_MSG) > 0) {
printf("Message du processus [%i] : %s", tid, message);
break;
}
sleep(1);
}
}
// Fonction qui attend chacun des processus fils
void waitForAll() {
int status;
pid_t pid;
int n = 0;
while (n < NB_FORK) {
pid = wait(&status);
n++;
}
}
int main() {
for (int i = 0; i < NB_FORK; i++) {
int tube[2];
pipe(tube);
pid_t pid = fork();
if (pid == -1) {
// Il y a une erreur
perror("fork");
return EXIT_FAILURE;
} else if (pid == 0) {
// On est dans le fils
close(tube[1]);
job(&tube[0]);
close(tube[0]);
exit(EXIT_SUCCESS);
} else {
// On est dans le père
// Ecriture du message dans le tableau
sprintf(message, "Fork [%i], je suis ton père !\n", pid);
// Ecriture du message dans le tube
write(tube[1], message, LENGTH_MSG);
close(tube[0]);
close(tube[1]);
}
}
waitForAll();
return EXIT_SUCCESS;
}
Utilisation bidirectionnelle
Dans cet exemple, on crée une structure qui va contenir les deux tubes pour plus de facilité:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
// Structure avec des tubes full-duplex
typedef struct fdpipe {
int father[2];
int son[2];
} fdpipe;
// Nombre total de thread
#define NB_FORK 10
// Taille du message
#define LENGTH_MSG 50
// Tableau contenant le message
char message[LENGTH_MSG] = "";
// Fonction exécutée dans le fork
void job(fdpipe * pipes) {
int tid = getpid();
// timer pour attendre maximum 5 secondes
int i = 5;
while (i > 0) {
// lecture dans le tube
if (read((*pipes).father[0], message, LENGTH_MSG) > 0) {
printf("Message du processus [%i] : %s\n", tid, message);
// Ecriture du message dans le tableau
sprintf(message, "je suis [%i] et j'ai bien reçu ton message !\n", tid);
write((*pipes).son[1], message, LENGTH_MSG);
break;
}
sleep(1);
}
}
// Fonction qui attend chacun des processus fils
void waitForAll() {
int status;
pid_t pid;
int n = 0;
while (n < NB_FORK) {
pid = wait(&status);
n++;
}
}
int main() {
for (int i = 0; i < NB_FORK; i++) {
fdpipe pipes;
pipe(pipes.father);
pipe(pipes.son);
pid_t pid = fork();
if (pid == -1) {
// Il y a une erreur
perror("fork");
return EXIT_FAILURE;
} else if (pid == 0) {
// On est dans le fils
close(pipes.father[1]);
job(&pipes);
close(pipes.father[0]);
close(pipes.son[0]);
close(pipes.son[1]);
exit(EXIT_SUCCESS);
} else {
// On est dans le père
// Ecriture du message dans le tableau
sprintf(message, "Fork [%i], je suis ton père !\n", pid);
// Ecriture du message dans le tube
write(pipes.father[1], message, LENGTH_MSG);
while (i > 0) {
// lecture dans le tube
if (read(pipes.son[0], message, LENGTH_MSG) > 0) {
printf("Réponse du fils : %s\n", message);
break;
}
sleep(1);
}
close(pipes.father[1]);
close(pipes.father[0]);
close(pipes.son[0]);
close(pipes.son[1]);
}
}
waitForAll();
return EXIT_SUCCESS;
}
Ce qui nous donne le résultat suivant :
Message du processus [18523] : Fork [18523], je suis ton père ! Réponse du fils : je suis [18523] et j'ai bien reçu ton message ! Message du processus [18522] : Fork [18522], je suis ton père ! Message du processus [18524] : Fork [18524], je suis ton père ! Réponse du fils : je suis [18524] et j'ai bien reçu ton message ! Message du processus [18525] : Fork [18525], je suis ton père ! Réponse du fils : je suis [18525] et j'ai bien reçu ton message !
Tube nommé
Fonctions utilisées
Il est possible de nommer un tube pour pouvoir y accéder depuis un autre processus. Pour ce faire nous allons utiliser les fonctions suivantes :
#include <sys/types.h> #include <sys/stat.h> int mkfifo (const char* name, mode_t mode);
- name est le nom du tube ;
- mode correspond aux permissions du tube (idem chmod) ;
- le code retour de la fonction varie entre :
- EACCES : droits insuffisants pour créer le tube ;
- EEXIST : un tube de même nom existe déjà ;
- ENAMETOOLONG : nom trop long ;
- ENOENT : le chemin n'existe pas ;
- ENOSPC : espace insuffisant sur le système de fichiers.
#include <fcntl.h> int open (const char* name, int options);
- name est le nom du tube ;
- options permet de désigner l'extrémité du tube:
- O_WRONLY: pour l'entrée ;
- O_RDONLY: pour la sortie.
- le retour est le descripteur pour la lecture ou l'écriture ou -1 en cas d'échec.
Exemple d'utilisation
Ci-dessous un exemple d'utilisation avec un thread qui lit dans un tube nommé:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
// Nom du tube
#define FIFO_NAME "/tmp/test.fifo"
// Longeur du buffer de lecture
#define BUFFER_LENGTH 30;
// Mode de lecture du tube
const mode_t FIFO_MODE = 0760;
// Fonction exécutée par le thread
void * job(void * args) {
// Récupération du descripteur en lecture
int fdread;
if ((fdread = open(FIFO_NAME, O_RDONLY)) == -1) {
fprintf(stderr, "Impossible d'ouvrir le tube en lecture: %s\n",
strerror(errno));
exit(EXIT_FAILURE);
}
// Création d'un tampon pour stocker le contenu du tube
char buffer[30];
// Lecture du contenu du tube
read(fdread, buffer, 30);
// Affichage du contenu
printf("Le tube contient : %s", buffer);
// Fin de l'exécution du thread
pthread_exit(EXIT_SUCCESS);
}
int main() {
// On supprime le tube s'il existe déjà
if (mkfifo(FIFO_NAME, FIFO_MODE) == -1) {
printf("Suppression du tube existant: %s\n", FIFO_NAME);
unlink(FIFO_NAME);
}
// Création de la variable qui va contenir le thread
pthread_t thread;
// Création du tube FIFO
if (mkfifo(FIFO_NAME, FIFO_MODE) == -1) {
printf("Erreur de création du tube: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}else{
printf("Création du tube: %s\n", FIFO_NAME);
}
// Création du thread
pthread_create(&thread, NULL, job, NULL);
// Récupération du descripteur en écriture
int fdwrite;
if ((fdwrite = open(FIFO_NAME, O_WRONLY)) == -1) {
printf("Impossible d'ouvrir le tube en écriture: %s\n",
strerror(errno));
exit(EXIT_FAILURE);
}
// Ecriture dans le tube
char message[] = "Bonjour thread";
write(fdwrite, message, sizeof(message));
// En attente de l'éxécution du thread
pthread_join(thread, NULL);
// Fin de l'exécution
return EXIT_SUCCESS;
}
