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.