GNU/Linux >> Linux Esercitazione >  >> Linux

Comunicazione tra processi in Linux:utilizzo di pipe e code di messaggi

Questo è il secondo articolo di una serie sulla comunicazione interprocesso (IPC) in Linux. Il primo articolo si è concentrato sull'IPC attraverso l'archiviazione condivisa:file condivisi e segmenti di memoria condivisa. Questo articolo si rivolge alle pipe, che sono canali che collegano i processi per la comunicazione. Un canale ha una fine di scrittura per la scrittura di byte e un fine lettura per leggere questi byte in ordine FIFO (first in, first out). Nell'uso tipico, un processo scrive sul canale e un processo diverso legge da questo stesso canale. I byte stessi potrebbero rappresentare qualsiasi cosa:numeri, record dei dipendenti, filmati digitali e così via.

Le pipe sono disponibili in due versioni, con nome e senza nome, e possono essere utilizzate in modo interattivo dalla riga di comando o all'interno dei programmi; gli esempi sono in arrivo. Questo articolo esamina anche le code di memoria, che sono passate di moda, ma immeritatamente.

Gli esempi di codice nel primo articolo hanno riconosciuto la minaccia di race condition (basata su file o basata sulla memoria) nell'IPC che utilizza l'archiviazione condivisa. Sorge naturalmente la domanda sulla concorrenza sicura per l'IPC basato sul canale, che sarà trattata in questo articolo. Gli esempi di codice per pipe e code di memoria utilizzano API con il timbro di approvazione POSIX e uno degli obiettivi principali degli standard POSIX è la sicurezza dei thread.

Considera le pagine man per mq_open funzione, che appartiene all'API della coda di memoria. Queste pagine includono una sezione sugli Attributi con questa piccola tabella:

Interfaccia Attributo Valore
mq_open() Sicurezza del filo MT-Safe

Il valore MT-Safe (con MT per multi-thread) significa che mq_open la funzione è thread-safe, che a sua volta implica process-safe:un processo viene eseguito esattamente nel senso in cui viene eseguito uno dei suoi thread e se non può verificarsi una race condition tra thread nello stesso processo, una tale condizione non può verificarsi tra thread in processi diversi. Il MT-Safe attributo assicura che non si verifichi una race condition nelle invocazioni di mq_open . In generale, l'IPC basato sul canale è sicuro per la concorrenza, sebbene negli esempi che seguono venga sollevata una nota cautelativa.

Pipe senza nome

Iniziamo con un esempio di riga di comando inventato che mostra come funzionano le pipe senza nome. Su tutti i sistemi moderni, la barra verticale | rappresenta una pipe senza nome nella riga di comando. Assumi % è il prompt della riga di comando e considera questo comando:

% sleep 5 | echo "Hello, world!" ## writer to the left of |, reader to the right

Il sonno e eco le utility vengono eseguite come processi separati e la pipe senza nome consente loro di comunicare. Tuttavia, l'esempio è inventato in quanto non si verifica alcuna comunicazione. Il saluto Ciao, mondo! appare sullo schermo; quindi, dopo circa cinque secondi, il prompt della riga di comando ritorna, indicando che entrambi sospendono e eco i processi sono usciti. Cosa sta succedendo?

Nella sintassi della barra verticale dalla riga di comando, il processo a sinistra (sleep ) è lo scrittore e il processo a destra (echo ) è il lettore. Per impostazione predefinita, il lettore si blocca finché non ci sono byte da leggere dal canale e lo scrittore, dopo aver scritto i suoi byte, termina inviando un marker di fine flusso. (Anche se lo scrittore termina prematuramente, al lettore viene inviato un indicatore di fine flusso.) La pipe senza nome persiste finché sia ​​lo scrittore che il lettore non terminano.

[Scarica la guida completa alla comunicazione tra processi in Linux]

Nell'esempio inventato, il sonno il processo non scrive alcun byte nel canale ma termina dopo circa cinque secondi, inviando un indicatore di fine flusso al canale. Nel frattempo, l'eco process scrive immediatamente il saluto sullo standard output (lo schermo) perché questo processo non legge alcun byte dal canale, quindi non attende. Una volta che il sonno e eco i processi terminano, la pipe senza nome, non utilizzata affatto per la comunicazione, scompare e viene restituito il prompt della riga di comando.

