Tema 9. Comunicación Mediante Tuberias.

1. COMUNICACIÓN ENTRE PROCESOS

La comunicación entre procesos habilita mecanismos para que los procesos puedan intercambiarse datos y sincronizarse. Existen tres formas muy elementales para que dos procesos se comuniquen: el envío de señales para la sincronización, el uso de ficheros ordinarios para el intercambio de datos y la llamada a ptrace (comunicación unidireccional) para que un proceso padre pueda manipular el espacio de direcciones virtuales de su hijo. Las señales no deben considerarse parte de la forma habitual de comunicar dos procesos y su uso debe restringirse a la comunicación de eventos o situaciones excepcionales. Los ficheros ordinarios tampoco son la forma más eficiente de comunicarse, ya que se ve involucrado el acceso a disco que suele ser 3 ó 4 órdenes de magnitud más lento que el acceso a memoria. Con llamada a ptrace sólo el padre puede leer datos del hijo.

Los mecanismos que se van a tratar ahora pretender dar soluciones más eficientes, empleando como canal de transmisión la memoria principal, por lo que se provoca un mayor aumento de velocidad de transferencia de datos.

A la hora de comunicar dos procesos, vamos a considerar dos situaciones claramente diferentes:

  • Que los procesos se estén ejecutando bajo el control de una misma maquina.
  • Que los procesos se estén ejecutando en maquinas separadas.

La primera situación se viene utilizando para comunicar dos o más procesos a nivel local, mediante un mecanismo como son las tuberías, y después pasaremos a ver las facilidades IPC del UNIX System V. Estas engloban tres mecanismos de comunicación: semáforos, memoria compartida y colas de mensajes.

El segundo escenario es más complejo, porque se ven involucradas las redes de ordenadores y la comunicación entre ellos.

Como introducción al tema de la comunicación entre máquinas remotas, vamos a exponer la interfaz que suministra el UNÍS de Berkeley para comunicar procesos. Esta interfaz se compone de una serie de llamadas que manejan un nuevo tipo de fichero conocido como conector o socket y que actuará como canal de comunicación entre procesos. Aunque un conector es tratado sintácticamente como un fichero, semánticamente no lo es. Esto significa que no vamos a tener los problemas de velocidad inherentes al acceso a disco.

Las tuberías son una de las primeras formas de comunicación implantadas en UNIX y muchos sistemas se ofrecen hoy día con esta facilidad. Incluso sistemas monoproceso como Dos ofrecen posibilidad de montar tuberías desde el punto de vista del intérprete de órdenes. Una tubería se puede considerar como un canal de comunicación entre dos procesos, y las hay de dos tipos: tuberías con nombre -FIFOS- y tuberías sin nombre.

2.TUBERÍAS SIN NOMBRE

Las tuberías sin nombre se crean con la llamada pipe y sólo el proceso que hace la llamada y sus descendientes pueden utilizarla; tiene la siguiente declaración:

#include <unistd.h>
int pipe(int fildes [2]);

Si llamada funciona correctamente, devolverá el valor 0 y creará una tubería sin nombre; en caso contrario, devolverá -1 y en errno estará el código del error producido. La tubería creada podrá ser manejada a través del array fildes. Los dos elementos de fildes se comportan como dos descriptores de fichero y los vamos a usar para escribir en la tubería y leer de ella.

Al escribir en fildes[1] estamos escribiendo datos en la tubería y al leer fildes[0] extraeremos datos de ella. Naturalmente, fildes[1] se comporta como un fichero de sólo escritura y fildes[0] como un fichero de sólo lectura.

Como el núcleo trata la tubería igual que a un fichero del sistema, al crearla debe asignarle un nodo-i. También le asigna un par de descriptores de fichero -fildes[0] y fildes[1]- y reserva las correspondientes entradas en la tabla de ficheros del sistema y en la tabla de descriptores del proceso.

Todo esto facilita el manejo de la tubería, ya que al recibir el mismo tratamiento que un fichero, podremos leer y escribir en ella con las llamadas read y write que empleamos para los ficheros ordinarios, los directorios y los ficheros especiales.

Los descriptores de fichero se heredan de padres a hijos tras la llamada a fork o a exec. Así, para que se comuniquen padre e hijo mediante una tubería, la abriremos en el padre y tanto padre como hijo podrán compartirla.

