Code di messaggi

Perché abbiamo bisogno di code di messaggi quando abbiamo già la memoria condivisa? Sarebbe per molteplici ragioni, proviamo a suddividerlo in più punti per semplificazione -

  • Come inteso, una volta ricevuto il messaggio da un processo, non sarebbe più disponibile per nessun altro processo. Mentre nella memoria condivisa, i dati sono disponibili per l'accesso a più processi.

  • Se vogliamo comunicare con piccoli formati di messaggio.

  • I dati della memoria condivisa devono essere protetti con la sincronizzazione quando più processi comunicano contemporaneamente.

  • La frequenza di scrittura e lettura utilizzando la memoria condivisa è elevata, quindi sarebbe molto complesso implementare la funzionalità. Non vale per quanto riguarda l'utilizzo in questo tipo di casi.

  • E se tutti i processi non avessero bisogno di accedere alla memoria condivisa ma solo pochi processi ne avessero bisogno, sarebbe meglio implementarli con le code di messaggi.

  • Se vogliamo comunicare con diversi pacchetti di dati, supponiamo che il processo A invii il tipo di messaggio 1 al processo B, il tipo di messaggio 10 al processo C e il tipo di messaggio 20 al processo D. In questo caso, è più semplice implementarlo con le code di messaggi. Per semplificare il tipo di messaggio dato come 1, 10, 20, può essere 0 o + ve o –ve come discusso di seguito.

  • Naturalmente, l'ordine della coda dei messaggi è FIFO (First In First Out). Il primo messaggio inserito in coda è il primo ad essere recuperato.

L'utilizzo della memoria condivisa o delle code di messaggi dipende dalle necessità dell'applicazione e dall'efficacia con cui possono essere utilizzate.

La comunicazione tramite le code di messaggi può avvenire nei seguenti modi:

  • Scrittura nella memoria condivisa da un processo e lettura dalla memoria condivisa da un altro processo. Come sappiamo, la lettura può essere eseguita anche con più processi.

  • Scrittura nella memoria condivisa da un processo con diversi pacchetti di dati e lettura da esso da più processi, cioè, in base al tipo di messaggio.

Dopo aver visto alcune informazioni sulle code dei messaggi, ora è il momento di controllare la chiamata di sistema (System V) che supporta le code dei messaggi.

Per eseguire la comunicazione utilizzando le code di messaggi, di seguito sono riportati i passaggi:

Step 1 - Crea una coda di messaggi o connettiti a una coda di messaggi già esistente (msgget ())

Step 2 - Scrivi nella coda dei messaggi (msgsnd ())

Step 3 - Leggi dalla coda dei messaggi (msgrcv ())

Step 4 - Eseguire operazioni di controllo sulla coda dei messaggi (msgctl ())

Ora, controlliamo la sintassi e alcune informazioni sulle chiamate precedenti.

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgget(key_t key, int msgflg)

Questa chiamata di sistema crea o alloca una coda di messaggi di System V. Devono essere passati i seguenti argomenti:

  • Il primo argomento, key, riconosce la coda dei messaggi. La chiave può essere un valore arbitrario o uno che può essere derivato dalla funzione di libreria ftok ().

  • Il secondo argomento, shmflg, specifica i flag della coda dei messaggi richiesti come IPC_CREAT (creazione della coda messaggi se non esiste) o IPC_EXCL (utilizzato con IPC_CREAT per creare la coda messaggi e la chiamata non riesce, se la coda messaggi esiste già). È necessario passare anche le autorizzazioni.

Note - Fare riferimento alle sezioni precedenti per i dettagli sulle autorizzazioni.

Questa chiamata restituirà un identificatore di coda di messaggi valido (utilizzato per ulteriori chiamate di coda di messaggi) in caso di successo e -1 in caso di errore. Per conoscere la causa del fallimento, controlla con la variabile errno o la funzione perror ().

Vari errori rispetto a questa chiamata sono EACCESS (autorizzazione negata), EEXIST (la coda esiste già non può creare), ENOENT (la coda non esiste), ENOMEM (memoria insufficiente per creare la coda), ecc.

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgsnd(int msgid, const void *msgp, size_t msgsz, int msgflg)

Questa chiamata di sistema invia / aggiunge un messaggio alla coda dei messaggi (Sistema V). Devono essere passati i seguenti argomenti:

  • Il primo argomento, msgstr, riconosce la coda dei messaggi, cioè l'identificatore della coda dei messaggi. Il valore dell'identificatore viene ricevuto dopo il successo di msgget ()

  • Il secondo argomento, msgp, è il puntatore al messaggio, inviato al chiamante, definito nella struttura del seguente form -

struct msgbuf {
   long mtype;
   char mtext[1];
};

