Examen de Fundamentos de sistemas distribuidos

Anuncio
Examen de Fundamentos de sistemas distribuidos
Tiempo total: 2 horas.
Problema: Barrera con canales.
(3.5 puntos) En programación concurrente se denomina barrera a la abstracción que permite sincronizar un
conjunto de N procesos. Cada proceso que llega a la barrera se bloquea hasta que hayan llegado los N. Una
vez han llegado todos, recomienzan su ejecución. Cuando los procesos han pasado la barrera, el código
anterior a ella se garantiza que ya se ha ejecutado en todos los procesos.
Implementa una barrera con el siguiente interfaz:
Barrier *newbarrier(int nprocs);
void waitbarrier(Barrier *b);
No es necesario liberar la barrera.
Se debe entregar un programa principal que cree una barrera y varios procesos que la usen que llamen
a sleep(2) con diferentes valores para comprobar que funciona. Sólo se pueden usar como primitivas de
sincronización y comunicación entre procesos las de canales de la librería thread(2). No se pueden utilizar
canales con buffering ni CHANNOP. Los procesos deben crearse con proccreate(). No debe haber condiciones de carrera ni hambruna.
Como referencia, los prototipos de las funciones y los tipos de datos de la biblioteca de threads de Plan 9
son:
#include <u.h>
#include <libc.h>
#include <thread.h>
typedef enum {
CHANEND,
CHANSND,
CHANRCV,
CHANNOP,
CHANNOBLK,
} ChanOp;
typedef struct Alt Alt;
struct Alt {
Channel *c;
/* channel */
void
*v;
/* pointer to value */
ChanOp op;
/* operation */
char
*err; /* did the op fail? */
/*
* the next variables are used internally to alt
* they need not be initialized
*/
Channel **tag;
/* pointer to rendez-vous tag */
int
entryno; /* entry number */
};
-2-
void
int
int
int
void
void
int
int
void
char*
Channel*
void
int
int
void*
ulong
int
int
int
Channel*
threadmain(int argc, char *argv[])
mainstacksize
proccreate(void (*fn)(void*), void *arg, uint stacksize)
threadcreate(void (*fn)(void*), void *arg, uint stacksize)
threadexits(char *status)
threadexitsall(char *status)
threadid(void)
threadpid(int id)
threadsetname(char *name, ...)
threadgetname(void)
chancreate(int elsize, int nel)
chanfree(Channel *c)
alt(Alt *alts)
recv(Channel *c, void *v)
recvp(Channel *c)
recvul(Channel *c)
send(Channel *c, void *v)
sendp(Channel *c, void *v)
sendul(Channel *c, ulong v)
threadwaitchan(void)
-3-
Solución
Cuando se usan canales es común utilizar un thread o un proc para modelar una abstracción. En este caso, la
barrera es un proceso que se encarga de contar los procesos antes de despertarlos. Con este fin utiliza dos
canales, uno para que los procesos avisen de que han llegado y otro para que el proceso que se encarga de la
barrera los despierte.