La sincronización entre los accesos de escritura y lectura la lleva a cabo el núcleo, de tal manera que las llamadas a read para sacar datos de la tubería no devolverán el control hasta que no haya datos escritos por otro proceso mediante la correspondiente llamada a write. También es el núcleo el encargado de gestionar la tubería para dotarla de una disciplina de acceso en hilera y, así, el proceso sacará los datos en el mismo orden en que los escribía el proceso emisor.

Los datos escritos en la tubería se gestionan en la memoria intermedia sin que lleguen al disco, por lo que al producirse la transferencia a través de memoria, las tuberías constituyen un mecanismo de comunicación mucho más rápido que el uso de ficheros ordinarios. El tamaño de una tubería, es decir, el bloque de datos más grande que podemos escribir en ella, depende del sistema, pero se garantiza que será inferior a 4.096 bytes.

Cuando la tubería está llena, las llamadas a write quedan bloqueadas hasta que no se saquen suficientes datos de la tubería como para escribir el bloque deseado.

Esquema de envío de mensajes mediante tuberías sin nombre:

Imagen que muestra el esquema de envio de mensajes por tuberias sin nombre

3. COMUNICACIÓN BIDIRECCIONAL

Uno de los problemas que presenta el ejemplo anterior es la falta de comunicación entre proceso hijo y el padre, de tal forma que el proceso emisor puede pedirnos que introduzcamos más mensajes antes de que el proceso receptor haya presentado el mensaje que acabamos de enviarle. Para solucionar este problema tenemos que implementar algún tipo de protocolo entre los procesos.

Para implementar esta comunicación necesitamos otra tubería que sirva de canal entre el proceso receptor y el emisor. Podríamos sentirnos tentados a aprovechar una sola tubería como canal bidireccional, pero esto plantea problemas de sincronismo y tendríamos que ayudarnos de señales o semáforos para controlar el acceso a la tubería. En efecto, si un proceso escribe en la tubería un mensaje para otro proceso y se pone a leer de ella la respuesta que le envía éste, puede darse el caso de que lea el mensaje que él mismo envió. Lo mejor es valernos de dos tuberías: una lleva los mensajes que van del proceso A al proceso B, y la otra lleva los mensajes en sentido contrario.

Esquema de comunicación bidireccional:

Imagen que muestra un esquema de comunicación bidireccional

4. TUBERÍAS EN LOS INTERPRETES DE ORDENES

Todos los intérpretes de órdenes ofrecen la posibilidad de redirigir los ficheros de entrada y salida asociados a un proceso.

Normalmente, los programas pueden servirnos para trabajar con otros ficheros sin necesidad de modificar su código, pero para ello el intérprete de órdenes que nos comunica con el sistema operativo debe contemplar la redirección. La redirección hacia otros ficheros se le indica al intérprete mediante los caracteres < y >.

Otra forma de redirigir es mediante las tuberías. Con ellas, lo que conseguimos es que la salida de un programa se convierta en entrada para otro. Esto es importante a la hora de aprovechar programas estándar, que realizan funciones sencillas, para construir otros que realizan funciones más complejas.

5. TUBERÍAS CON NOMBRE

Por medio de las tuberías sin nombre podemos comunicar procesos relacionados entre sí ya que el proceso que crea la tubería y sus descendientes tienen acceso a la misma. Para los procesos que no guardan ninguna relación de parentesco, no sirven los canales abiertos mediante tuberías sin nombre. Para comunicar este tipo de procesos tenemos que recurrir a las tuberías con nombre.

Una tubería con nombre es un fichero con una semántica idéntica a la de una tubería sin nombre, pero ocupa una entrada en un directorio y se accede a él a través de una ruta.

Un proceso puede abrir una tubería con nombre mediante una llamada a open, de la misma forma que abre un fichero ordinario. Así, para comunicar dos procesos mediante una tubería con nombre, uno de ellos debe abrir la tubería para escribir en ella y el otro para leer. La llamada open tiene un comportamiento ligeramente distinto, sigún se trate de abrir una tubería con nombre o un fichero ordinario. Así, cuando un proceso abre una tubería con nombre para escribir en ella, se pone a dormir hasta que no haya otro proceso que la abra para leer de ella. Cuando es el proceso lector el primero en abrir la tubería, se pone a dormir hasta que algún proceso la abre para escribir.

