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 }