Les mécanismes de communication entre processus (InterProcess Communication, ou IPC) d'Unix System V ont été repris dans de nombreuses variantes d'Unix. Il y a 3 mécanismes :
ftok() constitution d'une clé
# include <sys/types.h>
# include <sys/ipc.h>
key_t ftok ( char *pathname, char project )
La fonction ftok() constitue une clé à partir d'un chemin d'accès
et d'un caractère indiquant un « projet ». Plutôt que de risquer une
explication abstraite, étudions deux cas fréquents :
/opt/jeux/OuiOui.
Ce logiciel utilise deux objets partagés. On pourra utiliser les clés
ftok("/opt/jeux/OuiOui",'A') et
ftok("/opt/jeux/OuiOui",'B').
Ainsi tous les processus de ce logiciel se réfèreront aux mêmes objets
qui seront partagés entre tous les utilisateurs.ftok(getenv("HOME"),'A') et
ftok(getenv("HOME"),'B').
Ce mécanisme permet à plusieurs programmes de partager des segments
mémoire. Chaque segment mémoire est identifié, au niveau du système,
par une clé à laquelle correspond un identifiant. 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.
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, int size, int shmflg);
char *shmat (int shmid, char *shmaddr, int shmflg )
int shmdt (char *shmaddr)
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
La fonction shmget() 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.
shmat() 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é.
shmdt() ``libère'' le segment. shmctl() permet diverses
opérations, dont la destruction d'une mémoire partagée (voir exemple).
Exemple (deux programmes):
Le producteur :
1 /* prod.c */
2 /*
3 Ce programme lit une suite de nombres, et effectue le cumul dans une
4 variable en mémoire partagée. */
5 #include <sys/ipc.h>
6 #include <sys/shm.h>
7 #include <sys/types.h>
8 #include <stdlib.h>
9 #include <stdio.h>
10 #include <errno.h>
11 struct donnees {
12 int nb;
13 int total;
14 };
15 int main(void)
16 {
17 key_t cle;
18 int id;
19 struct donnees *commun;
20 int reponse;
21 cle = ftok(getenv("HOME"), 'A');
22 if (cle == -1) {
23 perror("ftok");
24 exit(EXIT_FAILURE);
25 }
26 id = shmget(cle, sizeof(struct donnees),
27 IPC_CREAT | IPC_EXCL | 0666);
28 if (id == -1) {
29 switch (errno) {
30 case EEXIST:
31 fprintf(stderr, "Le segment existe déjà\n");
32 break;
33 default:
34 perror("shmget");
35 break;
36 }
37 exit(EXIT_FAILURE);
38 }
39 commun = (struct donnees *) shmat(id, NULL, SHM_R | SHM_W);
40 if (commun == NULL) {
41 perror("shmat");
42 exit(EXIT_FAILURE);
43 }
44 commun->nb = 0;
45 commun->total = 0;
46 while (1) {
47 printf("+ ");
48 if (scanf("%d", &reponse) != 1)
49 break;
50 commun->nb++;
51 commun->total += reponse;
52 printf("sous-total %d= %d\n", commun->nb,
53 commun->total);
54 };
55 printf("---\n");
56 if (shmdt((char *) commun) == -1) {
57 perror("shmdt");
58 exit(EXIT_FAILURE);
59 }
60 /* suppression segment */
61 if (shmctl(id, IPC_RMID, NULL) == -1) {
62 perror("shmctl(remove)");
63 exit(EXIT_FAILURE);
64 };
65 exit(EXIT_SUCCESS);
66 }
Le consommateur :
1 /* cons.c */
2 /*
3 Ce programme affiche périodiquement le contenu de la
4 mémoire partagée. Arrêt par Contrôle-C
5 */
6 #include <sys/ipc.h>
7 #include <sys/shm.h>
8 #include <sys/types.h>
9 #include <unistd.h>
10 #include <stdlib.h>
11 #include <stdio.h>
12 #include <errno.h>
13 #include <signal.h>
14 #define DELAI 2
15 struct donnees {
16 int nb;
17 int total;
18 };
19 int encore;
20 void arret(int signal)
21 {
22 encore = 0;
23 }
24 int main(void)
25 {
26 key_t cle;
27 int id;
28 struct donnees *commun;
29 struct sigaction a;
30 cle = ftok(getenv("HOME"), 'A');
31 if (cle == -1) {
32 perror("ftok");
33 exit(EXIT_FAILURE);
34 }
35 id = shmget(cle, sizeof(struct donnees), 0);
36 if (id == -1) {
37 switch (errno) {
38 case ENOENT:
39 printf("pas de segment\n");
40 exit(EXIT_SUCCESS);
41 default:
42 perror("shmget");
43 exit(EXIT_FAILURE);
44 }
45 }
46 commun = (struct donnees *) shmat(id, NULL, SHM_R);
47 if (commun == NULL) {
48 perror("shmat");
49 exit(EXIT_FAILURE);
50 }
51 encore = 1;
52 a.sa_handler = arret;
53 sigemptyset(&a.sa_mask);
54 a.sa_flags = 0;
55 sigaction(SIGINT, &a, NULL);
56 while (encore) {
57 sleep(DELAI);
58 printf("sous-total %d= %d\n", commun->nb,
59 commun->total);
60 }
61 printf("---\n");
62 if (shmdt((char *) commun) == -1) {
63 perror("shmdt");
64 exit(EXIT_FAILURE);
65 }
66 exit(EXIT_SUCCESS);
67 }
Question : le second programme n'affiche pas forcément des informations cohérentes. Pourquoi ? Qu'y faire ?
Problème : écrire deux programmes qui partagent deux variables
i, j. Voici le pseudo-code:
processus P1 processus P2
| i=0 j=0 | tant que i==j
| repeter indefiniment | faire rien
| i++ j++ | ecrire i
fin fin
Au bout de combien de temps le processus P2 s'arrête-t-il ?
Faire plusieurs essais.
Exercice : la commande ipcs affiche des informations sur les
segments qui existent. Ecrire une commande qui permet d'afficher le contenu
d'un segment (on donne le shmid et la longueur en paramètres).
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg )
int semop(int semid, struct sembuf *sops, unsigned nsops)
int semctl(int semid, int semnum, int cmd, union semun arg )
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).
La fonction semget() demande à travailler sur le
sémaphore généralisé qui est identifié par la clé key (même notion que pour les clés
des segments partagés) et qui contient nsems sémaphores individuels.
Un nouveau sémaphore est créé, avec les droits donnés par les 9 bits de
poids faible de semflg, si
key est IPC_PRIVATE, ou si
semflg contient IPC_CREAT.
semop() agit sur le sémaphore semid en appliquant
simultanément à plusieurs sémaphores individuels les actions décrites
dans les nsops premiers éléments du tableau
sops. 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 (voir manuel). 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é.
semctl permet de réaliser diverses opérations sur les sémaphores,
selon la commande demandée. En particulier, on peut fixer le
n-ième sémaphore à la valeur val en faisant :
semctl(sem,n,SETVAL,val);
Exemple: primitives sur les sémaphores traditionnels.
1 /* sem.c */
2 /*
3 Opérations sur des sémaphores
4 */
5 #include <sys/types.h>
6 #include <sys/ipc.h>
7 #include <sys/sem.h>
8 #include <unistd.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <ctype.h>
12 typedef int SEMAPHORE;
13 void detruire_sem(SEMAPHORE sem)
14 {
15 if (semctl(sem, 0, IPC_RMID, 0) != 0) {
16 perror("detruire_sem");
17 exit(EXIT_FAILURE);
18 };
19 }
20 void changer_sem(SEMAPHORE sem, int val)
21 {
22 struct sembuf sb[1];
23 sb[0].sem_num = 0;
24 sb[0].sem_op = val;
25 sb[0].sem_flg = 0;
26 if (semop(sem, sb, 1) != 0) {
27 perror("changer_sem");
28 exit(EXIT_FAILURE);
29 };
30 }
31 SEMAPHORE creer_sem(key_t key)
32 {
33 SEMAPHORE sem;
34 int r;
35 sem = semget(key, 1, IPC_CREAT | 0666);
36 if (sem < 0) {
37 perror("creer_sem");
38 exit(EXIT_FAILURE);
39 };
40 r = semctl(sem, 0, SETVAL, 0); /* valeur initiale = 0 */
41 if (r < 0) {
42 perror("initialisation sémaphore");
43 exit(EXIT_FAILURE);
44 };
45 return sem;
46 }
47 void P(SEMAPHORE sem)
48 {
49 changer_sem(sem, -1);
50 }
51 void V(SEMAPHORE sem)
52 {
53 changer_sem(sem, 1);
54 }
55 /* --------------------------------------------- */
56 int main(int argc, char *argv[])
57 {
58 SEMAPHORE sem;
59 key_t key;
60 int encore = 1;
61 if (argc != 2) {
62 fprintf(stderr, "Usage: %s cle\n", argv[0]);
63 exit(EXIT_FAILURE);
64 };
65 key = atoi(argv[1]);
66 sem = creer_sem(key);
67 while (encore) {
68 char reponse;
69 printf("p,v,x,q ? ");
70 if (scanf("%c", &reponse) != 1)
71 break;
72 switch (toupper(reponse)) {
73 case 'P':
74 P(sem);
75 printf("Ok.\n");
76 break;
77 case 'V':
78 V(sem);
79 printf("Ok.\n");
80 break;
81 case 'X':
82 detruire_sem(sem);
83 printf("Sémaphore détruit\n");
84 encore = 0;
85 break;
86 case 'Q':
87 encore = 0;
88 break;
89 default:
90 printf("?\n");
91 };
92 };
93 printf("Bye.\n");
94 exit(EXIT_SUCCESS);
95 }
Exercice : que se passe-t-il si on essaie d'interrompre semop() ?
Exercice : utilisez les sémaphores pour ``sécuriser'' l'exemple présenté sur les mémoires partagées.
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.
Au moment de la réception, on peut choisir de sélectionner les messages d'un type donné.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget (key_t key, int msgflg)
int msgsnd (int msqid, struct msgbuf *msgp, int msgsz, int msgflg)
int msgrcv (int msqid, struct msgbuf *msgp, int msgsz,
long msgtyp, int msgflg)
int msgctl ( int msqid, int cmd, struct msqid_ds *buf )
msgget() demande l'accès à (ou la création de) la file de
message avec la clé key. msgget() retourne la valeur
de l'identificateur de file.
msgsnd() envoie un
message dans la file msqid. Le corps de ce message
contient msgsz octets, il est placé, précédé par le
type dans le tampon pointé par msgp. Ce tampon de la
forme:
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[...] /* message data */
};
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.
Exemple. 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.
1 /* snd.c */
2 /*
3 envoi des messages dans une file (IPC System V)
4 */
5 #include <errno.h>
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <stdio.h>
9 #include <sys/types.h>
10 #include <sys/ipc.h>
11 #include <sys/msg.h>
12 #define MAXTEXTE 1000
13 struct tampon {
14 long mtype;
15 char mtext[MAXTEXTE];
16 };
17 int main(int argc, char *argv[])
18 {
19 int cle, id, mtype;
20 if (argc != 3) {
21 fprintf(stderr, "Usage: %s clé type\n", argv[0]);
22 exit(1);
23 };
24 cle = atoi(argv[1]);
25 mtype = atoi(argv[2]);
26 id = msgget(cle, 0666);
27 if (id == -1) {
28 perror("msgget");
29 exit(EXIT_FAILURE);
30 };
31 while (1) {
32 int l;
33 struct tampon msg;
34 printf("> ");
35 fgets(msg.mtext, MAXTEXTE, stdin);
36 l = strlen(msg.mtext);
37 msg.mtype = mtype;
38 if (msgsnd(id, (struct msgbuf *) &msg, l + 1, 0) ==
39 -1) {
40 perror("msgsnd");
41 exit(EXIT_FAILURE);
42 };
43 };
44 }
1 /* rcv.c */
2 /*
3 affiche les messages qui proviennent
4 d'une file (IPC System V)
5 */
6 #include <errno.h>
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <stdio.h>
10 #include <sys/types.h>
11 #include <sys/ipc.h>
12 #include <sys/msg.h>
13 #define MAXTEXTE 1000
14 struct tampon {
15 long mtype;
16 char mtext[MAXTEXTE];
17 };
18 int encore = 1;
19 int main(int argc, char *argv[])
20 {
21 int cle, id;
22 if (argc != 2) {
23 fprintf(stderr, "Usage: %s cle\n", argv[0]);
24 exit(1);
25 };
26 cle = atoi(argv[1]);
27 id = msgget(cle, IPC_CREAT | 0666);
28 if (id == -1) {
29 perror("msgget");
30 exit(EXIT_FAILURE);
31 };
32 while (encore) {
33 int l;
34 struct tampon msg;
35 l = msgrcv(id, (struct msgbuf *) &msg, MAXTEXTE, 0L,
36 0);
37 if (l == -1) {
38 perror("msgrcv");
39 exit(EXIT_FAILURE);
40 };
41 printf("(type=%ld) %s\n", msg.mtype, msg.mtext);
42 };
43 exit(EXIT_SUCCESS);
44 }