Ecco un esempio più utile che utilizza due pipe senza nome. Supponiamo che il file test.dat assomiglia a questo:

this
is
the
way
the
world
ends

Il comando:

% cat test.dat | sort | uniq

reindirizza l'output dal cat (concatenare) nel ordinamento processo per produrre output ordinato, quindi convoglia l'output ordinato in uniq processo per eliminare i record duplicati (in questo caso, le due occorrenze di il ridurre a uno):

ends
is
the
this
way
world

La scena ora è impostata per un programma con due processi che comunicano attraverso una pipe senza nome.

Esempio 1. Due processi che comunicano tramite una pipe senza nome.

#include <sys/wait.h> /* wait */
#include <stdio.h>
#include <stdlib.h>   /* exit functions */
#include <unistd.h>   /* read, write, pipe, _exit */
#include <string.h>

#define ReadEnd  0
#define WriteEnd 1

void report_and_exit(const char* msg) {
  perror(msg);
  exit(-1);    /** failure **/
}

int main() {
  int pipeFDs[2]; /* two file descriptors */
  char buf;       /* 1-byte buffer */
  const char* msg = "Nature's first green is gold\n"; /* bytes to write */

  if (pipe(pipeFDs) < 0) report_and_exit("pipeFD");
  pid_t cpid = fork();                                /* fork a child process */
  if (cpid < 0) report_and_exit("fork");              /* check for failure */

  if (0 == cpid) {    /*** child ***/                 /* child process */
    close(pipeFDs[WriteEnd]);                         /* child reads, doesn't write */

    while (read(pipeFDs[ReadEnd], &buf, 1) > 0)       /* read until end of byte stream */
      write(STDOUT_FILENO, &buf, sizeof(buf));        /* echo to the standard output */

    close(pipeFDs[ReadEnd]);                          /* close the ReadEnd: all done */
    _exit(0);                                         /* exit and notify parent at once  */
  }
  else {              /*** parent ***/
    close(pipeFDs[ReadEnd]);                          /* parent writes, doesn't read */

    write(pipeFDs[WriteEnd], msg, strlen(msg));       /* write the bytes to the pipe */
    close(pipeFDs[WriteEnd]);                         /* done writing: generate eof */

    wait(NULL);                                       /* wait for child to exit */
    exit(0);                                          /* exit normally */
  }
  return 0;
}

Il pipeUN programma sopra utilizza la funzione di sistema fork per creare un processo. Sebbene il programma abbia un solo file di origine, l'elaborazione multipla si verifica durante l'esecuzione (riuscita). Ecco i dettagli in una rapida rassegna di come la funzione della libreria fork funziona:

  • Il fork funzione, chiamata nel genitore processo, restituisce -1 al genitore in caso di inadempimento. In pipeUN ad esempio, la chiamata è:
    pid_t cpid = fork(); /* called in parent */

    Il valore restituito viene archiviato, in questo esempio, nella variabile cpid di tipo intero pid_t . (Ogni processo ha il proprio ID processo , un numero intero non negativo che identifica il processo.) Il fork di un nuovo processo potrebbe non riuscire per diversi motivi, inclusa una tabella di processo completa , una struttura che il sistema mantiene per tenere traccia dei processi. I processi zombie, chiariti a breve, possono causare il riempimento di una tabella dei processi se questi non vengono raccolti.

  • Se il fork la chiamata ha esito positivo, quindi genera (crea) un nuovo processo figlio, restituendo un valore al genitore ma un valore diverso al figlio. Sia il processo padre che quello figlio eseguono lo stesso codice che segue la chiamata a fork . (Il figlio eredita copie di tutte le variabili dichiarate finora nel genitore.) In particolare, una chiamata riuscita a fork restituisce:
    • Zero al processo figlio
    • L'ID del processo del bambino al genitore
  • Un se/altro o un costrutto equivalente viene in genere utilizzato dopo un fork riuscito chiamata per separare il codice destinato al genitore dal codice destinato al figlio. In questo esempio, il costrutto è:
    if (0 == cpid) {    /*** child ***/
    ...
    }
    else {              /*** parent ***/
    ...
    }

