Tenemos varias formas de comunicación entre procesos (Inter-Process Communication o IPC) en los sistemas Unix. pipes, colas de mensajes, semáforos y segmentos de memoria.
Cuando un proceso quiere leer del pipe y este está vacío, se queda esperando (bloqueado) hasta que algún proceso ponga datos en el pipe. De la misma forma si algún proceso intenta escribir en el pipe y este está lleno este se bloquea hasta que se vacía. El pipe es bidireccional pero cada proceso lo utiliza en una única dirección.
Podemo entender el pipe como un falso fichero que conecta dos proceos, si por ejemplo A quiere enviar datos a B los escribe en el pipe. A partir de ahí el proceso B puede leer datos del pipe.
La función pipe() crea una pipe.
#include <unistd.h>
int pipe(int fd[2]);
Array de 2 enteros, el primero es el descriptor para la lectura mientras que el segundo lo es para la escritura. Si la función tiene éxito devuelve 0, y los dos descriptores, si hay problemas devolverá -1.
Para enviar datos al pipe se utiliza la función write() y para recuperar read().
int read (int fd, void *buf, int count);
int write (int fd, void *buf, int count);
Antes de utilizar el pipe, veamos un ejemplo de funcionamiento con archivos que utilizan las funciones open() y close().
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include<fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
void main(void) {
char saludo[] = "Saludos peña!!!\n";
char buffer[10];
int fd, bytesleidos;
fd = open("texto.txt",1); // abrimos para escritura
if (fd==-1) {
printf("Algo salió mal\");
exit (-1);
}
printf("Escribo el saludo en el fichero...");
write(fd,saludo, strlen(saludo));
close(fd);
fd=open("texto.txt",0); // abrimos para lectura
printf("Contenido del Fichero: \n");
bytesleidos = read(fd,buffer,1);
while (bytesleidos!=0) {
printf("%s", buffer);
bytesleidos = read(fd,buffer,1);
}
close (fd);
}
Compila y prueba.
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
void main(void) {
int fd[2]; // 0 lectura y 1 escritura
char buffer[30];
pid_t pid;
pipe (fd); // creamos el pipe
pid = fork(); // se crea el proceso hijo
switch(pid) {
case -1: // error
printf("Algo falló ...");
exit(-1);
break;
case 0: // soy el hijo
printf("El hijo escribe en el pipe ...\n");
write(fd[1], "Hola papi",10);
break;
default: // soy papi
wait(NULL); espero a fin del hijo
printf("El papi lee del pipe ...\n";
read(fd[0], buffer, 10);
printf("\tMensaje recibido: %s\n", buffer);
break;
}
}
Veamos un ejemplo, donde el padre le envía un mensaje al hijo de fomra segura:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
void main(void) {
char saludoPadre[]="Buenos días hijo.\0";
char buffer[30];
int fd[2]; // 0 lectura y 1 escritura
pipe (fd); // creamos el pipe
pid_t pid;
pid = fork(); // se crea el proceso hijo
switch(pid) {
case -1: // error printf("Algo falló ...");
exit(-1);
break;
case 0: // soy el hijo
close(fd[1]); // cierra el descriptor de escritura = entrada
read(fd[0], buffer, sizeof(buffer));
printf("\tEl hijo recibe algo del pipe: %s\n",buffer);
break;
default: // padre envía
close(fd[0]);
write(fd[1],saludoPadre, strlen(saludoPadre)); //escribo en pipe
printf("El padre envía el mensaje al hijo ...\n");
wait(NULL);
break;
}
return 0;
}
Siguiendo el ejemplo anterior crea un programa que cree un pipe en el que el HIJO será el que envíe el mensaje al padre por ejemplo “Buenos días padre“, y el padre muestre dicho mensaje en pantalla.
Ejemplo de comunicación entre tres procesos ABUELO, HIJO y NIETO.
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
//Abuelo-hijo-nieto
void main(void) {
pid_t pid, Hijo_pid, pid2, Hijo2_pid;
int fd1[2];
int fd[2];
// saludos de pantalla
char saludoAbuelo[]="Saludos del Abuelo al Hijo.\0";
char saludoPadre[]="Saludos del Hijo al Nieto.\0";
char saludoHijo[]="Saludos del Nieto al Hijo.\0";
char saludoNieto[]="Saludos del Hijo al Abuelo.\0";
char buffer[80]="";
pipe (fd1);
pipe (fd2);
pid = fork(); // Abuelo creando al hijo
if (pid ==-1) {
...
}
if (pid == 0) { //Soy el hijo
// creamos al nieto
pid2 = ...;
switch (pid2) {
case -1:
...
break;
case 0: // Soy el nieto
// cierro fd2 como escritura; y leo de fd2
...
// Imprimo el mensaje que me mandó Hijo (mi padre)
...
// Envío un mensaje a Hijo (mi padre) a través de fd1
// con lo que tengo que cerrar fd1 como lectura
...
break;
defaul:
// Soy el hijo el que tiene más faena
// Leo lo que me manda el abuelo por fd1 y lo imprimo por pantalla
...
// envio a nieto (mi hijo) por fd2 el mensaje de la variable "saludoPadre"
...
// me quedo esperando a que Nieto termine (mi hijo)
Hijo2_pid=wait(NULL);
// Recibo el mensaje de Nieto por fd1, y lo imprimo por pantalla
...
// Envío mensaje al abuelo a través de fd2
...
}
} else { // Soy el abuelo
...
printf("Abuelo envia mensaje al hijo ...\n");
// cierra fd1[0] para la lectura el abuelo lee por fd2[0]
...
// escribe en fd1 saludoAbuelo, acuérdate de calcular la longitud del string.
...
// EL abuelo se queda esperando a que termine el hijo
...
// El abuelo recibe el mensaje por fd2, luego cierra fd2 en modo escritura
...
// Lee de la pipe fd2, entendemos que su hijo le dejó un mensaje antes de terminar.
read(fd2[0],buffer, sizeof(buffer));
printf("El abuelo recibe el siguiente mensaje del hijo: %s\n",buffer);
}
exit(0);
}