La variabile mtype viene utilizzata per comunicare con diversi tipi di messaggi, spiegati in dettaglio nella chiamata msgrcv (). La variabile mtext è un array o un'altra struttura la cui dimensione è specificata da msgsz (valore positivo). Se il campo mtext non viene menzionato, viene considerato come messaggio di dimensione zero, il che è consentito.

  • Il terzo argomento, msgsz, è la dimensione del messaggio (il messaggio dovrebbe terminare con un carattere nullo)

  • Il quarto argomento, msgflg, indica determinati flag come IPC_NOWAIT (restituisce immediatamente quando nessun messaggio viene trovato in coda o MSG_NOERROR (tronca il testo del messaggio, se più di msgsz byte)

Questa chiamata restituirà 0 in caso di successo e -1 in caso di fallimento. Per conoscere la causa del fallimento, controlla con la variabile errno o la funzione perror ().

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgrcv(int msgid, const void *msgp, size_t msgsz, long msgtype, int msgflg)

Questa chiamata di sistema recupera il messaggio dalla coda dei messaggi (Sistema V). Devono essere passati i seguenti argomenti:

  • Il primo argomento, msgstr, riconosce la coda dei messaggi, cioè l'identificativo della coda dei messaggi. Il valore dell'identificatore viene ricevuto dopo il successo di msgget ()

  • Il secondo argomento, msgp, è il puntatore del messaggio ricevuto dal chiamante. È definito nella struttura del modulo seguente:

struct msgbuf {
   long mtype;
   char mtext[1];
};

La variabile mtype viene utilizzata per comunicare con diversi tipi di messaggi. La variabile mtext è un array o un'altra struttura la cui dimensione è specificata da msgsz (valore positivo). Se il campo mtext non viene menzionato, viene considerato come messaggio di dimensione zero, il che è consentito.

  • Il terzo argomento, msgsz, è la dimensione del messaggio ricevuto (il messaggio dovrebbe terminare con un carattere nullo)

  • Il quarto argomento, msgtype, indica il tipo di messaggio -

    • If msgtype is 0 - Legge il primo messaggio ricevuto nella coda

    • If msgtype is +ve - Legge il primo messaggio nella coda di tipo msgtype (se msgtype è 10, quindi legge solo il primo messaggio di tipo 10 anche se altri tipi potrebbero essere nella coda all'inizio)

    • If msgtype is –ve - Legge il primo messaggio di tipo più basso minore o uguale al valore assoluto del tipo di messaggio (ad esempio, se msgtype è -5, allora legge il primo messaggio di tipo inferiore a 5 cioè, tipo di messaggio da 1 a 5)

  • Il quinto argomento, msgflg, indica determinati flag come IPC_NOWAIT (restituisce immediatamente quando non viene trovato alcun messaggio nella coda o MSG_NOERROR (tronca il testo del messaggio se più di msgsz byte)

Questa chiamata restituirà il numero di byte effettivamente ricevuti nell'array mtext in caso di successo e -1 in caso di errore. Per conoscere la causa del fallimento, controlla con la variabile errno o la funzione perror ().

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgctl(int msgid, int cmd, struct msqid_ds *buf)

Questa chiamata di sistema esegue le operazioni di controllo della coda dei messaggi (Sistema V). Devono essere passati i seguenti argomenti:

  • Il primo argomento, msgstr, riconosce la coda dei messaggi, cioè l'identificativo della coda dei messaggi. Il valore dell'identificatore viene ricevuto dopo il successo di msgget ()

  • Il secondo argomento, cmd, è il comando per eseguire l'operazione di controllo richiesta sulla coda dei messaggi. I valori validi per cmd sono -

IPC_STAT- Copia le informazioni dei valori correnti di ogni membro di struct msqid_ds nella struttura passata indicata da buf. Questo comando richiede l'autorizzazione di lettura sulla coda dei messaggi.

IPC_SET - Imposta l'ID utente, l'ID gruppo del proprietario, i permessi, ecc. Puntati dalla struttura buf.

IPC_RMID - Rimuove immediatamente la coda dei messaggi.

IPC_INFO - Restituisce informazioni sui limiti e sui parametri della coda di messaggi nella struttura indicata da buf, che è di tipo struct msginfo

MSG_INFO - Restituisce una struttura msginfo contenente informazioni sulle risorse di sistema consumate dalla coda dei messaggi.

  • Il terzo argomento, buf, è un puntatore alla struttura della coda dei messaggi denominata struct msqid_ds. I valori di questa struttura verrebbero usati per set o get come da cmd.

Questa chiamata restituirà il valore a seconda del comando passato. Il successo di IPC_INFO e MSG_INFO o MSG_STAT restituisce l'indice o l'identificatore della coda dei messaggi o 0 per altre operazioni e -1 in caso di errore. Per conoscere la causa del fallimento, controlla con la variabile errno o la funzione perror ().

Dopo aver visto le informazioni di base e le chiamate di sistema relative alle code di messaggi, ora è il momento di controllare con un programma.

Vediamo la descrizione prima di guardare il programma -

Step 1 - Creare due processi, uno per l'invio nella coda dei messaggi (msgq_send.c) e un altro per il recupero dalla coda dei messaggi (msgq_recv.c)

Step 2- Creazione della chiave, utilizzando la funzione ftok (). Per questo, inizialmente viene creato il file msgq.txt per ottenere una chiave univoca.

Step 3 - Il processo di invio esegue quanto segue.

  • Legge l'input della stringa dall'utente

  • Rimuove la nuova riga, se esiste

  • Invia alla coda dei messaggi

  • Ripete il processo fino alla fine dell'inserimento (CTRL + D)

  • Una volta ricevuta la fine dell'input, invia il messaggio “end” per indicare la fine del processo

Step 4 - Nel processo di ricezione, esegue quanto segue.

  • Legge il messaggio dalla coda
  • Visualizza l'output
  • Se il messaggio ricevuto è "end", termina il processo ed esce

Per semplificare, non stiamo usando il tipo di messaggio per questo esempio. Inoltre, un processo sta scrivendo nella coda e un altro processo sta leggendo dalla coda. Questo può essere esteso in base alle necessità, ad esempio, idealmente un processo scriverebbe nella coda e più processi leggerebbero dalla coda.

Ora controlliamo il processo (invio del messaggio in coda) - File: msgq_send.c

/* Filename: msgq_send.c */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#define PERMS 0644
struct my_msgbuf {
   long mtype;
   char mtext[200];
};

int main(void) {
   struct my_msgbuf buf;
   int msqid;
   int len;
   key_t key;
   system("touch msgq.txt");
   
   if ((key = ftok("msgq.txt", 'B')) == -1) {
      perror("ftok");
      exit(1);
   }
   
   if ((msqid = msgget(key, PERMS | IPC_CREAT)) == -1) {
      perror("msgget");
      exit(1);
   }
   printf("message queue: ready to send messages.\n");
   printf("Enter lines of text, ^D to quit:\n");
   buf.mtype = 1; /* we don't really care in this case */
   
   while(fgets(buf.mtext, sizeof buf.mtext, stdin) != NULL) {
      len = strlen(buf.mtext);
      /* remove newline at end, if it exists */
      if (buf.mtext[len-1] == '\n') buf.mtext[len-1] = '\0';
      if (msgsnd(msqid, &buf, len+1, 0) == -1) /* +1 for '\0' */
      perror("msgsnd");
   }
   strcpy(buf.mtext, "end");
   len = strlen(buf.mtext);
   if (msgsnd(msqid, &buf, len+1, 0) == -1) /* +1 for '\0' */
   perror("msgsnd");
   
   if (msgctl(msqid, IPC_RMID, NULL) == -1) {
      perror("msgctl");
      exit(1);
   }
   printf("message queue: done sending messages.\n");
   return 0;
}

Fasi di compilazione ed esecuzione

message queue: ready to send messages.
Enter lines of text, ^D to quit:
this is line 1
this is line 2
message queue: done sending messages.

Di seguito è riportato il codice dal processo di ricezione del messaggio (recupero del messaggio dalla coda) - File: msgq_recv.c

/* Filename: msgq_recv.c */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#define PERMS 0644
struct my_msgbuf {
   long mtype;
   char mtext[200];
};

int main(void) {
   struct my_msgbuf buf;
   int msqid;
   int toend;
   key_t key;
   
   if ((key = ftok("msgq.txt", 'B')) == -1) {
      perror("ftok");
      exit(1);
   }
   
   if ((msqid = msgget(key, PERMS)) == -1) { /* connect to the queue */
      perror("msgget");
      exit(1);
   }
   printf("message queue: ready to receive messages.\n");
   
   for(;;) { /* normally receiving never ends but just to make conclusion 
             /* this program ends wuth string of end */
      if (msgrcv(msqid, &buf, sizeof(buf.mtext), 0, 0) == -1) {
         perror("msgrcv");
         exit(1);
      }
      printf("recvd: \"%s\"\n", buf.mtext);
      toend = strcmp(buf.mtext,"end");
      if (toend == 0)
      break;
   }
   printf("message queue: done receiving messages.\n");
   system("rm msgq.txt");
   return 0;
}

Fasi di compilazione ed esecuzione

message queue: ready to receive messages.
recvd: "this is line 1"
recvd: "this is line 2"
recvd: "end"
message queue: done receiving messages.