Se il fork di un figlio riesce, pipeUN il programma procede come segue. C'è una matrice intera:

int pipeFDs[2]; /* two file descriptors */

per contenere due descrittori di file, uno per la scrittura nella pipe e l'altro per la lettura dalla pipe. (L'elemento dell'array pipeFDs[0] è il descrittore di file per la fine della lettura e l'elemento dell'array pipeFDs[1] è il descrittore di file per la fine della scrittura.) Una chiamata riuscita al sistema pipe funzione, effettuata immediatamente prima della chiamata a fork , popola l'array con i due descrittori di file:

if (pipe(pipeFDs) < 0) report_and_exit("pipeFD");

Più risorse Linux

  • Comandi Linux cheat sheet
  • Cheat sheet sui comandi avanzati di Linux
  • Corso online gratuito:Panoramica tecnica RHEL
  • Cheat sheet della rete Linux
  • Cheat sheet di SELinux
  • Cheat sheet dei comandi comuni di Linux
  • Cosa sono i container Linux?
  • I nostri ultimi articoli su Linux

Il genitore e il figlio ora hanno copie di entrambi i descrittori di file, ma la separazione delle preoccupazioni pattern significa che ogni processo richiede esattamente uno dei descrittori. In questo esempio, il genitore scrive e il bambino legge, anche se i ruoli potrebbero essere invertiti. La prima affermazione nel bambino se -clause code, quindi, chiude la fine della scrittura della pipe:

close(pipeFDs[WriteEnd]); /* called in child code */

e la prima affermazione nel genitore else -clause code chiude l'estremità di lettura della pipe:

close(pipeFDs[ReadEnd]);  /* called in parent code */

Il genitore quindi scrive alcuni byte (codici ASCII) nella pipe senza nome e il figlio li legge e ne fa eco allo standard output.

Un altro aspetto del programma necessita di chiarimenti:l'invito all'attesa funzione nel codice padre. Una volta generato, un processo figlio è in gran parte indipendente dal suo genitore, come anche il breve pipeUN illustra il programma. Il bambino può eseguire codice arbitrario che potrebbe non avere nulla a che fare con il genitore. Tuttavia, il sistema avvisa il genitore tramite un segnale, se e quando il bambino finisce.

E se il genitore cessa prima del figlio? In questo caso, a meno che non vengano prese precauzioni, il bambino diventa e rimane uno zombie processo con una voce nella tabella dei processi. Le precauzioni sono di due grandi tipi. Una precauzione consiste nel fare in modo che il genitore comunichi al sistema che il genitore non ha alcun interesse nel licenziamento del figlio:

signal(SIGCHLD, SIG_IGN); /* in parent: ignore notification */

Un secondo approccio consiste nel far eseguire al genitore un attesa sulla cessazione del figlio, assicurando così che il genitore sopravviva al bambino. Questo secondo approccio viene utilizzato in pipeUN programma, dove il codice padre ha questa chiamata:

wait(NULL); /* called in parent */

Questa chiamata per attendere significa attendere fino al termine di qualsiasi figlio e in pipeUN programma, c'è solo un processo figlio. (Il NULL argomento potrebbe essere sostituito con l'indirizzo di una variabile intera per mantenere lo stato di uscita del bambino.) Esiste un waitpid più flessibile funzione per il controllo a grana fine, ad esempio per specificare un particolare processo figlio tra diversi.

Il pipeUN il programma prende un'altra precauzione. Quando il genitore ha finito di aspettare, il genitore termina con la chiamata alla normale uscita funzione. Al contrario, il bambino termina con una chiamata al _exit variante, che accelera la notifica di cessazione. In effetti, il bambino sta dicendo al sistema di notificare al genitore al più presto che il bambino ha terminato.

Se due processi scrivono nella stessa pipe senza nome, i byte possono essere intercalati? Ad esempio, se il processo P1 scrive:

