GNU/Linux >> Tutoriales Linux >  >> Linux

Comunicación entre procesos en Linux:uso de conductos y colas de mensajes

Este es el segundo artículo de una serie sobre la comunicación entre procesos (IPC) en Linux. El primer artículo se centró en IPC a través del almacenamiento compartido:archivos compartidos y segmentos de memoria compartida. Este artículo se refiere a las tuberías, que son canales que conectan procesos para la comunicación. Un canal tiene un extremo de escritura para escribir bytes y un fin de lectura para leer estos bytes en orden FIFO (primero en entrar, primero en salir). En el uso típico, un proceso escribe en el canal y un proceso diferente lee desde este mismo canal. Los bytes en sí mismos pueden representar cualquier cosa:números, registros de empleados, películas digitales, etc.

Las tuberías vienen en dos tipos, con nombre y sin nombre, y se pueden usar de forma interactiva desde la línea de comandos o dentro de los programas; se vienen ejemplos. Este artículo también analiza las colas de memoria, que han pasado de moda, pero inmerecidamente.

Los ejemplos de código en el primer artículo reconocieron la amenaza de las condiciones de carrera (basadas en archivos o en memoria) en IPC que usa almacenamiento compartido. Naturalmente, surge la pregunta sobre la concurrencia segura para el IPC basado en canales, que se tratará en este artículo. Los ejemplos de código para conductos y colas de memoria usan API con el sello de aprobación POSIX, y un objetivo central de los estándares POSIX es la seguridad de subprocesos.

Considere las páginas de manual para mq_open función, que pertenece a la API de la cola de memoria. Estas páginas incluyen una sección sobre Atributos con esta pequeña tabla:

El valor MT-Safe (con MT para subprocesos múltiples) significa que mq_open la función es segura para subprocesos, lo que a su vez implica seguridad para el proceso:un proceso se ejecuta precisamente en el sentido en que se ejecuta uno de sus subprocesos, y si no puede surgir una condición de carrera entre los subprocesos en el mismo proceso, tal condición no puede surgir entre subprocesos en diferentes procesos. El MT-Safe El atributo asegura que no surja una condición de carrera en las invocaciones de mq_open . En general, el IPC basado en canales es concurrentemente seguro, aunque se plantea una nota de advertencia en los siguientes ejemplos.

Tubos sin nombre

Comencemos con un ejemplo de línea de comando artificial que muestra cómo funcionan las canalizaciones sin nombre. En todos los sistemas modernos, la barra vertical | representa una canalización sin nombre en la línea de comando. Asumir % es la línea de comandos, y considere este comando:

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

El sueño y eco Las utilidades se ejecutan como procesos separados y la canalización sin nombre les permite comunicarse. Sin embargo, el ejemplo es artificial porque no se produce comunicación. El saludo ¡Hola, mundo! aparece en la pantalla; luego, después de unos cinco segundos, vuelve la línea de comandos, lo que indica que tanto el dormir y eco los procesos han salido. ¿Qué está pasando?

En la sintaxis de barra vertical de la línea de comando, el proceso a la izquierda (dormir ) es el escritor, y el proceso a la derecha (echo ) es el lector. De forma predeterminada, el lector bloquea hasta que haya bytes para leer del canal y el escritor, después de escribir sus bytes, termina enviando un marcador de fin de flujo. (Incluso si el escritor finaliza prematuramente, se envía un marcador de fin de flujo al lector). La tubería sin nombre persiste hasta que finalizan tanto el escritor como el lector.

[Descargar la guía completa de comunicación entre procesos en Linux]

En el ejemplo artificial, el dormir El proceso no escribe ningún byte en el canal pero termina después de unos cinco segundos, lo que envía un marcador de fin de transmisión al canal. Mientras tanto, el eco El proceso escribe inmediatamente el saludo en la salida estándar (la pantalla) porque este proceso no lee ningún byte del canal, por lo que no espera. Una vez que el dormir y eco los procesos finalizan, la canalización sin nombre, que no se utiliza en absoluto para la comunicación, desaparece y vuelve la línea de comandos.

