Volver al temario
Tema 04 · 70 páginas

Comunicación y sincronización

Tema completo Modo estudio
TEXTO ORIGINAL · Páginas 2934 Ver PDF

3.1.3. Paso de mensajes en Unix (colas de bytes)

Texto íntegro de la conversión. Las figuras y la disposición de las notas se pueden consultar en el PDF.

3.1.3. Paso de mensajes en Unix (colas de bytes)

Unix ofrece diferentes mecanismos de paso de mensajes de tipo indirecto, asíncrono y de tamaño variable. En esta sección, consideramos tres mecanismos: las pipes, las named pipes y los sockets.

Conceptualmente, los tres mecanismos son muy similares. Ofrecen una cola de bytes que se gestionará siguiendo la política FIFO (first in, first out). Algunos procesos (productores) añadirán información en un extremo de la cola y otros procesos (consumidores) podrán extraerla del otro extremo. Por lo tanto, serán mecanismos propicios para implementar modelos de comunicación productor-consumidor.

Los tres mecanismos difieren en los requisitos que imponen a los procesos que pretenden utilizarlos y en la serie de llamadas al sistema que deben invocar para utilizarlos.

  • Las pipes permiten comunicar dos procesos que tengan algún tipo de parentesco (padre e hijo, dos hermanos, etc.).
  • Las named pipes permiten comunicar dos procesos del sistema que tengan acceso al directorio del sistema de ficheros donde se haya creado un fichero de tipo named pipe.
  • Los sockets permiten comunicar procesos que se ejecutan en máquinas diferentes.

La tabla 1 muestra las llamadas al sistema Unix que hay que invocar para crear, abrir, leer y escribir sobre estos mecanismos. Una vez que el mecanismo está disponible para el proceso (el proceso de usuario dispone de un file descriptor para operar sobre el mecanismo de comunicación), los tres mecanismos se acceden con las mismas llamadas al sistema read y write y desde el punto de vista del usuario programador son idénticos.

Pipes

Llamadas al sistema

Leer

Tabla 1. Llamadas al sistema UNIX que permiten acceder a las pipes, named pipes y sockets.

Notas y pies de figura de la fuente

Named pipes Sockets Crear pipe mknod socket Abrir open bind, listen, accept (servidores), connect (clientes) read

Página 30

Pipes

Escribir

Cerrar

Tabla 1. Llamadas al sistema UNIX que permiten acceder a las pipes, named pipes y sockets.

Aunque las lecturas y escrituras sobre estos mecanismos se realizan con las mismas llamadas al sistema que permiten acceder a ficheros ordinarios (read y write), hay tres casos particulares que se tratan de manera especial en el caso de trabajar sobre colas de bytes:

  • Lectura sobre cola vacía. Cuando un proceso consumidor intenta leer de una cola que en este momento se encuentra vacía, la cola puede estar vacía debido a que el proceso consumidor "es más rápido" que el proceso productor o que el proceso consumidor ya ha consumido toda la información que el productor debía producir. Normalmente, al proceso consumidor le interesará tratar de modo diferente los dos casos; en el primer caso, lo más razonable sería esperar a que el productor produzca alguna cosa; en el segundo caso, lo más razonable sería recibir la notificación de final de fichero. Para que el SO pueda saber en qué caso nos encontramos, utilizará un dato que conoce con exactitud: el número de file descriptors de escritura abiertos actualmente sobre esta cola. Si este número es mayor que 0, considerará que estamos en el primer caso; si el número es 0, considerará que estamos en el segundo caso. Por lo tanto, en el primer caso la llamada read se bloqueará hasta que algún productor escriba alguna cosa en la cola; en el segundo caso, la llamada read volverá inmediatamente indicando que se han leído 0 caracteres.

  • Escritura sobre cola llena. Internamente, el SO asigna una medida a estas colas. Si se produce información sobre una cola a un ritmo superior al que la información es consumida, llegará un momento en el que la cola se llenará. Si en este momento se intenta añadir más información, el comportamiento por defecto es que la llamada write se bloquee13 hasta que haya bastante espacio disponible para escribir todos los datos.

  • Escritura sobre cola sin proceso consumidor. Unix considera que esta situación es anómala y probablemente provocada por un error en alguno de los procesos involucrados. Por lo tanto, avisa al proceso productor utilizando un mecanismo parecido a las excepciones vistas en el módulo 2, el mecanismo de las señales software (los veremos en el apartado 3.2). Si el proceso consumidor no está preparado para tratar esta señal, el SO le hará abortar.

