Monitores y Paso de Mensajes

Descargar como docx, pdf o txt
Descargar como docx, pdf o txt
Está en la página 1de 11

Monitores

Los monitores son estructuras de un lenguaje de programación que ofrecen una


funcionalidad equivalente a la de los semáforos y que son más fáciles de controlar.
Los monitores fueron propuestos por Brich Hansen y mejorados por Hoare para
poder utilizar procesos automáticos de sincronización. Un módulo monitor encapsula la
mutua exclusión de datos y procedimientos que pueden acceder a los datos protegidos.
Los usuarios pueden hacer llamadas a estos procedimientos usando al monitor como una
tabla de estado para determinar cuándo proceder y cuando suspender operaciones.
Los monitores tienen una importante propiedad que los hace útiles para lograr la
exclusión mutua: sólo puede haber un proceso activo en un monitor en cualquier
instante. Los monitores son una construcción del lenguaje de programación, por lo que
el compilador sabe que son especiales y puede manejar las llamadas a los
procedimientos del monitor en forma distinta a las llamadas a otros procedimientos. Por
lo general, cuando un proceso llama a un procedimiento de monitor, las primeras
instrucciones del procedimiento comprobarán si hay algún otro proceso activo en un
momento dado dentro del monitor. De ser así, el proceso invocador se suspenderá hasta
que el otro proceso haya dejado el monitor. Si no hay otro proceso utilizando el
monitor, el proceso invocador puede entrar.
Es responsabilidad del compilador implementar la exclusión mutua en las entradas
del monitor, pero una forma común es utilizar un mutex o semáforo binario. Como el
compilador (y no el programador) está haciendo los arreglos para la exclusión mutua, es
mucho menos probable que al o salga mal. En cualquier caso, la persona que escribe el
monitor no tiene que saber acerca de cómo el compilador hace los arreglos para la
exclusión mutua. Basta con saber que, al convertir todas las regiones críticas en
procedimientos de monitor, nunca habrá dos procesos que ejecuten sus regiones críticas
al mismo tiempo.
Los monitores ofrecen mayor seguridad (reliability), robustez y escalabilidad;
complementan al encapsulamiento de un objeto, sincronizando el acceso al mismo.
Monitores con señales
Un monitor es un módulo de software que consta de uno o más procedimientos,
una secuencia de inicialización y unos datos locales. Las características básicas de un
monitor son las siguientes:
 Las variables de datos locales están sólo accesibles para los procedimientos del
monitor y no para procedimientos externos.
 Un proceso entra en el monitor invocando a uno de sus procedimientos.
 Sólo un proceso puede estar ejecutando en el monitor en un instante dado;
cualquier otro proceso que haya invocado al monitor quedará suspendido
mientras espera que el monitor esté disponible.
 Solamente una llamada a un módulo monitor puede ser activada por vez. Esto
protege a los datos dentro del monitor de accesos simultáneos de múltiples
usuarios. Los usuarios que intentan acceder al monitor mientras este está
ocupado son bloqueados en una cola de entrada al monitor.
Las dos primeras características recuerdan a las de los objetos del software
orientado a objetos. En realidad, un sistema operativo o lenguaje de programación
orientado a objetos puede implementar un monitor fácilmente como un objeto con
características especiales. Si se cumple la norma de un proceso cada vez, el monitor
puede ofrecer un servicio de exclusión mutua. Las variables de datos del monitor
pueden ser accedidas solo por un proceso cada vez. Así pues, una estructura de datos
compartida puede protegerse situándola dentro de un monitor. Si los datos del monitor
representan a algún recurso, el monitor ofrecerá un servicio en exclusión mutua en el
acceso a este recurso. Para que resulten útiles en el proceso concurrente los monitores
deben incluir herramientas de sincronización. Por ejemplo, supóngase que un proceso
llama a un monitor y, mientras está en el monitor, debe suspenderse hasta que se cumpla
alguna condición. Hace falta un servicio para que el proceso no solo esté suspendido,
sino que libere el monitor y otro proceso pueda entrar. Más tarde, cuando se cumpla la
condición y el monitor está de nuevo disponible, el proceso puede reanudarse y tiene
que permitírsele volver a entrar en el monitor en el punto de la suspensión.
Figura 3 “Estructura de un monitor” Fuente: Stallings (2005)
Componentes
Un monitor tiene cuatro componentes: inicialización, datos privados,
procedimientos del monitor y cola de entrada.
• Inicialización: contiene el código a ser ejecutado cuando el monitor es creado
• Datos privados: contiene los procedimientos privados, que sólo pueden ser
usados desde dentro del monitor y no son visibles desde fuera
• Procedimientos del monitor: son los procedimientos que pueden ser llamados
desde fuera del monitor.
• Cola de entrada: contiene a los threads que han llamado a algún procedimiento
del monitor, pero no han podido adquirir permiso para ejecutarlos aún.
Un monitor soporta la sincronización mediante el uso de variables de condición
que están contenidas dentro del monitor y son accesibles solo dentro de ese monitor.
Las variables de condición son un tipo de dato especial en los monitores que se
manipula mediante dos funciones:
 cwait(c): Suspende la ejecución del proceso llamado bajo la condición c. El
