Programmation du noyau Linux : utilisation des files de messages pour la communication inter-processus

Introduction à la Communication Inter-Processus (IPC)

La communication inter-processus (IPC) est fondamentale pour permettre à différents processus d'échanger des données et de se synchroniser. Sous Linux, plusieurs mécanismes IPC sont disponibles :

  • Pipes (tubes) et FIFOs (canaux nommés)
  • Signaux
  • Files de messages
  • Mémoire partagée
  • Sémaphores
  • Sockets (pour la communication réseau, qui est aussi une forme d'IPC)

Les mécanismes allant des pipes aux sémaphores sont principalement utilisés pour la communication sur une seule machine. Les sockets, quant à eux, étendent cette capacité à travers les réseaux, reliant des applications sur différentes machines.

Les Files de Messages

Les files de messages offrent un moyen structuré pour un processus d'envoyer des blocs de données (souvent des structures) à un autre processus. Chaque message peut être étiqueté avec un type, permettant au processus récepteur de sélectionner les messages qu'il souhaite lire.

Comme les pipes, les files de messages ont des limites : la taille maximale d'un message individuel et la taille totale des messaegs dans une file sont restreintes par le système.

  • Les trois grands de la mémoire sous Linux : Files de messages, Mémoire partagée, Sémaphores.
  • Ces trois mécanismes sont gérés directement par le noyau du système d'exploitation.
  • Contrairement aux signaux et aux pipes dont les zones mémoire sont associées aux processus, la mémoire utilisée par les files de messages, la mémoire partagée et les sémaphores réside en dehors des espaces mémoire des processus, dans le noyau. Ils ne sont donc pas liés à un processus spécifique.

Une file de messages peut être vue comme une liste chaînée de messages, stockée dans le noyau et identifiée par un identifiant unique.

Utilisation des Files de Messages

Pour travailler avec les files de messages, les en-têtes suivants sont nécessaires : <sys/msg.h>, <sys/types.h>, et <sys/ipc.h>.

Fonctions Clés :

  • msgget(key_t key, int msgflg) : Crée une nouvelle file de messages ou accède à une file existante.
  • msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg) : Envoie un message dans la file.
  • msgrcv(int msqid, struct msgbuf *msgp, size_t msgsz, long msgtyp, int msgflg) : Reçoit un message de la file.
  • msgctl(int msqid, int cmd, struct msqid_ds *buf) : Effectue diverses opérations de contrôle sur la file (ex: suppression).

Détails de msgget

  • key : Une clé associée à la file de messages. Elle est utilisée pour identifier de manière unique la file.
  • msgflg : Définit les permissions d'accès et les options de création (similaire aux modes de permission Unix).
  • En cas de succès, msgget retourne un identifiant de file de messages non négatif ; sinon, il retourne -1.

Exemple de création de file de messages :


#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <cstdio> // Pour perror

int main() {
   // Tente de créer ou d'accéder à une file de messages avec la clé 1001
   // IPC_CREAT : crée la file si elle n'existe pas
   // 0777 : permissions de lecture/écriture/exécution pour propriétaire, groupe et autres
   int queueId = msgget(static_cast<key_t>(1001), IPC_CREAT | 0777);

   if (queueId == -1) {
       perror("Erreur msgget"); // Affiche un message d'erreur système
   } else {
       std::cout << "File de messages créée avec succès. ID: " << queueId << std::endl;
   }
   return 0;
}
 

Après l'exécution de ce code, la commande ipcs dans le terminal peut être utilisée pour vérifier l'existence de la file de messages. Les files de messages créées persistent même après la fermeture du processus qui les a créées, car elles sont gérées par le noyau.

Pour supprimer une file de messages, la commande ipcrm est utilisée. Par exemple, ipcrm -q <queue_id></queue_id> supprime la file avec l'ID spécifié. La commande ipcrm -a initialise toute la mémoire IPC, supprimant tous les objets IPC créés par l'utilisateur.

Notez que certains objets IPC font partie intégrante du système et ne peuvent pas être supprimés.

Envoi de Messages avec msgsnd

La fonction msgsnd permet d'ajouter un message à une file existante.

Prototype :


int msgsnd(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);
 
  • msgid : L'identifiant de la file de messages retourné par msgget.
  • msg_ptr : Un pointeur vers la structure du message à envoyer. La structure doit inclure un champ de type long pour le type de message (doit être supérieur à 0) et les données du message.
  • msg_sz : La taille du message à envoyer, N'incluant PAS le champ de type long.
  • msgflg : Flags contrôlant le comportement lorsque la file est pleine.

Retourne 0 en cas de succès, -1 en cas d'échec.

Exemple d'envoi de message :


#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <cstdio>  // Pour perror
#include <cstring> // Pour strcpy

// Structure pour le message
typedef struct {
   long messageType;       // Type de message, doit être > 0
   char messageText[50];   // Données du message
} MessageBuffer;