__________
_barrachan.c
#include <u.h>
#include <libc.h>
#include <thread.h>
typedef struct Barrier Barrier;
struct Barrier{
int nprocs;
Channel *askc;
Channel *waitc;
};
void
flagger(void *v)
{
Barrier
int
*b;
i;
b = v;
for(i = 0; i < b->nprocs; i++)
recvul(b->askc);
for(i = 0; i < b->nprocs; i++)
sendul(b->waitc, 1);
threadexits(nil);
}
Barrier *
newbarrier(int nprocs)
{
Barrier *b;
b = mallocz(sizeof(Barrier), 1);
if(b == nil)
return nil;
b->askc = chancreate(sizeof(ulong), 0);
if(b->askc == nil){
free(b);
return nil;
}
b->waitc = chancreate(sizeof(ulong), 0);
if(b->waitc == nil){
chanfree(b->askc);
free(b);
return nil;
}
b->nprocs = nprocs;
proccreate(flagger, b, 8*1024);
return b;
}
-4-
void
waitbarrier(Barrier *b)
{
sendul(b->askc, 1);
recvul(b->waitc);
}
Barrier *thebarr;
void
examproc(void *v)
{
int t;
t = (int)v;
sleep(t);
print("waiting\n");
waitbarrier(thebarr);
print("flagged\n");
threadexits(nil);
}
void
threadmain(int, char *[])
{
thebarr = newbarrier(3);
proccreate(examproc, (void *)1000, 8*1024);
proccreate(examproc, (void *)10, 8*1024);
proccreate(examproc, (void *)2000, 8*1024);
print("all created exiting\n");
threadexits(nil);
}
Problema: Cuestiones de teoría
Responda en un folio de examen las siguientes cuestiones:
A)
(3 puntos) Estamos depurando un programa. Sabemos que el resultado de ejecutar el siguiente
comando sobre el directorio de /proc correspondiente a un proceso que ejecuta dicho programa
(recuerde que en el fichero segment se muestran las direcciones de comienzo y fin de los segmentos, ver proc(3) ) es:
;cat /proc/345/segment
Stack
defff000 dffff000
Text
R 00001000 00011000
Data
00011000 00013000
Bss
00013000 0002b000
Este trozo de código del programa:
int
functest(int z)
{
print("0x%p\n", &z);
return z*28;
}
1
8
1
1
-5-
Imprime:
0xfffff000
¿Las direccion impresa es virtual o física? ¿Cree que hay algún problema en el programa?
-6-
Solución
La dirección es virtual. Los procesos de usuario no ven nunca direcciones físicas, sólo el kernel al actualizar
la tabla de páginas y al interactuar con algunos dispositivos. La dirección debería ser de la pila, ya que es la
dirección de un parámetro. Debería pues estar encontrarse en el segmento de pila, que se corresponde con el
rango 0xdefff000-0xdffff000 a menos que el proceso tenga la pila en algún otro segmento (ver la respuesta
a la siguiente pregunta). Lo que sí debería estar seguro es dentro de los segmentos del proceso. La
dirección impresa está fuera de los segmentos del proceso tal y como se ven en segment. Esto es posible
sólo si hay algún problema de corrupción de memoria en el programa.
¿Y si imprime?:
0x00020000
¿Puede suceder en algún caso? ¿Y si es un thread de una librería de threads? Razone sus respuestas.
-7-
Solución
La dirección 0x00020000 pertenece al Bss del proceso. Para que esto pueda suceder, la pila del proceso en
ese momento tiene que estar en el Bss. Esto puede ocurrir si el registro puntero de pila se ha cargado con un
valor que apunta a una variable global sin inicializar o a una reservada con malloc usando algo como
setjmp. Justo esto último ocurre en la librería de threads de plan 9, en la que cada thread que ejecuta
encima del proceso tiene una pila reservada con malloc, y por tanto en el heap, es decir, en el Bss.
Problema: Creación de un fichero
B)
(3.5 puntos)
En una máquina hay un sistema de ficheros tipo unix, es decir con inodos, y sólo bloques directos. Se ejecuta el comando:
echo -n h > /usr/esoriano/x
Describa las operaciones que debe realizar el sistema de ficheros. Suponga que el fichero no existía.
Razone sus respuestas.
-8-
Solución
Lo primero que hará el sistema es atravesar el árbol de ficheros comprobando permisos. Para ello empezará
con el inodo raíz, que es razonable suponer que estará ya en memoria en la caché desde el momento en el
que se montó el sistem de ficheros. Si, en cualquier caso, no es así, se leerá del superbloque. En ese inodo
se comprobarán que se tienen permisos para entrar en el directorio (permiso de ejecución). A continuación
se mirarán las entradas de directorio en los bloques de datos apuntados por ese inodo buscando la entrada
con nombre usr. De la misma forma se mirarán en los bloques de datos apuntados por el inodo para encontrar esoriano y finalmente x. Una vez comprobado que x no existe, se comprobará el permiso de escritura de
esoriano para crear un nuevo fichero.
Entonces, se reserva un nuevo inodo de la tabla de inodos marcándolo como ocupado. A continuación, se
rellena este inodo con los metadatos correspondientes, el usuario, los permisos, y asigna cero al tamaño.
Seguidamente, en el bloque de datos del directorio /usr/esoriano crea un nueva entrada de directorio
con el nombre x y con el número de inodo que acaba de reservar. Después, incrementa la cuenta de referencias del inodo del fichero, Actualiza los metadatos correspondientes en el inodo del directorio. A
continuación reserva un nuevo bloque de datos, marcándolo como ocupado y escribe ’h’ en él. Seguidamente actualiza el inodo del fichero para que apunte al bloque que acaba de reservar. Finalmente actualiza
los metadatos del inodo, el tamaño, y la fecha de modificación.
La cantidad de accesos a disco que habrá que realizar dependerá de si hay cachés, o no, de las
políticas de dichas cachés y de cuantos accesos simultáneos a este hay al mismo sistema de ficheros.
Descargar