monitor está ahora disponible para ser usado por otro proceso.
 csignal(c): Reanuda la ejecución de algún proceso suspendido después de
un cwait(c) bajo la misma condición. Si hay varios procesos, elige uno de ellos;
si no hay ninguno, no hace nada.
Implementación
Los operadores de sincronización del monitor son llamados cwait() y csignal() de
la misma manera que en programas basados en semáforos. Sin embargo, variables de
condición son usadas en lugar de semáforos y su comportamiento es diferente. Por
ejemplo, definen simplemente la cola de espera y no tienen valores numéricos como los
semáforos. En cualquier momento que una condición variable cwait()es encontrada, el
proceso ejecutando el cwait() está siempre bloqueado. En cualquier momento que
un csignal() es encontrado en una condición variable, un proceso esperando en la cola
de condiciones variables (el primero si es FIFO) es activado (puesto en la cola de listos).
Si no hay procesos esperando, el operador csignal() es ignorado.
Es importante destacar que un proceso con la marca csignal() debe ser el próximo
en entrar al monitor para que las condiciones existentes cuando ocurrió el csignal() no
sean cambiadas cuando éste entre al monitor. También, cuando no hay procesos
bloqueados en la cola de condiciones variables csignal(), pero hay procesos bloqueados
en la cola de entrada al monitor, csignal() activará “la cabeza” de la cola de entrada al
monitor pasándole el control del monitor.
Si hay al menos un proceso en una cola de condición, un proceso de dicha cola
deberá ejecutar en cuanto otro proceso ejecute un csignal() para la condición. Así pues,
el proceso que ejecuta el csignal() debe salir inmediatamente del monitor o suspenderse
en el monitor. Son varios los inconvenientes de esta solución:
 Si el proceso que ejecuta el csignal() no abandona el monitor, hacen falta
dos cambios de contexto adicionales: uno para suspender el proceso y otro
para reanudarlo cuando el monitor quede disponible.
 Cuando se ejecuta un csignal(), debe activarse inmediatamente un proceso
de la cola de la condición correspondiente y el planificador debe
asegurarse de que ningún otro proceso entre al monitor antes de la
activación. Si no es así, la condición bajo la que se ha activado el proceso
podría cambiar.
 Se puede utilizar el paso de mensajes para cumplir con la exclusión mutua.
Esta técnica dice que, si hay más de un proceso ejecutando la
acción receive concurrentemente, entonces:
 Si hay un mensaje, se entrega sólo a uno de los procesos y los otros se
bloquean.
 Si el buzón está vacío, todos los procesos se bloquean. Cuando haya un
