Examen final de Ampliación de Sistemas Operativos Solución

Anuncio
Examen final de Ampliación de Sistemas Operativos
Solución
5 de Febrero de 2003.
Tercer curso de Ingenierías Técnicas de Informática de Sistemas y de Gestión, URJC.
Problema 1 (3 puntos)
Tenemos una aplicación que tiene un cliente con una estructua de datos. El cliente
se limita a utilizar un algoritmo como el que sigue cada vez que modifica dicha estruc­
tura:
modificar_adt( val : Adt ) {
backup_adt(adt);
adt = val;
}
La idea es que backup_adt realice un backup del valor del adt en un servidor que se
encuentra en otra máquina.
Se pide responder razonadamente a las siguientes cuestiones:
a)
¿Usarías RPCs o simplemente paso de mensajes para implementar backup_adt ?
¿Qué usarías si quieres que modificar_adt no tenga que esperar a que se
complete el backup para retornar? ¿Te supone esto algún problema?
b)
Escribe en pseudo-código todo el código involucrado desde que el cliente llama a
backup_adt hasta que dicho procedimiento retorna en el caso en que se utilicen
RPCs para dicha operación.
c)
¿Qué semántica te parece más correcta para esta aplicación, at-most-once o atleast once? ¿Cómo implementas dicha semántica?
d)
Quieres poder arrancar el servidor en la máquina que desees sin necesidad de rear­
rancar el cliente. ¿Qué supone esto para tu mecanismo de RPCs? ¿Cómo lo haces?
e)
¿Podrías implementar un servidor sin estado empleando RMI?
Solución:
a)
Daría igual en realidad utilizar RPCs o paso de mensajes. Tanto en un caso como
en otro el cliente tendría que enviar una petición al servidor y el servidor enviar
una respuesta (posiblemente vacía) al cliente para notificar que se hizo el backup.
Dado que el enunciado plantea backup_adt como un procedimiento podríamos
optar por RPCs (lo que tiene la ventaja de que si utilizamos un compilador de IDLs
obtenemos los stubs automáticamente y nos podemos olvidar del aplanado y desa­
planado de adt).
Si no queremos esperar a que se complete el backup podríamos eliminar el
receive en el lado del cliente (por ej. editando los stubs) y continuar con la
ejecución una vez solicitado el backup. Dada la simplicidad de backup_adt no
tiene sentido utilizar ARPC (RPC asíncrona), tardaríamos prácticamente lo mismo
que con una síncrona.
­2­
El problema es que eliminar el receive nos dejaría en la ignorancia respecto a si
el servidor realmente ha realizado el backup o no.
b)
En el lado del cliente tendríamos:
# Código del cliente
modificar_adt(val: Adt){
backup_adt(val);
adt = val;
}
# Stub del cliente. 1 parametro de entrada
backup_adt(val: Adt){
# el mensaje y la respuesta.
msg, repl : array of byte
# resolvemos el servidor si no lo hicimos antes
if ( serveraddr == nil)
resolve(serveraddr, "backups");
# el msg tiene el id del proc y
# el adt aplanado (en formato de red).
msg = append(aplanar_id("backup_adt"), aplanar_adt(adt));
send(serveraddr, msg);
recv(nil, repl);
# no hay parametros de salida. Nada que desaplanar.
return;
}
En el lado del servidor tendríamos un proceso atendiendo las peticiones:
main() {
# notificamos al binder quienes somos y escuchamos
# peticiones
bind("backups", NetAddr);
listen(NetAddr);
for(;;){
# recibimos un mensaje y lo servimos
recv(cliaddr, msg);
spawn service(cliaddr, msg);
}
}
Para cada petición, se demultiplexa según el identificador de procedimiento y se
llama al stub del servidor:
­3­
service(cliaddr: string; msg: array of byte){
op := desaplanar_id(msg);
switch(op){
case "backup_adt" =>
msg = backup_adt_stub(msg);
case * =>
msg = aplanar_id("error");
}
send(cliaddr, msg);
}
backup_adt_stub(msg: array of byte) : array of byte {
# hacemos el unmarshalling de los parametros
adt := desaplanar_adt(msg);
# llamamos al procedimiento
backup_adt(adt);
# no hay resultados, nada de lo que hacer
# marshaling. Usamos un mensaje vacio.
return aplanar_id("");
}
Nos hemos inventado el interfaz de las operaciones para enviar, recibir y escuchar
peticiones en la red. Sólo decir que recv devuelve tanto la dirección del remitente
del mensaje como el mensaje que recibimos. Suponemos que listen informa al
sistema de la dirección en que queremos recibir mensajes (dejando que el sistema
escoja la dirección en otro caso).
c)
At least once parece apropiada, dado que la operación es idempotente y queremos
que en caso de errores al menos se haga una vez. La forma de implementarla es
retransmitir la petición hasta estar seguro de que el servidor la ha procesado. Por
ejemplo, utilizando esto como parte del stub del cliente:
backup_adt(val: Adt){
...
do {
send(serveraddr, msg);
recv(nil, repl);
} while (geterror() == "timed out");
...
}
d)
Supone que habría que resolver la dirección del servidor cada vez que vamos a
efectuar la RPC. Una sóla vez no basta, dado que la dirección antigua puede que no
corresponda a la nueva dirección del servidor. Otra opción sería resolver de nuevo
cuando se presentan errores y reintentar la operación.
e)
Dado que, en general, el objeto sujeto de la RMI posee estado, es imposible. Sólo
podríamos si utilizamos los objetos como si fuesen interfaces a procedimientos y
nos aseguramos de que ninguno mantenga estado.
­4­
Problema 2 (3 puntos)
La realización de backups ha tenido tanto éxito que ahora disponemos de tres pro­
tocolos distintos para hacer backups. El servidor dispone de una única estructura de
datos que es considerada el backup de la estructura del cliente. Naturalmente, se puede
emplear cualquiera de los tres protocolos para terminar haciendo el backup sobre dicha
estructura.
Además, nuestro servidor desea permitir que un número indeterminado de clientes
pueda realizar sus backups simultáneamente.
Se pide responder razonadamente a las siguientes cuestiones:
a)
Escribe en pseudocódigo un servidor multithread que atienda dichos protocolos,
prestando atención a la concurrencia en el servidor.
b)
¿Tiene sentido limitar el número de threads que dicho servidor puede arrancar?
c)
¿Tiene sentido utilizar procesos en lugar de threads en este servidor?
Solución
a)
En realidad, el servidor mostrado en el problema anterior ya utiliza múltiples
threads (uno para atender a cada llamada). Para resolver las condiciones de carrera
que afectan a la actualización del backup, podríamos utilizar un semáforo para
implementar una sección crítica con exclusión mutua. También habría que modi­
ficar la demultiplexación en el servidor para atender a los distintos protocolos. El
código que cambia respecto al problema anterior quedaría como sigue:
sem := Semaphore(1);
service(cliaddr: string; msg: array of byte){
op := desaplanar_id(msg);
down(sem);
switch(op){
case "backup_adt1" =>
msg = backup_adt1_stub(msg);
case "backup_adt2" =>
msg = backup_adt2_stub(msg);
case "backup_adt3" =>
msg = backup_adt3_stub(msg);
case * =>
msg = aplanar_id("error");
}
up(sem);
send(cliaddr, msg);
}
b)
Una posible razón para hacerlo es evitar problemas de sobrecarga en el servidor.
Dado que cada thread se va a pasar la mayor parte del tiempo en la región crítica,
podríamos incluso limitarlo a uno y no usar multiples threads en absoluto. Pero la
mejor opción es mantener los múltiples threads aunque sólo sea para evitar que
conexiones lentas de determinados clientes afecten a otros clientes.
c)
No hay ninguna razón por la que fuese más conveniente el uso de procesos en
lugar de threads. Por otro lado, a no ser que tengamos problemas serios de rendi­
miento tampoco hay ninguna razón que lo desaconseje.
­5­
Problema 3 (2 puntos)
Sea un sistema de ficheros distribuidos con un sistema de nombrado único para
todo el sistema. El tipo de nombrado que usa es de tipo UNIX, esto es, mediante una
estructura de directorios permite nombrar a partir de un directorio raiz a cualquier
directorio o fichero del sistema usando una lista de nombres de subdirectorios.
Supóngase que el sistema permite enlaces físicos tipo UNIX, esto es, a partir de un nom­
bre que referencia a un identificador de fichero o de directorio, se pueden crear nuevos
nombres que referencian a ese mismo identificador de fichero o directorio.
Se pide responder razonadamente a las siguientes cuestiones:
a)
¿Qué tipo de grafo forma este sistema de nombrado?
b)
¿Se pueden utilizar contadores de referencia para eliminar ficheros o directorios sin
nombre desde la raiz?
c)
¿Se puede utilizar "marcado y barrido" ( mark and sweep) para eliminar ficheros o
directorios sin nombre desde la raiz?
d)
¿Se necesitaría alguna de las dos técnicas mencionadas en b) y c) para el caso en
que en el sistema de nombrado sólo se permitiera enlazado simbólico tipo UNIX en
vez del enlazado físico antes mencionado?
Solución:
a)
Un grafo general del que ni siquiera podemos asegurar que sea un DAG. Por
supuesto podría tener ciclos, debido a la presencia de enlaces duros.
b)
No podríamos utilizar contadores de referencia (RCs) dado que la presencia de cic­
los hace que algunos nodos tengan un número de referencias mayor que cero (no
sean recolectados) a pesar de no ser alcanzables desde ningún nombre. Nos
quedarían pues nodos sin recolectar que en realidad no son útiles.
c)
Este tipo de recolección de basura si podríamos emplearla dado que procesa ade­
cuadamente nodos inalcanzables tengan o no referencias.
d)
En ese caso el grafo es en realidad un árbol, por lo que no sería precisa ninguna de
las dos técnicas anteriores.
En ninguno de los casos hemos tenido en cuenta la presencia de fallos en los nodos
involucrados.
Problema 4 (2 puntos)
El algoritmo de elección de coordinador del abusón (bully algorithm) funciona
porque el sistema de comunicación es síncrono, esto es, el tiempo de entrega de los
mensajes está acotado y es conocido. Se pide responder razonadamente y con un ejem­
plo de ejecución por qué podría no funcionar en un sistema asíncrono, esto es, el
tiempo de entrega de los mensajes no está acotado.
Solución:
La asincronía hace imposible saber si un nodo que ha de responder a un mensaje
está muerto o tan sólo tiene una pésima conectividad. No podríamos emplear un time­
out para decidir si un nodo esta muerto o no. No tiene sentido esperar indefinidamente,
puesto que la muerte del coordinador actual bloquearía todo el sistema (todos estarían
esperando). Podríamos entonces elegir un plazo de tiempo arbitrariamente largo para
decidir que no nos han contestado a una petición. Incluso en tal caso, supongamos que
el nodo 1 inicia el algoritmo enviando mensajes a los nodos 2 y 3. El 1 puede recibir
una respuesta del 2, quien a su vez ejecuta del nuevo el algoritmo y no recibe respuesta
alguna. En un sistema síncrono el 2 sería el nuevo coordinador, en uno asíncrono puede
­6­
que además el nodo 3 considere que él es el coordinador (basta con que sus respuestas
se retrasen lo suficiente como para que los otros nodos lo consideren muerto).
Descargar