Blue Flower

Chercher

Communication entre processus (IPC System V)

Caractéristiques communes
Les IPC sont les mécanismes de communication inter-processus. Ce sont les files de messages, la mémoire partagée, et les sémaphores.
Ces mécanismes de communication (qui ne sont pas introduits dans la norme POSIX) ne sont pas identifiés par des descripteurs de fichiers et ne peuvent donc pas se manipuler en utilisant les appels système relatifs aux fichiers ou les fonctions de la bibliothèque standard d'E/S.

Ces outils de communication peuvent être partagés entre des processus n'ayant pas immédiatement d'ancêtre commun. Pour cela, les IPC introduisent le concept de clé. Dans chaque système UNIX on dispose d'une table spécifique qui gère les IPC System V. Chaque objet (Objet ici n'est pas vu comme l'instance d'une classe) possède une identification interne (nombre entier naturel) dont la connaissance est indispensable pour accéder à l'objet ; on obtient cette identification par héritage ou par interrogation du système à l'aide de routines comme msgget pour les files de messages, shmget() pour la mémoire partagée et semget() pour les sémaphores .

ftok(): constitution d'une clé
La fonction ftok() est définie comme suit dans <sys/ipc.h>

key_t ftok (char * nom_fichier, char projet);

Cette fonction crée une clé (un entier de 32 bits) à partir d'un chemin d'accès (un fichier - le numéro d'inode du fichier et son device -) et d'un caractère indiquant un «projet». si on change l'inode associée à la référence, cela change la valeur de la clé, donc il n'est plus possible de retrouver la clé originale.
La meilleure façon pour avoir une clé consiste à demander au système de la créer lui-même, en se basant sur des références communes pour tous les processus. De cette manière, tous les processus d'un ensemble donné pourront choisir de créer leur clé commune en utilisant le chemin d'accès du fichier exécutable de l'application principale, ainsi qu'un numéro de version par exemple. La constante symbolique IPC_PRIVATE, définie dans <sys/ipc.h> représente une clé privée, demandant sans condition la création d'une nouvelle ressource IPC.
Une fois qu'on a obtenu une clé ou qu'on a choisi d'utiliser une ressource privée avec IPC_PRIVATE, on peut demander l'accès à l'IPC proprement dite. l'emploi de IPC_PRIVATE n'empêche pas l'accès à la ressource par un autre processus, mais garantit uniquement qu'une nouvelle ressource sera créée. L'identifiant renvoyé par la routine d'ouverture n'aura rien d'exceptionnel; un autre processus pourra l'employer à condition d'avoir les autorisations d'accès nécessaires.

Ouverture d'un IPC
Chaque ressource IPC( file de message, sémaphore ou mémoire partagée) possède une routine qui permet de l'obtenir.
Pour les files de message, on peut envoyer ou recevoir un message en se servant respectivement de msgsnd() et msgget(). Ces fonctions demandent au système de créer éventuellement la ressource si elle n'existe pas, puis de renvoyer un numéro d'identification. Si la ressource existe déjà et si le processus appelant n'a pas les autorisations nécessaires pour y accéder, les routines échouent en renvoyant -1. On peut attacher puis de détacher un segment de mémoire partagée dans l'espace d'adressage du processus avec shmat( ) ou shmdt( ) et enfin lever de manière bloquante ou non un sémaphore, puis de le relâcher avec la fonction commune semop( ).

Contrôle : paramètres
Les IPC sont accompagnés aussi de fonctions qui permettent de positionner des options de paramètrages; ces fonctions msgctl( ), shmctl( ) et semctl( ) permettent d'atteindre et de modifier les attributs des options de paramamétrage qui se trouvent dans les trois structures msgid_ds, shmid_ds et semid_ds. Ces structures permettent l'accès à un objet de type

struct ipcperm {
_key key_t           ; 	/* Clé associée à la ressource IPC*/
_seq unsigned short  ; 	/* Numéro de séquence,*/
                      	 /* utilisé de manière interne par le*/
                       	/* système, à ne pas toucher*/
mode unsigned short  ; /* Autorisations d'accès à la ressource, comme pour les*/
                       	/* permissions des fichiers  */
uid uid_t          ;   /* UID effectif de la ressource IPC*/
gid gid_t          ;   /* GID effectif de la ressource IPC*/
cuid uid_t         ;   /* UID du créateur de la ressource*/
cgid gid_t         ;   /* GID du créateur de la ressource*/
}

défini dans <sys/ipc.h> . Les modifications de mode ne peuvent être réalisées que par le propriétaire,créateur de la ressource ou par un processus ayant la capacité CAP_IPC_OWNER. Les fonctions de contrôle permettent également de détruire une ressource IPC.


Mémoires partagées

Généralités
Ce mécanisme permet à plusieurs processus distincts de partager des segments de mémoire définis dynamiquement par l'un d'eux. Chaque segment de mémoire est identifié, au niveau du système, par une clé à laquelle correspond un identifiant appelé shmid (Shared Memory Identification) qui référence une entrée dans la table des segments de mémoire.
Chaque entrée dans cette table a une structure shmid_ds définie dans <sys/shm.h> qui contient notamment les champs :

  • Shm_perm : structure ipc_perm décrivant les permissions.
  • Shm_segsz : taille du segment.
  • Shm_lpid : pid du dernier processus utilisateur.
  • Shm_cpid : pid du processus créateur du segment.
  • Shm_natch : nombre d'attachements.
  • Shm_atime : date du dernier attachement.
  • Shm_dtime : date du dernier détachement.
  • Shm_ctime : date de la dernière opération de contrôle.

Création d'un segment
La routine shmget(key_t key, int size, int shmflag) permet de créer ou de retrouver un segment de mémoire partagée. En cas de succès, elle renvoie un shmid (entier > 0), sinon elle renvoie «-1» et «errno...» . Le paramètre key est une clé d'accès au segment. Pour réserver le segment au processus créateur et à sa descendance, on lui donne la valeur IPC_PRIVATE. Le paramètre size est la taille du segment. Le paramètre shmflag règle les droits d'accès et certains indicateurs de création et de recherche :

  • - IPC_CREAT : création d'un IPC s'il n'existe pas.
  • - IPC_ALLOC : recherche d'un IPC supposé exister.
  • - IPC_EXCL : erreur si l'IPC existe déjà.
  • - 0xyz : codage octal des droits d'accès.
  • Donc en un mot la fonction shmget() dont le prototype est "int shmget (key_t key, int size, int shmflg);"

donne l'identifiant du segment ayant la clé key. Un nouveau segment (de taille size) est créé si key est IPC_PRIVATE, ou bien si les indicateurs de shmflg contiennent IPC_CREAT. Combinées, les options IPC_EXCL | IPC_CREAT indiquent que le segment ne doit pas exister préalablement. Les bits de poids faible de shmflg indiquent les droits d'accès.
Exemples:
1 : IPC_CREAT|IPC_EXCL|0666 : création d'un s.m.p. avec rw, avec échec si le segment existe déjà.
2 : IPC_ALLOC : recherche de segment existant avec retour du shmid si existence et retour de –1 sinon.

Attachement et détachement à un segment
La fonction shmat() dont le protype est

char *shmat (int shmid, char *shmaddr, int shmflag );

attache le segment shmid en mémoire, avec les droits spécifiés dans shmflag (SHM_R, SHM_W, SHM_RDONLY). shmaddr précise où ce segment doit être situé dans l'espace mémoire (la valeur NULL demande un placement automatique).
shmat() renvoie l'adresse où le segment a été placé.
Lorsqu'un segment est attaché à un programme, les données qu'il contient sont accessibles en mémoire par l'intermédiaire d'un pointeur.
La routine shmat() rendre visible un segment de mémoire partagée dans l'espace d'adressage d'un processus. Elle retourne l'adresse d'attachement du segment, sinon elle retourne «-1» ;
le paramètre shmid est le descripteur du segment,
le paramètre shmaddr l'adresse d'attachement ( il est conseillé de laisser le système de choisir le point de greffe en passant zéro comme argument) et le paramètre shmflag est en général égal à zéro, ce qui signifie que le segment est attaché en lecture/écriture. On peut interdire toute écriture en passant SHM_RDONLY comme argument.
shmdt() permet de libérer le segment et en cas d'erreur il retourne «-1».

int shmdt (char *shmaddr);

Contrôle d'un segment
Une fois que le segement est créé ou retrouvé par la routine shmget(), on contrôle son utilisation par la routine shmctl() en tenant compte des fichiers «.h» à déclarer avant d'utiliser shmctl() dont le protype est

int shmctl(int shmid, int cmd, struct shmid_ds *buf) ;

Cette routine shmctl(int shmid, int cmd, struct shmid_ds *buf) permet de Consulter, modifier les caractéristiques d'un segment, ou même le supprimer. Elle retourne «0» en cas de succès, et «-1» sinon.
Le paramètre shmid est un descripteur de segment.
Le paramètre cmd précise l'opération de contrôle à réaliser :
- IPC_STAT : dans ce si à la place de cmd on IPC_STAT on a une Consultation,
- IPC_SET : entraîne une Modification du segment de mémoire et
- IPC_RMID : entre purement et simplement la Suppression du segment.
Le paramètre buf est l'adresse d'un objet de structure shmid_ds déclaré par l'utilisateur.

Exemples : producteur/consommateur
Le producteur :
Ce programme lit une suite de nombres, et effectue le cumul dans une variable en mémoire partagée.

Le producteur :
           /* prod.c */
           /*
              Ce programme lit une suite de nombres, et effectue le cumul dans une
              variable en mémoire partagée. 
	   */
           #include <sys/ipc.h>
           #include <sys/shm.h>
           #include <sys/types.h>
           #include <stdlib.h>
           #include <stdio.h>
          #include <errno.h>
          void abandon(char message[]){
              perror(message);
              exit(EXIT_FAILURE);
          }
          struct donnees {
              int nb;
              int total;
          };
          int main(void)
          {
              key_t cle;
              int id;
              struct donnees *commun;
              int reponse;
              cle = ftok(getenv("HOME"), 'A');
              if (cle == -1)
                  abandon("ftok");
              id = shmget(cle, sizeof(struct donnees),
                          IPC_CREAT | IPC_EXCL | 0666);
              if (id == -1) {
                  switch (errno) {
                  case EXIST:
                      abandon("Note: le segment existe déjà\n");
                  default:
                      abandon("shmget");
                  }
              }
              commun = (struct donnees *) shmat(id, NULL, SHM_R | SHM_W);
              if (commun == NULL)
                  abandon("shmat");
              commun->nb = 0;
              commun->total = 0;

              while (1) {
                  printf("+ ");
                  if (scanf("%d", &reponse) != 1)
                      break;
                  commun->nb++;
                  commun->total += reponse;
                  printf("sous-total %d= %d\n", commun->nb, commun->total);
              }
              printf("---\n");

              if (shmdt((char *) commun) == -1)
                  abandon("shmdt");
              /* suppression segment */
              if (shmctl(id, IPC_RMID, NULL) == -1)
                  abandon("shmctl(remove)");
              return EXIT_SUCCESS;
          }

Explications:
La création de la clé avec la fonction ftok();. Si ce qu'on retourne dans «cle» est égale à «-1» on sort sinon on continue c'est à dire qu'on a une bonne clé.
Avec cette clé (cle) on créé le segment de mémoire avec la routine shmget(). Si id vaut «-1 » on abandonne sinon on continue
en attachant ce segment de mémoire à une variable en l'occurrence ici "commun" qui est une adresse dont la taille peut contenir la structure «struct donnee » . La valeur de retour est l'adresse où l'attachement a été effectivement réalisé, c'est-à-dire celle attribuée au premier octet du segment. cette opération est réalisée par la routine shmat() ; une fois qu'on a réalisé l'attachement (on vérifie si l'adresse retournée n'est pas égale à NULL) on initialise les champs de la structure «commun» (commun->nb = 0; et commun->total = 0;)

Le consommateur :
Ce programme affiche périodiquement le contenu de la mémoire partagée. et pour l'arrêter on fait Contrôle-C

         /* cons.c */
           /*
              Ce programme affiche périodiquement le contenu de la 
              mémoire partagée. Arrêt par Contrôle-C
           */
           #include <sys/ipc.h>
           #include <sys/shm.h>
           #include <sys/types.h>
           #include <unistd.h>
          #include <stdlib.h>
          #include <stdio.h>
          #include <errno.h>
          #include <signal.h>
          #define DELAI 2
          void abandon(char message[])
          {
              perror(message);
              exit(EXIT_FAILURE);
          }
          struct donnees {
              int nb;
              int total;
          };
          int continuer_boucle = 1;
          void arreter_boucle(int signal)
          {
              continuer_boucle = 0;
          }
          int main(void)
          {
              key_t cle;
              int id;
              struct donnees *commun;
              struct sigaction a;
              cle = ftok(getenv("HOME"), 'A');
              if (cle == -1)
                  abandon("ftok");
              id = shmget(cle, sizeof(struct donnees), 0);
              if (id == -1) {
                  switch (errno) {
                  case ENOENT:
                      abandon("pas de segment\n");
                  default:
                      abandon("shmget");
                  }
              }
              commun = (struct donnees *) shmat(id, NULL, SHM_R);
              if (commun == NULL)
                  abandon("shmat");
              continuer_boucle = 1;
              a.sa_handler = arreter_boucle;
              sigemptyset(&a.sa_mask);
              a.sa_flags = 0;
              sigaction(SIGINT, &a, NULL);
              while (continuer_boucle) {
                  sleep(DELAI);
                  printf("sous-total %d= %d\n", commun->nb, commun->total);
              }
              printf("---\n");
              if (shmdt((char *) commun) == -1)
                  abandon("shmdt");
              return EXIT_SUCCESS;
          }

Eplications:
Ce programme va lire ce qui est écrit dans le segment de mémoire partagé ; par le programme consommateur; avec la fonction ftock() on va récupérer la clé "cle", s'en servir pour recupérer son "id" à travers la fonction shmget(),


Sémaphores

Sémaphores : présentation
Les sémaphores sont des variables partagées, dont l'accès ne se fait que grâce aux deux opérations atomiques( opérations totalement ininterruptibles, qui ont toujours lieu séquentiellement même sur une machine multi-processeurs) Pn et Vn . Ils permettent de réaliser l'accès en exclusion mutuelle à une ressource ; Cela veut dire que lorsque des processus ont accès à une ressource partagée, si un d'entre eux y accède, les autres ne pourront pas y accèder tant que celui qui est dessus n'ait fini .
Un sémaphore peut servir à l' accès et au contrôle non pas d'une simple ressource critique mais à plusieurs exemplaires d'une même ressource. Ainsi on a à faire à un compteur qu'on augmente ou qu'on diminue d'une valeur entière qui n'est pas nécessairement «1». L'opération Pn( ) est alors bloquante tant que le compteur du sémaphore est inférieur à la valeur n demandée, puis elle diminue le compteur de cette quantité. Parallèlement, l'opération Vn( ) doit incrémenter le compteur de la valeur «n» d'exemplaires libérés de la ressource. En C/C++ pour se servir des sémaphores on utilise les bibliothèques

 #include <sys/types.h>
 #include <sys/ipc.h>
 #include <sys/sem.h>

Les opérations System V travaillent en fait sur des tableaux de sémaphores généralisés (pouvant évoluer par une valeur entière quelconque).

Création de sémaphore
Un sémaphore est une variable dont les valeurs possibles sont des entiers positives ou nuls; le système associe à cette variable une structure du type «struct sem »définie comme suit:

struct sem{
ushort semval;  /* Valeur du sémaphore                  */
ushort sempid;  /* PID dernière opération               */
ushort semncnt; /* Nb proc. en attente incrément semval */
ushort semzcnt; /* Nb proc. en attente de semval = 0    */
}

La fonction semget(key_t key, int nsems, int semflg ) retourne l'identificateur de l'ensemble de sémaphores généralisé associé à la valeur de clé key; cet ensemble contient nsems sémaphores individuels. Le prototype de semget() est:

int semget(key_t key, int nsems, int semflg );

Le nouveau ensemble de sémaphores créé ou sollicité, a les droits donnés par les 9 bits de poids faible de semflg; si key est égale à IPC_PRIVATE, ou si semflg contient IPC_CREAT c'est automatiquement un nouveau ensemble de nsems sémaphores qui est créé. A chaque ensemble de sémaphores le noyau associe une structure d'informations semid_ds:

struct semid_ds {
	struct ipc_perm sem_perm;	/* operation permission struct */
	struct sem	*sem_base;	/* ptr to first semaphore in set */
	ushort_t	sem_nsems;	/* # of semaphores in set */
	time_t		sem_otime;	/* last semop time */
	long		sem_pad1;	/* reserved for time_t expansion */
	time_t		sem_ctime;	/* last change time */
	long		sem_pad2;	/* time_t expansion */
	long		sem_pad3[4];	/* reserve area */
};

Pendant la création, la structure de données semid_ds qui contrôle le jeu de sémaphores est initialisée comme suit :
sem_perm.cuid et sem_perm.uid contiennent l'UID effectif du processus appelant.
sem_perm.cgid et sem_perm.gid contiennent le GID effectif du processus appelant.
le 9 bits de poids faibles de sem_perm.mode sont remplis avec les 9 bits de poids faibles de semflg.
sem_nsems reçoit la valeur nsems.
sem_otime est mis a 0.
sem_ctime est rempli avec l'heure actuelle.
l'argument nsems peut valoir 0 (ignore) si l'appel système n'est pas une création d'ensemble de sémaphores. Autrement nsems doit être supérieur à 0 et inférieur ou égal au nombre maximal de sémaphores SEMMSL par ensemble.
Si le jeu de sémaphores existe déjà, les permissions d'accès sont contrôlées, et l'on vérifie si l'ensemble est sur le point d'être détruit.
L'entier retourné par semget() est le semid de l'ensemble de sémaphores ou «-1 » en cas d'erreur

synchronisation des opérations liées au sémaphore
La routine semop(int semid, struct sembuf *sops, unsigned nsops) agit sur l'ensemble de sémaphores identifié par semid en appliquant simultanément à plusieurs sémaphores individuels les actions décrites dans les nsops premiers éléments du tableau sops. Son prototype est :

int semop(int semid, struct sembuf *sops, unsigned nsops);

La routine semop( ) sert à la fois pour les opérations Pn( ) et Vn( ), sur de multiples sémaphores appartenant au jeu indiqué en premier argument. L'opération effectuée est déterminée ainsi :
-Lorsque le champ sem_op d'une structure sembuf est strictement positif, le noyau incrémente le compteur interne associé au sémaphore de la valeur indiquée et réveille les processus en attente.
Quand sembuf.sem_op = n, avec n > 0, alors l'opération est Vn( ).
-Lorsque le champ sem_op est strictement négatif, le noyau endort le processus jusqu'à ce que le compteur associé au sémaphore soit supérieur à sem_op, puis il décrémente le compteur de cette valeur avant de continuer l'exécution du processus.
Quand sembuf.sem_op = n, avec n < 0. alors l'opération est Pn( ).
-Lorsque le champ sem_op est nul, le noyau endort le processus jusqu'à ce que le compteur associé au sémaphore soit nul, puis il continue l'exécution du programme. Cette fonctionnalité permet de synchroniser les processus.
La routine semop( ) prend en second argument une table de structures sembuf. Le nombre d'éléments dans cette table est indiqué en dernière position. Le noyau garantit que les opérations seront atomiquement liées, ce qui signifie qu'elles seront toutes réalisées ou qu'aucune ne le sera. Bien entendu, il suffit qu'une seule opération avec sem_op négatif ou nul échoue avec l'attribut IPC_NOWAIT pour que toutes les modifications soient annulées.
Chaque sembuf est une structure de la forme

 struct sembuf{ 
  short sem_num;  /* semaphore number: 0 = first */
  short sem_op;   /* semaphore operation */
  short sem_flg;  /* operation flags */
  }

sem_flg est une combinaison d'indicateur qui peut contenir IPC_NOWAIT et SEM_UNDO . Ici nous supposons que sem_flg est 0.
sem_num indique le numéro du sémaphore individuel sur lequel porte l'opération.
sem_op est un entier destiné (sauf si il est nul) à être ajouté à la valeur courante semval du sémaphore. L'opération se bloque si «sem_op + semval < 0 ».
Cas particulier : si sem_op est 0, l'opération est bloquée tant que semval est non nul. Les valeurs des sémaphores ne sont mises à jour que lorsque aucun d'eux n'est bloqué.
Il existe deux options possibles pour le membre sem_flg :
- IPC_NOWAIT : l'opération ne sera pas bloquante, même si le champ sem_op est négatif ou nul, mais l'appel-système indiquera l'erreur EAGAIN dans errno si l'opération n'est pas réalisable.
- SEM_UNDO : pour être sûr que le sémaphore retrouvera un état correct même en cas d'arrêt intempestif du programme, le noyau va mémoriser l'opération inverse de celle qui a été réalisée et l'effectuera automatiquement à la fin du processus.

Contrôle de sémaphore
semctl(sem,n,SETVAL,val) permet de réaliser diverses opérations sur l'ensemble des sémaphores, selon la commande demandée et son prototype est :

int semctl(int semid, int semnum, int cmd, union semun arg )

En particulier, on peut fixer le n-ième sémaphore à la valeur val en faisant :
semctl(sem,n,SETVAL,val);


Exemples sur les sémaphores IPC V:

1) Exemple 1 :
Ce programme est une illustration des sémaphores dijkstra. Avec la routine fork() on créé un processus fils qui va donc s'éxecuter parallèlement avec le processus père ; chaque processus utilise un sémaphore pour communiquer; les deux processus font uniquement ce q'on leur demande. Quand Le processus notifie que le sémaphore track est libre quand il retourne 0; chaque processus doit modifier le sémaphore en conséquence.

 
#include <stdio.h>
 #include <sys/types.h>
 #include <sys/ipc.h>
 #include <sys/sem.h>
 union semun {
               int val;
               struct semid_ds *buf;
               ushort *array;
          };
