Examen de Fundamentos de sistemas distribuidos Tiempo total: 2 horas Problema: Programa: Rendezvous con semáforos(5 puntos) Utilizando como único mecanismo de sincronización los semáforos descritos en semacquire(2), es decir: int semacquire(long *addr, int block); long semrelease(long *addr, long count); implemente una biblioteca rendez que ofrezca el mecanismo de sincronización rendezvous(2), pero intercambiando una cadena de texto. La interfaz, descrita en un fichero de cabeceras rendez.h, debe ser la siguiente: void initrendez(void); char *rendez(int tag, char* msg); La función initrendez sirve para inicializar la biblioteca y puede no hacer nada si no es necesario. Cuando un proceso llama a rendez con un tag, dicho proceso se debe bloquear hasta que otro proceso llame a rendez con el mismo tag. En ese momento, el parámetro msg que ha suministrado el primer proceso será el valor devuelto por rendez para el segundo proceso (y viceversa). Si un tercer proceso llama a rendez con el mismo tag, se quedará bloqueado de forma similar al primer proceso hasta que un cuarto proceso llame a rendez y así sucesivamente. Dicho de otro modo, rendez permite a dos procesos sincronizarse e intercambiar punteros a char de forma ligada a un número (tag) dado. El mensaje sólo se intercambiará copiando el valor del puntero, no su contenido. Impleméntese aparte un programa de prueba llamado main.c. -2- Solución _______ _rendez.h void initrendez(void); char *rendez(int tag, char* msg); _________ rendez9.c #include <u.h> #include <libc.h> #include "rendez.h" typedef struct Rz Rz; struct Rz { int tag; long wait; char* value; Rz *next; }; long mutex = 1; Rz *rz; void initrendez(void) { } Rz * taketag(int tag) { Rz *r, *p; p = nil; for(r = rz; r != nil; r = r->next) { if(r->tag == tag){ if(p == nil) rz = rz->next; else p->next = r->next; return r; } p = r; } return nil; } char * rendez(int tag, char *msg) { int i; Rz *r; char *omsg; -3- semacquire(&mutex, 1); r = taketag(tag); if(r != nil){ omsg = r->value; r->value = msg; semrelease(&r->wait, 1); semrelease(&mutex, 1); return omsg; } r = malloc(sizeof(Rz)); if(r == nil) sysfatal("no memory"); r->tag = tag; r->value = msg; r->wait = 0; r->next = rz; rz = r; semrelease(&mutex, 1); semacquire(&r->wait, 1); omsg = r->value; free(r); return omsg; } Un único mutex protege la estructura de datos, compuesta de un array rz (con una entrada por rendezvous en curso) y un índice nrz que indica el número de entradas en uso en el array. En realidad, el código admite que haya entradas nulas bajo &rz[nrz], cosa que ocurre cuando una entrada en uso ha dejado de estarlo. El array rz se hace crecer de Incr en Incr entradas cuando es preciso. Tras obtener el mutex, se escanea el array buscando una entrada con el tag suministrado. Si esa entrada existe, somos el segundo en llamar a rendez con ese valor y lo que hacemos es intercambiar los valores. En caso contrario inicializamos una entrada en el array y esperamos con un down(r->wait) a que otro proceso llame con el mismo valor. Nótese que el primer proceso (el que duerme) es el responsable de liberar los recursos. No obstante, el segundo proceso quita dichos recursos del array para evitar condiciones de carrera. -4- Problema: Cuestiones de teoría Responda en un folio de examen las siguientes cuestiones: A) Memoria virtual (2 puntos) Ponga un ejemplo de tablas de páginas de dos procesos que comparten memoria. Suponga páginas de 4K, el tipo de tabla de páginas que quiera y una organización de la memoria virtual similar a la de Plan 9, por ejemplo: Stack Text Data Bss R defff000 00001000 00012000 00017000 dffff000 00012000 00017000 0002f000 1 8 1 1 ¿Sucede algo similar cuando con la tabla de páginas cuando se llama a fork? ¿Por qué? Responda razonadamente. -5- Solución Cuando dos procesos comparten memoria lo que sucede es que hay entradas de la tabla de páginas de los diferentes procesos que apuntan a las misma direcciones físicas. Si vamos al caso más sencillo de tablas de páginas de un nivel, y los procesos sólo tienen cuatro páginas, una de pila, una de texto, una del BSS y una de datos el resto de entradas serán cero (no tendrán instalada traducción). Suponiendo que las páginas son de 4K, la tabla podría ser algo como (suponiendo que el bit 5 puesto a 1 hace que la página sea de escritura) y que el resto de los bits son cero: [00000] = 0 [00001] = 0x0003f000 [00002] = 0 [00011] = 0 [00012] [00013] [00017] [00018] [00019] [0001a] [0002f] [defff] [dffff] = = = = = = = = = 0x0005f010 0 0 0 0x0005f010 0 0 0 0x0002f010 El otro proceso tendrá las mismas entradas en todos los casos salvo que puede que no tenga alguna instalada o tenga alguna de más (se instalan en demanda) y que la pila nunca se comparte: [00000] = 0 [00001] = 0x0003f000 [00002] = 0 [00011] = 0 [00012] [00013] [00017] [00018] [00019] [0001a] [0002f] [defff] [dffff] = = = = = = = = = 0x0005f010 0 0 0x0006f010 0x0005f010 0 0 0 0x00025010 Cuando se llama a fork, hay varias razones por las que se puede compartir memoria y sucede lo mismo. El segmento de texto, se suele compartir siempre, ya que es de de sólo lectura. Como parte del proceso de hacer fork, el segmento datos y el BSS se comparten tras cambiarles la protección a todas las páginas a sólo lectura. Cuando haya un fallo de protección por escritura se copian, se instalan las nuevas traducciones y se desprotegen. Esto mecanismo se llama copy on write (COW). Aparte de todo esto, se puede compartir un segmento, utilizando ensegattach(2) Plan 9 o mmap(2) en Unix. B) Segmentación (1.5 puntos) En un asignador de memoria que estamos utilizando, hay un bug y la memoria que devuelve al usuario está en el segmento de texto. Sorprendentemente, el siguiente programa, funciona. Explique por qué y de dónde vienen la memoria de las diferentes variables. -6- #include <u.h> #include <libc.h> char datos[128]; void main(int, char *[]) { char *masdatos; int i; masdatos = malloc(16); for(i = 0; i < sizeof(datos); i++) datos[i] = ’z’; memmove(datos, masdatos, 16); for(i = 16; i < sizeof(datos); i++) print("%c", datos[i]); } -7- Solución La única memoria que viene del asignador de memoria es a la que apunta el puntero masdatos. Esta memoria sólo se lee (cuando se copia utilizando el memmove). Aunque debería venir del BSS (del heap, que está en el BSS), el enunciado dice que por un bug viene del segmento de texto. Como el segmento de texto es de sólo lectura, no hay problema con copiar memoria de él. El programa procede e imprime el valor de datos sin problema. La memoria datos es global sin inicializar y por tanto viene del BSS. Las variables masdatos (el puntero, no la memoria a la que apunta) y la variable i vienen ambas de la pila y por tanto del segmento de pila al ser variables locales. C) Sistemas de ficheros (1,5 puntos) En un sistema de ficheros con inodos sólo con punteros directos, se ha borrado completamenteun inodo de la tabla de inodos (si hay varias copias de la tabla de inodos, se ha borrado en todas) . Responda reazonadamente ¿Qué se ha perdido exactamente? Llevamos el disco duro a una empresa de recuperación de datos y nos dan un listado de los nombres de los ficheros y directorios y no falta ninguno. ¿Es esto posible? Responda razonadamente. -8- Solución Si se ha perdido un inodo, se ha perdido un fichero, directorio o valor de un enlace simbólico junto con sus metadatos (salvo el nombre). Un inodo contiene los metadatos (permisos, fechas de modificación, dueño etc.) y los punteros a los datos. Se ha perdido, pues (metadatos aparte), la asociación entre los bloques de datos (apuntados por el inodo) y el elemento, un fichero, directorio o enlace simbólico. Más concretamente, qué bloques pertenecen al elemento y en qué orden. Si es un directorio, hemos perdido las entradas de directorio que contienen los bloques de datos, es decir, nombres y números de inodo. En este caso hemos perdido los nombres de todo lo que contenía, es decir ese nodo del árbol (aunque el subárbol se puede recuperar mirando qué inodos se han quedado huérfanos, a menos que hubiese un enlace duro a ellos, en cuyo caso, sencillamente están ya presentes en otro punto del sistema de ficheros, pero no hay manera de recuperar la correspondencia). Es posible que los bloques sigan existiendo y se puedan quizás recuperar, aunque depende del contenido del fichero o directorio que esto se pueda interpretar, ya que hemos perdido en qué orden van y el tamaño del fichero o directorio. Si es un enlace simbólico, hemos perdido el nombre al que apuntaba. De nuevo, en ambos casos, es posible que se puedan recuperar los bloques de datos, que no se han borrado. Si el inodo que se ha borrado es el de un fichero no se ha perdido más que su contenido. El nombre está en la entrada de directorio del directorio que lo contiene, con lo cual, no se ha perdido. En este caso, la empresa puede estar diciendo la verdad. Si es el inodo correspondiente a un directorio, deberían haberse perdido nombres (los de los datos que contenía), aunque como hemos dicho antes, quizás se puedan recuperar mirando qué bloques están ocupados. No sabiendo el tamaño, es difícil saber qué nombres realmente se estában usando y cuales no. En este caso lo que dice la empresa es más difícil que sea posible.