Aquí hay un ejemplo más útil usando dos tuberías sin nombre. Supongamos que el archivo test.dat se parece a esto:

this
is
the
way
the
world
ends

El comando:

% cat test.dat | sort | uniq

canaliza la salida del cat (concatenar) proceso en el ordenar procesa para producir una salida ordenada y luego canaliza la salida ordenada al uniq proceso para eliminar registros duplicados (en este caso, las dos ocurrencias de la reducir a uno):

ends
is
the
this
way
world

El escenario ahora está listo para un programa con dos procesos que se comunican a través de una tubería sin nombre.

Ejemplo 1. Dos procesos que se comunican a través de un canal sin nombre.

#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;
}

La tubería de la ONU el programa anterior usa la función del sistema fork para crear un proceso. Aunque el programa tiene un solo archivo fuente, se produce un procesamiento múltiple durante la ejecución (exitosa). Estos son los detalles en una revisión rápida de cómo funciona la biblioteca bifurcación funciona:

  • El tenedor función, llamada en el padre proceso, devuelve -1 a los padres en caso de falla. En la tuberíaUN ejemplo, la llamada es:
    pid_t cpid = fork(); /* called in parent */

    El valor devuelto se almacena, en este ejemplo, en la variable cpid de tipo entero pid_t . (Cada proceso tiene su propio ID de proceso , un número entero no negativo que identifica el proceso). La bifurcación de un nuevo proceso podría fallar por varias razones, incluida una tabla de procesos completa , estructura que mantiene el sistema para realizar el seguimiento de los procesos. Los procesos zombis, aclarados en breve, pueden hacer que una tabla de procesos se llene si no se recolectan.

  • Si el tenedor la llamada tiene éxito, genera (crea) un nuevo proceso hijo, devolviendo un valor al padre pero un valor diferente al hijo. Tanto el proceso padre como el hijo ejecutan el mismo código que sigue a la llamada a fork . (El hijo hereda copias de todas las variables declaradas hasta ahora en el padre). En particular, una llamada exitosa a fork devuelve:
    • Cero al proceso secundario
    • El ID de proceso del niño al padre
  • Un si/si no o una construcción equivalente generalmente se usa después de una bifurcación exitosa llamada para segregar el código destinado al padre del código destinado al hijo. En este ejemplo, la construcción es:
    if (0 == cpid) {    /*** child ***/
    ...
    }
    else {              /*** parent ***/
    ...
    }

Si bifurcar a un niño tiene éxito, el pipeUN programa procede de la siguiente manera. Hay una matriz de enteros:

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

para contener dos descriptores de archivo, uno para escribir en la tubería y otro para leer desde la tubería. (El elemento de matriz pipeFDs[0] es el descriptor de archivo para el final de lectura y el elemento de matriz pipeFDs[1] es el descriptor de archivo para el final de escritura). Una llamada exitosa al sistema tubería función, realizada inmediatamente antes de la llamada a fork , llena la matriz con los dos descriptores de archivo:

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

Más recursos de Linux

  • Hoja de trucos de los comandos de Linux
  • Hoja de trucos de comandos avanzados de Linux
  • Curso en línea gratuito:Descripción general técnica de RHEL
  • Hoja de trucos de red de Linux
  • Hoja de trucos de SELinux
  • Hoja de trucos de los comandos comunes de Linux
  • ¿Qué son los contenedores de Linux?
  • Nuestros últimos artículos sobre Linux

El padre y el hijo ahora tienen copias de ambos descriptores de archivos, pero la separación de preocupaciones patrón significa que cada proceso requiere exactamente uno de los descriptores. En este ejemplo, el padre escribe y el niño lee, aunque los roles podrían invertirse. La primera declaración en el niño if El código de cláusula, por lo tanto, cierra el extremo de escritura de la canalización:

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

y la primera declaración en el padre else El código de cláusula cierra el extremo de lectura de la canalización:

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

Luego, el padre escribe algunos bytes (códigos ASCII) en la tubería sin nombre, y el hijo los lee y los repite en la salida estándar.