main()
{ int i,j; 
  int pid;
  int semid; /* semid de l'ensemble de semaphores */
  key_t key = 1234; /* la clé associée  */
  int semflg = IPC_CREAT | 0666; /* semflg to pass to semget() */
  int nsems = 1; /* nsems to pass to semget() */
  int nsops; /* number of operations to do */
  struct sembuf *sops = (struct sembuf *) malloc(2*sizeof(struct sembuf)); 
  /* ptr to operations to perform */
  /* set up semaphore */  
  (void) fprintf(stderr, "\nsemget: Setting up seamaphore: semget(%#lx, %\
 %#o)\n",key, nsems, semflg);
   if ((semid = semget(key, nsems, semflg)) == -1) {
	perror("semget: semget failed");
	exit(1);
      } else 
	(void) fprintf(stderr, "semget: semget succeeded: semid =\
%d\n", semid);
  /* get child process */ 
   if ((pid = fork()) < 0) {
        perror("fork");
        exit(1);
    }   
if (pid == 0)
     { /* child */
       i = 0;      
       while (i  < 3) {/* allow for 3 semaphore sets */       
       nsops = 2;       
       /* wait for semaphore to reach zero */       
       sops[0].sem_num = 0; /* We only use one track */
       sops[0].sem_op = 0; /* wait for semaphore flag to become zero */
       sops[0].sem_flg = SEM_UNDO; /* take off semaphore asynchronous  */              
       sops[1].sem_num = 0;
       sops[1].sem_op = 1; /* increment semaphore -- take control of track */
       sops[1].sem_flg = SEM_UNDO | IPC_NOWAIT; /* take off semaphore */      
     /* Recap the call to be made. */       
    (void) fprintf(stderr,"\nsemop:Child  Calling semop(%d, &sops, %d) with:", semid, nsops);
       for (j = 0; j < nsops; j++)
	{
	  (void) fprintf(stderr, "\n\tsops[%d].sem_num = %d, ", j, sops[j].sem_num);
	  (void) fprintf(stderr, "sem_op = %d, ", sops[j].sem_op);
	  (void) fprintf(stderr, "sem_flg = %#o\n", sops[j].sem_flg);
	}	
       /* Make the semop() call and report the results. */
	if ((j = semop(semid, sops, nsops)) == -1) {
		perror("semop: semop failed");
		} 
	   else 
      {
		(void) fprintf(stderr, "\tsemop: semop returned %d\n", j);	
		(void) fprintf(stderr, "\n\nChild Process Taking Control of Track: %d/3 times\n", i+1);
		sleep(5); /* DO Nothing for 5 seconds */
		nsops = 1;     
       	/* wait for semaphore to reach zero */
       		sops[0].sem_num = 0;
           sops[0].sem_op = -1; /* Give UP COntrol of track */
           sops[0].sem_flg = SEM_UNDO | IPC_NOWAIT; /* take off semaphore, asynchronous  */
       	if ((j = semop(semid, sops, nsops)) == -1) {
			perror("semop: semop failed");
			} 
		   else
		   (void) fprintf(stderr, "Child Process Giving up Control of Track: %d/3 times\n", i+1);
       	   sleep(5); /* halt process to allow parent to catch semaphor change first */
     }
      ++i;
      }      
     }
  else /* parent */
     {  /* pid hold id of child */    
       i = 0;       
       while (i  < 3) { /* allow for 3 semaphore sets */       
       nsops = 2;       
       /* wait for semaphore to reach zero */
       sops[0].sem_num = 0;
       sops[0].sem_op = 0; /* wait for semaphore flag to become zero */
       sops[0].sem_flg = SEM_UNDO; /* take off semaphore asynchronous  */      
       sops[1].sem_num = 0;
       sops[1].sem_op = 1; /* increment semaphore -- take control of track */
       sops[1].sem_flg = SEM_UNDO | IPC_NOWAIT; /* take off semaphore */     
     /* Recap the call to be made. */     
     (void) fprintf(stderr,"\nsemop:Parent Calling semop(%d, &sops, %d) with:", semid, nsops);
      for (j = 0; j < nsops; j++)
	{
	  (void) fprintf(stderr, "\n\tsops[%d].sem_num = %d, ", j, sops[j].sem_num);
	  (void) fprintf(stderr, "sem_op = %d, ", sops[j].sem_op);
	  (void) fprintf(stderr, "sem_flg = %#o\n", sops[j].sem_flg);
	}
	/* Make the semop() call and report the results. */
	if ((j = semop(semid, sops, nsops)) == -1) {
		perror("semop: semop failed");
		} 
	   else 
      {
		(void) fprintf(stderr, "semop: semop returned %d\n", j);	
		(void) fprintf(stderr, "Parent Process Taking Control of Track: %d/3 times\n", i+1);
		sleep(5); /* Do nothing for 5 seconds */
      nsops = 1;       
      /* wait for semaphore to reach zero */
      sops[0].sem_num = 0;
      sops[0].sem_op = -1; /* Give UP COntrol of track */
      sops[0].sem_flg = SEM_UNDO | IPC_NOWAIT; /* take off semaphore, asynchronous  */       
      if ((j = semop(semid, sops, nsops)) == -1) {
			perror("semop: semop failed");
		} 
		else
		(void) fprintf(stderr, "Parent Process Giving up Control of Track: %d/3 times\n", i+1);
      sleep(5); /* halt process to allow child to catch semaphor change first */
       }
       ++i;       
      }      
     }
}