mensaje disponible, sólo se activa y toma el mensaje uno de los procesos
bloqueados. Una vez que un proceso ha conseguido el mensaje, ejecuta su
sección crítica y, después, devuelve el mensaje al buzón. De este modo, el
mensaje funciona como un testigo (token) que se pasa de un proceso a
otro.
Modelo alternativo de monitores con notificación y difusión
Monitor Hoare
La definición de monitor de Hoare requiere que, si hay al menos un proceso en
una cola de una condición, un proceso de dicha cola se ejecuta inmediatamente cuando
otro proceso racilce un csignal sobre dicha condición- Así, el proceso que realiza el
csignal debe bien salir inmediatamente del monitor o bien bloquearse dentro del
monitor.
Hay dos desventajas en esta solución:
Si el proceso que realiza el csignal no ha terminado con el monitor, entonces se
necesitaran dos cambios de procesos adicionales: uno para bloquear este proceso y otro
para retomarlo cuando el monitor quede disponible.
La planificación de procesos asociada con una señal debe ser perfectamente
fiable. Cuando se realiza un csignal, un proceso de la cola de la correspondiente
condición debe ser activado inmediatamente y el planificador debe asegurar que ningún
otro proceso entra en el monitor antes de la activación.
Monitor Lampson y Redell
Lampson y Redell desarrollaron una definición diferente de monitor para el
lenguaje Mesa [LAMP80]. Su solución resuelve los problemas que se anunciaron
anteriormente y aporta varias extensione sutiles. La estructura del monitor de Mesa se
utiliza también en el lenguaje de programación de sistemas Modula-3[NELS91]. En
mesa, la primitiva csignal se sustituye por la cnotify, con la siguiente interpretación;
cuando un proceso ejecutando un monitor ejecuta cnotify(x), provoca que la cola de la
condición x sea notificada, pero el proceso que señaló continúa ejecutando. El resultado
de la notificación es que el proceso en cabeza de la cola de la condición será retomado
en un momento futuro conveniente, cuando el monitor esté disponible. Sin embargo,
como no hay garantía de que algún otro proceso entre en el monitor antes que el proceso
sea notificado, el proceso notificado deberá volver a comprobar la condición.
Las sentencias if se reemplazan por bucles while. Así, este convenio requiere al
menos una evaluación extra de la variable condición. A cambio, sin embargo, no hay
cambios de proceso extra ni tampoco hay otras restricciones sobre cuando, tras el
cnotify, debe ejecutar el proceso notificado.
Una ventaja de los monitores de Lampson/Redell sobre los monitores de Hoare es
que la solución de Lampson/Redell es menos propensa a error. En esta solución, dado
que, al usarse la construcción while, cada procedimiento comprueba la variable
condición después de ser señalado. Un proceso puede señalar o difundir incorrectamente
sin causar un error en el programa señalado. El programa señalado comprobará la
variable relevante y si la condición no se cumple, volverá a esperar.
Otra ventaja del monitor Lampson/Redell es que se presta a un enfoque más
modular de la construcción de programas.

Paso de mensajes
Cuando los procesos interactúan unos con otros, se deben satisfacer dos requisitos
básicos: la sincronización y la comunicación. Los procesos tienen que sincronizarse
para cumplir la exclusión mutua; los procesos cooperantes pueden necesitar
intercambiar información. Un método posible para ofrecer ambas funciones es el paso
de mensajes. El paso de mensajes tiene la ventaja adicional de que se presta a ser
implementado en sistemas distribuidos, así como en sistemas multiprocesador y
monoprocesador de memoria compartida.
La funcionalidad real del paso de mensajes se ofrece, normalmente, por medio de
un par de primitivas:
 send (destino, mensaje)
 receive (origen, mensaje)
