4.3 Sin Utilizar Memoria Compartida. Mensajes
[MILE94]Los monitores y los semáforos se diseñaron para resolver problemas de sincronización en un sistema mono o multiprocesador, es decir, un sistema con una o varias CPU's que tienen acceso a una memoria compartida. La comunicación se logra compartiendo una zona de memoria donde los procesos guardan información, los semáforos y monitores se utilizan para sincronizar el acceso a dicha zona. Si pasamos a un sistema distribuido, con varias CPU's, cada una con su memoria privada, unidas mediante una red, estos mecanismos de comunicación entre procesos ya no se pueden aplicar. Además, estos dos mecanismos sirven para la sincronización, pero no para el intercambio de información entre procesos. En el siguiente apartado veremos un mecanismo que solventa ambos problemas.
Transferencia de mensajes
Este mecanismo de comunicación entre procesos se basa en dos primitivas: send y receive, las cuales, al igual que los semáforos, y a diferencia de los monitores, son servicios (llamadas al sistema) proporcionados por el sistema operativo. Los lenguajes de programación tendrán rutinas de biblioteca para invocar tales servicios desde un programa. Dichas rutinas de biblioteca tendrán un formato parecido al siguiente:
send(mensaje, destino) ,y
receive(mensaje, origen)
La primera función envía un mensaje a un destino dado, y la segunda recibe un mensaje desde cierto origen (o desde cualquier origen, si al receptor no le importa el origen).
Los sistemas operativos suelen implementar dos tipos de comunicación basadas en transferencia de mensajes: la directa y la indirecta. El comportamiento de los procesos en cuanto a la sincronización es distinto en los dos tipos de comunicación. A continuación se exponen ambos.
Transferencia de mensajes con designación directa
En este sistema, tanto el proceso emisor como el proceso receptor deben especificar explícitamente el proceso destino y origen respectivamente. Las funciones tendrá un formato similar al siguiente:
send(mensaje, identificador proceso destino)
receive(mensaje, identificador proceso origen)
En cuanto a la sincronización, viene determinada por la ausencia de un lugar donde almacenar el mensaje. Si se ejecuta send antes de receive en el proceso destino, el proceso emisor se bloquea hasta la ejecución de receive, momento en el cual el mensaje se puede copiar de manera directa desde el emisor al receptor sin almacenamiento intermedio. Análogamente, si se ejecuta primero receive, el receptor se bloquea hasta que se ejecute una operación send en el proceso emisor. Este modo de sincronización se conoce como cita (rendezvous).
Transferencia de mensajes con designación indirecta
En este sistema, los mensajes se envían o reciben de unos objetos proporcionados por el sistema operativo denominados buzones. Un buzón es un lugar donde almacenar un número determinado de mensajes, la cantidad de mensajes que alberga el buzón se especifica al crearlo. Al utilizar buzones, los parámetros de dirección en las llamadas send y receive son buzones, y no procesos:
send(mensaje, buzón)
receive(mensaje, buzón)
El sistema operativo debe proporcionar servicios para la creación y destrucción de buzones, así como para la especificación de los usuarios y/o procesos que pueden utilizar un buzón.
En cuanto a la sincronización, un proceso que haga una operación send sobre un buzón, sólo se bloquea si dicho buzón está lleno. Un proceso que realice una operación receive sobre un buzón, sólo se bloquea si el buzón está vacío, es decir, no contiene mensajes.
Los sistemas de transferencia de mensajes con designación directa son más fáciles de implementar. Sin embargo, son menos flexibles, puesto que el emisor y el receptor deben estar ejecutándose simultáneamente. Si se utilizan buzones, el emisor puede almacenar sus mensajes en un buzón, pudiendo el receptor recuperarlos posteriormente.
Ejemplos de utilización de transferencia de mensajes
En este apartado vamos a ver cómo resolver los problemas de la exclusión mutua y de los productores y consumidores utilizando transferencia de mensajes con designación indirecta. En el apartado de Comunicación y Sincronización se emplea la designación directa para crear una aplicación cliente-servidor.
Exclusión mutua
Se necesita un proceso de inicialización, en el que se crea el buzón exmut con un tamaño de un mensaje, y tamaño de mensaje de un byte. A continuación se llena el buzón (el contenido del mensaje, el carácter '*', no es relevante). Para realizar la exclusión mutua en la sección crítica del proceso Pi se tiene en cuenta que la operación receive es indivisible, y representa un bloqueo potencial si el buzón está vacío. Obsérvese que la solución es análoga a la realizada mediante semáforos. En aquella se definía un semáforo de valor incial 1, representando este valor la ausencia de procesos dentro de una sección crítica. El cero representaba el que un proceso estaba en una sección crítica. En esta solución, se define un buzón de tamaño 1, la existencia de un mensaje en ese buzón significa la no existencia de procesos en una sección crítica. La ausencia de mensajes en el buzón implica que un proceso está en una sección crítica.
Productores y consumidores
Para la solución se crean dos buzones de tamaño N (longitud del buffer). El tamaño de un mensaje es sizeof(int), sizeof es una macro de C que sirve para calcular el número de bytes que ocupa el tipo de dato que representa su único parámetro (en este caso un entero). En el buzón esp habrá tantos mensajes (de valor insustancial 0) como espacios haya en el buzón ele. La información almacenada en el buzón ele sí es importante, son los elementos producidos por los productores.
Llamadas a procedimientos remotos
La noción de llamadas a procedimientos remotos (remote procedure calls, RPC) se introdujo con el objeto de ofrecer un mecanismo estructurado de alto nivel para realizar la comunicación entre procesos en sistemas distribuidos. Con una llamada a un procedimiento remoto, un proceso de un sistema puede llamar a un procedimiento de un proceso de otro sistema. El proceso que llama se bloquea esperando el retorno desde el procedimiento llamado en el sistema remoto y después continúa su ejecución desde el punto que sigue a la llamada.
El procedimiento llamado y el que llama residen en máquinas distintas, con espacios de direcciones distintos, no existe la noción de variables globales compartidas, como en los procedimientos normales dentro de un proceso. Las RPC transfieren la información a través de parámetros de la llamada. Una RPC se puede realizar con los mecanismo del tipo enviar/recibir.
send(proceso_remoto,parámetros_entrada);
receive(proceso_remoto, parámetros_salida);
Una llamada a procedimiento remoto debería ser igual a una llamada a un procedimiento local, desde el punto de vista del usuario. Una llamada por valor es más fácil enviando copias de los datos mediante mensajes. Las llamadas por referencia son más difíciles, porque una RPC cruza a otro espacio de direcciones. Los formatos internos de los datos pueden cambiar en una red de máquinas heterogéneas; requieren conversiones complejas y un considerable trabajo adicional. Deben tenerse en cuenta la posibilidad de transmisiones defectuosas y la pérdida de mensajes en la red.