ISTIC 2018-2019
Communication entre processus par tubes anonymes
Amira BELHEDI belhedi.amira@yahoo.fr
Référence livre Programmation système en C sous Linux Signaux, processus, threads, IPC et sockets
Communication par les tubes (pipes)
- Tube de communication: canal ou tuyau (en anglais pipe) dans lequel un processus peut écrire des données (producteur, écrivain ou rédacteur) et un autre processus peut les lire (consommateur).
- C’est un moyen de communication unidirectionnel inter- processus. C’est le moyen de communication le plus simple entre deux processus.
- Pour avoir une communication bidirectionelle entre deux processus, il faut créer deux tubes et les employer dans des sens opposés.
2/33
Communication par les tubes (pipes)
- Il existe 2 types de tubes:
– Les tubes ordinaires (anonymes): ne permettent que la
communication entre processus issus de la même application (entre père et fils, ou entre frères).
– Les tubes nommés: permettent la communication entre processus qui sont issus des applications différentes. (communication entre processus créés dans des applications différentes.
3/33
Caractéristiques
- Un tube possède deux extrémités – une est utilisée pour la lecture – l’autre pour l’écriture.
- La gestion des tubes se fait en mode FIFO
– Les données sont lues dans l’ordre dans lequel elles ont été écrites dans le tube.
- La lecture dans un tube est destructrice.
– Une fois une donnée est lue elle sera supprimée. – Plusieurs processus peuvent lire dans le même tube mais ils ne liront pas les mêmes données, car la lecture est destructive.
- Un tube a une capacité finie.
– Il y a une synchronisation type producteur/consommateur – si le tube est vide le lecteur (consommateur) attend qu’on écrit dans le tube avant de lire. – si le tube est plein, un écrivain (producteur) attend qu’il y a de la place pour pouvoir écrire.
4/33
Création d’un tube anonyme
- Le tube est créé par appel de la primitive « pipe() », déclaré dans « unistd.h ».
- La création d’un tube correspond à celle de deux descripteurs de fichiers,
– l’un permet de lire dans le tube par la primitive
«read()») . – l’autre permet d’écrire dans le tube par la primitive
«write()»).
5/33
Tubes anonymes
#include <unistd.h> int pipe (int p[2]);
- En cas de succès, le tableau «p» est remplit par les descripteurs des 2 extrémités du tube qui seront utilisés pour accéder (en lecture/écriture) au tube.
- Par définition:
– Le descripteur d’indice 0 (p[0]) désigne la sortie du tube, il est ouvert en lecture seule. – Le descripteur d’indice 1 (p[1]) désigne l’entrée du tube, il est ouvert en écriture seule.
6/33
Tubes anonymes
- La valeur retournée par « pipe() »:
0 en cas de succès. -1 sinon (en cas d’échec).
main () { int p[2]; pipe(p); /* création du tube p*/ …… }
7/33
Tubes anonymes
- Fermeture d’un descripteur
- Une fois le tube est créé, il est directement utilisable. (pas besoin de l’ouvrir)
- On doit fermer les descripteurs dont on n’a plus besoin avec la primitive «close()». close (int fd)
- Exemple:
close (p [1]); // fermeture du descripteur en écriture. close (p [0]); // fermeture du descripteur en lecture.
8/33
Ecriture dans un tubes anonymes
- L’écriture dans un tube se fait avec la primitive «write()» en utilisant le descripteur p[1]. int write (int p[1], void *zone, int nb_car); – « p[1] » : descripteur du flux. – « zone » : pointeur sur la zone mémoire contenant les données à écrire dans le tube. – « nb_car »: nombre d’octets (caractères) que l’on souhaite écrire dans le tube.
- Code retour:
– En cas de succès → write() retourne le nombre d’octets écrits. – En cas d’erreur → write() retourne -1.
9/33
Exemple
#include <stdio.h> #include <stdlib.h> #define N 6 main () {
char *c; int p[2]; int nb_ecrits; c=(char *)malloc(N*sizeof(char)); c= »ABCDEF »; pipe(p); /* création de tube */ if ((nb_ecrits=write(p[1],c,6)) = = -1) {
printf(« Erreur d’ecriture dans le tube \n »); exit(0);} printf(« nb_ecrits = %d \n « , nb_ecrits);}
10/33
Tubes anonymes
- Si le tube est plein → l’appelant est bloqué
– jusqu’à ce qu’il y ait suffisamment de place pour pouvoir écrire dans le tube. – La place se libère par la lecture dans le tube
- Un tube peut avoir plusieurs producteurs (possibilité d’entrelacement)
- Un producteur dans le tube est un processus qui détient le descripteur associé à ce tube. – Le signal « SIGPIPE » arrête l’exécution du processus.
11/33
Tubes anonymes
- Lorsqu’un processus tente d’écrire dans un tube sans lecteur → il reçoit le signal SIGPIPE et il sera interrompu (si on ne traite pas ce signal). main () { int p[2]; pipe (p); // création de tube close (p [0]); // descripteur en lecture est fermé printf(“ Debut d’ecritue dans le tube ”); if ( write(p[1], »abc »,3)==-1)
printf(“ erreur ecriture dans le tube”); else
printf(“Pas d erreur”); printf(“ processus termine ”); }
- Résultat d’exécution: Ce programme affiche le message « Debut d’ecritue dans le tube » et s’arrête.
12/33
Lecture dans un tube
- La lecture s’effectue avec la primitive de lecture de fichier « read()»
int read (int p[0], void *zone, int nb_car); – « P[0] » : descripteur du flux – « zone » : pointeur sur une zone mémoire dans laquelle les données seront écrites après lecture. – « nb_car »: nombre d’octets (caractères) que l’on souhaite lire à partir du tube.
- Code retour:
– En cas de succès, elle retourne le nombre d’octets effectivement lus. – En cas d’erreur cette fonction retourne -1
13/33
Exemple
#include <stdio.h> #include <stdlib.h> #define N 6 main () {
char *c, *s; int p[2]; int nb_lus, nb_ecrits; c=(char *)malloc(N*sizeof(char)); s=(char *)malloc(N*sizeof(char)); c= »ABCDEF »; pipe(p); /* création de tube */
14/33
Exemple
if ((nb_ecrits=write(p[1],c,N))==-1) {
printf(« Erreur d’ecriture dans le tube \n »); exit(0);} printf(« nb_ecrits = %d \n « , nb_ecrits); if ((nb_lus=read(p[0],s,N))==-1) {
printf(« Erreur de lecture dans le tube \n »); exit(0);} else if (nb_lus==0) {
printf(« pas de caractère lu \n »); exit(0);} printf( » la chaîne lue est : %s \n », s); }
15/33
Lecture dans un tube
- Remarques:
- Si le tube contient moins de « nb_car » octets, l’appelant est bloqué: – jusqu’à ce qu’il y ait au moins « nb_car » octets dans le tube. – ou jusqu’à ce que ce tube ne soit plus ouvert en écriture (close(p[1])).
- Si un lecteur tente de lire dans un tube vide alors:
– Si le tube n’a pas de rédacteur (le descripteur en écriture est fermé) alors la fonction « read() » retourne 0 caractères lus. – Si le tube a un rédacteur, alors il reste bloqué jusqu’à ce que le tube ne soit pas vide.
16/33
Exemple: Lecture dans un tube vide avec rédacteur
void main () { char c; int p[2]; int nb_lus; pipe (p); // création de tube if ((nb_lus==read(p[0],&c,1))==-1)
printf(« erreur lecture dans le tube »); else if (nb_lus==0)
printf(« Pas de caractère lu \n »); printf(« processus termine \n »); }→ Ce programme reste bloqué.
17/33
Exemple: Lecture dans un tube vide sans rédacteur
void main () { char c; int p[2]; int nb_lus; pipe (p); // création de tube close(p[1]); if ((nb_lus==read(p[0],&c,1))==-1)
printf(« erreur lecture dans le tube »); else if (nb_lus==0)
printf(« Pas de caractère lu \n »); printf(« processus termine \n »); }
Résultat Pas de caractère lu
processus termine → il n’y a pas de producteur (close(p[1]);), la fonction «read()» retourne 0.
18/33
Communication entre 2 processus
- Utilisation de l’appel système fork()
- Supposons que le père écrit dans le tube et le fils lit dans le tube
- Le processus crée le tube ; 2. Le processus fait un appel à fork() pour créer un fils. →Le père et le fils possèdent chacun un descripteur en lecture et en écriture sur le même tube 3. Le père ferme son descripteur en lecture. Le fils ferme son descripteur en écriture sur le tube. 4. Le processus père peut écrire sur le tube ; les valeurs écrites pourront être lues par le fils.
19/33
Tubes anonymes
- Exercice
– Ecrire un programme c qui permet de créer un
processus fils.
- Le père doit envoyer une chaine de caractère à son fils, puis attendre sa terminaison
- Le fils doit lire et afficher cette chaine.
20/33
#include <stdio.h> #include <unistd.h> #define TAILLE 30 main () {char envoi [TAILLE], reception [TAILLE];
int p [2], i, pid,val, etat; strcpy ( envoi, « texte transmis au tube »); val=pipe(p) ; if (val==-1) {
printf (« erreur de creation du tube « ); exit (1); }else { pid = fork();
if (pid == -1) {
printf(« erreur de creation du fils »); exit (2); }
if (pid > 0) /* le père écrit dans le tube */
{
close(p[0]) ; write (p[1], envoi, TAILLE); close (p[1]) ; wait (&etat); }
if (pid == 0) /* le fils lit à partir du tube */
{close(p[1]);
read (p[0], reception, TAILLE); printf ( » –> %s\n », reception); close (p[0]); exit (0); } } }
Duplication de descripteurs
- Duplication de descripteurs avec les primitives:
– « dup » – « dup2 »
#include <unistd.h> int dup(int ancien_fd) int dup2(int ancien_fd, int nouveau_fd)
- Les primitives « dup » and « dup2 »
– créent une copie du descripteur « ancien_fd ». – retournent
- le nouveau descripteur dans le cas de succès
- -1 dans le cas d’échec
23/33
Duplication de descripteurs
Valeur entière Nom int dup(int ancien_fd)
0 (STDIN_FILENO) clavier – copie « ancien_fd » dans
1 la première entrée libre
de la table des descripteurs
(STDOUT_FILENO) 2 ……….. Ecran Ecran
………………
…………. …………………. int dup2(int ancien_fd, int nouveau_fd) – Ferme le descripteur « nouveau_fd » – remplace nouveau_fd par « ancien_fd »
24/33
Duplication de descripteurs
- Après un appel réussi à « dup » ou « dup2 »,
– l’ancien et le nouveau descripteurs peuvent être utilisés
d’une manière interchangeable. – Ces primitives sont
- utile pour la redirection des flots d’entrées-sorties
- particulièrement utiles lors du recouvrement de processus
25/33
Tubes anonymes
- Exercice
– Ecrire un programme équivalent à la commande Shell suivante: ls|wc
– Pour cet exercice vous allez utiliser
- dup ou dup2 et• execlp variante de la primitive exec (expliqué dans ce qui suit)
26/33
Primitive exec()
- La famille de primitives « exec() »
– permet le lancement de l’exécution d’un programme externe provenant d’un fichier binaire. – permet le recouvrement d’un processus par un autre exécutable – Il n’y a pas création d’un nouveau processus, mais simplement changement de programme. – Remplace l’image mémoire du processus en cours par un nouveau processus. – L’identité du processus étant conservée.
- Diverses variantes de la primitive « exec() » existent et diffèrent selon le type et le nombre de paramètres passés.
– Une des variante est la primitive execlp()
27/33
Primitive execlp()
- La primitive «execlp() »
– int execlp(char *fiche, char *arg0,char *arg1,…,char *argn, NULL)
– « fiche » indique le nom du programme à exécuter: le programme à exécuter est situé dans un chemin de recherche de l’environnement
- Exemple: pour exécuter la commande « ls », « fiche » vaut « ls ». – « arg0 »: est le nom du programme à exécuter. En général identique au premier si aucun chemin explicite n’a été donné. Par exemple si le premier paramètre est « ls », « arg0 » vaut aussi: « ls ».
28/33
Primitive execlp()
- Exemple
– Lancer la commande « ls -l /tmp » à partir d’un programme.
#include <stdio.h> #include <unistd.h> int main(void){ execlp(« ls », »ls », »-l », »/tmp », NULL) ; perror(« echec de execlp \n »); }
29/33
#include <stdio.h> #include <unistd.h> int main() {
int p[2]; /* ouverture d’un pipe */ if(pipe(p)) {
printf (« pipe »); exit(1); }switch (fork()) {case -1 : /* erreur */ printf ( » pb fork « ); exit(1); break;
1ère version avec dup2
programme équivalent à la commande Shell suivante : ls|wc
30/33
1ère version avec dup2
case 0 : /* le processus fils execute la commande ls */
/* la sortie standard du processus est redirigee vers le tube */
dup2(p[1],1);//copie le descripteur p[1] en STDOUT_FILENO, en fermant
STDOUT_FILENO auparavant s’il était ouvert close (p[1]); close (p[0]); /* le processus ne lit pas dans le tube */ execlp (« ls », « ls », NULL); printf (« pb execlp(ls) »); exit(1); break;
default : /* le processus pere execute la commande wc */
/* l’entree standard du processus est redirigee vers le tube */ dup2(p[0],0); //copie le descripteur p[0] en STDIN_FILENO, en fermant
STDIN_FILENO auparavant s’il était ouvert
close (p[0]); close (p[1]); execlp (« wc », « wc », NULL);
printf (« pb execlp(wc) »); exit(1); break; } }
#include <stdio.h> #include <unistd.h> int main() {
int p[2]; /* ouverture d’un pipe */ if(pipe(p)) {
printf (« pipe »); exit(1); }switch (fork()) {case -1 : /* erreur */ printf ( » pb fork « ); exit(1); break;
2ère version avec dup
programme équivalent à la commande Shell suivante : ls|wc
32/33
case 0 : /* le processus fils execute la commande ls */
/* la sortie standard du processus est redirigee vers le tube */ close (STDOUT_FILENO); // ou close (1); dup(p[1]); // copie le descripteur p[1] vers la 1ere entrée libre de la table des descripteurs
(ici STDOUT_FILENO) close (p[1]); close (p[0]); /* le processus ne lit pas dans le tube */ execlp (« ls », « ls », NULL); printf (« pb execlp(ls) »); exit(1); break;
default : /* le processus pere execute la commande wc */
2ère version avec dup
/* l’entree standard du processus est redirigee vers le tube */ close (STDIN_FILENO);// ou close (0); dup(p[0]); // copie le descripteur p[1] vers la 1ere entrée libre de la table des descripteurs
(ici STDOUT_FILENO) close (p[0]); close (p[1]); execlp (« wc », « wc », NULL); printf(« pb execlp(wc) »); exit(1); break; } }
télécharger gratuitement Cours De Communication entre processus par tubes anonymes