Este es el conjunto mínimo de operaciones necesario para que los procesos
puedan dedicarse al paso de mensajes. Un proceso envía información en forma de un
mensaje a otro proceso designado como destino. Un proceso recibe información
ejecutando la primitiva receive, que indica el proceso emisor (origen) y el mensaje.
Sincronización
La comunicación de un mensaje entre dos procesos implica cierto nivel de
sincronización entre ambos. El receptor no puede recibir un mensaje hasta que sea
enviado por otro proceso. Además, hace falta especificar qué le sucede a un proceso
después de ejecutar una primitiva send o receive.
Considérese en primer lugar la primitiva send. Cuando se ejecuta una primitiva
send en un proceso, hay dos posibilidades: O bien el proceso emisor se bloquea hasta
que se recibe el mensaje o no se bloquea. Análogamente, cuando un proceso ejecuta una
primitiva receive, hay dos posibilidades:
Si previamente se ha enviado algún mensaje, éste es recibido y continúa la
ejecución.
Si no hay ningún mensaje esperando entonces, o bien (a) el proceso se bloquea
hasta que llega un mensaje o (b) el proceso continúa ejecutando, abandonando el intento
de recepción.
Así pues, tanto el emisor como el receptor pueden ser bloqueantes o no
bloqueantes. Son habituales las siguientes tres combinaciones, aunque cualquier sistema
concreto implementa sólo una o dos combinaciones:
 Envío bloqueante, recepción bloqueante: tanto el emisor como el receptor se
bloquean hasta que se entrega el mensaje; esta técnica se conoce como
rendezvous. Esta combinación permite una fuerte sincronización entre procesos.
 Envío no bloqueante, recepción bloqueante: aunque el emisor puede
continuar, el receptor se bloquea hasta que llega el mensaje solicitado. Esta es,
probablemente, la combinación más útil. Permite que un proceso envíe uno o
más mensajes a varios destinos tan rápido como sea posible. Un proceso que
debe recibir un mensaje antes de poder hacer alguna función útil tiene que
bloquearse hasta que llegue el mensaje. Un ejemplo es el de un proceso servidor
que ofrezca un servicio o un recurso a otros procesos.
 Envío no bloqueante, recepción no bloqueante: nadie debe esperar.
El send no bloqueante es la forma más natural para muchas tareas de
programación concurrente. Por ejemplo, si se usa para solicitar una operación de salida,
tal como una impresión, permite al proceso solicitante realizar la solicitud en forma de
mensaje y continuar. Un posible riesgo del send no bloqueante es que un error puede
llevar a una situación en la que el proceso genere mensajes repetidamente. Como no hay
bloqueo para hacer entrar en disciplina al proceso, esos mensajes pueden consumir
recursos del sistema, incluido tiempo del procesador y espacio en buffer, en detrimento
de otros procesos y del sistema operativo. Además, el send no bloqueante carga sobre el
programador el peso de determinar qué mensaje se ha recibido: Los procesos deben
emplear mensajes de respuesta para acusar La recepción de un mensaje.
Para la primitiva receive, la versión bloqueante parece ser la más natural para
muchas tareas de programación concurrente. En general, un proceso que solicita un
mensaje necesitará la información esperada antes de continuar. Sin embargo, si se
pierde un mensaje, un proceso receptor puede quedarse bloqueado indefinidamente.
Este problema puede resolverse por medio del receive no bloqueante. Sin embargo, el
riesgo de esta solución es que, si un mensaje se envía después de que un proceso haya
ejecutado el correspondiente receive, el mensaje se perderá. Otro método posible
consiste en permitir a un proceso comprobar si hay un mensaje esperando antes de
ejecutar un receive y en permitir a un proceso especificar más de un origen en una
primitiva receive. Esta última técnica resulta útil si un proceso espera mensajes de más
de un origen y puede continuar si llega cualquiera de estos mensajes.
Direccionamiento
Evidentemente, es necesario disponer de alguna forma de especificar en la
primitiva send qué proceso va a recibir el mensaje. De forma similar, la mayoría de las
implementaciones permiten a los procesos receptores indicar el origen del mensaje que
se va a recibir.
Los distintos esquemas para hacer referencia a los procesos en las primitivas send
y receive se encuadran dentro de dos categorías: direccionamiento directo e indirecto.
Con el direccionamiento directo, la primitiva send incluye una identificación específica
del proceso destino. La primitiva receive se puede gestionar de dos formas. Una
posibilidad requiere que el proceso designe explícitamente un proceso emisor. Así pues,
el proceso debe conocer de antemano de qué proceso espera un mensaje. Esto suele ser
eficaz para procesos concurrentes y cooperantes. En otros casos, sin embargo, es
imposible especificar el proceso de origen por anticipado. Un ejemplo es un proceso
servidor de impresoras, que aceptará mensajes de solicitud de impresión de cualquier
otro proceso. Para tales aplicaciones, una técnica más efectiva consiste en usar
direccionamiento implícito. En este caso, el parámetro origen de la primitiva receive
tendrá un valor de retomo cuando se haya realizado la operación de recepción.
El otro enfoque es el direccionamiento indirecto. En este caso, los mensajes no se
envían directamente del emisor al receptor, sino a una estructura de datos compartida
formada por colas que pueden guardar los mensajes temporalmente. Estas colas se
denominan generalmente buzones (mailboxes). De este modo, para que dos procesos se
comuniquen, uno envía mensajes al buzón apropiado y el otro los coge del buzón.
Una ventaja del direccionamiento indirecto es que se desacopla a emisor y
receptor, permitiendo una mayor flexibilidad en el uso de los mensajes. La relación
entre emisores y receptores puede ser uno a uno, de muchos a uno, de uno a muchos o
de muchos a muchos. Una relación uno a uno permite que se establezca un enlace
privado de comunicaciones entre dos procesos, lo que aísla su interacción de injerencias
erróneas de otros procesos. Una relación de muchos a uno resulta útil para interacciones
cliente/servidor, donde un proceso ofrece un servicio a un conjunto de procesos. En este
caso, el buzón se denomina puerto. Una relación uno a muchos permite un emisor y
muchos receptores; es útil para aplicaciones en las que un mensaje o alguna información
se difunda a un conjunto de procesos.