Explications :
- Après la création de l'ensemble de sémaphore avec la clé 1234 deux processus sont créés
- Chaque processus ( père et fils) produit les mêmes opérations:
- Chaque processus accède au même sémaphore track (sops[].sem_num = 0).
- Chaque processus attend que track soit libre et prend donc le contr&ocrc;le.
Cela prend fin par la mise de la bonne valeur dans sops[].sem_op.
- Une fois que le processus a le contrôle il attend 5 secondes (in reality some processing would take place in place of this simple illustration)
- Puis le processus abandonne le contrôle de track sops[1].sem_op = -1 .
- Un slep suplémentaire est réalisé pour être sûr qu'un autre n'ait pas le temps de prendre la main.
Remarque: Il n'y a pas de synchronisation ici; dans ce simple exemple seul l'OS positionne le processus qui doit prendre la main.

2 ) exemple :
C'est une implementation du probleme "producteur-consommateur" avec un buffer de capacite limitée. la synchronisation se fait au moyen de semaphores.

/*buffer.c*/
#include <stdio.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/ipc.h>
#include "../include/shmdef.h"
#include "../include/semdef.h"
#define BUFSIZE 10
int produce(){
  int n;
  n = rand() % 1000;
  fprintf(stdout,"producing %d\n",n);
  return(n);
}
consume(n)
     int n;
{
  fprintf(stdout,"            consuming %d\n",n);
}
append(n,b,in)
     int n,*b,*in;
{
  b[*in] = n;
  *in = (*in+1) % BUFSIZE;
}
int take(b,out)
     int *b,*out;
{
  int n;
  n = b[*out];
  *out = (*out+1) % BUFSIZE;
  return(n);
}
main(argc,argv)
     int argc;
     char *argv[];
{
  int shmid1, size, semid1, semid2, semid3, pid, fathersleep, sonsleep;
  int in, out;
  if (argc != 3) {
    fathersleep = 0;
    sonsleep = 0;
  }
  else {
    fathersleep = atoi(argv[1]);
    sonsleep = atoi(argv[2]);
  }
  size = BUFSIZE * sizeof(int);
  shmid1 = shmcreate(size);
  semid1 = screate();
  semid2 = screate();
  semid3 = screate();

  ssetval(semid1,1);
  ssetval(semid2,0);
  ssetval(semid3,BUFSIZE);
  in = 0;
  out = 0;
  if((pid=fork()) == 0) {
    int *addr, n, *b, i;
    addr = (int *)shmattach(shmid1);
    b = addr;
    for(i=1;i<=20;i++){
      swait(semid2);
      swait(semid1);
      n = take(b,&out);
      ssignal(semid1);
      ssignal(semid3);
      consume(n);
      sleep(sonsleep);
    }
  }
  else {
    if (pid != -1) {
      int *addr, n, *b, i, status;
      addr = (int *)shmattach(shmid1);
      b = addr;
      for(i=1;i<=20;i++) {
	sleep(fathersleep);
	n = produce();
	swait(semid3);
	swait(semid1);
	append(n,b,&in);
	ssignal(semid1);
	ssignal(semid2);
      }
      while(wait(&status) != -1);
      semremove(semid1);
      semremove(semid2);
      semremove(semid3);
      shmremove(shmid1);
    }
    else {
      fprintf(stderr,"No more procs\n");
      exit(1);
    }
  }
}
defintion des semdef.h
#ifndef	__semdef_h
#define	__semdef_h
#include <sys/sem.h>
#define PERMS 0666
extern int errno;
extern char *sys_errlist[];
union semun
{
  int val;
  struct semid_ds *buf;
  ushort *array;
};

