original in it: Leonardo Giordani
it to en: Leonardo Giordani
en to fr: Paul Delannoy
Etudiant ingénieur en télécommunications à l'Ecole Polytechnique de Milan, il travaille comme administrateur réseau et s'intéresse à la programmation (surtout en langage assembleur et en C/C++). Depuis 1999 il ne travaille que sous Linux/Unix.
Pour comprendre l'article il faudrait avoir :
Synchroniser des processus nécessite de chronométrer leur travail, non pas dans un référentiel absolu (qui donnerait un instant précis pour le démarrage des opérations d'un processus donné) mais dans un référentiel relatif, dans lequel nous pourrons indiquer quel processus agira le premier et lequel agira le second.
L'usage de sémaphores pour cet objectif se révèle complexe et limité : complexe parce que chaque processus doit gérer un sémaphore pour chaque processus distinct avec lequel il doit se synchroniser. Limité parce que cette solution ne permet pas d'échanger des paramètres entre les processus. Prenons l'exemple de la création d'un processus nouveau : cet événement doit être notifié à chacun des processus en cours d'exécution, mais la technique des sémaphores ne permet pas de le faire.
De plus, le contrôle par un sémaphore de l'accès concurrentiel à une ressource partagée peut conduire au blocage permanent d'un processus si l'un des autres processus concernés libère la ressource puis la verrouille à nouveau avant qu'un autre ait pu se l'approprier : comme nous l'avons vu, dans ce monde du multitâche, il n'est pas possible de prévoir quel processus sera actif, ni quand il le sera.
Ces quelques remarques rapides nous font comprendre que la technique des sémaphores n'apportera pas de solution à des problèmes de synchronisation de processus un peu complexes. Une solution élégante à ce problème est l'utilisation de files d'attente de messages : dans cet article nous allons étudier la théorie de ce système de communication entre processus et écrire un petit programme avec les primitives de SysV.
L'utilisation des files est ainsi celle d'un système de courrier entre processus : chacun dispose d'une adresse et peut connaître celle des autres. Le processus peut lire ses messages dans un ordre préférentiel et il agira en fonction de ce qui lui sera ainsi notifié.
La synchronisation de deux processus peut ainsi être réalisée grâce à des échanges de messages entre eux : les ressources garderont leurs sémaphores pour que les processus connaissent leur état, mais le partage du temps entre processus peut être obtenu directement. Nous voyons immédiatement que cette technique va simplifier considérablement dès le départ ce qui était un problème complexe.
Avant de commencer l'écriture en langage C d'une mise en oeuvre de telles files d'attente, il faut parler d'un autre problème posé par la synchronisation : il nous faut un protocole de communication.
Voici un exemple simple de protocole basé sur l'échange de messages : deux processus A et B s'exécutent et traitent des données distinctes; lorsqu'ils ont terminé ces traitements, ils doivent regrouper les résultats. Un protocole pour régler une telle interaction pourrait être
PROCESSUS B:
Ce protocole peut simplement être étendu au cas de n processus : chacun des processus, sauf A, traite ses propres données puis adresse un message à A. Lorsque A répond chacun envoie ses résultats : la structure de chaque processus (hormis A) n'a pas à être modifiée.
La structure fondamentale du système de description d'un message se nomme msgbuf; elle est déclarée dans linux/msg.h
/* tampon de message pour les appels msgsnd et msgrcv */ struct msgbuf { long mtype; /* type du message */ char mtext[1]; /* texte du message */ };
struct message { long mtype; /* type du message */ long sender; /* id émetteur */ long receiver; /* id destinataire */ struct info data; /* contenu du message */ ... };
Pour créer une nouvelle file un processus doit appeler la fonction msgget()
int msgget(key_t key, int msgflg)qui reçoit en argument une clé IPC et quelques drapeaux, qui pour l'instant seront fixés comme
IPC_CREAT | 0660(crée la file si elle n'existe pas déjà et donne accès à cette file au propriétaire et au groupe), puis retourne comme résultat l'identificateur attribué à la file.
Comme dans les articles précédents nous allons supposer qu'aucune erreur ne survient, ce qui va simplifier notre code, même si dans un article à venir nous parlerons de programmes 'sécurisés IPC'.
Pour envoyer un message à une file dont on connaît l'identificateur nous utiliserons la primitive msgsnd()
int msgsnd(int msqid, struct msgbuf *msgp, int msgsz, int msgflg)
length = sizeof(struct message) - sizeof(long);
Pour lire un message disponible dans une file nous utiliserons l'appel
système
msgrcv()
int msgrcv(int msqid, struct msgbuf *msgp, int msgsz, long mtype, int msgflg)
La suppression d'une file peut être obtenue par la primitive
msgctl()
avec IPC_RMID comme valeur du drapeau.
msgctl(qid, IPC_RMID, 0)
#include <stdio.h> #include <stdlib.h> #include <linux/ipc.h> #include <linux/msg.h> /* Redefinir la structure msgbuf */ typedef struct mymsgbuf { long mtype; int int_num; float float_num; char ch; } mess_t; int main() { int qid; key_t msgkey; mess_t sent; mess_t received; int length; /* Initialiser la base du générateur pseudo-aléatoire */ srand (time (0)); /* Longueur du message */ length = sizeof(mess_t) - sizeof(long); msgkey = ftok(".",'m'); /* Crée la file */ qid = msgget(msgkey, IPC_CREAT | 0660); printf("QID = %d\n", qid); /* Construit un message */ sent.mtype = 1; sent.int_num = rand(); sent.float_num = (float)(rand())/3; sent.carattere = 'f'; /* Envoie ce message */ msgsnd(qid, &sent, length, 0); printf("MESSAGE ENVOYE...\n"); /* Recoit le message */ msgrcv(qid, &received, length, sent.mtype, 0); printf("MESSAGE RECU...\n"); /* Controle l'égalité entre message envoyé et mesage reçu */ printf("Interger number = %d (sent %d) -- ", received.int_num, sent.int_num); if(received.int_num == sent.int_num) printf(" OK\n"); else printf("ERREUR\n"); printf("Float numero = %f (sent %f) -- ", received.float_num, sent.float_num); if(received.float_num == sent.float_num) printf(" OK\n"); else printf("ERREUR\n"); printf("Char = %c (sent %c) -- ", received.ch, sent.ch); if(received.ch == sent.ch) printf(" OK\n"); else printf("ERREUR\n"); /* Detruit la file */ msgctl(qid, IPC_RMID, 0); }
Le code que j'ai écrit crée une file utilisée par le processus fils
pour envoyer ses données au processus père : le fils génère des nombres
aléatoires, les envoie au père, et les deux les affichent sur la sortie
standard.
#include <stdio.h> #include <stdlib.h> #include <linux/ipc.h> #include <linux/msg.h> #include <sys/types.h> /* Redéfinit la structure message*/ typedef struct mymsgbuf { long mtype; int num; } mess_t; int main() { int qid; key_t msgkey; pid_t pid; mess_t buf; int length; int cont; length = sizeof(mess_t) - sizeof(long); msgkey = ftok(".",'m'); qid = msgget(msgkey, IPC_CREAT | 0660); if(!(pid = fork())){ printf("PERE - QID = %d\n", qid); srand (time (0)); for(cont = 0; cont < 10; cont++){ sleep (rand()%4); buf.mtype = 1; buf.num = rand()%100; msgsnd(qid, &buf, length, 0); printf("FILS - MESSAGE NUMERO %d: %d\n", cont+1, buf.num); } return 0; } printf("PERE - QID = %d\n", qid); for(cont = 0; cont < 10; cont++){ sleep (rand()%4); msgrcv(qid, &buf, length, 1, 0); printf("PERE - MESSAGE NUMERO %d: %d\n", cont+1, buf.num); } msgctl(qid, IPC_RMID, 0); return 0; }