Figura 4 “Comunicación indirecta entre procesos” Fuente: Stallings (2005)

La asociación de procesos a buzones puede ser estática o dinámica. Los puertos


suelen estar asociados estáticamente con algún proceso en particular, es decir, el puerto
se crea y se asigna al proceso permanentemente. De forma similar, una relación uno a
uno normalmente se define de forma estática y permanente. Cuando hay varios
emisores, la asociación de un emisor a un buzón puede realizarse dinámicamente. Se
pueden usar primitivas como conectar y desconectar con este propósito.
Una cuestión afín es la de la propiedad del buzón. En el caso de un puerto,
normalmente pertenece y es creado por el proceso receptor. De este modo, cuando se
destruye el proceso, también se destruirá el puerto. Para el caso general de los buzones,
el sistema operativo puede ofrecer un servicio de creación de buzones. Estos buzones
pueden ser considerados como propiedad del proceso creador, en cuyo caso se
destruyen junto con el proceso o pueden ser considerados como propiedad del sistema
operativo, en cuyo caso se necesita una orden explícita para destruir el buzón.
Formato de Mensajes
El formato de los mensajes depende de los objetivos del servicio de mensajería y
de si el servicio ejecuta en un ordenador independiente o en un sistema distribuido. Para
algunos sistemas operativos, los diseñadores han elegido mensajes cortos y de tamaño
fijo para minimizar el procesamiento y el coste de almacenamiento. Si se va a pasar una
gran cantidad de datos, los datos pueden ponerse en un archivo y el mensaje
simplemente hará referencia a este archivo. Una solución más flexible es permitir
mensajes de longitud variable.

Figura 5 “Formato general de mensaje” Fuente: Stallings (2005)

La figura 5 muestra un formato típico de mensajes para sistemas operativos que


permiten mensajes de longitud variable. El mensaje se divide en dos partes: una
cabecera, que alberga información sobre el mensaje y un cuerpo, que alberga el
contenido real del mensaje. La cabecera puede contener una identificación del origen y
el destino deseados, un campo de longitud y un campo de tipo para distinguir entre
varios tipos de mensajes. Puede haber también información de control adicional, como
un campo apuntador para poder crear una lista enlazada de mensajes; un número de
secuencia, para guardar constancia del orden y número de mensajes pasados entre
origen y destino; y un campo de prioridad.

También podría gustarte