Un aspecto más del programa necesita aclaración:la llamada a la espera función en el código principal. Una vez generado, un proceso hijo es en gran medida independiente de su padre, ya que incluso el pipeUN corto ilustra el programa. El hijo puede ejecutar código arbitrario que puede no tener nada que ver con el padre. Sin embargo, el sistema notifica al padre a través de una señal, siempre y cuando el hijo termine.

¿Qué pasa si el padre termina antes que el niño? En este caso, a menos que se tomen precauciones, el niño se convierte y sigue siendo un zombie proceso con una entrada en la tabla de procesos. Las precauciones son de dos grandes tipos. Una precaución es hacer que el padre notifique al sistema que el padre no tiene interés en la terminación del niño:

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

Un segundo enfoque es hacer que el padre ejecute una espera en la terminación del niño, asegurando así que el padre sobreviva al niño. Este segundo enfoque se utiliza en el pipeUN programa, donde el código padre tiene esta llamada:

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

Esta llamada a esperar significa esperar hasta que ocurra la terminación de cualquier niño , y en el pipeUN programa, sólo hay un proceso hijo. (El NULO El argumento se puede reemplazar con la dirección de una variable entera para mantener el estado de salida del niño). Hay un waitpid más flexible función para un control detallado, por ejemplo, para especificar un proceso secundario particular entre varios.

La tubería de la ONU programa toma otra precaución. Cuando el padre termina de esperar, el padre termina con la llamada a la salida regular función. Por el contrario, el niño termina con una llamada al _exit variante, que acelera la notificación de rescisión. En efecto, el niño le está diciendo al sistema que notifique al padre lo antes posible que el niño ha terminado.

Si dos procesos escriben en la misma canalización sin nombre, ¿se pueden intercalar los bytes? Por ejemplo, si el proceso P1 escribe:

foo bar

a una tubería y el proceso P2 escribe al mismo tiempo:

baz baz

a la misma tubería, parece que el contenido de la tubería podría ser algo arbitrario, como:

baz foo baz bar

El estándar POSIX garantiza que las escrituras no se intercalen siempre que ninguna escritura exceda PIPE_BUF bytes En sistemas Linux, PIPE_BUF tiene un tamaño de 4.096 bytes. Mi preferencia con las tuberías es tener un solo escritor y un solo lector, evitando así el problema.

Tubos con nombre

Una tubería sin nombre no tiene un archivo de respaldo:el sistema mantiene un búfer en memoria para transferir bytes del escritor al lector. Una vez que el escritor y el lector terminan, el búfer se reclama, por lo que la tubería sin nombre desaparece. Por el contrario, una canalización con nombre tiene un archivo de respaldo y una API distinta.

Veamos otro ejemplo de línea de comando para obtener la esencia de las canalizaciones con nombre. Estos son los pasos:

  • Abra dos terminales. El directorio de trabajo debe ser el mismo para ambos.
  • En uno de los terminales, ingrese estos dos comandos (la indicación nuevamente es % , y mis comentarios comienzan con ## ):
    % mkfifo tester  ## creates a backing file named tester
    % cat tester     ## type the pipe's contents to stdout

    Al principio, no debería aparecer nada en la terminal porque aún no se ha escrito nada en la canalización nombrada.

  • En el segundo terminal, ingresa el 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

    Todo lo que se escribe en este terminal se repite en el otro. Una vez Ctrl+C se ingresa, el indicador de línea de comando regular regresa en ambas terminales:la tubería se ha cerrado.

  • Limpie eliminando el archivo que implementa la canalización con nombre:
    % unlink tester

Como el nombre de la utilidad mkfifo implica que una canalización con nombre también se denomina FIFO porque el primer byte de entrada es el primer byte de salida, y así sucesivamente. Hay una función de biblioteca llamada mkfifo que crea una canalización con nombre en los programas y se utiliza en el siguiente ejemplo, que consta de dos procesos:uno escribe en la canalización con nombre y el otro lee desde esta canalización.

Ejemplo 2. El fifoWriter programa

#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;
}