foo bar

in una pipe e nel processo P2 scrive contemporaneamente:

baz baz

alla stessa pipe, sembra che il contenuto della pipe possa essere qualcosa di arbitrario, come:

baz foo baz bar

Lo standard POSIX garantisce che le scritture non siano intercalate fintanto che nessuna scrittura supera PIPE_BUF byte. Sui sistemi Linux, PIPE_BUF ha una dimensione di 4.096 byte. La mia preferenza con le pipe è avere un solo scrittore e un solo lettore, aggirando così il problema.

Pipe con nome

Una pipe senza nome non ha file di supporto:il sistema mantiene un buffer in memoria per trasferire i byte dallo scrittore al lettore. Una volta terminato lo scrittore e il lettore, il buffer viene recuperato, quindi la pipe senza nome scompare. Al contrario, una named pipe ha un file di supporto e un'API distinta.

Diamo un'occhiata a un altro esempio da riga di comando per ottenere l'essenza delle pipe con nome. Ecco i passaggi:

  • Apri due terminali. La directory di lavoro dovrebbe essere la stessa per entrambi.
  • In uno dei terminali, inserisci questi due comandi (il prompt è di nuovo % e i miei commenti iniziano con ## ):
    % mkfifo tester  ## creates a backing file named tester
    % cat tester     ## type the pipe's contents to stdout

    All'inizio, nel terminale non dovrebbe apparire nulla perché non è stato ancora scritto nulla sulla named pipe.

  • Nel secondo terminale, inserisci il comando:
    % cat > tester  ## redirect keyboard input to the pipe
    hello, world!   ## then hit Return key
    bye, bye        ## ditto
    <Control-C>     ## terminate session with a Control-C

    Tutto ciò che viene digitato in questo terminale viene ripreso nell'altro. Una volta Ctrl+C viene inserito, il normale prompt della riga di comando ritorna in entrambi i terminali:la pipe è stata chiusa.

  • Ripulisci rimuovendo il file che implementa la named pipe:
    % unlink tester

Come nome dell'utilità mkfifo implica, una named pipe è anche chiamata FIFO perché il primo byte in è il primo byte in uscita e così via. Esiste una funzione di libreria denominata mkfifo che crea una named pipe nei programmi e viene utilizzata nell'esempio successivo, che consiste in due processi:uno scrive nella named pipe e l'altro legge da questa pipe.

Esempio 2. Il fifoWriter programma

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>
#include <stdio.h>

#define MaxLoops         12000   /* outer loop */
#define ChunkSize           16   /* how many written at a time */
#define IntsPerChunk         4   /* four 4-byte ints per chunk */
#define MaxZs              250   /* max microseconds to sleep */

int main() {
  const char* pipeName = "./fifoChannel";
  mkfifo(pipeName, 0666);                      /* read/write for user/group/others */
  int fd = open(pipeName, O_CREAT | O_WRONLY); /* open as write-only */
  if (fd < 0) return -1;                       /* can't go on */

  int i;
  for (i = 0; i < MaxLoops; i++) {          /* write MaxWrites times */
    int j;
    for (j = 0; j < ChunkSize; j++) {       /* each time, write ChunkSize bytes */
      int k;
      int chunk[IntsPerChunk];
      for (k = 0; k < IntsPerChunk; k++)
        chunk[k] = rand();
      write(fd, chunk, sizeof(chunk));
    }
    usleep((rand() % MaxZs) + 1);           /* pause a bit for realism */
  }

  close(fd);           /* close pipe: generates an end-of-stream marker */
  unlink(pipeName);    /* unlink from the implementing file */
  printf("%i ints sent to the pipe.\n", MaxLoops * ChunkSize * IntsPerChunk);

  return 0;
}

Il fifoWriter programma sopra può essere riassunto come segue:

  • Il programma crea una pipe denominata per la scrittura:
    mkfifo(pipeName, 0666); /* read/write perms for user/group/others */
    int fd = open(pipeName, O_CREAT | O_WRONLY);

    dove nomepipe è il nome del file di supporto passato a mkfifo come primo argomento. La named pipe viene quindi aperta con l'ormai familiare chiamata a open funzione, che restituisce un descrittore di file.

  • Per un tocco di realismo, il fifoWriter non scrive tutti i dati in una volta, ma invece scrive un blocco, dorme un numero casuale di microsecondi e così via. In totale, nella named pipe vengono scritti 768.000 valori interi a 4 byte.
  • Dopo aver chiuso la named pipe, il fifoWriter scollega anche il file:
    close(fd);        /* close pipe: generates end-of-stream marker */
    unlink(pipeName); /* unlink from the implementing file */

    Il sistema recupera il file di supporto una volta che ogni processo connesso alla pipe ha eseguito l'operazione di scollegamento. In questo esempio, ci sono solo due di questi processi:fifoWriter e il fifoReader , entrambi effettuano uno scollegamento operazione.

I due programmi devono essere eseguiti in terminali diversi con la stessa directory di lavoro. Tuttavia, il fifoWriter dovrebbe essere avviato prima di fifoReader , poiché il primo crea la pipe. Il fifoReader quindi accede alla named pipe già creata.

Esempio 3. Il fifoReader programma

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>

unsigned is_prime(unsigned n) { /* not pretty, but efficient */
  if (n <= 3) return n > 1;
  if (0 == (n % 2) || 0 == (n % 3)) return 0;

  unsigned i;
  for (i = 5; (i * i) <= n; i += 6)
    if (0 == (n % i) || 0 == (n % (i + 2))) return 0;

  return 1; /* found a prime! */
}

int main() {
  const char* file = "./fifoChannel";
  int fd = open(file, O_RDONLY);
  if (fd < 0) return -1; /* no point in continuing */
  unsigned count = 0, total = 0, primes_count = 0;

  while (1) {
    int next;
    int i;

    ssize_t count = read(fd, &next, sizeof(int));
    if (0 == count) break;                  /* end of stream */
    else if (count == sizeof(int)) {        /* read a 4-byte int value */
      total++;
      if (is_prime(next)) primes_count++;
    }
  }

  close(fd);       /* close pipe from read end */
  unlink(file);    /* unlink from the underlying file */
  printf("Received ints: %u, primes: %u\n", total, primes_count);

  return 0;
}

Il fifoReader programma sopra può essere riassunto come segue:

  • Perché il fifoWriter crea la named pipe, il fifoReader necessita solo della chiamata standard aperta per accedere alla pipe tramite il file di supporto:
    const char* file = "./fifoChannel";
    int fd = open(file, O_RDONLY);

    Il file si apre in sola lettura.

  • Il programma entra quindi in un ciclo potenzialmente infinito, cercando di leggere un blocco di 4 byte su ogni iterazione. La lettura call:
    ssize_t count = read(fd, &next, sizeof(int));

    restituisce 0 per indicare la fine del flusso, nel qual caso il fifoReader interrompe il ciclo, chiude la named pipe e scollega il file di backup prima di terminare.

  • Dopo aver letto un intero di 4 byte, il fifoReader controlla se il numero è primo. Ciò rappresenta la logica aziendale che un lettore di livello di produzione potrebbe eseguire sui byte ricevuti. In un'analisi campione, c'erano 37.682 numeri primi tra i 768.000 interi ricevuti.

Su esecuzioni di campioni ripetute, il fifoReader ha letto correttamente tutti i byte che fifoWriter ha scritto. Questo non è sorprendente. I due processi vengono eseguiti sullo stesso host, eliminando i problemi di rete dall'equazione. Le pipe con nome sono un meccanismo IPC altamente affidabile ed efficiente e, quindi, ampiamente utilizzato.

Ecco l'output dei due programmi, ciascuno lanciato da un terminale separato ma con la stessa directory di lavoro:

% ./fifoWriter
768000 ints sent to the pipe.
###
% ./fifoReader
Received ints: 768000, primes: 37682

Code di messaggi

Le pipe hanno un comportamento FIFO rigoroso:il primo byte scritto è il primo byte letto, il secondo byte scritto è il secondo byte letto e così via. Le code di messaggi possono comportarsi allo stesso modo, ma sono sufficientemente flessibili da consentire il recupero di blocchi di byte fuori dall'ordine FIFO.

Come suggerisce il nome, una coda di messaggi è una sequenza di messaggi, ognuno dei quali ha due parti:

  • Il carico utile, che è un array di byte (char in C)
  • Un tipo, dato come valore intero positivo; i tipi categorizzano i messaggi per un recupero flessibile

Considera la seguente rappresentazione di una coda di messaggi, con ogni messaggio etichettato con un tipo intero:

          +-+    +-+    +-+    +-+
sender--->|3|--->|2|--->|2|--->|1|--->receiver
          +-+    +-+    +-+    +-+

Dei quattro messaggi mostrati, quello contrassegnato con 1 è in primo piano, cioè il più vicino al ricevitore. Poi arrivano due messaggi con etichetta 2 e, infine, un messaggio etichettato 3 sul retro. Se fosse in gioco un comportamento FIFO rigoroso, i messaggi verrebbero ricevuti nell'ordine 1-2-2-3. Tuttavia, la coda dei messaggi consente altri ordini di recupero. Ad esempio, i messaggi potrebbero essere recuperati dal destinatario nell'ordine 3-2-1-2.

La mcoda esempio consiste di due programmi, il sender che scrive nella coda dei messaggi e nel destinatario che legge da questa coda. Entrambi i programmi includono il file di intestazione queue.h mostrato di seguito:

Esempio 4. Il file di intestazione queue.h

#define ProjectId 123
#define PathName  "queue.h" /* any existing, accessible file would do */
#define MsgLen    4
#define MsgCount  6

typedef struct {
  long type;                 /* must be of type long */
  char payload[MsgLen + 1];  /* bytes in the message */
} queuedMessage;

Il file di intestazione definisce un tipo di struttura denominato queuedMessage , con carico utile (array di byte) e tipo (numeri interi). Questo file definisce anche le costanti simboliche (il #define istruzioni), le prime due delle quali vengono utilizzate per generare una chiave che, a sua volta, viene utilizzata per ottenere un ID coda messaggi. L'ID progetto può essere qualsiasi valore intero positivo e PathName deve essere un file esistente e accessibile, in questo caso il file queue.h . Le istruzioni di installazione in entrambi mittente e il ricevitore i programmi sono:

key_t key = ftok(PathName, ProjectId);   /* generate key */
int qid = msgget(key, 0666 | IPC_CREAT); /* use key to get queue id */

L'ID qid è, in effetti, la controparte di un descrittore di file per le code di messaggi.

Esempio 5. Il messaggio sender programma

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <string.h>
#include "queue.h"

void report_and_exit(const char* msg) {
  perror(msg);
  exit(-1); /* EXIT_FAILURE */
}

int main() {
  key_t key = ftok(PathName, ProjectId);
  if (key < 0) report_and_exit("couldn't get key...");

  int qid = msgget(key, 0666 | IPC_CREAT);
  if (qid < 0) report_and_exit("couldn't get queue id...");

  char* payloads[] = {"msg1", "msg2", "msg3", "msg4", "msg5", "msg6"};
  int types[] = {1, 1, 2, 2, 3, 3}; /* each must be > 0 */
  int i;
  for (i = 0; i < MsgCount; i++) {
    /* build the message */
    queuedMessage msg;
    msg.type = types[i];
    strcpy(msg.payload, payloads[i]);

    /* send the message */
    msgsnd(qid, &msg, sizeof(msg), IPC_NOWAIT); /* don't block */
    printf("%s sent as type %i\n", msg.payload, (int) msg.type);
  }
  return 0;
}

Il mittente il programma sopra invia sei messaggi, due ciascuno di un tipo specificato:i primi messaggi sono di tipo 1, i successivi due di tipo 2 e gli ultimi due di tipo 3. L'istruzione di invio:

msgsnd(qid, &msg, sizeof(msg), IPC_NOWAIT);

è configurato per essere non bloccante (il flag IPC_NOWAIT ) perché i messaggi sono così piccoli. L'unico pericolo è che una coda piena, improbabile in questo esempio, provochi un errore di invio. Il ricevitore programma di seguito riceve anche messaggi utilizzando IPC_NOWAIT bandiera.

Esempio 6. Il messaggio destinatario programma

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include "queue.h"

void report_and_exit(const char* msg) {
  perror(msg);
  exit(-1); /* EXIT_FAILURE */
}

int main() {
  key_t key= ftok(PathName, ProjectId); /* key to identify the queue */
  if (key < 0) report_and_exit("key not gotten...");

  int qid = msgget(key, 0666 | IPC_CREAT); /* access if created already */
  if (qid < 0) report_and_exit("no access to queue...");

  int types[] = {3, 1, 2, 1, 3, 2}; /* different than in sender */
  int i;
  for (i = 0; i < MsgCount; i++) {
    queuedMessage msg; /* defined in queue.h */
    if (msgrcv(qid, &msg, sizeof(msg), types[i], MSG_NOERROR | IPC_NOWAIT) < 0)
      puts("msgrcv trouble...");
    printf("%s received as type %i\n", msg.payload, (int) msg.type);
  }

  /** remove the queue **/
  if (msgctl(qid, IPC_RMID, NULL) < 0)  /* NULL = 'no flags' */
    report_and_exit("trouble removing queue...");

  return 0;
}

Il ricevitore il programma non crea la coda dei messaggi, sebbene l'API lo suggerisca. Nel ricevitore , la chiamata:

int qid = msgget(key, 0666 | IPC_CREAT);

è fuorviante a causa di IPC_CREAT flag, ma questo flag significa davvero crea se necessario, altrimenti accedi . Il mittente chiama il programma msgsnd per inviare messaggi, mentre il destinatario chiama msgrcv per recuperarli. In questo esempio, il mittente invia i messaggi nell'ordine 1-1-2-2-3-3, ma il destinatario quindi li recupera nell'ordine 3-1-2-1-3-2, mostrando che le code di messaggi non sono vincolate a un comportamento FIFO rigoroso:

% ./sender
msg1 sent as type 1
msg2 sent as type 1
msg3 sent as type 2
msg4 sent as type 2
msg5 sent as type 3
msg6 sent as type 3

% ./receiver
msg5 received as type 3
msg1 received as type 1
msg3 received as type 2
msg2 received as type 1
msg6 received as type 3
msg4 received as type 2

L'output sopra mostra che il mittente e il ricevitore può essere lanciato dallo stesso terminale. L'output mostra anche che la coda dei messaggi persiste anche dopo il mittente process crea la coda, la scrive ed esce. La coda scompare solo dopo il ricevitore process lo rimuove esplicitamente con la chiamata a msgctl :

if (msgctl(qid, IPC_RMID, NULL) < 0) /* remove queue */

Conclusione

Le pipe e le API della coda dei messaggi sono fondamentalmente unidirezionali :un processo scrive e un altro legge. Esistono implementazioni di named pipe bidirezionali, ma i miei due centesimi sono che questo meccanismo IPC dà il meglio di sé quando è più semplice. Come notato in precedenza, le code di messaggi sono diventate popolari, ma senza una buona ragione; queste code sono ancora un altro strumento nella casella degli strumenti IPC. La parte 3 completa questo rapido tour della cassetta degli attrezzi IPC con esempi di codice di IPC tramite socket e segnali.


Linux
  1. Presentazione della guida alla comunicazione tra processi in Linux

  2. Comunicazione tra processi in Linux:socket e segnali

  3. Come avviare il comando Linux in background e scollegare il processo nel terminale

  4. Come uccidere un processo su Linux usando il comando?

  5. Pipe e reindirizzamento in Linux - Spiegazione!

Comando Wall in Linux

Suggerimenti e trucchi per l'utilizzo del comando wget Linux

Come clonare e ripristinare la partizione Linux usando il comando dd

comando mailx in linux – invia e ricevi posta Internet

Utilizzo del comando Watch in Linux

Come uccidere i processi in Linux usando kill, killall e pkill