Communication inter processus par ipc sous *nix

Description

Ce programme illustre l'utilisation des mécanismes de communication inter processus par IPC avec les systèmes d'exploitation reprenant l'implémentation IPC SYSTEM V. Si vous voulez plus d'information sur ce sujet, je vous invite à lire le tutorial qui va avec et qui est posté sur le même site CS.
Ce programme illustre également la mise en place d'un mécanisme de temporisation par l'intermédiaire des signaux et/ou du timer système.
Le scénario global est l'envoi de signaux SIGUSR1 par l'emetteur au récepteur. Puis toutes les 2 secondes, les deux processus affichent l'un combien il en a envoyé et l'autre combien il en a reçu.

Note : la synchronisation n'est pas réalisée sur l'émission/réception des signaux, pour montrer la faiblesse de ces mécanismes (ils sont plus anciens que les IPC). Comme vous le montrera une exécution, l'émetteur envoie plus de signaux que n'en reçoit le récepteur. L'émetteur envoie ses signaux en rafale, et comme il n'existe qu'un seul "slot" système pour chaque signal, certains sont écrasés avant que le récepteur n'ait le temps de les capter.

Source / Exemple :


//
// Fichier d'entête commune : msg.h
//
/* type booleen */
enum { FAUX, VRAI };

/* Mode d'alarme utilisable (alarme simple ou timer) */
enum { NOALRM, ALARM, TIMER };

/* mode d'ipc utilisable (file de messages ou segment de memoire partage */
enum { NOIPC, MSG, SHM };

/* type des messages attendus */
enum { MSGPIDE = 1, MSGPIDR, MSGALRM, MSGIPC, MSGCPT };

/* structure des messages ipc */
typedef struct bufipc {
	long mtype; /* type de message */
	int info;   /* contenu du message */
} msgipc;

/* structure d'un message par segment de memoire partage */
typedef struct bufshm {
	int pide;		/* pid de l'emetteur */
	int pidr;		/* pid du recepteur  */
	int ModeAlrm;	/* mode d'alarme */
	int cptr;		/* nombre de signaux recus */
} msgshm;

/* 256 octets pour un segment de memoire partage */
#define TAILLESHM 256

