Вы находитесь на странице: 1из 6

Examen de Fundamentos de sistemas distribuidos

Tiempo total: 2 horas y media.

Problema: Programa: Driver de disco (5 puntos)


Estamos programando el driver de un dispositivo de disco. Este driver tendrá un proceso que se ocupará del
dispositivo en sí y otros procesos que se ocuparan de procesar las peticiones de los usuarios del disco.
Al primer proceso lo llamaremos deviceproc y al resto userproc. El dispositivo tendrá tres estados:
ocupado, esperando peticiones y enviando respuestas. Los procesos de tipo userproc tendrán tres esta-
dos; esperando al dispositivo, enviando datos y recogiendo respuestas.
El dispositivo comenzará en el estado esperando peticiones. En ese punto los procesos pueden lla-
mar a la operación put para mandar las peticiones. Cuando todos los N procesos hayan llamado a la
operación put el dispositivo llamará a la operación run y pasará al estado ocupado en el que seguirá hasta
que run termine. Una vez termine el dispositivo pasará al estado enviando respuestas. En este momento
los procesos userproc llamarán a get para recibir las respuestas a sus peticiones. El proceso deviceproc no
debe atender peticiones de nuevo hasta que todos los userproc hayan hecho sus get.
Resumiendo:
 El proceso deviceproc tiene tres partes: empezar a atender peticiones, run y terminar de atender
peticiones.
 Los procesos userproc tienen dos operaciones put y get.
 Las operaciones en sí no tienen que hacer nada salvo controlar la concurrencia. No es necesario que
se mantenga ningún tipo de buffer compartido.
Se pide: Escriba el código de deviceproc y userproc de forma que cumpla lo dicho anteriormente sin
condiciones de carrera. Los únicos mecanismos de sincronización que se pueden usar para resolver este
problema son los semáforos de Plan 9 descritos en semacquire(2). Escriba adicionalmente una función
main que pruebe el programa con 10 procesos userproc.
-2-

Solución
Una solución podría ser la siguiente:

______
dma.c 
#include <u.h>
#include <libc.h>
#include <thread.h>

enum{
Nmaxcli = 10,
};

long putsendsem;
long getsendsem;

long getsstartsem;
long putsstartsem;
long devmutex = 1;
int ncli;

void
put(void)
{
int t;
semacquire(&devmutex, 1);
print("put\n");
t = nrand(100);
sleep(t);
if(++ncli == Nmaxcli){
semrelease(&putsendsem, 1);
ncli = 0;
}
semrelease(&devmutex, 1);
}

void
get(void)
{
int t;
semacquire(&devmutex, 1);
print("get\n");
t = nrand(100);
sleep(t);
if(++ncli == Nmaxcli){
semrelease(&getsendsem, 1);
ncli = 0;
}
semrelease(&devmutex, 1);
}
-3-

void
run(void)
{
int t;
semacquire(&devmutex, 1);
print("run\n");
t = nrand(100);
sleep(t);
semrelease(&devmutex, 1);
}

void
deviceproc(void *)
{
threadsetname("deviceproc");
for(;;){
semrelease(&putsstartsem, Nmaxcli);
semacquire(&putsendsem, 1);
run();
semrelease(&getsstartsem, Nmaxcli);
semacquire(&getsendsem, 1);
}
}

void
userproc(void *v)
{
char *name;
int id;

id = (int) v;
name = smprint("userproc: %d", id);
threadsetname(name);

print("starting: %s\n", name);


semacquire(&putsstartsem, 1);
put();
print("finished puts: %s\n", name);
semacquire(&getsstartsem, 1);
print("starting gets: %s\n", name);
get();
print("finishing gets: %s\n", name);
free(name);
}

void
threadmain(int, char *[])
{
int i;

srand(time(0));
proccreate(deviceproc, nil, 8*1024);
for( i = 0; i < 5*Nmaxcli; i++){
proccreate(userproc, (void *)i, 8*1024);
}
}


-4-

Aunque se podría poner un sólo semáforo para sincronizar el proceso del dispositivo, hemos decidido poner
dos para que sea más fácil de entender: putsendsem, getsendsem. Estos tres semáforos los utiliza el último
cliente para avisar al dispositivo de que ya han acabado los puts o los gets. Por otro lado, putsstartsem y
getsstartsem se utilizan para dar paso a Nmaxcli clientes para que hagan los puts o los gets. Finalmente
devmutex se utiliza para evitar el acceso concurrente al dispositivo, incluyendo la variable del dispositivo
que controla cuantos clientes han acabado con la operación actual, ncli.