/******************************************************************************
 * semcreate(i): creates a set of i semaphores and returns the associated     *
 * semid.                                                                     *
 ******************************************************************************/
int semcreate(i)
     int i;
{
  int semid;
  if ((semid = semget(IPC_PRIVATE,i,PERMS)) == -1)
    {
      fprintf(stderr,"error %d in semget: %s\n",errno,sys_errlist[errno]);
      exit(errno);
    };
  return(semid);
}
/******************************************************************************
 * screate(): creates a set of one semaphore and returns the associated semid *
 ******************************************************************************/
int screate()
{
  int semid;
  if ((semid = semget(IPC_PRIVATE,1,PERMS)) == -1)
    {
      fprintf(stderr,"error %d in semget: %s\n",errno,sys_errlist[errno]);
      exit(errno);
    };
  return(semid);
}
/******************************************************************************
 * semsetval(semid,n,i): sets to i the value of semaphore number n in the set *
 * of semaphores associated to semid.                                         *
 ******************************************************************************/
void semsetval(semid,n,i)
     int semid,n,i;
{
  union semun arg;
  arg.val=i;
  if (semctl(semid,n,SETVAL,arg) == -1)
    {
      fprintf(stderr,"error %d in semctl (semsetval): %s\n",
	      errno,sys_errlist[errno]);
      exit(errno);
    }
}
/******************************************************************************
 * ssetval(semid,i): sets to i the value of the semaphore associated to semid *
 ******************************************************************************/
