Examen de Fundamentos de sistemas operativos Tiempo total: 2 horas. Problema: Implementación de canales con Rendez (monitores) Implemente canales con buffering para enteros mediante variables condición Rendez. Para ello, debe crear un tipo de datos Canal. La interfaz de los canales debe ser la siguiente: Canal * ccreate(int size); void csend(Canal *c, int i); int creceive(Canal *c); void cfree(Canal *c); -2- Solución _________ _channels.c #include <u.h> #include <libc.h> typedef struct Canal Canal; struct Canal { QLock; int exiting; int nproc; int sz; int hd,tl; int *q; Rendez full; Rendez empty; }; Canal* ccreate(int sz) { Canal *c; c = mallocz(sizeof(*c), 1); if(c == nil) return nil; /* needs +1 to signal that queue is full */ c->q = malloc(sizeof(int) * (sz+1)); if(c->q == nil){ free(c); return nil; } c->full.l = c; c->empty.l = c; return c; } int next(int i, int n) { return (i+1)%n; } -3- void csend(Canal *c, int i) { qlock(c); c->nproc++; while(next(c->hd, c->sz) == c->tl && !c->exiting) rsleep(&c->full); if(c->exiting == 0){ c->q[c->hd] = i; c->hd = next(c->hd, c->sz); rwakeup(&c->empty); } c->nproc--; qunlock(c); } int creceive(Canal *c) { int r; r = -1; qlock(c); c->nproc++; while(c->hd == c->tl && !c->exiting) rsleep(&c->empty); if(!c->exiting){ r = c->q[c->tl]; c->tl = next(c->tl, c->sz); rwakeup(&c->full); } c->nproc--; qunlock(c); return r; } void cfree(Canal *c) { qlock(c); c->exiting = 1; while(c->nproc > 0){ rwakeup(&c->full); rwakeup(&c->empty); qunlock(c); sleep(0); qlock(c); } qunlock(c); free(c->q); free(c); } El canal se crea como un buffer de enteros que se mantiene en q protegido por un QLock y ayudado de dos variables-condición Rendez descritas en lock(2) para dormir a los procesos que esperan debido a que el buffer está lleno o vacío. En realidad, el problema es un típico productor-consumidor con el buffer acotado. La única diferencia reseñable es que tanto csend como creceive prestan atención a que el canal se esté -4- destruyendo cuando aún hay procesos utilizándolo. En tal caso, el campo exiting señala tal suceso y la función cfree espera (con espera activa) a que ningún proceso siga utilizando el canal. Entrega: Copie un fichero llamado login.c, donde login es tu nombre de usuario, en el directorio /usr/elf/examen. ATENCIÓN: Sólo se puede efectuar la entrega una vez. Ponga un comentario con su nombre y apellidos en el fichero. Problema: Cuestiones de teoría Responda en un folio de examen las siguientes cuestiones: A) (2 puntos) Estamos depurando un programa y sabemos que el resultado de ejecutar el siguiente comando sobre el directorio de /proc correspondiente a un proceso ejecutando el 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 1 8 1 1 Una parte del resultado de ejecutar nm sobre el binario es: 1d3e 8eac 2d68 82ee 32ae 32e2 d018 d1ac e08c e104 T T T T t t D d d d strlen strncpy strrchr xdata swapb swapi swidth tab tab1 tab2 Responda a las siguientes preguntas justificando las respuestas: ¿Qué cree que sucedería si se ejecutase el siguiente código en una función del programa anterior? int a; char *p; p = (char *)0x11d3c; p[2] = ’c’; print("%p\n", &a); Explique detalladamente y paso a paso la intervención del sistema operativo en caso de haberla. ¿Las direcciones mostradas en nm son virtuales? ¿Y las que imprime el trozo de código anterior? ¿Entre qué valores estaría la dirección mostrada en el código anterior? -5- Solución Tanto las direcciones mostradas por el programa como las mostradas por nm son virtuales, que son las únicas a las que tiene acceso el proceso. Todo el proceso de compilación supone que las direcciones son virtuales, y por eso el enlazador tiene libertad para elegir en qué posiciones (dentro del modelo de segmentos que ofrece el sistema operativo) pone los símbolos. La dirección impresa por el programa es la de una variable local que vendrá de la pila, luego estará en el rango 0xdefff000 y 0xdffff000. B) (1.5 puntos) Se le ha pedido que escriba una aplicación que recupere todos los ficheros de una partición formateada con un sistema de ficheros basado en inodos en el que se han perdido todos los bloques de datos apuntados por los inodos que representan directorios (los ha borrado un troyano). Explique detalladamente qué podría hacer su aplicación, qué datos se podrían recuperar, y cuáles no y por qué. -6- Solución Al haber perdido los bloques de datos de los directorios se han perdido las entradas de directorios. En ellas se encuentran los nombres de los ficheros y directorios con el número de inodo al que apunta la entrada de directorio. Esto es lo único que se ha perdido. Como consecuencia, de forma implícita, se ha perdido, la jerarquía de directorios y ficheros. Nuestra aplicación puede recorrerse la tabla de inodos para ver qué inodos están ocupados. De esta forma puede recuperar completamente todos los ficheros con sus metadatos (contenidos en el inodo) y sus datos, (el inodo apunta a los bloques de datos). No puede recuperar, sin embargo, sus nombres ni su posición en la jerarquía. De los directorios sólo se pueden recuperar los metadatos, pero sin saber a qué nombre de directorio pertenecen, ni su contenido, (que estaba en los bloques de datos) no serán demasiado útiles. C) (1,5 puntos) Tiene un programa que reserva y libera continuamente estos dos tipos de datos: stuct Buf { char buf[2]; }; stuct Trail { char trail; }; Mide el uso de memoria y descubre que el programa utiliza 30 veces más memoria de lo que esperaría (malloc devuelve un error diciendo de lo que hay memoria mucho antes de lo esperado). Explique qué está sucediendo, cómo se llama el problema y cómo lo resolvería. Escriba el login en el examen. -7- Solución Dado que ambos tipos de datos son muy pequeños, seguramente haya un problema de fragmentación interna, seguramente ambos tipos de datos son de un tamaño menor que el tamaño mínimo que reserva el asignador. Una posible solución sería bajar el tamaño mínimo que reserva el asignador, pero esto tendría el problema de generar fragmentación externa a su vez y cambiar el alineamiento de la memoria. La mejor manera de resolver el problema (suponiendo que realmente necesito reservar trozos tan pequeños) sería hacerme un asignador especializado que se ajuste más a las necesidades del programa. Este asignador puede reservar trozos más grandes mediante malloc y repartir el espacio en el interior de forma acorde. Para el asignador especializado, una solución sencilla puede ser crear dos estructuras de datos, una que contenga un array de tipo Buf y otra que contenga un array de tipo Trail, que puedo enlazar en varias listas (llenas, con hueco, vacías). Dentro de esta estructura de datos tendré que anotar también (por ejemplo en un bitmap), qué elementos están ocupados.