Comunicación entre procesos: tuberías

Pipe es un medio de comunicación entre dos o más procesos relacionados o interrelacionados. Puede ser dentro de un proceso o una comunicación entre el proceso secundario y el padre. La comunicación también puede ser de varios niveles, como la comunicación entre el padre, el hijo y el nieto, etc. La comunicación se logra mediante un proceso de escritura en la tubería y otro proceso de lectura de la tubería. Para lograr la llamada al sistema de tuberías, cree dos archivos, uno para escribir en el archivo y otro para leer desde el archivo.

El mecanismo de la tubería se puede ver con un escenario en tiempo real, como llenar agua con la tubería en algún recipiente, digamos un balde, y alguien que lo recupere, digamos con una taza. El proceso de llenado no es más que escribir en la tubería y el proceso de lectura no es más que recuperar de la tubería. Esto implica que una salida (agua) es entrada para la otra (cubo).

#include<unistd.h>

int pipe(int pipedes[2]);

Esta llamada al sistema crearía un conducto para la comunicación unidireccional, es decir, crea dos descriptores, el primero está conectado para leer desde el conducto y el otro está conectado para escribir en el conducto.

Descriptor pipedes [0] es para lectura y pipedes [1] es para escritura. Todo lo que esté escrito en las canalizaciones [1] se puede leer desde las canalizaciones [0].

Esta llamada devolvería cero en caso de éxito y -1 en caso de error. Para conocer la causa de la falla, verifique con la variable errno o la función perror ().

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

Aunque las operaciones básicas para el archivo son de lectura y escritura, es esencial abrir el archivo antes de realizar las operaciones y cerrar el archivo después de completar las operaciones requeridas. Por lo general, de forma predeterminada, se abren 3 descriptores para cada proceso, que se utilizan para entrada (entrada estándar - stdin), salida (salida estándar - stdout) y error (error estándar - stderr) con descriptores de archivo 0, 1 y 2 respectivamente.

Esta llamada al sistema devolvería un descriptor de archivo utilizado para operaciones de archivo adicionales de lectura / escritura / búsqueda (lseek). Por lo general, los descriptores de archivos comienzan desde 3 y aumentan en un número a medida que se abren los archivos.

Los argumentos que se pasan a la llamada al sistema abierto son nombre de ruta (ruta relativa o absoluta), banderas que mencionan el propósito de abrir el archivo (por ejemplo, abrir para lectura, O_RDONLY, escribir, O_WRONLY, leer y escribir, O_RDWR, para agregar al archivo existente O_APPEND, para crear un archivo, si no existe con O_CREAT y así sucesivamente) y el modo requerido que proporciona permisos de lectura / escritura / ejecución para el usuario o propietario / grupo / otros. El modo se puede mencionar con símbolos.

Leer - 4, Escribir - 2 y Ejecutar - 1.

Por ejemplo: valor octal (comienza con 0), 0764 implica que el propietario tiene permisos de lectura, escritura y ejecución, el grupo tiene permisos de lectura y escritura, otro tiene permisos de lectura. Esto también se puede representar como S_IRWXU | S_IRGRP | S_IWGRP | S_IROTH, que implica una operación de 0700 | 0040 | 0020 | 0004 → 0764.

Esta llamada al sistema, si tiene éxito, devuelve el nuevo identificador de descriptor de archivo y -1 en caso de error. La causa del error se puede identificar con la variable errno o la función perror ().

#include<unistd.h>

int close(int fd)

El cierre de la llamada al sistema anterior ya abrió el descriptor de archivo. Esto implica que el archivo ya no está en uso y los recursos asociados pueden ser reutilizados por cualquier otro proceso. Esta llamada al sistema devuelve cero en caso de éxito y -1 en caso de error. La causa del error se puede identificar con la variable errno o la función perror ().

#include<unistd.h>

ssize_t read(int fd, void *buf, size_t count)

La llamada al sistema anterior es para leer del archivo especificado con argumentos del descriptor de archivo fd, búfer adecuado con memoria asignada (ya sea estática o dinámica) y el tamaño del búfer.

La identificación del descriptor de archivo es para identificar el archivo respectivo, que se devuelve después de llamar al sistema open () o pipe (). El archivo debe abrirse antes de leerlo. Se abre automáticamente en caso de llamar al sistema pipe ().