void ssetval(semid,i)
int semid,i;
{
  union semun arg;
  arg.val=i;
  if (semctl(semid,0,SETVAL,arg) == -1)
    {
      fprintf(stderr,"error %d in semctl (ssetval): %s\n",
	      errno,sys_errlist[errno]);
      exit(errno);
    }
}
/******************************************************************************
 * semgetval(semid,n): returns the value of the semaphore number n in the set *
 * of semaphores associated to semid.                                         *
 ******************************************************************************/
int semgetval(semid,n)
     int semid,n;
{
  int nb;
  union semun arg;
  arg.val=0;

  if ((nb=semctl(semid,n,GETVAL,arg)) == -1)
    {
      fprintf(stderr,"error %d in semctl (semgetval): %s\n",
	      errno,sys_errlist[errno]);
      exit(errno);
    };
  return(nb);
}    
/******************************************************************************
 * sgetval(semid): returns the value of the semaphore associated to semid.    *
 ******************************************************************************/
int sgetval(semid)
     int semid;
{
  int nb;
  union semun arg;
  arg.val=0;

  if ((nb=semctl(semid,0,GETVAL,arg)) == -1)
    {
      fprintf(stderr,"error %d in semctl (sgetval): %s\n",
	      errno,sys_errlist[errno]);
      exit(errno);
    };
  return(nb);
}    
/******************************************************************************
 * semwanb(semid,n): returns the number of processes that are currently       *
 * suspended awaiting the value of semaphore number n associated to semid     *
 * to become greater than its current value.                                  *
 ******************************************************************************/