El fifoWriter El programa anterior se puede resumir de la siguiente manera:

  • El programa crea una canalización con nombre para escribir:
    mkfifo(pipeName, 0666); /* read/write perms for user/group/others */
    int fd = open(pipeName, O_CREAT | O_WRONLY);

    donde nombre de tubería es el nombre del archivo de respaldo pasado a mkfifo como primer argumento. La canalización con nombre se abre con la ya familiar llamada a open función, que devuelve un descriptor de archivo.

  • Para un toque de realismo, el fifoWriter no escribe todos los datos a la vez, sino que escribe un fragmento, duerme un número aleatorio de microsegundos, etc. En total, se escriben 768 000 valores enteros de 4 bytes en la canalización con nombre.
  • Después de cerrar la canalización con nombre, el fifoWriter también desvincula el archivo:
    close(fd);        /* close pipe: generates end-of-stream marker */
    unlink(pipeName); /* unlink from the implementing file */

    El sistema recupera el archivo de respaldo una vez que todos los procesos conectados a la canalización han realizado la operación de desvinculación. En este ejemplo, solo hay dos procesos de este tipo:el fifoWriter y el fifoReader , los cuales hacen un desvincular operación.

Los dos programas deben ejecutarse en diferentes terminales con el mismo directorio de trabajo. Sin embargo, el fifoWriter debe iniciarse antes que el fifoReader , ya que el primero crea la tubería. El fifoReader luego accede a la canalización con nombre ya creada.

Ejemplo 3. El fifoReader programa

#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;
}

El fifoReader El programa anterior se puede resumir de la siguiente manera:

  • Porque el fifoWriter crea la canalización con nombre, el fifoReader solo necesita la llamada estándar abrir para acceder a la canalización a través del archivo de respaldo:
    const char* file = "./fifoChannel";
    int fd = open(file, O_RDONLY);

    El archivo se abre como de solo lectura.

  • El programa luego entra en un ciclo potencialmente infinito, tratando de leer un fragmento de 4 bytes en cada iteración. La lectura llamada:
    ssize_t count = read(fd, &next, sizeof(int));

    devuelve 0 para indicar el final de la secuencia, en cuyo caso el fifoReader sale del bucle, cierra la canalización con nombre y desvincula el archivo de respaldo antes de terminar.

  • Después de leer un entero de 4 bytes, el fifoReader comprueba si el número es primo. Esto representa la lógica comercial que un lector de grado de producción podría realizar en los bytes recibidos. En una ejecución de muestra, hubo 37 682 números primos entre los 768 000 números enteros recibidos.

En ejecuciones de muestra repetidas, el fifoReader leer con éxito todos los bytes que el fifoWriter escribió. Esto no es sorprendente. Los dos procesos se ejecutan en el mismo host, eliminando los problemas de red de la ecuación. Las canalizaciones con nombre son un mecanismo IPC altamente confiable y eficiente y, por lo tanto, de uso generalizado.

Aquí está el resultado de los dos programas, cada uno lanzado desde una terminal separada pero con el mismo directorio de trabajo:

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

Colas de mensajes

Los conductos tienen un comportamiento FIFO estricto:el primer byte escrito es el primer byte leído, el segundo byte escrito es el segundo byte leído, y así sucesivamente. Las colas de mensajes pueden comportarse de la misma manera, pero son lo suficientemente flexibles como para recuperar fragmentos de bytes fuera del orden FIFO.

Como sugiere el nombre, una cola de mensajes es una secuencia de mensajes, cada uno de los cuales tiene dos partes:

  • La carga útil, que es una matriz de bytes (char en C)
  • Un tipo, dado como un valor entero positivo; los tipos clasifican los mensajes para una recuperación flexible

Considere la siguiente representación de una cola de mensajes, con cada mensaje etiquetado con un tipo de número entero:

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

De los cuatro mensajes que se muestran, el que tiene la etiqueta 1 está al frente, es decir, más cerca del receptor. Luego vienen dos mensajes con la etiqueta 2 y, finalmente, un mensaje con la etiqueta 3 en la parte posterior. Si estuviera en juego un comportamiento FIFO estricto, los mensajes se recibirían en el orden 1-2-2-3. Sin embargo, la cola de mensajes permite otras órdenes de recuperación. Por ejemplo, el receptor podría recuperar los mensajes en el orden 3-2-1-2.