Notas y pies de figura de la fuente

Named pipes Sockets write close Los file descriptors de escritura El hecho de que el SO utilice el número de file descriptors de escritura abiertos provoca que dejar abierto innecesariamente algún file descriptor de escritura sobre una cola de bytes pueda provocar comportamientos anómalos en nuestros programas. Es aconsejable cerrar los file descriptors tan pronto como dejen de ser necesarios. (13)No consideramos el caso de que una llamada write intente escribir un número de bytes superior a la medida interna de la cola de bytes.

Página 31

Otras diferencias entre pipes y ficheros ordinarios son que no es posible utilizar la llamada lseek para reposicionar el puntero de lectura escritura sobre una pipe (la gestión de los datos es estrictamente FIFO) y que las pipes son volátiles. Si detenemos el sistema operativo (shutdown), se pierde toda la información que no haya sido leída de una pipe.

Ejemplo: pipes

Una pipe es un dispositivo que no se puede abrir de manera explícita mediante la llamada al sistema open; se crea en el momento en el que se invoca la llamada en el sistema pipe y se destruye cuando el último proceso que lo tiene abierto lo cierra. La llamada pipe crea el dispositivo y le asocia dos file descriptors, uno de lectura y otro de escritura. Si ahora el proceso crea procesos hijos (mediante llamada al sistema fork), éstos heredarán los file descriptors de su proceso padre y podrán acceder a esta misma pipe.

La figura 27 muestra un ejemplo de utilización de las pipes (asumimos que ninguna llamada al sistema devolverá error).

int descFichero[2], n, estado; char buf[512];

