Solution de l'exercice unique

Allez, dites-le, j'ai exagéré la difficulté de l'exercice … Pourtant, il n'a rien d'infaisable, la séquence opératoire décrite dans l'énoncé permet de réaliser les opérations sans difficulté technique majeure. Aussi, je ne donnerai ici que le code brutal du processus émetteur emetteur.c, du processus récepteur recepteur.c ainsi que du fichier header commun commipc.h.

Fichier header commun commipc.h

#ifndef __COMM_IPC_H__
#define __COMM_IPC_H__


struct _MESSAGE_ALLER
{
  long  typeMessage;
  int   segID;
  int   semID;
  int   tailleDonnees;
  char  description[256];  
};

typedef struct _MESSAGE_ALLER MessageAller;

#define TYPE_MESSAGE 203

#endif

Ce fichier décrit simplement la structure utilisée pour les messages ainsi que la constante numérique définissant le type du message envoyé.

Processus récepteur recepteur.c

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include "commipc.h"

int main(int argc,char *argv[])
{
  key_t         clef;
  
  int           identBAL;
 
  FILE         *clients;
  FILE         *cible;
  
  char         *attacheMoi;
  

  struct sembuf semaphore;
  
  MessageAller  message;

  if (argc < 4)
  {
    puts("Arguments de ligne de commande manquants");
    printf("Usage : %s clef fichier_des_clients fichier_a_ecrire \n",argv[0]);
    exit(1);
  }
  
  clef=atoi(argv[1]);
  identBAL=msgget(clef,IPC_CREAT|IPC_EXCL|0666);
  
  if (identBAL == -1)
  {
    puts("Impossible de creer la boite aux lettres");
    exit(1);
  }
  
  
  /* Ajout d'une ligne dans le fichier des clients */
  
  clients=fopen(argv[2],"a+t");
  
  if (clients==NULL)
  {
    printf("Impossible d'ouvrir le fichier des clients %s en ajout\n",argv[2]);
    exit(1);
  }
  
  fprintf(clients,"%d\n",identBAL);
  fclose(clients);
  
  /* Mise en attente de la boite aux lettres */
  
  puts("Je me mets en attente");
  fflush(stdout);

  /* Ne pas oublier de retirer la taille du type de message lors de la reception */
  msgrcv(identBAL,&message,sizeof(message)-sizeof(long),0,MSG_NOERROR);
  
  
  puts(message.description);
  fflush(stdout);
  
  printf("Semaphore a debloquer : %d\n",message.semID);
  printf("Segment a lire        : %d\n",message.segID);


  /* Ecriture du fichier cible */
  
  attacheMoi=shmat(message.segID,NULL,0);
  
  cible=fopen(argv[3],"wt");
  fwrite(attacheMoi,1,message.tailleDonnees,cible);
  fclose(cible);
  
  shmdt(attacheMoi);
    
  /* Operation V */

  semaphore.sem_num =  0;
  semaphore.sem_op  =  1;
  semaphore.sem_flg =  0;
  
  semop(message.semID,&semaphore,1);
  
  /* destruction de la BAL */
  
  msgctl(identBAL,IPC_RMID,0);
  
  return 0;
  
}

Ce fichier est peu complexe et reprend la structure de communication du processus récepteur décrite dans l'énoncé. Il ne comprend aucune astuce significative.

Code du processus émetteur emetteur.c

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include "commipc.h"


/**************************************************************************
 * Processus emetteur
 *
 * Creation d'un segment de memoire partagee
 * Creation d'un semaphore de controle
 *
 *
 *
***************************************************************************/

/*******************************************************************************
 Nom :            fileToSharedMem
 
 But :            Lire un fichier et envoyer le contenu en memoire partagee
 
 Fonctionnement : Recupere la taille du fichier, alloue la memoire partagee
                  Lit le fichier et le ferme
                  
 Remarque       : Le transfert est de type binaire 
                
 Parametres :      
 
   Entree   :      clef : int         ; clef de creation du segment de memoire 
                                        partagee
                   nom  : const char* ; nom du fichier a lire

   Sortie   :      tailleF : int       ; taille du segment alloué
   
   Modif    :      NEANT
   
   Retour   :      entier : < 0 si operation echoue 
                               -1 : pbm avec la memoire partagee
                               -2 : pbm avec le fichier
                            handle de memoire partagee autrement
  
 
 
*******************************************************************************/

int fileToSharedMem(int clef, const char *nom, int *tailleF)
{
  FILE           *fichier;
  int             taille;
  int             resultatLecture;
  int             handleMemoirePartagee;
  char           *attacheMoi;
  
  struct stat     infosInode;
   
  
  /* Recuperation de la taille du fichier par lecture des informations
     contenues dans l'inode */
  
  if (stat(nom,&infosInode))
    return -2;

  taille=infosInode.st_size;
  
  /* Creation du segment de memoire partagee */
  
  handleMemoirePartagee=shmget(clef,taille,IPC_CREAT|IPC_EXCL|0666);
  if (handleMemoirePartagee == -1)
    return -1;
  
  /* Attachement du segment de memoire partagee */
  
  attacheMoi=shmat(handleMemoirePartagee,NULL,0);
  
  if (((int)attacheMoi)==-1)
    return -1;
    
  /* Lecture du fichier a la sauvage */
  
  fichier=fopen(nom,"rb");
  if (fichier == NULL)
    return -2;
  
  resultatLecture=fread(attacheMoi,1,taille,fichier);
  fclose(fichier);
  
  /* Detachement du segment de memoire partagee */
  
  shmdt(attacheMoi);
  
  if (resultatLecture != taille)
    return -2;
  else
  {
    *tailleF=taille;
    return handleMemoirePartagee;  
  }
}