-5-

Problema: Cuestiones de teoría


Responda en un folio de examen las siguientes cuestiones:
A) (2 puntos) Tiene un programa que reserva y libera continuamente llamando a malloc estructuras de
datos de 10 bytes, 1Kbyte y 1 Mbyte sin ningún patrón observable. ¿Se producirá mucha
fragmentación de memoria?. ¿De qué tipo y por qué?. Explica qué estrategia y qué política podría uti-
lizarse para resolver este problema y por qué funcionan.
B) (1.5 puntos) Un programa ejecutando en un PC como el del laboratorio, nada más arrancar hace un
malloc de 100 Mbytes y a continuación un memset del último Kbyte. Explique cuanta memoria
utiliza, qué llamadas al sistema se realizan y como interviene el sistema en caso de hacerlo durante la
ejecución del programa.

C) (1,5 puntos) Suponiendo la siguiente tabla FAT, indique los clusters en orden que forman el fichero
cuya entrada de directorio indica que el cluster inicial es 4.

Cluster  
_ ___________
1  5
 
_ ___________
2  3 
 -1 
_ ___________
3
 9 
_ ___________
4
 
 6 
_ ___________
5
 8 
_ ___________
6
 
_ ___________
7  -1 
 7 
_ ___________
8
 2 
_ ___________
9
 
...  ... 
-6-

Solución
A) Al reservar y liberar muchas estructuras de tamaños dispares va a haber una gran cantidad de
fragmentación externa si la implementación del asignador de memoria no contempla esta posibilidad.
Este tipo de fragmentación se produce cuando, como en este caso quedan huecos cada vez menores
dispersos y no va a haber espacio para las estructuras de tamaño mayor.
Como hay uno de los tamaños reservados que es pequeño, también habrá fragmentación interna si el
tamaño mínimo que reserva el asignador es mayor que él. Esto se resuelve bajando el tamaño mínimo
que se reserva. Esto a su vez es posible que aumente la fragmentación externa.
La estrategia que podemos utilizar para mejorar la fragmentación externa dado el tipo de programa
que se nos presenta requiere aprovecharse de la regularidad en los tamaños reservados. Una política
que implemente algún tipo de segregación de las peticiones para esos tamaños comunes funcionará
bien en este caso. Un ejemplo de política que funcionaría bien es quick-fit, que implementa una caché
para huecos de tamaño común. De esta forma se minimizaría la fragmentación al impedir que los
huecos grandes se dividiesen. Adicionalmente se obtendría el beneficio de que sería más rápido
encontrar estos huecos, obteniéndose un incremento en la velocidad de ejecución además de mini-
mizar el uso de la memoria.
B) Si suponemos que el texto del programa cabe en una página la pila en otra y que puede haber otra en
la que quepan los datos inicializados y el BSS (que pueden compartir página), el programa utilizará
en el arranque tres marcos de memoria física. Los marcos para estas páginas se reservarán al arrancar
el programa mediante sus correspondientes fallos de página.
La llamada malloc sólo apuntará la memoria como reservada pero no incrementará el uso de
memoria física. Más adelante al hacer el memset habrá un fallo de página y se utilizará otra marco
adicional. Hay que recordar que en un PC como el del laboratorio, las páginas son de 4Kbytes, con lo
que los datos del memset caben perfectamente en una página. Hemos supuesto que el memset cae
alineado dentro de una página, si no, podrían necesitarse dos.
El sistema intervendrá (cuando el programa ya está ejecutando, es decir, descontando los fallos de
página iniciales), dos veces. La primera, al hacer un malloc de un tamaño de memoria tan grande
que no cabe en las direcciones virtuales asignadas en el arranque al BSS, el asignador tendrá que lla-
mar a la llamada al sistema brk para pedir que el sistema haga crecer el segmento. Más adelante,
cuando se haga el memset, habrá un fallo de página y el sistema tendrá que intervenir para asignar
un marco para respaldar la página asociada a esa región de memoria.
C) Siguiendo las entradas en la tabla FAT, los clusters que formarán el fichero serán en este orden 4 9 2
3. El 3 es el cluster final porque tiene como entrada -1 o 0xffffffff.