Université Pierre et Marie Curie

Systèmes d'exploitation des ordinateurs

Chapitre 1. Les systèmes d'exploitation
Chapitre 2. Mécanismes d'exécution et de communication
Chapitre 3. Gestion des activités parallèles
3.1. Activités simultanées : pseudo parallèlisme
3.2. Mécanismes de synchronisation
3.2.1. Synchronisation par moniteur et sémaphores
3.2.2. Synchronisation par messages
3.2.3. Les pipes sous Unix
3.2.4. Etats d'un processus
Chapitre 4. Gestion des fichiers
Chapitre 5. Partage des ressources
Chapitre 6. Au-dessus du système d'exploitation
Chapitre 7. Notions sur les communications
Chapitre 8. Notions sur la sécurité
Bibliographie
Chapitre 9. Exercices et TPs
Examens
Page d'accueilTable des matièresNiveau supérieurPage précédenteBas de la pagePage suivante

3.2.3. Les pipes sous Unix

Principes

dd
figure 3.8 : Principe d'un pipe

Un pipe fonctionne à sens unique, d'où son nom (tuyau). L'information disparaît après lecture. Il existe deux catégories de pipes :

  • les pipes non nommés utilisables entre processus parents qui partagent le même environnement. Comme ces processus partagent le même envirtonnement ils peuvent se transmettre les caractéristiques des pipes sans difficulté.
  • les pipes nommés employés pour les communications entre processus sans liens de parenté. Ils s'apparentent à des fichiers en mémoire.

Le fonctionnement est sensiblement le même. Ce sont des fichiers d'un type spécial à usage interne pour la communication entre processus.  Le processus Q, lorsqu'il se met en attente de lecture d'une information que P doit lui transmettre à travers le pipe,   se synchronise sur ce dernier. Il reçoit de plus un message qui peut être utilisé pour diriger ses actions.

Pipes non nommés

Pour comprendre leur fonctionnement, en voici un exemple réduit à sa plus simple expression. On crée un pipe par la fonction pipe() puis un processus fils par fork(). Ce fils hérite de tout l'environnement du père donc du pipe et de ses caractéristiques. close(p[0]) ferme le pipe en lecture qui ne peut donc plus servir qu'en écriture. Celle-ci se fait par la fonction write() qui comprend trois arguments :

  • la structure d'écriture soit l'entrée p[1] du pipe
  • la cahîne à transmettre
  • le nombre de caractères de la chaine.

Notons que son usage est rigoureusement identique à son emploi lorsqu'on écrit dans un fichier.

Un deuxième fils est créé puis le pipe est fermé pour l'écriture puis employé pour lire les caractères envoyés par le premier fils de façon symétrique.

/*  communication entre deux processus fils  par pipe non nommé  */    
#include <stdio.h>  
#include <sys/signal.h>  
main()  
{     
   int fils1, fils2, m, n, p[2], count;     
      char buf[6];    
	  /*  création du pipe */     
	     pipe(p);    
		 /*  création premier fils  écrivain */     
		    if ((fils1=fork())== 0){          
			       close (p[0]);          
       fprintf(stdout,"fils 1 ( %d ) : écrit dans le pipe \n",getpid());          
       count = write(p[1],"salut",5);          
       fprintf(stdout,"         %d caractères dans le buffer\n", count);  
/*   fermeture du pipe  */          
       close(p[1]);          
       fprintf(stdout,"fils 1 : fin d'écriture dans le pipe\n");
       exit(2);
   }  
/*  création du deuxième fils lecteur */  
   else if ((fils2=fork())==0){      
       fprintf(stdout,"fils 2 ( %d ) : lit dans le pipe\n",getpid());     
       close (p[1]);     
       count = read(p[0],buf,5);      
       buf[5]='\0';      
       fprintf(stdout,"fils 2 : lu %d caractères :  %s\n",count,buf);       
       exit(3);   
   }
   else{      
      fprintf(stdout,"processus père %d\n",getpid());     
      close(p[0]);      
      close(p[1]);     
      fprintf(stdout, "wait fils : %d\n",wait(&m));       
      fprintf(stdout, "wait fils : %d\n",wait(&n));      
      fprintf(stdout, "fin du père\n");     
      exit(0);   
   }     
}

On notera la similarité avec l'emploi d'écriture et de lecture par fichier pour faire communiquer deux programmes.