Esta llamada devolvería el número de bytes leídos (o cero en caso de encontrar el final del archivo) en caso de éxito y -1 en caso de error. Los bytes de retorno pueden ser más pequeños que el número de bytes solicitados, en caso de que no haya datos disponibles o el archivo esté cerrado. El número de error adecuado se establece en caso de falla.

Para conocer la causa de la falla, verifique con la variable errno o la función perror ().

#include<unistd.h>

ssize_t write(int fd, void *buf, size_t count)

La llamada al sistema anterior es para escribir en el archivo especificado con argumentos del descriptor de archivo fd, un búfer adecuado con memoria asignada (ya sea estática o dinámica) y el tamaño del búfer.

La identificación del descriptor de archivo es para identificar el archivo respectivo, que se devuelve después de llamar al sistema open () o pipe ().

El archivo debe abrirse antes de escribir en el archivo. Se abre automáticamente en caso de llamar al sistema pipe ().

Esta llamada devolvería el número de bytes escritos (o cero en caso de que no se escriba nada) en caso de éxito y -1 en caso de error. El número de error adecuado se establece en caso de falla.

Para conocer la causa de la falla, verifique con la variable errno o la función perror ().

Programas de ejemplo

A continuación se muestran algunos programas de ejemplo.

Example program 1 - Programa para escribir y leer dos mensajes usando pipe.

Algoritmo

Step 1 - Crea una tubería.

Step 2 - Envía un mensaje a la tubería.

Step 3 - Recupere el mensaje de la tubería y escríbalo en la salida estándar.

Step 4 - Envía otro mensaje a la tubería.

Step 5 - Recupere el mensaje de la tubería y escríbalo en la salida estándar.

Note - La recuperación de mensajes también se puede realizar después de enviar todos los mensajes.

Source Code: simplepipe.c

#include<stdio.h>
#include<unistd.h>

int main() {
   int pipefds[2];
   int returnstatus;
   char writemessages[2][20]={"Hi", "Hello"};
   char readmessage[20];
   returnstatus = pipe(pipefds);
   
   if (returnstatus == -1) {
      printf("Unable to create pipe\n");
      return 1;
   }
   
   printf("Writing to pipe - Message 1 is %s\n", writemessages[0]);
   write(pipefds[1], writemessages[0], sizeof(writemessages[0]));
   read(pipefds[0], readmessage, sizeof(readmessage));
   printf("Reading from pipe – Message 1 is %s\n", readmessage);
   printf("Writing to pipe - Message 2 is %s\n", writemessages[0]);
   write(pipefds[1], writemessages[1], sizeof(writemessages[0]));
   read(pipefds[0], readmessage, sizeof(readmessage));
   printf("Reading from pipe – Message 2 is %s\n", readmessage);
   return 0;
}

Note- Idealmente, el estado de devolución debe comprobarse para cada llamada al sistema. Para simplificar el proceso, no se realizan comprobaciones para todas las llamadas.

Pasos de ejecución

Compilacion

gcc -o simplepipe simplepipe.c

Ejecución / Salida

Writing to pipe - Message 1 is Hi
Reading from pipe – Message 1 is Hi
Writing to pipe - Message 2 is Hi
Reading from pipe – Message 2 is Hell

Example program 2 - Programa para escribir y leer dos mensajes a través de la tubería usando los procesos padre e hijo.

Algoritmo

Step 1 - Crea una tubería.

Step 2 - Crear un proceso hijo.

Step 3 - El proceso padre escribe en la tubería.

Step 4 - El proceso hijo recupera el mensaje de la tubería y lo escribe en la salida estándar.

Step 5 - Repita el paso 3 y el paso 4 una vez más.

Source Code: pipewithprocesses.c

#include<stdio.h>
#include<unistd.h>

int main() {
   int pipefds[2];
   int returnstatus;
   int pid;
   char writemessages[2][20]={"Hi", "Hello"};
   char readmessage[20];
   returnstatus = pipe(pipefds);
   if (returnstatus == -1) {
      printf("Unable to create pipe\n");
      return 1;
   }
   pid = fork();
   
   // Child process
   if (pid == 0) {
      read(pipefds[0], readmessage, sizeof(readmessage));
      printf("Child Process - Reading from pipe – Message 1 is %s\n", readmessage);
      read(pipefds[0], readmessage, sizeof(readmessage));
      printf("Child Process - Reading from pipe – Message 2 is %s\n", readmessage);
   } else { //Parent process
      printf("Parent Process - Writing to pipe - Message 1 is %s\n", writemessages[0]);
      write(pipefds[1], writemessages[0], sizeof(writemessages[0]));
      printf("Parent Process - Writing to pipe - Message 2 is %s\n", writemessages[1]);
      write(pipefds[1], writemessages[1], sizeof(writemessages[1]));
   }
   return 0;
}