int semwanb(semid,n)
     int semid,n;
{
  int nb;
  union semun arg;
  arg.val=0;
  if ((nb = semctl(semid,n,GETNCNT,arg)) == -1)
    {
      fprintf(stderr,"error %d in semctl (semwanb): %s\n",errno,sys_errlist[errno]);
      exit(errno);
    };
  return(nb);
}    

/******************************************************************************
 * swanb(semid): returns the number of processes that are currently suspended *
 * awaiting the value of the semaphore associated to semid to become greater  *
 * than its current value.                                                    *
 ******************************************************************************/
int swanb(semid)
     int semid;
{
  int nb;
  union semun arg;
  arg.val=0;
  if ((nb = semctl(semid,0,GETNCNT,arg)) == -1)
    {
      fprintf(stderr,"error %d in semctl (swanb): %s\n",errno,sys_errlist[errno]);
      exit(errno);
    };
  return(nb);
}    
/******************************************************************************
 * semwait(semid,n,i): suspends the calling process until the value of        *
 * semaphore number n associated to semid becomes greater than or equal to i. *
 * Then, this value is decremented by i and the calling process resumes its   *
 * execution.                                                                 *
 ******************************************************************************/
void semwait(semid,n,i)
     int semid,n,i;
{
  struct sembuf *sops;
  sops=(struct sembuf *)malloc(sizeof(struct sembuf));
  sops[0].sem_num = n;
  sops[0].sem_op = -i;
  sops[0].sem_flg = 0;
  if (semop(semid,sops,1) == -1){
      fprintf(stderr,"pid %d, error %d in semop (semwait), semid %d: %s\n",
	     getpid(),errno,semid,sys_errlist[errno]);
      exit(errno);
    };
  free((char *)sops);
}
/******************************************************************************
 * swait(semid): suspends the calling process until the value of the          *
 * semaphore associated to semid becomes greater than or equal to 1. Then,    *
 * this value is decremented by 1 and the calling process resumes its         *
 * execution. This is the "classical" wait(s) operation.                      *
 ******************************************************************************/