La mcola El ejemplo consta de dos programas, el sender que escribe en la cola de mensajes y el receptor que lee de esta cola. Ambos programas incluyen el archivo de cabecera queue.h se muestra a continuación:

Ejemplo 4. El archivo de cabecera 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;

El archivo de encabezado define un tipo de estructura llamado queuedMessage , con carga útil (matriz de bytes) y tipo (enteros) campos. Este archivo también define constantes simbólicas (el #define declaraciones), los dos primeros de los cuales se utilizan para generar una clave que, a su vez, se utiliza para obtener un ID de cola de mensajes. El ID del proyecto puede ser cualquier valor entero positivo y PathName debe ser un archivo existente y accesible; en este caso, el archivo queue.h . Las declaraciones de configuración tanto en el sender y el receptor los programas son:

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

El ID qid es, en efecto, la contraparte de un descriptor de archivo para colas de mensajes.

Ejemplo 5. El mensaje remitente programa

#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;
}

El remitente El programa anterior envía seis mensajes, dos de cada uno de un tipo específico:los primeros mensajes son del tipo 1, los dos siguientes del tipo 2 y los dos últimos del tipo 3. La sentencia de envío:

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

está configurado para no bloquear (el indicador IPC_NOWAIT ) porque los mensajes son muy pequeños. El único peligro es que una cola llena, poco probable en este ejemplo, provoque un error de envío. El receptor programa a continuación también recibe mensajes usando el IPC_NOWAIT bandera.

Ejemplo 6. El receptor de mensajes programa

#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;
}

El receptor El programa no crea la cola de mensajes, aunque la API lo sugiere. En el receptor , la llamada:

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

es engañoso debido a la IPC_CREAT bandera, pero esta bandera realmente significa crear si es necesario, de lo contrario acceder . El remitente llamadas de programa msgsnd para enviar mensajes, mientras que el receptor llama a msgrcv para recuperarlos. En este ejemplo, el remitente envía los mensajes en el orden 1-1-2-2-3-3, pero el receptor luego los recupera en el orden 3-1-2-1-3-2, mostrando que las colas de mensajes no están sujetas a un comportamiento FIFO estricto:

% ./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

El resultado anterior muestra que el remitente y el receptor se puede iniciar desde el mismo terminal. El resultado también muestra que la cola de mensajes persiste incluso después de que el remitente El proceso crea la cola, escribe en ella y sale. La cola desaparece solo después de que el receptor el proceso lo elimina explícitamente con la llamada a msgctl :

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

Concluyendo

Las tuberías y las API de la cola de mensajes son fundamentalmente unidireccionales :un proceso escribe y otro lee. Hay implementaciones de canalizaciones con nombre bidireccionales, pero mi granito de arena es que este mecanismo IPC es mejor cuando es más simple. Como se señaló anteriormente, las colas de mensajes han perdido popularidad, pero sin una buena razón; estas colas son otra herramienta más en la caja de herramientas de IPC. La Parte 3 completa este recorrido rápido por la caja de herramientas de IPC con ejemplos de código de IPC a través de sockets y señales.


Linux
  1. Presentamos la guía para la comunicación entre procesos en Linux

  2. Comunicación entre procesos en Linux:sockets y señales

  3. Cómo iniciar el comando de Linux en segundo plano y separar el proceso en la terminal

  4. Tuberías y redirección en Linux - ¡Explicado!

  5. Cómo verificar el sistema operativo y la versión usando un comando de Linux

Comando de pared en Linux

Consejos y trucos para usar el comando wget Linux

Cómo clonar y restaurar una partición de Linux usando el comando dd

Comando mailx en linux:enviar y recibir correo de Internet

Usando el comando Watch en Linux

Cómo matar procesos en Linux usando kill, killall y pkill

    Interfaz Atributo Valor
    mq_open() Seguridad de subprocesos MT-seguro