int main() {
   int queueId = msgget(static_cast<key_t>(1001), IPC_CREAT | 0777);
   if (queueId == -1) {
       perror("Erreur msgget");
       return 1;
   } else {
       std::cout << "File de messages créée/accédée. ID: " << queueId << std::endl;
   }

   MessageBuffer message;
   message.messageType = 1; // Définir le type de message
   strcpy(message.messageText, "Bonjour le monde !");

   // Envoyer le message. La taille est celle du contenu (messageText).
   // Note: La taille passée à msgsnd est celle des données utiles, pas de la structure entière.
   // sizeof(MessageBuffer) - sizeof(long) correspond à la taille de mtext.
   if (msgsnd(queueId, &message, sizeof(message.messageText), 0) == -1) {
       perror("Erreur msgsnd");
       return 1;
   }

   std::cout << "Message envoyé avec succès." << std::endl;

   // Note: La file de messages persiste après la fin du programme.
   // Utilisez `ipcrm -q <queueid>` pour la supprimer manuellement si nécessaire.

   return 0;
}
 </queueid>

Après l'exécution, ipcs -q montrera un message dans la file.

Réception de Messages avec msgrcv

La fonction msgrcv permet de lire des messages depuis une file.

Prototype :


long msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
 
  • msqid : Identifiant de la file.
  • msgp : Pointeur vers la structrue où le message reçu sera stocké.
  • msgsz : Taille maximale du message à recevoir (la taille des données utiles).
  • msgtyp : Le type de message à recevoir. Si 0, le premier message de la file est reçu. Si > 0, le premier message du type spécifié est reçu.
  • msgflg : Flags pour le comportement de réception (ex: IPC_NOWAIT pour ne pas bloquer).

Retourne le nombre d'octets lus en cas de succès, -1 en cas d'échec.

Exemple d'envoi et de réception de plusieurs messages

Processus d'envoi :


#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <cstdio>  // Pour perror et sprintf
#include <cstring> // Pour strcpy

typedef struct {
   long messageType;
   char messageText[50];
} MessageBuffer;

int main() {
   int queueId = msgget(static_cast<key_t>(1001), IPC_CREAT | 0777);
   if (queueId == -1) {
       perror("Erreur msgget (sender)");
       return 1;
   } else {
       std::cout << "Sender: File de messages prête. ID: " << queueId << std::endl;
   }

   for (int i = 0; i < 3; ++i) {
       MessageBuffer message;
       message.messageType = 1; // Tous les messages sont de type 1
       sprintf(message.messageText, "Message numéro %d", i);

       // Envoyer le message. La taille passée est celle des données utiles.
       if (msgsnd(queueId, &message, sizeof(message.messageText), 0) == -1) {
           perror("Erreur msgsnd");
           // Continuer même en cas d'erreur pour tenter d'envoyer les autres
       } else {
           std::cout << "Sender: Message '" << message.messageText << "' envoyé." << std::endl;
       }
   }
   return 0;
}
 

Processus de réception :


#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <cstdio>  // Pour perror
#include <cstring> // Pour memset

typedef struct {
   long messageType;
   char messageText[50];
} MessageBuffer;

int main() {
   // Accéder à la file existante. Pas besoin de IPC_CREAT si le sender l'a déjà créée.
   int queueId = msgget(static_cast<key_t>(1001), 0); // 0 signifie ne pas créer
   if (queueId == -1) {
       perror("Erreur msgget (receiver)");
       return 1;
   } else {
       std::cout << "Receiver: File de messages trouvée. ID: " << queueId << std::endl;
   }

   MessageBuffer receivedMessage;

   // Recevoir les 3 messages de type 1
   for (int i = 0; i < 3; ++i) {
       // Initialiser la structure pour éviter des comportements inattendus
       memset(&receivedMessage, 0, sizeof(receivedMessage));

       // Recevoir un message de type 1. sizeof(receivedMessage.messageText) pour la taille des données.
       if (msgrcv(queueId, &receivedMessage, sizeof(receivedMessage.messageText), 1, 0) < 0) {
           perror("Erreur msgrcv");
           // Sortir si la réception échoue
           return 1;
       } else {
           std::cout << "Receiver: Reçu: '" << receivedMessage.messageText << "' (Type: " << receivedMessage.messageType << ")" << std::endl;
       }
   }
   return 0;
}
 

Pour exécuter cet exemple :

  1. Compilez et exécutez d'abord le processus d'envoi.
  2. Ensuite, compilez et exécutez le processus de réception.
  3. Utilisez ipcs -q pour observer l'état de la file de messages entre les étapes.

Les files de messages fonctionnent sur le principe du FIFO (First-In, First-Out). Les messages sont ajoutés à la fin et retirés du début.

Étiquettes: Linux noyau IPC file de messages programmation système

Publié le 9 juillet à 23h27