//
// Fichier emetteur.c
//
/* 
Petit exercice sur les IPC
Auteur   : Le gaijin
Fichier  : emetteur.c = emetteur de signaux
Scenario :
 
1) Un fork, 
Parent a un compteur de SIGUSR1 recus
Enfant a un compteur de SIGUSR1 emis
2) Enfant envoit l'alarme toute les 2 secondes a Parent
3) une fonction unique pour la gestion de tous les signaux
4) un gestionnaire global des signaux

5) Couper le fichier en deux :
- un avec le code du pere
- un avec le code du fils
apres le fork, on fait un exec pour lancer le fils
on passe le pid du pere dans les arguments

6) Plus de fork : un emetteur, un recepteur, les IPC
- emetteur  : l'ancien fils 
- recepteur : l'ancien pere

Communication par IPC-file de messages :
Avant de demarrer la sequence des signaux, les processus se synchronisent :
- le premier processus lance determine la methode de travail (ALARM ou TIMER)
  et la communique au second.
- Communication respective de leur PID pour la synchronisation des terminaisons
- A chaque alarme, Recepteur envoie son compteur de signaux recus par la file 
  de messages

Synchronisation par signal :
le processus qui recoit le signal de terminaison le regenere pour l'autre
afin de synchroniser leur terminaison

7) Double canal de communication : file de message et segment de memoire partage :
- D'abord par la file de message pour savoir quelle sera la methode de 
  communication dans la suite du programme (file ou segment)

N.B. : Les deux IPC utilisent la meme cle

Communication par IPC-segment de memoire partages :
- A chaque alarme, Recepteur ecrit la valeur de son compteur de signaux recus
  dans le segment

8) Correction des bugs
Raffinement de la sortie du programme (atexit), pour nettoyer les IPC.

  • /
#include <sys/time.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #include <sys/shm.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <signal.h> #include <getopt.h> #include "msg.h" /* Macro pour la lisibilite */ #define ERREUR(msg) { perror(msg); exit(errno); } /* Qui est le premier processus lance (emetteur ou recepteur) */ int EmetteurPremier = FAUX; /* Mode d'alarme (alarme simple ou timer) */ int ModeAlarme = NOALRM; /* Mode d'IPC (file de messages ou segment de memoire partage) */ int ModeIPC = NOIPC; int cpte = 0; /* compteur de signaux emis */ int cptr = 0; /* compteur de signaux recus */ int pidr = 0; /* pid du recepteur */ key_t cle = 0; /* cle de l'ipc */ int msqid = 0; /* dipc de la file de messages */ int shmid = 0; /* dipc du segment de memoire */ msgshm *seg = NULL; /* adresse du debut du segment */ msgipc message; /* un message IPC */ struct msqid_ds md; /* pour les stats de la file de messages */ struct shmid_ds sd; /* pour les stats du segment de memoire */ /* Affichage des usages */ void AfficherLesUsages (char **argv) { printf("Usage : %s [-t(imer)|-a(larm)] [-m(essages)|-s(egment)]\n", argv[0]); exit (1); } /* fonction de terminaison du programme */ void DetruireIPC (void) { msgctl(msqid, IPC_RMID, NULL); /* detruire la file de messages */ if (ModeIPC == SHM ) shmctl(shmid, IPC_RMID, NULL); /* detruire le segment de memoire */ } /* gestionnaire global des signaux */ void GestSig (int sig, siginfo_t *siginfo, void *pvoid) { switch (sig) { case SIGALRM: kill(pidr, SIGALRM); /* envoi du signal de synchro au recepteur */ switch (ModeIPC) { case MSG: /* reception du message du recepteur : nombre de signaux recus */ if (msgrcv (msqid, &message, sizeof(message.info), MSGCPT, 0) == -1 ) ERREUR("msgrcv emetteur") /* mise a jour de cptr en fonction du message recu */ cptr = message.info; break; case SHM: /* lire le nombre de signaux recus dans le segment */ while (cptr == seg->cptr); /* synchro optionnelle */ cptr = seg->cptr; /* lecture du compteur depuis le segment */ break; } /* rearmement de l'alarme */ if (ModeAlarme == ALARM) alarm(2); /* affichage du message apres synchro */ printf("Emetteur = %8d, Recepteur = %8d\n", cpte, cptr); break; case SIGINT: kill (pidr, SIGINT); /* regenerer le signal pour le recepteur */ exit(errno); break; } } int main (int argc, char **argv) { struct sigaction act; struct itimerval val, oval; int c; /* gestion de la terminaison */ atexit (DetruireIPC); /* gestion des arguments */ if (argc != 1) { while ( (c = getopt(argc, argv, "atms")) != EOF ) { switch ( c ) { case 'a' : ModeAlarme = ALARM; break; case 't' : ModeAlarme = TIMER; break; case 'm' : ModeIPC = MSG; break; case 's' : ModeIPC = SHM; break; default : AfficherLesUsages(argv); break; } } } /* Armement des signaux */ act.sa_flags = SA_SIGINFO; act.sa_sigaction = GestSig; sigemptyset (&act.sa_mask); if ( sigaction (SIGUSR1, &act, NULL) == -1 ) ERREUR("sigaction SIGUSR1") if ( sigaction (SIGALRM, &act, NULL) == -1 ) ERREUR("sigaction SIGALRM") if ( sigaction (SIGINT, &act, NULL) == -1 ) ERREUR("sigaction SIGINT") /* creer ou retrouver l'ipc */ if ( (cle = ftok ("/bin", 'a')) == -1 ) ERREUR("ftok emetteur") /* lire d'abord la file de message pour savoir si emetteur est le premier */ /* processus lance, auquel cas */ /* il impose le mode d'ipc et le mode d'alarme (alarme ou timer) */ if ( (msqid = msgget (cle, IPC_CREAT|0660)) == -1 ) ERREUR("msgget emetteur") /* obtenir les stats de la file de message pour savoir qui est le createur */ /* et donc qui impose le mode d'ipc et le mode d'alarme (alarme ou timer) */ if ( msgctl (msqid, IPC_STAT, &md) == -1 ) ERREUR("msgctl emetteur IPC_STAT") /* Est-ce qu'un message a deja ete envoye par un autre processus ? */ if ( md.msg_lspid == 0 ) { EmetteurPremier = VRAI; /* Emetteur est le createur : il impose le mode d'ipc */ if ( ModeIPC == NOIPC ) ModeIPC = MSG; /* forcer le mode d'IPC */ /* preparer le message */ message.mtype = MSGIPC; message.info = ModeIPC; /* envoyer le message : mode d'IPC */ if ( msgsnd (msqid, &message, sizeof (message.info), 0) == -1 ) ERREUR("msgsnd emetteur MSGIPC") } else { /* Emetteur n'est pas le createur */ /* lire un autre message : mode d'ipc */ if ( msgrcv (msqid, &message, sizeof(message.info), MSGIPC, 0) == -1 ) ERREUR("msgrcv emetteur MSGIPC") if ( ModeIPC == NOIPC ) ModeIPC = message.info; else if ( ModeIPC != message.info ) { printf ("Emetteur n'est pas le createur, mode d'ipc deja donne : %s\n", (ModeIPC = message.info) == MSG ? "Message" : "Segment"); } } switch (ModeIPC) { case MSG: if ( EmetteurPremier ) { /* Emetteur est le createur : il impose le mode d'alarme */ /* et la communique au recepteur */ /* envoyer un message : mode d'alarme utilise */ if ( ModeAlarme == NOALRM ) ModeAlarme = ALARM; /* forcer le mode d'alarme */ /* preparer le message */ message.mtype = MSGALRM; message.info = ModeAlarme; /* envoyer le message : mode d'alarme */ if ( msgsnd (msqid, &message, sizeof (message.info), 0) == -1 ) ERREUR("msgsnd emetteur MSGALRM") } else { /* Emetteur n'est pas le createur */ /* lire un autre message : mode d'alarme */ if ( msgrcv (msqid, &message, sizeof(message.info), MSGALRM, 0) == -1 ) ERREUR("msgrcv emetteur MSGALRM") if ( ModeAlarme == NOALRM ) ModeAlarme = message.info; else if ( ModeAlarme != message.info ) printf ("Emetteur n'est pas le createur, mode d'alarme deja donne : %s\n", (ModeAlarme = message.info) == ALARM ? "ALARM" : "TIMER"); } /* preparer le message */ message.mtype = MSGPIDE; message.info = getpid(); /* envoyer le message : pid de l'emetteur */ if ( msgsnd (msqid, &message, sizeof (message.info), 0) == -1 ) ERREUR("msgsnd emetteur MSGPIDE") /* attente bloquante du premier message, le pid du recepteur */ if ( msgrcv (msqid, &message, sizeof(message.info), MSGPIDR, 0) == -1 ) ERREUR("msgrcv emetteur") /* initialiser le pidr en fonction du message recu */ pidr = message.info; break; case SHM: /* creer ou retrouver le segment de memoire partage et le mapper */ if ( (shmid = shmget (cle, TAILLESHM, IPC_CREAT|0660)) == -1 ) ERREUR("shmget emetteur") if ( (seg = (msgshm *) shmat(shmid, NULL, 0)) == NULL ) ERREUR("shmat emetteur") if ( EmetteurPremier ) { /* Emetteur est le createur : il impose le mode d'alarme */ /* et la communique au recepteur */ if ( ModeAlarme == NOALRM ) ModeAlarme = ALARM; /* forcer le mode d'alarme */ seg->pide = getpid(); /* ecrire le pid de l'emetteur */ seg->ModeAlrm = ModeAlarme; /* imposer le mode d'alarme */ while ( !(pidr = seg->pidr) ); /* attendre le pid du recepteur */ } else { seg->pide = getpid(); /* ecrire le pid de l'emetteur */ while ( !(pidr = seg->pidr) ); /* attendre le pid du recepteur */ if ( ModeAlarme == NOALRM ) ModeAlarme = seg->ModeAlrm; /* lire le mode d'alarme du recepteur */ else if ( ModeAlarme != seg->ModeAlrm ) printf ("Emetteur n'est pas le createur, mode d'alarme deja donne : %s\n", (ModeAlarme = seg->ModeAlrm) == ALARM ? "ALARM" : "TIMER"); } break; } /* mise en place du mode d'alarme */ switch (ModeAlarme) { case ALARM : alarm(2); break; case TIMER : /* mise en place du timer */ val.it_interval.tv_sec = 2; /* toutes les deux secondes */ val.it_interval.tv_usec = 0; val.it_value.tv_sec = 2; /* a partir de la 2 eme seconde du prog */ val.it_value.tv_usec = 0; setitimer (ITIMER_REAL, &val, &oval); /* Armement du timer */ break; } /* message d'initialisation */ printf ("Emetteur (pid = %-8d) : %s IPC et Mode %s\n", getpid(), ModeIPC == MSG ? "Message" : "Segment", ModeAlarme == ALARM ? "ALARM" : "TIMER"); /* emission des signaux */ while (1) { kill(pidr,SIGUSR1); cpte++; } } #undef ERREUR // // Fichier recepteur.c // #include <sys/time.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #include <sys/shm.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <signal.h> #include <getopt.h> #include "msg.h" /* Macro pour la lisibilite */ #define ERREUR(msg) { perror(msg); exit(errno); } /* Qui est le premier processus lance (emetteur ou recepteur) */ int RecepteurPremier = FAUX; /* Mode d'alarme (alarme simple ou timer) */ int ModeAlarme = NOALRM; /* Mode d'IPC (file de messages ou segment de memoire partage) */ int ModeIPC = NOIPC; int pide = 0; /* pid de l'emetteur */ key_t cle = 0; /* cle de l'ipc */ int msqid = 0; /* dipc de la file de messages */ int shmid = 0; /* dipc du segment de memoire */ msgshm *seg = NULL; /* adresse de debut du segment */ msgipc message; /* un message IPC */ struct msqid_ds md; /* pour les stats de la file de messages */ struct shmid_ds sd; /* pour les stats du segment de memoire */ /* Affichage des usages */ void AfficherLesUsages (char **argv) { printf("Usage : %s [-t(imer)|-a(larm)] [-m(essages)|-s(egment)]\n", argv[0]); exit (1); } /* fonction de terminaison du programme */ void DetruireIPC (void) { msgctl(msqid, IPC_RMID, NULL); /* detruire la file de messages */ if (ModeIPC == SHM ) shmctl(shmid, IPC_RMID, NULL); /* detruire le segment de memoire */ } /* gestionnaire global des signaux */ void GestSig (int sig, siginfo_t *siginfo, void *pvoid) { static int cptr = 0; /* compteurs de signaux recus */ switch (sig) { case SIGUSR1 : cptr++; break; case SIGALRM: switch (ModeIPC) { case MSG: /* preparer le message */ message.mtype = MSGCPT; message.info = cptr; /* envoyer le message : nombre de signaux recus */ if (msgsnd (msqid, &message, sizeof (message.info), 0) == -1) ERREUR("msgsnd recepteur MSGCPT") break; case SHM: /* ecriture du nombre de signaux recus dans le segment */ seg->cptr = cptr; break; } break; case SIGINT: kill (pide, SIGINT); /* regenerer le signal pour l'emetteur */ exit(errno); break; } } int main (int argc, char **argv) { struct sigaction act; struct itimerval val, oval; int c; /* gestion de la terminaison */ atexit (DetruireIPC); /* gestion des arguments */ if (argc != 1) while ( (c = getopt(argc, argv, "atms")) != EOF ) { switch ( c ) { case 'a' : ModeAlarme = ALARM; break; case 't' : ModeAlarme = TIMER; break; case 'm' : ModeIPC = MSG; break; case 's' : ModeIPC = SHM; break; default : AfficherLesUsages(argv); break; } } /* Armement des signaux */ act.sa_flags = SA_SIGINFO; act.sa_sigaction = GestSig; sigemptyset (&act.sa_mask); if ( sigaction (SIGUSR1, &act, NULL) == -1 ) ERREUR("sigaction SIGUSR1") if ( sigaction (SIGALRM, &act, NULL) == -1 ) ERREUR("sigaction SIGALRM") if ( sigaction (SIGINT, &act, NULL) == -1 ) ERREUR("sigaction SIGINT") /* creer ou retrouver l'ipc */ if ( (cle = ftok ("/bin", 'a')) == -1 ) ERREUR("ftok recepteur") /* lire d'abord la file de message pour savoir si recepteur est le premier */ /* processus lance, auquel cas */ /* il impose le mode d'ipc et le mode d'alarme (alarme ou timer) */ if ( (msqid = msgget (cle, IPC_CREAT|0660)) == -1 ) ERREUR("msgget recepteur") /* obtenir les stats de la file de message pour savoir qui est le createur */ /* et donc qui impose le mode d'ipc et le mode d'alarme (alarme ou timer) */ if ( msgctl (msqid, IPC_STAT, &md) == -1 ) ERREUR("msgctl recepteur IPC_STAT") /* Est-ce qu'un message a deja ete envoye par un autre processus ? */ if ( md.msg_lspid == 0 ) { RecepteurPremier = VRAI; /* Recepteur est le createur : il impose le mode d'ipc */ if ( ModeIPC == NOIPC ) ModeIPC = MSG; /* forcer le mode d'IPC */ /* preparer le message */ message.mtype = MSGIPC; message.info = ModeIPC; /* envoyer le message : mode d'IPC */ if ( msgsnd (msqid, &message, sizeof (message.info), 0) == -1 ) ERREUR("msgsnd recepteur MSGIPC") } else { /* Recepteur n'est pas le createur */ /* lire un autre message : mode d'ipc */ if ( msgrcv (msqid, &message, sizeof(message.info), MSGIPC, 0) == -1 ) ERREUR("msgrcv recepteur MSGIPC") if ( ModeIPC == NOIPC ) ModeIPC = message.info; else if ( ModeIPC != message.info ) { printf ("Recepteur n'est pas le createur, mode d'ipc deja donne : %s\n", (ModeIPC = message.info) == MSG ? "Message" : "Segment"); } } switch (ModeIPC) { case MSG: if ( (msqid = msgget (cle, IPC_CREAT|0660)) == -1 ) ERREUR("msgget recepteur") /* obtenir les stats de la liste d'ipc pour savoir qui est le createur */ /* et donc qui impose le mode d'alarme (alarm ou timer) */ if ( msgctl (msqid, IPC_STAT, &md) == -1 ) ERREUR("msgctl emetteur IPC_STAT") if ( RecepteurPremier ) { /* Recepteur est le createur : il impose le mode d'alarme */ /* et la communique au recepteur */ /* envoyer un message : mode d'alarme utilise */ if ( ModeAlarme == NOALRM ) ModeAlarme = ALARM; /* forcer le mode d'alarme */ /* preparer le message */ message.mtype = MSGALRM; message.info = ModeAlarme; /* envoyer le message : mode d'alarme */ if ( msgsnd (msqid, &message, sizeof (message.info), 0) == -1 ) ERREUR("msgsnd recepteur MSGALRM") } else { /* Recepteur n'est pas le createur */ /* lire un message : mode d'alarme */ if ( msgrcv (msqid, &message, sizeof(message.info), MSGALRM, 0) == -1 ) ERREUR("msgrcv recepteur MSGALRM") if ( ModeAlarme == NOALRM ) ModeAlarme = message.info; else if ( ModeAlarme != message.info ) printf ("Recepteur n'est pas le createur, mode d'alarme deja donne : %s\n", (ModeAlarme = message.info) == ALARM ? "ALARM": "TIMER"); } /* preparer le message */ message.mtype = MSGPIDR; message.info = getpid(); /* envoyer le message : pid du recepteur */ if (msgsnd (msqid, &message, sizeof (message.info), 0) == -1) ERREUR("msgsnd recepteur MSGPIDR") /* attente bloquante du pid de l'emetteur */ if (msgrcv (msqid, &message, sizeof(message.info), MSGPIDE, 0) == -1) ERREUR("msgrcv recepteur") /* initialiser le pide en fonction du message recu */ pide = message.info; break; case SHM: /* creer ou retrouver le segment de memoire partage et le mapper */ if ( (shmid = shmget (cle, TAILLESHM, IPC_CREAT|0660)) == -1 ) ERREUR("shmget recepteur") if ( (seg = (msgshm *) shmat(shmid, NULL, 0)) == NULL ) ERREUR("shmat recepteur") if ( RecepteurPremier ) { /* Recepteur est le createur : il impose le mode d'alarme */ /* et la communique a l'emetteur */ if ( ModeAlarme == NOALRM ) ModeAlarme = ALARM; /* forcer le mode d'alarme */ seg->pidr = getpid(); /* ecrire le pid de l'emetteur */ seg->ModeAlrm = ModeAlarme; /* imposer le mode d'alarme */ while ( !(pide = seg->pide) ); /* attendre que le pid de l'emetteur */ } else { seg->pidr = getpid(); /* ecrire le pid du recepteur */ while ( !(pide = seg->pide) ); /* attendre que le pid de l'emetteur */ if ( ModeAlarme == NOALRM ) ModeAlarme = seg->ModeAlrm; /* lire le mode d'alarme du recepteur */ else if ( ModeAlarme != seg->ModeAlrm ) printf ("Recepteur n'est pas le createur, mode d'alarme deja donne : %s\n", (ModeAlarme = seg->ModeAlrm) == ALARM ? "ALARM" : "TIMER"); } break; } /* message d'initialisation */ printf ("Recepteur (pid = %-8d) : %s IPC et Mode %s\n", getpid(), ModeIPC == MSG ? "Message" : "Segment", ModeAlarme == ALARM ? "ALARM" : "TIMER"); /* attente des signaux et de la terminaison de l'emetteur */ while (1) pause(); } #undef ERREUR

Conclusion :


Pour compiler :
make emetteur
make recepteur

ou utiliser le script mak (pour les feignants)

Pour tester :
emetteur [-options] &
recepteur [-option]

ou l'inverse, cela n'a pas d'importance puisque les processus se synchronisent.

Les options sont :
-a : utilisation d'une temporisation par la primitive alarm()
-t : utilisation d'une temporisation par la primitive setitimer()
-m : utilisation d'une file de message
-s : utilisation d'un segment de mémoire partagé

NB : il faut un système *NIX, l'implémentation des IPC sous cygwin étant incomplète.

Codes Sources

A voir également

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.