Différences entre versions de « C pipe »
(19 versions intermédiaires par 2 utilisateurs non affichées) | |||
Ligne 1 : | Ligne 1 : | ||
= Introduction = | = Introduction = | ||
− | Un tube est un canal par lequel les informations circulent de manière uni- | + | 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 = | = Manipulation des tubes = | ||
Ligne 24 : | Ligne 24 : | ||
#include <unistd.h> | #include <unistd.h> | ||
− | ssize_t write(int tube[ | + | ssize_t write(int tube[1], const void *message, size_t longueur); |
</source> | </source> | ||
− | *tube[ | + | *tube[1] → le fichier descripteur de l'extrémité d'écriture |
*message → le message à écrire | *message → le message à écrire | ||
*longueur → la longueur du message | *longueur → la longueur du message | ||
*ssize_t → le nombre d'octets écrits | *ssize_t → le nombre d'octets écrits | ||
+ | |||
== Lecture == | == Lecture == | ||
Pour lire dans un tube : | Pour lire dans un tube : | ||
Ligne 35 : | Ligne 36 : | ||
#include <unistd.h> | #include <unistd.h> | ||
− | ssize_t read(int tube[ | + | ssize_t read(int tube[0], void *message, size_t longueur); |
</source> | </source> | ||
− | *tube[ | + | *tube[0] → le fichier descripteur de l'extrémité de lecture |
− | *message → un tableau de | + | *message → un tableau de caractères qui contiendra le message à lire |
*longueur → la longueur du message à lire | *longueur → la longueur du message à lire | ||
*ssize_t → le nombre d'octets lus | *ssize_t → le nombre d'octets lus | ||
==fermeture== | ==fermeture== | ||
− | Un fois la lecture | + | Un fois la lecture terminée on ferme le tube: |
<source lang="c"> | <source lang="c"> | ||
int close(int descripteur); | int close(int descripteur); | ||
Ligne 49 : | Ligne 50 : | ||
*descripteur : une extrémité du tube | *descripteur : une extrémité du tube | ||
− | = Utilisation uni- | + | = Utilisation dans un contexte d'exécution parallélisé = |
− | Ci-dessous un exemple qui permet au père de communiquer avec | + | == Utilisation uni-directionnelle == |
+ | Ci-dessous un exemple qui permet au père de communiquer avec ses fils: | ||
<source lang="c"> | <source lang="c"> | ||
#include <stdio.h> | #include <stdio.h> | ||
Ligne 79 : | Ligne 81 : | ||
sleep(1); | sleep(1); | ||
} | } | ||
− | |||
− | |||
} | } | ||
// Fonction qui attend chacun des processus fils | // Fonction qui attend chacun des processus fils | ||
Ligne 104 : | Ligne 104 : | ||
} else if (pid == 0) { | } else if (pid == 0) { | ||
// On est dans le fils | // On est dans le fils | ||
+ | close(tube[1]); | ||
job(&tube[0]); | job(&tube[0]); | ||
+ | close(tube[0]); | ||
+ | exit(EXIT_SUCCESS); | ||
} else { | } else { | ||
// On est dans le père | // On est dans le père | ||
Ligne 111 : | Ligne 114 : | ||
// Ecriture du message dans le tube | // Ecriture du message dans le tube | ||
write(tube[1], message, LENGTH_MSG); | write(tube[1], message, LENGTH_MSG); | ||
+ | close(tube[0]); | ||
close(tube[1]); | close(tube[1]); | ||
} | } | ||
Ligne 118 : | Ligne 122 : | ||
} | } | ||
</source> | </source> | ||
+ | |||
+ | == Utilisation bidirectionnelle == | ||
+ | Dans cet exemple, on crée une structure qui va contenir les deux tubes pour plus de facilité: | ||
+ | <source lang="c"> | ||
+ | #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; | ||
+ | } | ||
+ | </source> | ||
+ | Ce qui nous donne le résultat suivant : | ||
+ | <pre> | ||
+ | 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 ! | ||
+ | </pre> | ||
+ | |||
+ | = 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 : | ||
+ | <source lang="c"> | ||
+ | #include <sys/types.h> | ||
+ | #include <sys/stat.h> | ||
+ | |||
+ | int mkfifo (const char* name, mode_t mode); | ||
+ | </source> | ||
+ | * 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. | ||
+ | |||
+ | <source lang="c"> | ||
+ | #include <fcntl.h> | ||
+ | |||
+ | int open (const char* name, int options); | ||
+ | </source> | ||
+ | * 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é: | ||
+ | <source lang="c"> | ||
+ | #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; | ||
+ | } | ||
+ | </source> | ||
+ | {|style="width:800px" align="center" | ||
+ | | | ||
+ | [[Fichier:Warning manual.jpg|centré|300px]] | ||
+ | |valign="top"| | ||
+ | Dans l'exemple précédent, vous remarquerez que le thread est créé avant l'ouverture du tube. C'est absolument important de démarrer le thread avant, car la fonction ''open'' va bloquer le fil d'exécution principal tant que le thread n'ouvre pas le tube en lecture ! Pour eviter cela il faut utiliser l'option '''O_NONBLOCK''' | ||
+ | |} |
Version actuelle datée du 14 novembre 2020 à 17:16
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;
}
Dans l'exemple précédent, vous remarquerez que le thread est créé avant l'ouverture du tube. C'est absolument important de démarrer le thread avant, car la fonction open va bloquer le fil d'exécution principal tant que le thread n'ouvre pas le tube en lecture ! Pour eviter cela il faut utiliser l'option O_NONBLOCK |