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.
• Laspipes permiten comunicar dos procesos que tengan algún tipo de pa-
rentesco (padre e hijo, dos hermanos, etc.).
• Lasnamedpipes 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.
• Lossockets 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:
• Lecturasobrecolavací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.
• Escriturasobrecolallena. 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.
• Escriturasobrecolasinprocesoconsumidor. 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-