int main(int argc,char *argv[])
{
  key_t         clef;
  
  int           identMem;
  int           identSem;
  
  int           tailleFichier;
  
  FILE         *clients;
  
  int           nbClients;
  int           compteurClients;
  int           balClients[16];
  char          chaineClient[256];
  
  struct sembuf semaphore;
  
  MessageAller  message;
  


  if (argc < 4)
  {
    puts("Arguments de ligne de commande manquants");
    printf("Usage : %s clef fichier_a_lire fichier_des_clients \n",argv[0]);
    exit(1);
  }
  
  clef=atoi(argv[1]);
  identMem=fileToSharedMem(clef,argv[2],&tailleFichier);
  
  switch (identMem)
  {
    case -2:
      printf("Pbm sur le fichier de nom %s\n",argv[2]);
      exit(1);
      break;
    case -1:
      printf("Impossible d'allouer ou d'attacher la memoire partagee");
      exit(1);
      break;
  }
    
  
  /* Creation des semaphores */
  
  
  identSem=semget(clef,1,IPC_CREAT |IPC_EXCL | 0666);
  if (identSem == -1)
  {
    puts("Impossible de creer un semaphore");
    exit(1);
  }
  
  /* Lecture du fichier des clients */
  
  puts("Lecture du fichier des clients");fflush(stdout);
  
  nbClients=0;
  
  clients=fopen(argv[3],"rt");
  
  while (!feof(clients))
  {
    fgets(chaineClient,255,clients);    
    if (!feof(clients))
    {
      balClients[nbClients]=atoi(chaineClient);
      nbClients++;
    }
  }
  
  fclose(clients);
  
  /* Envoi du message a tous les clients */
  
  message.typeMessage=TYPE_MESSAGE;
  message.semID=identSem;
  message.segID=identMem;
  message.tailleDonnees=tailleFichier;
  
  
  for (compteurClients = 0;compteurClients<nbClients;compteurClients++)
  {
    sprintf(message.description,"Envoi du message au client # %d",compteurClients);
    msgsnd(balClients[compteurClients],&message,sizeof(message),0);
  }
  
  semaphore.sem_num =  0;
  semaphore.sem_op  = -nbClients;
  semaphore.sem_flg =  0;
  
  printf("Nombre de clients : %d\n",nbClients);
  
  /* Operation P bloquante ! */
  
  semop(identSem,&semaphore,1);
  
  /* destructions en serie */
  
  semctl(identSem,0,IPC_RMID);
  shmctl(identMem,IPC_RMID,0);
  
  return 0;
  
}

Une fois de plus, le fichier est étonnament simple et ne comprend aucune astuce de programmation. Notez le cartouche de présentation de la procédure fileToSharedMem. En théorie, tous vos TPs devraient être présentés de la sorte !

Le script de lancement lanceur

Ce script Ksh lance les différents processus en mettant en place le bon environnement. Vous noterez qu'il utilise de manière intensive les spécificités de Ksh concernant les expressions arithmétiques !

#!/bin/ksh

if (( $# < 4 ))
then
  echo "Ligne de commande invalide"
  echo "Usage : $0 clef_de_base nombre_clients fichier_clients fichier_a_transferer"
  exit
else
  clefBase=$1
  nombreClients=$2
  fichierClients=$3
  fichierTransfert=$4
  rm -f ${fichierClients}
  compteurClients=1
  clef=${clefBase}
  while ((compteurClients < nombreClients))
  do
    ((clef+=1))
    ./recepteur ${clef} ${fichierClients} ${fichierTransfert}client${compteurClients} > /dev/null &
    ((compteurClients+=1))
  done
  ./emetteur ${clefBase} ${fichierTransfert} ${fichierClients} 
fi

Un utilitaire bien pratique !

L'utilitaire suivant permet de supprimer toutes les fonctionnalités ipc de l'utilisateur courant. Il est composé d'un script awk et d'un script shell appelant awk. Il peut être utile de se se référer à la documentation sur awk afin de comprendre les 2 subtilités incluses dans le script.

Commençons tout de suite par le script shell d'une simplicité biblique : il se contente d'appeler awk. Je l'ai appelé ipcclean sur mon système. Il m'a été particulièrement utile lors de la mise au point de l'exercice.

ipcs | awk -f ipcclean.awk

Le script awk est lui plus compliqué, voici son source, il doit s'appeler ipcclean.awk pour être compatible avec le script shell précédent. Son but est de construire petit à petit une ligne de commande pour la commande ipcrm à partir du résultat de la commande ipcs.

BEGIN{
  commande=""
  "whoami" | getline utilisateur
}
$5 == utilisateur{
  commande=commande " -"$1 " "$2
}
END{
  system("ipcrm "commande)
}

Etudions brièvement les trois parties :

Partie BEGIN
Cette partie initialise une variable chaîne à la chaîne vide. Je sais, c'est fait automatiquement, mais je ne peux pas m'empêcher d'être paranoïaque.
La variable utilisateur est lue à partir du résultat de la commande whoami qui renvoie le nom de l'utilisateur courant.
Partie Pattern
La reconnaissance porte ici sur le 5ème champ qui doit concorder exactement avec le nom d'utilisateur courant. Un simple coup d'oeil à la commande ipcs vous montrera immédiatement que le 5ème champ d'une ligne d'ipcs correspond au propriétaire d'une fonctionnalité. Si la condition est vérifiée, on concatène à la ligne de commande courante, un signe -, la première colonne (qui correspond au type de fonctionnalité), un espace puis la seconde colonne (qui dénote l'identificateur numérique de la facilité).
Partie END
Il ne reste plus qu'à appeler ipcrm avec la ligne de commande ainsi construite à l'aide de la fonction interne system.