estado = pipe(descFichero); /* Creación de la pipe */ switch(fork()){ case 0:

/* El proceso hijo lee del canal 0 y escribe en la pipe */ close(descFichero[0]);

while ((n = read(0, buf, sizeof(buf))) > 0) { write(descFichero[1], buf, n); }

close(descFichero[1]); /* Permite que el padre detecte fin de fichero */ break;

default: /* El proceso padre lee de la pipe y escribe en el canal 1 / close(descFichero[1]); / Permite detectar el final de transmisión cuando el

while ((n = read(descFichero[0], buf, sizeof(buf))) > 0) {

write(1, buf, n); } close(descFichero[0]);

} ...

Figura 27. Fragmento de programa que utiliza una pipe para comunicar un proceso hijo con un proceso padre.

Notas y pies de figura de la fuente

hijo acabe */

Página 32

El proceso pide crear una pipe. El SO le devolverá dos file descriptors (uno de lectura y otro de escritura) para acceder a la pipe, como podéis ver en la figura 28, donde se muestra esquemáticamente el estado del proceso (espacio lógico, PCB14, tabla de file descriptors) y del SO (tabla de punteros de lectura/escritura).

Figura 28. Estado del proceso justo después de haber creado la pipe

Para que esta pipe pueda comunicar dos procesos, hay que invocar la llamada al sistema fork. De esta manera, el proceso hijo hereda del padre los file descriptors entre los que encontramos los que se han creado con la llamada. pipe: descFichero[0] es el de lectura, y descFichero[1], el de escritura.

Después de la creación del hijo, para establecer un canal de comunicación unidireccional entre el hijo y el padre, los dos procesos cierran el canal del sentido de la comunicación que no utilizarán.

Finalmente, el proceso hijo lee datos de su entrada estándar (canal 0, stdin) y las escribe en la pipe. El proceso padre lee los datos de la pipe y los escribe en su salida estándar (canal 1, stdout).

La figura 29 muestra el estado de los dos procesos mientras éstos están ejecutando el código correspondiente a las sentencias while.

Notas y pies de figura de la fuente

(14)Process control block, estructura de datos gestionada por el sistema operativo en la que éste almacena la información necesaria para gestionar cada proceso.

Página 33

Figura 29. Estado del proceso padre y del proceso hijo mientras los procesos se comunican.

Finalmente, la figura 30 muestra un ejemplo completo de un programa que utiliza pipes. Se trata de un programa que emula las llamadas al sistema que debe ejecutar el intérprete de comandos cuando el usuario introduce la línea de comandos ps aux | grep getty (el metacarácter | de los intérpretes de comandos Unix permite comunicar la salida estándar de un proceso con la entrada estándar de otro siguiendo el paradigma de comunicación productor-consumidor). El programa crea una pipe y dos procesos hijos, uno ejecutará el comando ps y el otro el comando grep; la salida estándar del primer hijo y la entrada estándar del segundo hijo estarán comunicadas gracias a la pipe y a las redirecciones efectuadas (llamada al sistema dup2). Notad que los ejecutables ps y grep son totalmente ajenos al hecho de estar escribiendo/leyendo sobre una pipe; ps escribe sobre su salida estándar (que en este caso está redireccionada al canal de escritura sobre la pipe) y grep lee de su entrada estándar (que en este caso está redireccionada al canal de lectura sobre la pipe).

#include <unistd.h> #include <sys/wait.h>

int main (int argc, char *argv[]) {

int p[2], st;

if (pipe (p) < 0)

error ();

switch (fork ())

{ case -1: error ();

case 0:

Página 34

if (dup2 (p[0], 0) < 0) error ();

if (close (p[0]) < 0) error (); if (close (p[1]) < 0)

error (); execlp ("grep", "grep", "getty", NULL); error ();

default:

switch (fork ()) { case -1:

error (); case 0: if (dup2 (p[1], 1) < 0)

error (); if (close (p[0]) < 0) error ();

if (close (p[1]) < 0) error (); execlp ("ps", "ps", "aux", NULL);

error (); }

} if (close (p[0]) < 0) error ();

if (close (p[1]) < 0) error ();

if (wait (&st) < 0) error (); if (wait (&st) < 0)

error (); return (0); }

Figura 30. Programa que emula las llamadas al sistema que debe ejecutar el intérprete de comandos cuando el usuario introduce el comando ps aux | grep getty.

Notas y pies de figura de la fuente

Ved también Podéis consultar la documen-

Ver fragmento extraído sin normalizar
3.1.3.  Paso de mensajes en Unix (colas de bytes)



    Unix ofrece diferentes mecanismos de paso de mensajes de tipo indi-
    recto, asíncrono y de tamaño variable. En esta sección, consideramos
    tres mecanismos: las pipes, las named pipes y los sockets.



Conceptualmente, los tres mecanismos son muy similares. Ofrecen una cola
de bytes que se gestionará siguiendo la política FIFO (first in, first out). Algunos
procesos (productores) añadirán información en un extremo de la cola y otros
procesos (consumidores) podrán extraerla del otro extremo. Por lo tanto, se-
rán mecanismos propicios para implementar modelos de comunicación pro-
ductor-consumidor.


Los tres mecanismos difieren en los requisitos que imponen a los procesos que
pretenden utilizarlos y en la serie de llamadas al sistema que deben invocar
para utilizarlos.


•  Las￿pipes permiten comunicar dos procesos que tengan algún tipo de pa-
     rentesco (padre e hijo, dos hermanos, etc.).
•  Las￿named￿pipes permiten comunicar dos procesos del sistema que tengan
     acceso al directorio del sistema de ficheros donde se haya creado un fichero
     de tipo named pipe.
•  Los￿sockets permiten comunicar procesos que se ejecutan en máquinas
     diferentes.


La tabla 1 muestra las llamadas al sistema Unix que hay que invocar para crear,
abrir, leer y escribir sobre estos mecanismos. Una vez que el mecanismo está
disponible para el proceso (el proceso de usuario dispone de un file descriptor
para operar sobre el mecanismo de comunicación), los tres mecanismos se
acceden con las mismas llamadas al sistema read y write y desde el punto de
vista del usuario programador son idénticos.



                                                         Pipes                       Named pipes                           Sockets

 Llama-                  Crear                            pipe                           mknod                             socket
  das al
 sistema                 Abrir                                                            open                   bind, listen, accept (servi-
                                                                                                                 dores), connect (clientes)

                         Leer                                                               read


Tabla 1. Llamadas al sistema UNIX que permiten acceder a las pipes, named pipes y sockets.

## Página 30

<!-- source-page: 30 -->

GNUFDL • PID_00214803                                                                                  30                                                                                         Comunicación y sincronización


                                                          Pipes                       Named pipes                           Sockets

                        Escribir                                                             write

                         Cerrar                                                              close


Tabla 1. Llamadas al sistema UNIX que permiten acceder a las pipes, named pipes y sockets.

Aunque las lecturas y escrituras sobre estos mecanismos se realizan con las
mismas llamadas al sistema que permiten acceder a ficheros ordinarios (read
y write), hay tres casos particulares que se tratan de manera especial en el
caso de trabajar sobre colas de bytes:


•  Lectura￿sobre￿cola￿vacía. Cuando un proceso consumidor intenta leer                                       Los file descriptors de
     de una cola que en este momento se encuentra vacía, la cola puede estar                                 escritura
     vacía debido a que el proceso consumidor "es más rápido" que el proceso                                   El hecho de que el SO utilice
     productor o que el proceso consumidor ya ha consumido toda la informa-                                    el número de file descriptors de
                                                                                                               escritura abiertos provoca que
     ción que el productor debía producir. Normalmente, al proceso consumi-                                    dejar abierto innecesariamente
     dor le interesará tratar de modo diferente los dos casos; en el primer caso,                              algún file descriptor de escritu-
                                                                                                               ra sobre una cola de bytes pue-
     lo más razonable sería esperar a que el productor produzca alguna cosa; en                                da provocar comportamientos
                                                                                                               anómalos en nuestros progra-
     el segundo caso, lo más razonable sería recibir la notificación de final de                               mas. Es aconsejable cerrar los
                                                                                                               file descriptors tan pronto como
     fichero. Para que el SO pueda saber en qué caso nos encontramos, utilizará                                dejen de ser necesarios.
     un dato que conoce con exactitud: el número de file descriptors de escritura
     abiertos actualmente sobre esta cola. Si este número es mayor que 0, con-
     siderará que estamos en el primer caso; si el número es 0, considerará que
     estamos en el segundo caso. Por lo tanto, en el primer caso la llamada read
     se bloqueará hasta que algún productor escriba alguna cosa en la cola; en
     el segundo caso, la llamada read volverá inmediatamente indicando que
     se han leído 0 caracteres.

•  Escritura￿sobre￿cola￿llena. Internamente, el SO asigna una medida a es-                                  (13)No consideramos el caso de
     tas colas. Si se produce información sobre una cola a un ritmo superior al                             que una llamada write intente es-
                                                                                                            cribir un número de bytes superior
     que la información es consumida, llegará un momento en el que la cola                                  a la medida interna de la cola de
     se llenará. Si en este momento se intenta añadir más información, el com-                              bytes.

     portamiento por defecto es que la llamada write se bloquee13 hasta que
     haya bastante espacio disponible para escribir todos los datos.


•  Escritura￿sobre￿cola￿sin￿proceso￿consumidor. Unix considera que esta
     situación es anómala y probablemente provocada por un error en alguno
     de los procesos involucrados. Por lo tanto, avisa al proceso productor uti-
     lizando un mecanismo parecido a las excepciones vistas en el módulo 2,
     el mecanismo de las señales software (los veremos en el apartado 3.2). Si
     el proceso consumidor no está preparado para tratar esta señal, el SO le
     hará abortar.

## Página 31

<!-- source-page: 31 -->

GNUFDL • PID_00214803                                                                                  31                                                                                         Comunicación y sincronización

Otras diferencias entre pipes y ficheros ordinarios son que no es posible utilizar
la llamada lseek para reposicionar el puntero de lectura escritura sobre una
pipe (la gestión de los datos es estrictamente FIFO) y que las pipes son volátiles.
Si detenemos el sistema operativo (shutdown), se pierde toda la información
que no haya sido leída de una pipe.


Ejemplo: pipes

Una pipe es un dispositivo que no se puede abrir de manera explícita mediante
la llamada al sistema open; se crea en el momento en el que se invoca la llamada
en el sistema pipe y se destruye cuando el último proceso que lo tiene abierto
lo cierra. La llamada pipe crea el dispositivo y le asocia dos file descriptors, uno
de lectura y otro de escritura. Si ahora el proceso crea procesos hijos (mediante
llamada al sistema fork), éstos heredarán los file descriptors de su proceso padre
y podrán acceder a esta misma pipe.


La figura 27 muestra un ejemplo de utilización de las pipes (asumimos que
ninguna llamada al sistema devolverá error).


    int descFichero[2], n, estado;
    char buf[512];


    estado = pipe(descFichero); /* Creación de la pipe */
    switch(fork()){
        case 0:

            /* El proceso hijo lee del canal 0 y escribe en la pipe */
            close(descFichero[0]);

            while ((n = read(0, buf, sizeof(buf))) > 0) {
                write(descFichero[1], buf, n);
            }

            close(descFichero[1]); /* Permite que el padre detecte fin de fichero */
            break;


        default:
            /* El proceso padre lee de la pipe y escribe en el canal 1 */
            close(descFichero[1]); /* Permite detectar el final de transmisión cuando el

                                  hijo acabe */


            while ((n = read(descFichero[0], buf, sizeof(buf))) > 0) {

                write(1, buf, n);
            }
            close(descFichero[0]);

    }
    ...


Figura 27. Fragmento de programa que utiliza una pipe para comunicar un proceso hijo con
un proceso padre.

## Página 32

<!-- source-page: 32 -->

GNUFDL • PID_00214803                                                                                  32                                                                                         Comunicación y sincronización

El proceso pide crear una pipe. El SO le devolverá dos file descriptors (uno de                                   (14)Process control block, estructura
lectura y otro de escritura) para acceder a la pipe, como podéis ver en la figura                                 de datos gestionada por el sistema
                                                                                                                  operativo en la que éste almacena
28, donde se muestra esquemáticamente el estado del proceso (espacio lógico,                                      la información necesaria para ges-
                                                                                                                  tionar cada proceso.
PCB14, tabla de file descriptors) y del SO (tabla de punteros de lectura/escritura).
































    Figura 28. Estado del proceso justo después de haber creado la pipe

Para que esta pipe pueda comunicar dos procesos, hay que invocar la llamada
al sistema fork. De esta manera, el proceso hijo hereda del padre los file des-
criptors entre los que encontramos los que se han creado con la llamada. pipe:
descFichero[0] es el de lectura, y descFichero[1], el de escritura.


Después de la creación del hijo, para establecer un canal de comunicación
unidireccional entre el hijo y el padre, los dos procesos cierran el canal del
sentido de la comunicación que no utilizarán.


Finalmente, el proceso hijo lee datos de su entrada estándar (canal 0, stdin) y
las escribe en la pipe. El proceso padre lee los datos de la pipe y los escribe en
su salida estándar (canal 1, stdout).


La figura 29 muestra el estado de los dos procesos mientras éstos están ejecu-
tando el código correspondiente a las sentencias while.

## Página 33

<!-- source-page: 33 -->

GNUFDL • PID_00214803                                                                                  33                                                                                         Comunicación y sincronización




































    Figura 29. Estado del proceso padre y del proceso hijo mientras los procesos se comunican.

Finalmente, la figura 30 muestra un ejemplo completo de un programa que
utiliza pipes. Se trata de un programa que emula las llamadas al sistema que
debe ejecutar el intérprete de comandos cuando el usuario introduce la línea
de comandos ps aux | grep getty (el metacarácter | de los intérpretes
de comandos Unix permite comunicar la salida estándar de un proceso con
la entrada estándar de otro siguiendo el paradigma de comunicación produc-
tor-consumidor). El programa crea una pipe y dos procesos hijos, uno ejecutará
el comando ps y el otro el comando grep; la salida estándar del primer hijo
y la entrada estándar del segundo hijo estarán comunicadas gracias a la pipe y
a las redirecciones efectuadas (llamada al sistema dup2). Notad que los ejecu-
tables ps y grep son totalmente ajenos al hecho de estar escribiendo/leyendo
sobre una pipe; ps escribe sobre su salida estándar (que en este caso está redi-
reccionada al canal de escritura sobre la pipe) y grep lee de su entrada estándar
(que en este caso está redireccionada al canal de lectura sobre la pipe).


    #include <unistd.h>
    #include <sys/wait.h>


    int
    main (int argc, char *argv[])
    {

      int p[2], st;


      if (pipe (p) < 0)

        error ();


      switch (fork ())

        {
        case -1:
          error ();


        case 0:

## Página 34

<!-- source-page: 34 -->

GNUFDL • PID_00214803                                                                                  34                                                                                         Comunicación y sincronización


          if (dup2 (p[0], 0) < 0)
            error ();

          if (close (p[0]) < 0)
            error ();
          if (close (p[1]) < 0)

            error ();
          execlp ("grep", "grep", "getty", NULL);
          error ();


        default:

          switch (fork ())
           {
          case -1:

           error ();
          case 0:
           if (dup2 (p[1], 1) < 0)

            error ();
           if (close (p[0]) < 0)
            error ();

           if (close (p[1]) < 0)
            error ();
           execlp ("ps", "ps", "aux", NULL);

           error ();
          }


        }
      if (close (p[0]) < 0)
        error ();

      if (close (p[1]) < 0)
        error ();

      if (wait (&st) < 0)
        error ();
      if (wait (&st) < 0)

        error ();
      return (0);
    }


Figura 30. Programa que emula las llamadas al sistema que debe ejecutar el intérprete de                    Ved también
comandos cuando el usuario introduce el comando ps aux | grep getty.

                                                                                                             Podéis consultar la documen-
Descargar Markdown originalEstudiar este apartado