Esto tiene sentido, ya que no valdría para nada escribir en la tubería cuando nadie va a recoger esos datos. Otra diferencia que hay entre las tuberías con nombre y los ficheros ordinarios es que, para las primeras, el núcleo sólo emplea los bloques directos de direcciones de su nodo-i, por lo que la cantidad total de bytes que se pueden enviar a una tubería con nombre en una sola operación de escritura está limitada.

Para controlar los accesos de escritura y lectura de la tubería, el núcleo emplea dos punteros de tal forma que cuando el puntero de escritura llega al ultimo de los bloques, empieza por el primero, y lo mismo ocurre para el puntero de lectura.

Para poder abrir una tubería con nombre, esta debe existir. Hay dos formas de crearla: desde la línea de ordenes, mediante una llamada a mknod(1M), y desde programas, mediante una llamada a mknod(2).

Para crear en nuestro directorio de trabajo actual una tubería de nombre fifo_1, usando mknod(1M), tenemos que escribir:

$mknod fifo_1 p

Para crear esa misma tuberia con la llamada mknod(2), debemos incluir en nuestro programa unas lineas parecidas a las siguientes:

If (mknod("fifo_1", S_IFIFO | permisos, 0) == -1) {
// fallo al crear la tuberia
// tratamiento del fallo
}

donde permisos es la máscara, ya conocida, de permisos asociados a un fichero.

En algunos sistemas la ejecución de la orden mknod requiere privilegios de superusuario.

En estos sistemas se dispone de la orden mkfifo que le permite a los usuarios sin estos privilegios crear tuberías con nombre. La forma de invocar esta orden es:

$ mkfifo [-m mode] fifo_name

Donde mode es la máscara de modo que codifica los permisos habituales de lectura-escrituraejecución de la tubería y fifo_name es el nombre de la tubería.

De igual forma, allí donde se requieran privilegios de superusuario para ejecutar la llamdad mknod, se dispone de la llamada mkfifo que le permite a los usuarios sin privilegios crear tuberías con nombre. La declaración de esta llamada es:

#include<sys/types.h>
#include<sys/stat.h>
int mkfifo (char *path, mode_t mode);

Donde path es la ruta de la tubería con nombre que se va a crear y mode es la máscara de modo que codifica los permisos de lectura-escritura-ejecución de la tubería. La llamada devuelve el valor 0 o el valor -1 dependiendo de que se ejecute correctamente o no. Una posible causa de fallo es el intento de crear una tubería que ya exista. En este caso, el fallo de la llamada no significa que haya un error en nuestro programa, sólo significa que la tubería ya existe y por lo tanto no puede ser creada de nuevo; dependiendo del programa que estamos escribiendo, ante esta situación podemos proceder con normalidad pasando a abrir la tubería para establecer la comunicación y continuar con la ejecución normal del programa. En caso de que la llamada falle porque la tubería ya existe, mkfifo devuelve -1 y errno toma el valor EEXIST.

Como ejemplo de aplicación de las tuberías con nombre, vamos a escribir la pareja de programas llamar-a y responder-a. Estos programas permitirán que dos usuarios se comuniquen mediante el intercambio de mensajes. Supongamos que el usuario usr1 desea comunicarse con usr2, entonces deberá escribir:

$ llamar-a usr2
y usr2 recibirá por pantalla el mensaje:
llamada procedente del usuario usr1
responder escribiendo: responder-a usr1
si usr2 desea responder, tendrá que escribir:
$ responder-a usr1
para iniciar la comunicación.

Una vez iniciada la conversación, los dos usuarios se intercambiarán mensajes alternativamente, iniciando el envío usr1. Cada mensaje consta de una serie de líneas de texto, finalizando con la línea clave cambio. Esta línea servirá para pasarle el turno al otro usuario. Cuando alguno de los usuarios envíe la línea corto, la conversación terminará.

La comunicación se llevará a cabo mediante dos tuberías con nombre creadas por el programa llamar-a en el directorio /tmp. Estas tuberías tendrán los nombres:

/tmp/fifo_usr1_usr2 y /tmp/fifo_usr2_usr1.

Una de ellas es para los mensajes que vayan de usr2; la otra, para los mensajes que viajen en sentido contrario.

Bajate esta documentación en un archivo Acrobat Reader

Imagen que hace referencia a un archivo para Acrobat Reader