Communication inter processus par IPC (System V) sous *NIX
Ce petit tutorial vous présente différentes méthodes proposées par les systèmes *NIX pour permettre une communication entre processus locaux. Ces mécanismes offrent de bien meilleures performances et de nombreux avantages par rapport aux mécanismes classiques. Ils ne sont pas encore implémentés dans le noyau windows, qui lui a surtout pris de lavance dans la variété des mécanismes de synchronisation inter processus. Mais qui sait ? Peut être que la prochaine mouture réserve quelque surprise.
Il nest pas nécessaire dêtre un expert pour lire ce tutorial. Il faut cependant avoir quelques notions sur le système UNIX ou linux. Si des expressions comme structure de données, primitive (ou appel système), processus, espace dadressage, système de fichier ou pointeur ne vous disent rien, il est préférable daller dabord se renseigner sur ces notions avant dentamer la lecture de ce document.
Les méthodes de communication les plus connues sont les fichiers, les tubes et les sockets. Ces méthodes font appel au système de fichier. Ce qui signifie que les mécanismes mis en uvre pour la communication par ces méthodes peuvent être lourds et lents (en terme système, pas en terme humain, uf corse !).
Les IPC, ou Inter Process Communication, apparus avec SystemV dUNIX et repris dans linux, ne sont pas des fichiers mais des éléments mémoires gérés par le noyau, donc avec de meilleures performances de gestion. Cependant, on verra que la philosophie UNIX, « Tout est fichier » est respectée par des méthodes de rattachement de ces objets au système de fichier.
Il existe 3 sortes dIPC :
- Les files de messages
- Les segments de mémoire partagés
- Les sémaphores
Ce tutorial sattachera sur les deux premiers types, car il traite principalement de la communication inter processus, tandis que les sémaphores sont plutôt réservés à des mécanismes comme la synchronisation des processus. Ils peuvent être utilisés comme mécanisme de communication mais ce nest pas leur rôle principal.
1) Les caractéristiques communes des IPC
a) Les IPC sont gérées par le noyau par lintermédiaire de tables. Cest-à-dire quil existe trois tables système IPC, une pour chaque type.
b) Du point de vue interne, chaque objet de type IPC est identifié au sein du système par un identificateur (positif ou nul), qui joue un rôle similaire aux identificateurs de fichier. Ainsi, un processus qui souhaite utiliser un des ces objets devra en connaître lidentificateur.
c) Du point de vue externe, les IPC sont identifiés par uneclé, qui joue le rôle des références pour les fichiers. Le type key_t, défini dans sys/types.h, est le type utilisé pour manipuler ces clés numériques.
d) le fichier contient des déclarations destructures, de constantes symboliques et de fonctions communes pour leur manipulation.
Les principales constantes symboliques sont des commandes utilisées avec les primitives de manipulation.
Les primitives de création (*get) utilisent :
IPC_PRIVATE : elle permet la création dun IPC personnel qui ne pourra être connu que depuis la filiation du processus créateur.
IPC_CREAT : elle permet la création dune IPC si celui-ci nexiste pas déjà. Sinon il est retrouvé.
IPC_EXCL : utilisée en conjonction avec IPC_CREAT, elle permet lémission dune erreur si lIPC existe déjà.
IPC_ALLOC : elle permet de retrouver un IPC. Celui-ci doit déjà exister. Sinon, il y a émission dune erreur.
Les primitives dopération (semop, msgrcv, msgsnd) utilisent :
IPC_NOWAIT : elle permet de mettre en uvre des opérations non bloquantes.
Les primitives de controle (*ctl) utilisent :
IPC_RMID : elle permet la suppression dune IPC.
IPC_STAT : elle permet dobtenir des informations sur une IPC.
IPC_SET : elle permet de modifier les caractéristiques dune IPC.
La structure ipc_perm regroupe des informations sur une IPC :
structipc_perm
{
uid_t uid; /*Propriétaire actuel de lIPC. */
gid_t gid; /* Groupe du propriétaire de lIPC. */
uid_t cuid; /* Créateurde lIPC. */
gid_t cgid; /* Groupe du créateur de lIPC. */
mode_t mode; /* Droits daccès à lIPC (ces notions sont restreintes à r et w) . */
key_t key; /* Clé de lIPC*/
unsigned short seq; /* Compteur dutilisation de lentrée IPC*/
};
Enfin, la fonction key_t ftok (const char *path, int id);
Cette fonction permet de générer une clé unique à partir de la référence path et de lentier id.
2) Les fonctions shell de manipulation des IPC
- ipcs : elle permet la consultation des tables dIPC.(ipcs --help pour connaître ses options).
Il existe un autre moyen de consulter les IPC, en passant par le pseudo système de fichier /proc et son pseudo répertoire sysvipc.Dautres caractéristiques des IPC sont regroupées dans le pseudo répertoire/proc/sys/kernel (entre parenthèse, la valeur par défaut sur mon système).
Pour les files de messages (cat /proc/sys/kernel/msg*):
msgmni (16): Nombre maximum dIPC actuellement autorisé.
msgmax (8192): Taille maximum dun message.
msgmnb (16384): Taille maximum en octets dune file de messages.
Pour les sémaphores (cat /proc/sys/kernel/sem) :
sem : regroupe toutes les limites pour les sémaphores avec dans lordre
semmsl (250) : Nombre maximum de sémaphores par groupe de sémaphores.
semmns (32000) : Nombre maximum de sémaphores autorisés dans le système.
semopm (32) : Nombre maximum dopérations multisémaphore par un appel semop ().
semmni (128) : Nombre maximum dentrées dans la table de sémaphores.
Pour les segments de mémoire partagés (cat/proc/sys/kernel/shm*) :
shmall (2 097 152) : Nombre maximum de pages partageable dans le système
shmmax (33 554 452) : Taille maximum dun segment de mémoire partagé.
Shmmni (4096) : Nombre maximum de segment que lon peutcréer.
- ipcrm : elle permet la suppression dune entrée dans une des tables dIPC. Les arguments optionnels q, -m et s permettent de spécifier la catégorie de lobjet que lon cherche. Seul le créateur ou le propriétaire de lIPC ainsi que le super utilisateur peuvent demander sa suppression.
3) Les files de messages
Il sagit en quelque sorte dune implémentation du principe de la boîte aux lettres. Cependant, elles autorisent des mécanismes de multiplexage, cest-à-dire la possibilité denvoyer des messages à plusieurs processus dans la même file. Ce que ne permet pas, par exemple, une socket simple.
Le fichier contient les déclarations de structures et de constantes symboliques spécifiques dont la structure msqid_ds,qui est la structure dune entrée dans la table des files de messages. On accède à une structure msqid_ds par lintermédiaire de la primitive msgctl ().
structmsqid_ds
{
struct ipc_perm msg_perm; /* Permissions sur la file. */
struct msg *msg_first; /* Pointeur vers le premier message de la file */
struct msg *msg_last; /*Pointeur vers le dernier message de la file */
msglen_t msg_cbytes; /* Nombre doctets actuellement dans la file. */
msgqnum_t msg_qnum; /*Nombre de messages actuellement dans la file. */
msglen_t msg_qbytes; /* Nombre maximum doctets autorisés dans la file. */
pid_t msg_lspid; /*pid du dernier msgsnd (). */
pid_t msg_lrpid; /*pid du dernier msgrcv (). */
time_t msg_stime; /*Date du dernier msgsnd (). */
time_t msg_rtime; /*Date du dernier msgrcv (). */
time_t msg_ctime; /*Date du dernier changement msgctl (). */
}
La structure msg nest pas en principe utilisée par les applications utilisateur :
struct msg
{
struct msg *msg_next ; /*Pointeur sur le message suivant */
long msg_type ; /*Type du message */
ushort msg_ts ; /*Taille du message (hors msg_type) */
short msg_spot ; /*Adresse du texte du message */
}
Enfin la structure générique dun message :
struct msg_buf
{
long mtype; /*type du message */
char mtext[1] ; /* Texte du message */
}
Cette structure nest pas directement utilisable. Il faut en général définir une structure spécifique à lapplication basée sur ce modèle.Le champ mtype est obligatoire et doit être un entier strictement positif. Cest ce type qui permet à un processus de choisir une catégorie de message dans la file et donc le multiplexage. La suite de la structure peut être nimporte quoi, à la seule condition que ce soit des objets contigus (pas de pointeurs).
Les quatre primitives de manipulations sont :
int msgget (key_t key, int msgflg);
int msgctl (int msqid, int cmd, structmsqid_ds *buf);
ssize_tmsgrcv (int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
int msgsnd (int msqid, const void *msgp,size_t msgsz, int msgflg);
- La primitive msgget () permet de créer ou de retrouver une file de message à partir de sa clé. Le champ msgflg est une combinaison des constantes symboliques avec les droits daccès.
- La primitive msgctl () permet daccéder ou de modifier les caractéristiques dune entrée de la table, ou de supprimer une entrée si cmd est égal à IPC_RMID. Lentrée est identifiée par son msqid.
Loption IPC_STAT permet de ramener lentête de la file, dans laquelle le champ msg_perm.mode permet de savoir si des lecteurs ou des écrivains sont bloqués :
-> MSG_RWAIT : des lecteurs sont en attente.
-> MSG_WWAIT : des écrivains sont en attente.
Loption IPC_SET permet de modifier les caractéristiques de lentrée.
-> Pour modifier msg_perm.uid, msg_perm.gid ou msg_perm.mode, il faut un euid identique à msg_perm_cuid ou msg_perm_uid ou SU.
-> Pour modifier msg_perm.qbytes, il faut un euid identique à SU.
Loption IPC_RMID permet de supprimer une file de message. Il faut cependant avoir un euid identique à SU.
- La primitive msgsnd () permet à un processus denvoyer un message dans la file.
Si msgflg contient IPC_NOWAIT, lappel est non bloquant, alors que par défaut cet appel est bloquant en mode interruptible. Une autre solution pour éviter le mode bloquant est dutiliser un système de délai (timeout).
Exemple :
MyAlarm (int sig) { return ; /* Juste pour provoquer la sortie du mode bloquant */ }
MyWriter () {
alarm(3) ; /* délai dattente de 3 secondes */
ret =msgsnd(
, 0) ; /* 0 nest pas IPC_NOWAIT */
if (ret != 0 ) {
switch(errno) {
case EINTR : /* timeout réalisé */
}
}
}
- La primitive msgrcv () permet de retirer un message de la file en fonction de son type. Comme pour msgsnd (), IPC_NOWAIT rend lappel non bloquant.
msgsz spécifie la taille attendue du message. Si msgflg contient MSG_NOERROR, alors lappel ne génère pas derreur si le message retiré est de taille supérieur à la taille demandée, il est alors tronqué, et la partie non lue est perdue. Dans le cas où MSG_NOERROR nest pas utilisé, le message nest pas lu, il reste dans la file et E2BIG est retourné dans errno.
msgtyp :
Si il vaut0, alors le premier message est lu.
Si il estsupérieur à 0, alors le premier message de type msgtype est lu
Si il estinférieur à 0, alors le premier message dont le type est inférieur ou égal à |msgtyp| est lu.
4) Les segments de mémoire partagés
Un soucis des mécanismes de communication classique (fichiers, tubes, etc.), lorsquon échange de grande quantités de données est quils nécessitent le transfert des données depuis lespace dadressage du processus émetteur vers lespace noyau, puis de lespace noyau vers lespace dadressage du processus destinataire, voir même par le système de fichier. Perte de temps. Lun des avantages des segments de mémoire partagés est que les processus vont partager des pages mémoires directement par lintermédiaire de leur espace dadressage. Gain de temps. Cependant, lutilisation de ces espaces partagés peut entraîner des effets de bords si les processus ne se synchronisent pas (signal, sémaphore, etc.). Dautre part, un segment a une existence indépendante des processus. Ainsi si tout les processus utilisant un segment se terminent, un autre processus, connaissant la clé du segment pourra demander lattachement (mapping) du segment dans son espace dadressage et lutiliser à son tour.
Le fichier sys/shm.h contient les déclarations de structures et de constantes symboliques spécifiques dont la structure shmid_ds, qui est la structure dune entrée dans la table des segments de mémoire partagés. On accède à une structure shmid _ds par lintermédiaire de la primitive shmctl().
structshmid_ds
{
struct ipc_perm shm_perm; /*Permissions sur le segment. */
size_t shm_segsz; /* Taille du segment en octets. */
pid_t shm_lpid; /* pid de la dernière opération. */
pid_t shm_cpid; /* pid du créateur. */
shmatt_t shm_nattch; /* Nombre dattachements. */
timestruc_t shm_atim; /* Date du dernier attachement shmat (). */
timestruc_t shm_dtim; /* Date du dernier détachement shmdt (). */
timestruc_t shm_ctim; /* Date du dernier chagement shmctl (). */
}
Les quatre primitives de manipulations sont :
int shmget (key_t key, size_t size, int shmflg);
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt (const void *shmaddr);
int shmctl (int shmid, int cmd, struct shmid_ds*buf);
- Le fonctionnement de shmget est identique à celui de msgget (). Le paramètre size spécifie la taille du segment.
Loption IPC_PRIVATE permet de créer un IPC qui ne sera partagé que par la filiation du processus créateur (les enfants nauront pas besoin de faire un ftok () pour retrouver cet IPC).
- La primitive shmat () est la primitive dattachement (mapping) ou dassociation du segment identifié par shmid à ladresse shmadr de lespace dadressage du processus demandeur. Après un appel réussi à cette primitive, le processus pourra lire ou écrire dans le segment de la même manière quà nimporte quel endroit de son espace dadressage. La valeur de retour est ladresse effective où lattachement a été réalisé (ladresse du premier octet utilisable) ou -1 en cas déchec. Le paramètre shmadr doit respecter certaines règles :
-> Ne pas entrer en conflit, immédiatement ou plus tard (du fait de laugmentation de la pile ou du déplacement du point de rupture), avec des adresses déjà utilisées ou susceptible de lêtre.
-> Ne pas violer la forme générale dune adresse imposée par le système : les segments commencent à des limites de pages et ont des adresses alignées.
Si shmadr est NULL, alors ladresse est choisie par le système. Cest la solution qui garanti la meilleure portabilité. Si shmadr est non NULL, alors ladresse est choisie par lutilisateur. Le système se chargera dajuster cette valeur si nécessaire.
Loption SHM_RND permet lattachement à ladresse la plus proche dont la valeur est multiple de la constante système SHMLBA.
Loption SHM_RDONLY permet lattachement dun segment en lecture seule. Un signal SIGSEGV sera levé si le processus tente une écriture dans le segment ainsi attaché.
Il existe également deux autres options SHM_LOCK et SHM_UNLOCK, utilisable uniquement par le super utilisateur qui empêche ou autorise le segment dêtre swappé.
Un shmat () nest pas utile lors dun fork () car les IPC sont hérités.
Un segment de mémoire partagé est toujours initialisé à 0.
- La primitive shmdt () permet le détachement dun segment de mémoire qui a été attaché par un appel à shmat (). Comme dit plus haut, le détachement dun segment de tous les processus ne provoque pas sa destruction.
- La primitive shmctl () permet de réaliser différentes opérations de contrôle sur un segment. Les valeurs possibles pour cmd sont :
IPC_RMID : suppression du segment identifié par shmid. Cette destruction est différée si le segment est encore attaché à dautres processus.
IPC_STAT : demande dinformation sur le segment par le remplissage de la structure pointée par buf.
IPC_SET : demande de modification des caractéristiques de lentrée identifiée par shmid avec les informations contenues dans la structure pointée par buf. Les seuls champs modifiables sont shm_perm.uid, shm_perm.gid et shm_perm.mode.
Après lattachement dun segment de mémoire partagé, ladresse retourné par shmat () est un pointeur vers le premier octet du segment. Cependant plusieurs demandes dattachement par plusieurs processus sur le même segment ne retournent pas la même adresse, puisque les espaces dadressage des processus sont différents et même dans le cas dun attachement multiple sur le même processus. Par conséquent lors de lutilisation dune structure chaînée (comme une liste simple ou double) dans un segment, le chaînage doit être fait en adressage relatif (par rapport au début du segment ou les éléments chaînés les uns par rapport aux autres) et non en adressage absolu. Les autres types de structures de données sutilisent de manière classique.
5) Conclusion
Voilà donc les mécanismes de communication inter processus proposés par les implémentations conformes à SYSTEM V. Jai passé sous silence les sémaphores, parce que le texte est suffisamment long et ceux qui sont intéressés par cet aspect du système feront comme moi, ils se renseigneront par eux-mêmes et dautre part, le mécanisme de communication par sémaphore est plutôt rudimentaire. Les sémaphores, proposés au départ par Dijkstra, servent plus à des mécanismes comme la synchronisation de processus ou le partage de ressources par exclusion mutuelle des processus quà la communication de données. Ce texte ne prétend pas être exhaustif et est loin dêtre complet sur le sujet.
Pour illustrer ce texte, jai réalisé, pour comprendre moi aussi, un programme, présentant lutilisation des files de messages et des segments de mémoire partagés. Il est disponible dans la partie « Sources » de CS.
Vos commentaires et critiques sont les bienvenues, dans la mesure où elles sont constructives.