Subsecciones

22. Comunicación entre procesos (IPC Interprocess Communication), PIPES

A continuación se iniciará la revisión de como múltiples procesos pueden estar siendo ejecutados en una máquina y quizás siendo controlados (generados por la función fork()) por uno de nuestros programas.

En numerosas aplicaciones hay una necesidad clara para que estos procesos se comuniquen para el intercambio de datos o información de control. Hay unos cuantos métodos para hacer lo anterior. Se pueden considerar los siguientes:

En este capítulo se estudia el entubamiento de dos procesos, en los siguientes capítulos se revisan los otros métodos.

22.1 Entubando en un programa de C <stdio.h>

El entubamiento es un proceso donde la entrada de un proceso es hecha con la salida de otro. Se ha visto ejemplos de lo anterior en la línea de comandos de Unix cuando se usa el símbolo |.

Se verá como hacer lo anterior en un programa de C teniendo dos (o más) procesos divididos. Lo primero que se debe hacer es abrir una tubería, en Unix permite dos formas de hacerlo:

22.1.1 popen() Tubería formateada

FILE *popen(char *orden, char *tipo) Abre una tubería de E/S donde el comando es el proceso que será conectado al proceso que lo esta llamando de esta forma creando el pipe. El tipo es "r" -- para lectura, o "w" para escritura.

La función popen() regresa un apuntador a un flujo o NULL para algún tipo de error.

Una tubería abierta con popen() deberá siempre cerrarse con pclose(FILE *flujo).

Se usa fprintf() y fscanf() para comunicarse con el flujo de la tubería.

22.1.2 pipe() Tubería de bajo nivel

int pipe(int descf[2]) Crea un par de descritores de archivo, que apuntan a un nodo i de una tubería, y los pone en el vector de dos elementos apuntado por descf. descf[0] es para lectura y descf[1] es para escritura.

pipe() regresa 0 en caso de éxito, y en caso de error se devuelve -1 y se pone un valor apropiado en errno.

El modelo de programación estándar es que una vez que la tubería ha sido puesta, dos (o más) procesos cooperativos serán creados por división y los datos serán pasados empleando read() y write().

Las tuberías abiertas con pipe() deberán ser cerradas con close(int fd). A continuación se muestra un ejemplo de entubamiento que se utiliza para estar enviando datos al programa gnuplot, empleado para graficar. La descripción de las funciones de los módulos es la siguiente:

El listado del código para grafica.c es el siguiente:

/* grafica.c - 
 * Ejemplo de la tuberias (pipe) de Unix. Se llama al paquete "gnuplot"
 * para graficar desde un programa en C.
 * La informacion es entubada a gnuplot, se crean 2 tuberias, una dibujara la 
 * grafica de y=0.5 e y= random 0-1.0, la otra graficara y=sen(1/x) e y=sen x
 */

#include "externals.h"
#include <signal.h>

#define DEG_TO_RAD(x) (x*180/M_PI)

void salir();

FILE *fp1, *fp2, *fp3, *fp4;

main()
{   
    float i;
    float y1,y2,y3,y4;

    /* abrir archivos en los cuales se guardaran los datos a graficar */
    if ( ((fp1 = fopen("plot11.dat","w")) == NULL) ||
           ((fp2 = fopen("plot12.dat","w")) == NULL) ||
            ((fp3 = fopen("plot21.dat","w")) == NULL) ||
             ((fp4 = fopen("plot22.dat","w")) == NULL) )
              { printf("Error no se puede abrir uno o varios archivos de datos\n");
 exit(1);
              }
              
    signal(SIGINT,salir); /* Atrapar la llamada de la funcion de salida ctrl-c */
    IniciarGraf();
    y1 = 0.5;
    srand48(1); /* poner semilla */
    for (i=0;;i+=0.01) /* incrementar i siempre */
    {                  /* usar ctrl-c para salir del programa */
	     y2 =  (float) drand48();
        if (i == 0.0)
           y3 = 0.0;
        else
           y3 = sin(DEG_TO_RAD(1.0/i));
        y4 = sin(DEG_TO_RAD(i));
        
        /* cargar archivos */
        fprintf(fp1,"%f %f\n",i,y1);
        fprintf(fp2,"%f %f\n",i,y2);
        fprintf(fp3,"%f %f\n",i,y3);
        fprintf(fp4,"%f %f\n",i,y4);
      
		  /* asegurarse que los buffers son copiados al disco */
		  /* para que gnuplot pueda leer los datos            */
        fflush(fp1);
        fflush(fp2);
        fflush(fp3);
        fflush(fp4);
        
        /* graficar alot graph */
        Graf1Vez();
        usleep(10000); /* dormir por un corto tiempo (250 microsegundos) */
      }
}

void salir()
{
   printf("\nctrl-c atrapada:\n Terminando las tuberias\n");
   PararGrafica();
   
   printf("Cerrando los archivos de datos\n");
   fclose(fp1);
   fclose(fp2);
   fclose(fp3);
   fclose(fp4);
   
   printf("Borrando los archivos de datos\n");
   BorrarDat();
}

El módulo graficador.c es el siguiente:

/**********************************************************************/
/* modulo: graficador.c                                               */
/* Contiene rutinas para graficar un archivo de datos producido por   */
/* programa. En este caso se grafica en dos dimensiones               */
/**********************************************************************/

#include "externals.h"

static FILE *plot1,
       *plot2,
       *ashell;

static char *iniciargraf1 = "plot [] [0:1.1] 'plot11.dat' with lines, 'plot12.dat' with lines\n";

static char *iniciargraf2 = "plot 'plot21.dat' with lines, 'plot22.dat' with lines\n";

static char *comando1= "/usr/bin/gnuplot> dump1";
static char *comando2= "/usr/bin/gnuplot> dump2";
static char *borrarchivos = "rm plot11.dat plot12.dat plot21.dat plot22.dat";
static char *set_term = "set terminal x11\n";

void IniciarGraf(void)
{ 
   plot1 = popen(comando1, "w");
   fprintf(plot1, "%s", set_term);
   fflush(plot1);
   if (plot1 == NULL)
      exit(2);
   plot2 = popen(comando2, "w");
   fprintf(plot2, "%s", set_term);
   fflush(plot2);
   if (plot2 == NULL)
      exit(2);
 }

void BorrarDat(void)
{ 
   ashell = popen(borrarchivos, "w");
   exit(0);
}

void PararGrafica(void)
{
   pclose(plot1);
   pclose(plot2);
}

void Graf1Vez(void)
{ 
   fprintf(plot1, "%s", iniciargraf1);
   fflush(plot1);

   fprintf(plot2, "%s", iniciargraf2);
   fflush(plot2);
}

El archivo de cabecera externals.h contiene lo siguiente:

/* externals.h */
#ifndef EXTERNALS
#define EXTERNALS

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

/* prototipos */

Con la finalidad de tener un mejor control en la compilación del programa se luede usar el siguiente archivo Makefile:

FUENTES.c= grafica.c graficador.c
INCLUDES=
CFLAGS=
SLIBS= -lm
PROGRAMA= grafica

OBJETOS= $(FUENTES.c:.c=.o)

$(PROGRAMA): $(INCLUDES) $(OBJETOS)
	gcc -o $(PROGRAMA) $(OBJETOS) $(SLIBS)

grafica.o: externals.h grafica.c
	gcc -c grafica.c

graficador.o: externals.h graficador.c
	gcc -c graficador.c

clean:
	rm -f $(PROGRAMA) $(OBJETOS)