void swait(semid)
     int semid;
{
  struct sembuf *sops;
  sops=(struct sembuf *) malloc(sizeof(struct sembuf));
  sops[0].sem_num = 0;
  sops[0].sem_op = -1;
  sops[0].sem_flg = 0;
  if (semop(semid,sops,1) == -1){
      fprintf(stderr,"pid %d, error %d in semop (swait), semid %d: %s\n",
	     getpid(),errno,semid,sys_errlist[errno]);
      exit(errno);
    };
  free((char *)sops);
}
/******************************************************************************
 * semsignal(semid,n,i): adds i to the value of semaphore number n associated *
 * to semid.                                                                  *
 ******************************************************************************/
void semsignal(semid,n,i)
     int semid,n,i;
{
  struct sembuf *sops;
  sops=(struct sembuf *) malloc(sizeof(struct sembuf));
  sops[0].sem_num = n;
  sops[0].sem_op = i;
  sops[0].sem_flg = 0;
  if (semop(semid,sops,1) == -1)
    {
      fprintf(stderr,"pid %d, error %d in semop (semsignal), semid %d: %s\n",
	     getpid(),errno,semid,sys_errlist[errno]);
      exit(errno);
    };
  free((char *)sops);
}
/******************************************************************************
 * ssignal(semid): adds 1 to the value of the semaphore associated to semid.  *
 * This is the "classical" signal(s) operation.                               *
 ******************************************************************************/
void ssignal(semid)
     int semid;
{
  struct sembuf *sops;
  sops=(struct sembuf *) malloc(sizeof(struct sembuf));
  sops[0].sem_num = 0;
  sops[0].sem_op = 1;
  sops[0].sem_flg = 0;
  if (semop(semid,sops,1) == -1)
    {
      fprintf(stderr,"pid %d, error %d in semop (ssignal),semid %d: %s\n",
	     getpid(),errno,semid,sys_errlist[errno]);
      exit(errno);
    };
  free((char *)sops);
}
/******************************************************************************
 * semremove(semid): removes the semaphore identifier specified by semid from   *
 * the system and destroys the set of semaphores and data structure           *
 *  associated with it.                                                       *
 ******************************************************************************/
void semremove(semid)
     int semid;
{
  union semun arg;
  arg.val=0;
  if (semctl(semid,0,IPC_RMID,arg) == -1){
      fprintf(stderr,"error %d in semctl (semremove): %s\n",
	      errno,sys_errlist[errno]);
      exit(errno);
  }
}
#endif /* !__semdef_h */

Files de messages

Files de messages
Une file de messages est une liste chaînées qui contient des données organisées sous forme d'un type suivi d'un bloc de messages. Cette liste chaînée est gérée par le noyau qui tient compte des priorités des différents processus accèdant à la même file de messages.
Pour communiquer, deux processus doivent partager au moins une file de messages et Une file de messages peut être paratgée par plus de deux processus. Il existe une différenciation a l'intérieur d'une même file de plusieurs type de messages. La structure d'une file de message est la suivante :

struct msqid_ds {
      struct ipc_perm  msg_perm;   /*  droits d'acces à l'objet */
      struct _msg      *msg_first; /*  pointeur sur le premier message */
      struct _msg      *msg_last;  /*  pointeur sur le dernier message */
      u_short          msg_qnum;   /*  nombre de messages dans la file */
      u_short          msg_bytes;  /*  nombre maximum d'octets */
      pid_t            msg_lspid;  /*  pid du dernier processus emetteur */
      pid_t            msg_lrpid;  /*  pid du dernier processus recepteur *
      time_t           msg_stime;  /*  date de derniere emission (msgsnd) *
      time_t           msg_rtime;  /*  date de derniere reception (msgrcv)
      time_t           msg_ctime;  /*  date de dernier changement (msgctl)
      u_short           msg_cbytes;/*  nombre total actuel d'octets */
   };

Ce mécanisme permet l'échange de messages par des processus. Chaque message possède un corps de longueur variable et un type (entier strictement positif) qui peut servir à préciser la nature des informations contenues dans le corps.
Un message est une structure regroupant au minimum deux champs. Le premier est le type du message. Ce type n'a aucun sens pour le système. Il sert juste à pouvoir partitionner les messages dans une file. Les autres champs forment le corps du message. Le système donne une définition générique d'un message.

struct msgbuf {
        long mtype;    /* type du message strictement positif*/
        char mtext[1]; /* texte du message */
   };

Au moment de la réception, on peut choisir de sélectionner les messages d'un type donné. Chaque utilisateur devra définir sa propre structure sur cette base.
Pour la création, l'envoi ou la reception on a besoin entre autre des bibliothèques <sys/types.h>, <sys/ipc.h> et <sys/msg.h>.
Le noyau gère un maximum de MSGMNI files indépendantes - 128 par défaut - chacune pouvant comporter des messages de tailles inférieures à MSGMAX, soit 4 056 octets (et pas 4 096). Pour accéder à une file existante ou en créer une nouvelle, on appelle la routine msgget( ).