Pipes nommés

Les pipes nommés fonctionnent sur un principe sensiblement équivalent. Les différences essentielles sont les suivantes :

  • les programmes de lecture et d'écriture dans le pipe sont séparés puisqu'ils correspondent au fonctionnement de deux processus indépendants.
  • Le pipe est créé indépendemment des deux processus et avant leur emploi. Pour cela on emploie la commande :
    mknod nom_du_pipe pi
    qui est analogue à une création de fichier.

Le pipe est ensuite employé de façon très similaire à un fichier avec ouverture par open et close après usage. Nous donnons ci-dessous des exemples de codes simples en écriture et en lecture.

Ecriture

/*   Ecriture non bloquante dans pipe nomme  */  
#include <stdio.h>  
#include <sys/signal.h>  
#include <fcntl.h>  
main()  
{
      int d, i, n;
      char buf[2000];  
/*  ouverture du pipe
       Il a été créé par mknod mon_pipe pi
       Ouverture :
         O_RDWR       lecture/écriture
         O_NDELAY non bloquant
         2 signifie  droit d'écriture (convention Unix) 
*/
      d = open("mon_pipe",O_RDWR|O_NDELAY,2);
      if (d < 0){
          fprintf(stderr,"problème d'ouverture du pipe\n");
          exit(1);
      }  
/*  remplissage du pipe    */
      for (i=0; i<2000; i++){
           buf[i]='n';
      }
      if ((n = write(d,buf,2000))>0)
          fprintf(stdout,"Ecriture de %d caractères\n",n);
      sleep(20);
      exit(0);
}

Il y a peu d'explications à donner. L'usage est très similaire à celui d'un fichier.

Lecture

/*  lecture bloquante dans pipe nomme  mon_pipe  */  
#include <stdio.h>  
#include <sys/signal.h>  
#include <fcntl.h>
main()  
{
     int d,i,n;
     char buf[2000];  
/*       open du pipe mon_pipe lecture bloquante
         O_RDONLY  lecture seulement  4  droit de lecture (convention Unix)  
*/
     d = open("mon_pipe",O_RDONLY,4);
     if (d < 0){
          fprintf(stderr,"Erreur ouverture du pipe\n");
          exit(1);
     }
     fprintf(stdout,"pipe ouvert\n");
     n = read(d,buf, 2000);
     fprintf(stdout,"Lecture de %d caractères\n",n);
     fprintf(stdout,"Les 20 premiers sont\n");
     for (i=0;i<20;i++){
     fprintf(stdout,"%c",buf[i]);
     }
     fprintf(stdout,"\n");
     exit(0);
} 

Le pipe est ouvert en lecture bloquante. Ceci signifie que le processus attendra l'arrivée de 2000 caractères avant de passer à l'instruction qui suit read.

Les pipes dans les shells de commande et sous Windows

pipes
Fig. 3.9 Fonctionnement d'un pipe sous Unix

L'emploi des pipes permet de réaliser des filtres d'une extrème puissance dans les shells.

Ils permettent de connecter la sortie d'une commande (stdout), affichée normalement sur l'écran, à l'entrée d'une suivante (stdin) qui est usuellement affectée au clavier (fig. 3.9).

Par exemple, imaginons que l'on veuille afficher la liste des fichiers contenus dans un répertoire par la commande ls. Si leur nombre est trop grand on ne pourra pas voir l'ensemble dans la fenêtre sur l'écran. En enchaînant la commande avec "more" l'affichage se trouvera bloqué par cette seconde commande qui permettra d'examiner à loisir la réponse avant de passer à la suite. Le pipe est symbolisé par le caractère | et la commande s'affiche comme suit :

ls -l | more

Les commandes ne se limitent pas à celles reconnues par les shells. Ce peuvent être les programmes des utilisateurs. C'est une façon fort commode d'enchaîner des programmes, la sortie à l'écran d'un premier étant reprise comme si elle était fournie par le clavier au second. Il n'y a pas de limite théorique au nombre de programmes ou de commandes que l'on peut ainsi enchaîner.

Windows permet également de créer des pipes, dans l'interface ligne à ligne.


Copyright Yves Epelboin, université P.M. Curie, 2000, MAJ 30 janvier, 2006

Page d'accueilTable des matièresNiveau supérieurPage précédenteHaut de la pagePage suivante