Pasos de ejecución

Compilation

gcc pipewithprocesses.c –o pipewithprocesses

Execution

Parent Process - Writing to pipe - Message 1 is Hi
Parent Process - Writing to pipe - Message 2 is Hello
Child Process - Reading from pipe – Message 1 is Hi
Child Process - Reading from pipe – Message 2 is Hello

Comunicación bidireccional mediante tuberías

La comunicación de canalización se considera una comunicación unidireccional, es decir, el proceso padre escribe y el proceso hijo lee o viceversa, pero no ambos. Sin embargo, ¿qué pasa si tanto el padre como el niño necesitan escribir y leer en las tuberías simultáneamente? La solución es una comunicación bidireccional mediante tuberías. Se requieren dos conductos para establecer una comunicación bidireccional.

Los siguientes son los pasos para lograr una comunicación bidireccional:

Step 1- Crea dos tubos. El primero es para que el padre escriba y el niño lea, digamos como pipe1. El segundo es para que el niño escriba y el padre lea, digamos como pipe2.

Step 2 - Crear un proceso hijo.

Step 3 - Cierre los extremos no deseados ya que solo se necesita un extremo para cada comunicación.

Step 4 - Cierre los extremos no deseados en el proceso principal, lea el final de la tubería1 y escriba el final de la tubería2.

Step 5 - Cierre los extremos no deseados en el proceso hijo, escriba end of pipe1 y lea end of pipe2.

Step 6 - Realice la comunicación según sea necesario.

Programas de muestra

Sample program 1 - Lograr una comunicación bidireccional mediante tuberías.

Algoritmo

Step 1 - Cree pipe1 para que escriba el proceso padre y lea el proceso hijo.

Step 2 - Cree pipe2 para que el proceso hijo escriba y el proceso padre lea.

Step 3 - Cierre los extremos no deseados de la tubería del lado del padre y del niño.

Step 4 - Proceso principal para escribir un mensaje y proceso secundario para leer y mostrar en la pantalla.

Step 5 - Proceso hijo para escribir un mensaje y proceso padre para leer y mostrar en la pantalla.

Source Code: twowayspipe.c

#include<stdio.h>
#include<unistd.h>

int main() {
   int pipefds1[2], pipefds2[2];
   int returnstatus1, returnstatus2;
   int pid;
   char pipe1writemessage[20] = "Hi";
   char pipe2writemessage[20] = "Hello";
   char readmessage[20];
   returnstatus1 = pipe(pipefds1);
   
   if (returnstatus1 == -1) {
      printf("Unable to create pipe 1 \n");
      return 1;
   }
   returnstatus2 = pipe(pipefds2);
   
   if (returnstatus2 == -1) {
      printf("Unable to create pipe 2 \n");
      return 1;
   }
   pid = fork();
   
   if (pid != 0) // Parent process {
      close(pipefds1[0]); // Close the unwanted pipe1 read side
      close(pipefds2[1]); // Close the unwanted pipe2 write side
      printf("In Parent: Writing to pipe 1 – Message is %s\n", pipe1writemessage);
      write(pipefds1[1], pipe1writemessage, sizeof(pipe1writemessage));
      read(pipefds2[0], readmessage, sizeof(readmessage));
      printf("In Parent: Reading from pipe 2 – Message is %s\n", readmessage);
   } else { //child process
      close(pipefds1[1]); // Close the unwanted pipe1 write side
      close(pipefds2[0]); // Close the unwanted pipe2 read side
      read(pipefds1[0], readmessage, sizeof(readmessage));
      printf("In Child: Reading from pipe 1 – Message is %s\n", readmessage);
      printf("In Child: Writing to pipe 2 – Message is %s\n", pipe2writemessage);
      write(pipefds2[1], pipe2writemessage, sizeof(pipe2writemessage));
   }
   return 0;
}

Pasos de ejecución

Compilacion

gcc twowayspipe.c –o twowayspipe

Ejecución

In Parent: Writing to pipe 1 – Message is Hi
In Child: Reading from pipe 1 – Message is Hi
In Child: Writing to pipe 2 – Message is Hello
In Parent: Reading from pipe 2 – Message is Hello