Création de files de messages
la fonction msgget(key_t key, int msgflg) demande l'accès à (ou la création de) la file de messages avec la clé key. msgget() retourne la valeur de l'identificateur de la file ; son prototype est

 int msgget(key_t key, int msgflg);

S'il y a un problème elle retourne «-1». Ici le paramètre
key est la clé retournée par la fonction ftok() et le paramètre
msgflg la composition binaire des constantes IPC_CREAT et IPC_EXCL .
IPC_CREAT crée une nouvelle file s'il n'y en a aucune présente et associée à la clé transmise en premier argument et
IPC_EXCL crée une nouvelle file de messages . lLa fonction msgget( ) échoue si une file existe déjà avec la clé indiquée.

Envoi d'un message dans une file de messages
La fonction msgsnd(int msqid, struct msgbuf *msgp, int msgsz, int msgflg) envoie un message dans une file de messages msqid . Le corps de ce message contient msgsz octets; il est placé et précédé par le type dans le tampon pointé par msgp. Ce tampon a la forme:

    struct msgbuf {
         long mtype;     /* message type, must be > 0 */
         char mtext[...] /* message data */
    };

et le le prototype de la fonction est msgsnd():

int msgsnd (int msqid, struct msgbuf *msgp, int msgsz, int msgflg); 

En cas d'erreur la fonction msgsnd (int msqid, struct msgbuf *msgp, int msgsz, int msgflg) retourne «-1».

Recevoir un message dans une files de messages
La fonction msgrcv() lit dans la file un message d'un type donné (si type > 0) ou indifférent (si type==0), et le place dans le tampon pointé par msgp. La taille du corps ne pourra excéder msgsz octets, sinon il sera tronqué. msgrcv() renvoie la taille du corps du message.

int msgrcv (int msqid, struct msgbuf *msgp, int msgsz,long msgtyp, int msgflg);

contrôle d'une file de message
le contrôle et le paramétrage d'une file de messages se font à l'aide de la fonction msgctl() dont le prototype est

int msgctl ( int msqid, int  cmd, struct msqid_ds *buf );

Il y a trois commandes possibles, qu'on passe en second argument :
1) IPC_STAT : pour obtenir les paramètres concernant la file de messages et les stocker dans la structure msqid_ds passée en dernière position. Cette structure sera détaillée plus bas. Il faut avoir l'autorisation de lecture sur la file de messages.
2)IPC_SET : pour configurer certains paramètres en utilisant la structure passée en troisième argument. Les paramètres qui sont mis à jour seront décrits ci-dessous. Pour pouvoir modifier ces éléments, il faut que le processus appelant soit le propriétaire ou le créateur de la file de messages, ou qu'il ait la capacité CAP_SYS_ADMIN.
3)IPC_RMID : pour supprimer la file de messages. Tous les processus en attente de lecture ou d'écriture sur la file seront réveillés. Les opérations ultérieures d'accès à cette file échoueront. Il y a toutefois un risque qu'une nouvelle file soit créée par la suite et que le noyau lui attribue le même identifiant. Si un processus attend longtemps avant d'accéder à la file supprimée, il risque de se trouver en face de la nouvelle file sans s'y attendre. Ce manque de fiabilité est l'un des arguments employés par les détracteurs des IPC Système V

Exemples :
Deux programmes, l'un pour envoyer des messages (lignes de texte) sur une file avec un type donné, l'autre pour afficher les messages reçus.
Envoi des messages par snd.c

          /* snd.c */
           /*
             envoi des messages dans une file (IPC System V)
           */
           #include <errno.h>
           #include <stdio.h>
           #include <stdlib.h>
           #include <stdio.h>

           #include <sys/types.h>
          #include <sys/ipc.h>
          #include <sys/msg.h>
          #define MAX_TEXTE 1000
          struct tampon {
              long mtype;
              char mtext[MAX_TEXTE];
          };
          void abandon(char message[]){
              perror(message);
              exit(EXIT_FAILURE);
          }
          int main(int argc, char *argv[]){
              int cle, id, mtype;
              if (argc != 3) {
                  fprintf(stderr, "Usage: %s clé type\n", argv[0]);
                  abandon("mauvais nombre de paramètres");
              }
              cle = atoi(argv[1]);
              mtype = atoi(argv[2]);
              id = msgget(cle, 0666);
              if (id == -1)
                  abandon("msgget");
              while (1) {
                  struct tampon m             int l, r;
                  printf("> ");             
fgets(msg.mtext, MAX_TEXTE, stdin); l = strlen(msg.mtext); msg.mtype = mtype; r = msgsnd(id, (struct msgbuf *) &msg, l + 1, 0); if (r == -1) abandon("msgsnd"); } }

Explications:

Reception des messages par rcv.c

           /* rcv.c */
           /*
              affiche les messages qui proviennent 
              d'une file (IPC System V)
           */
           #include <errno.h>
           #include <stdio.h>
           #include <stdlib.h>
           #include <stdio.h>

          #include <sys/types.h>
          #include <sys/ipc.h>
          #include <sys/msg.h>

          #define MAX_TEXTE 1000
          struct tampon {
              long mtype;
              char mtext[MAX_TEXTE];
          };
          int continuer_boucle = 1;
          void abandon(char message[]){
              perror(message);
              exit(EXIT_FAILURE);
          }
          int main(int argc, char *argv[]){
              int cle, id;
              if (argc != 2) {
                  fprintf(stderr, "Usage: %s cle\n", argv[0]);
                  abandon("Mauvais nombnre de paramètres");
           }
              cle = atoi(argv[1]);
              id = msgget(cle, IPC_CREAT | 0666);
              if (id == -1)
                  abandon("msgget");
              while (continuer_boucle) {
                  struct tampon msg;
                  int l = msgrcv(id, (struct msgbuf *) &msg, MAX_TEXTE, 0L, 0);
                  if (l == -1)
                      abandon("msgrcv");
                  printf("(type=%ld) %s\n", msg.mtype, msg.mtext);
              }
              return EXIT_SUCCESS;
          }

Explications:

précédent suivant