Calificación SISTEMAS OPERATIVOS Convocatoria extraordinaria, 13 de septiembre de 2006 Nombre SOLUCIONES 1 4 2 5 3 6 Titulación Dispone de tres horas y media para realizar el examen 1Responda brevemente a estas preguntas: (1,50 puntos) Definir breve y específicamente: a) procesamiento asimétrico; b) espera activa; c) consistencia (entre niveles de memoria); d) microkernel (micronúcleo); e) polling; f) bootstrap; g) independencia de dispositivo de E/S. a) Procesamiento asimétrico: Modelo de procesamiento con múltiples procesadores, en el que a cada procesador se le asigna una tarea específica, existiendo un procesador maestro que controla el sistema y es el responsable de asignar tareas al resto de procesadores. b) Espera activa: Espera en la que incurren los programas cuando necesitan conocer la ocurrencia de un evento en el sistema y lo hacen interrogando permanentemente al procesador, de esta forma y mientras no ocurra el evento, el programa no avanza y hace que el procesador esté ocupado permanentemente. c) Consistencia (entre niveles de memoria): En una estructura jerárquica de almacenamiento, los mismos datos pueden aparecer en diferentes niveles del sistema. En un ambiente multitarea, en donde la CPU se conmuta de un lado a otro entre los diversos procesos, se debe tener extremo cuidado para asegurar que, si varios procesos quieren acceder a un dato, cada uno de ellos obtenga el valor de dicho dato actualizado más recientemente. Podría ocurrir en un cierto dato A esté en varios distintos niveles de memoria (soporte magnético, memoria principal, caché, registro). La consistencia consiste en asegurar que si un dato ha sido modificado en uno de los niveles de memoria (por ejemplo, en un registro), el resto de los niveles de memoria en los que también está copiado el dato sea modificado también, de forma que los procesos puedan acceder al verdadero valor del dato modificado. d) Microkernel: Este método estructura al SO removiendo todos los componentes no esenciales del kernel, e implementándolos como programas del sistema y de nivel de usuario. El resultado es un kernel más pequeño. No hay mucho consenso en torno a cuáles servicios deben permanecer en el kernel y cuáles deben implementarse en el espacio de usuario. En general, sin embargo, los microkernels Nombre típicamente proporcionan una administración mínima de los procesos y de la memoria, además de un servicio de comunicaciones. e) Polling: En un sistema basado en interrupciones, cuando un dispositivo interrumpe a la CPU, ha de averiguarse cuál de los dispositivos del sistema ha realizado dicha interrupción. La técnica del Polling consiste en “preguntar” uno por uno a todos los dispositivos para averiguar la procedencia de dicha interrupción. f) BootStrap: Es un sencillo programa de arranque inicial del sistema. Además, sirve para inicializar todos los aspectos del sistema, desde los registros de la CPU y los controladores de dispositivos, hasta los contenidos de la memoria. Debe saber cómo cargar el sistema operativo y empezar a ejecutar dicho sistema; para lograrlo el programa debe localizar y cargar en la memoria el kernel del sistema operativo. g) Independencia del dispositivo de E/S: Objetivo que consiste en dar a los programas canales genéricos de E/S en los que éstos realizan sus operaciones de E/S mediante primitivas genéricas. De esta manera, los programadores están liberados de hacer referencias a dispositivos concretos, por lo que los programas no dependen de dispositivos concretos de E/S. (0,25 puntos) Dada la frase: «según el algoritmo del banquero, un sistema es inseguro cuando en él existe interbloqueo». ¿Es dicha afirmación cierta? Justificar detalladamente la respuesta. El algoritmo del banquero nos proporciona una forma de garantizar que existe lo que se llama un “estado seguro del sistema”, es decir, que existe al menos una forma de gestionar (asignar/desasignar) recursos del sistema de forma que puedan ejecutarse sin problema todas los procesos del sistema. El hecho de que no exista un “estado seguro” no implica que el sistema esté en interbloqueo. Simplemente que no se garantiza la ejecución de todos los procesos del sistema. (0,25 puntos) ¿Cuál es el propósito de las llamadas al sistema? ¿Y de los programas del sistema? ¿Qué diferencias hay entre ambos? Las llamadas al sistema son instrucciones especiales que utilizan los usuarios para solicitar al sistema operativo la utilización de un recurso del sistema. En cambio, los programas del sistema son programas que forman parte del sistema operativo pero que no forman parte del monitor residente, como por ejemplo los editores, intérpretes de comando, etc. En realidad, los programas del sistema utilizan llamadas al sistema para utilizar recursos. Nombre (0,50 puntos) Indicar detalladamente el proceso de carga del sistema operativo guiado por interrupciones en un sistema de cómputo, indicando cómo quedaría la memoria principal al final de la carga. Al encender el sistema informático, lo primero que se ejecuta es un programa llamado BIOS, ubicado en la placa base del sistema en memoria ROM. Este programa se encarga de detectar todos los dispositivos hardware que hay en el sistema y almacena sus características y parámetros en memoria principal, dando la posibilidad de inicializar o modificar los parámetros a través de un programa que posee denominado SETUP. Una vez finalizada esta primera inicialización se busca al programa cargador del sistema operativo, el Bootstrap, ubicado en el primer sector del dispositivo de arranque, normalmente floppy, disco duro o CDROM, y tras cargarlo en las direcciones más bajas de memoria, lo ejecuta. Este programa Bootstrap es el encargado de leer el sistema operativo desde el dispositivo donde se encuentra, ubicarlo en memoria principal y ejecutarlo. Una vez que se da paso al sistema operativo, una de las primeras funciones de este es instalar el vector de interrupciones que contiene todas las direcciones de las rutinas que atienden a las interrupciones. Después de cargar todos los manejadores de dispositivos, los servidores y de inicializar todo el sistema, el sistema operativo queda en espera hasta que alguna tarea del sistema o algún programa de usuario demande trabajo y le ceda la CPU. La memoria podíra quedar como sigue: Nombre 0000 Monitor residente Vector de interrupciones Interprete de JCL Cargador de programas Manejadores de dispositivos E/S Memorias intermedias buffers Rutinas de tratamiento de errores Memoria usuario de FFFF 2(1,5 puntos) Muchos sistemas operativos requieren que, antes de leer o escribir en un fichero, se invoque a una operación de «abrir fichero», que devuelve un identificador que luego puede ser utilizado para acceder al fichero. Cuando se termina de trabajar con el fichero, se invoca a una operación de «cerrar fichero», como en este ejemplo en UNIX: char texto[] = "hola", otroTexto[] = “adios”; int longitudTexto = 4, otraLongitud = 4; int fich = open ("mifichero.txt",O_RDONLY); write (fich, texto, longitudTexto); write (fich, otroTexto, otraLongitud); close (fich); Alguien afirma que las operaciones de abrir y cerrar son un artificio innecesario, ya que las operaciones de lectura y escritura podrían trabajar directamente con el nombre del fichero, como en este ejemplo: Nombre write ( "mifichero.txt", posicionEnElFichero, texto, longitudTexto ); posicionEnElFichero indica el lugar dentro del fichero donde se pretende guardar el texto. Con este mecanismo, no sería necesario ni abrir ni cerrar los ficheros con los que trabajamos. ¿Es cierta esta tesis de que abrir y cerrar son operaciones innecesarias? ¿Perderíamos algo si las operaciones de acceso a los archivos fueran siempre directas, como en el último ejemplo propuesto? Es perfectamente posible trabajar con ficheros sin recurrir a operaciones de abrir y cerrar. Pero esto no significa que estas dos operaciones no aporten nada. En el ejemplo propuesto sin «open», cada vez que quisiéramos hacer una escritura tendríamos que pasar el nombre del archivo. Eso significa que en cada escritura el sistema operativo debe localizar esa ruta en el disco, comprobar que el fichero existe, que el usuario tiene privilegios suficientes para realizar la operación, etc. Todas estas acciones se realizan una sola vez cuando se abre el fichero, pero si la operación de abrir no está disponible, hay que realizarlas continuamente, con un gran impacto en el rendimiento del sistema. Por tanto la operación de abrir ahorra trabajo al sistema operativo. Otra cuestión es que las operaciones de abrir y cerrar sirven para ofrecer acceso secuencial a los ficheros, tal y como se muestra en el primer ejemplo. Si no existen las operaciones de abrir y cerrar, los usuarios tendrán que implementar el acceso secuencial manualmente. 3(0,75 puntos) Te dan a elegir tres tamaños de página para un sistema operativo: 16 bytes, 800 bytes y 2048 bytes. El sistema trabaja con direcciones de 32 bits y el tamaño medio de un programa es de 4 megabytes. ¿Con qué tamaño de página te quedarías y por qué? El tamaño de 800 bytes hay que descartarlo de antemano, porque no es potencia de dos. Requeriría un hardware de traducción de direcciones mucho más complejo que un sistema de paginación convencional: para calcular el número de página, no basta con quedarse con los bits más significativos de la dirección, sino que hay que realizar una división. El tamaño de 16 bytes tampoco es recomendable, porque genera mucho espacio en tablas de páginas. Un programa de cuatro megabytes (un tamaño típico, según el enunciado) ocuparía 222 ÷ 24 = 218 = 256 Kpáginas, que necesitarían en torno a un megabyte de espacio (suponiendo cuatro bytes por cada entrada de la tabla de páginas). No parece sensato dedicar a estructuras de gestión una cantidad de memoria similar a la que necesita el programa. Además, para garantizar una tasa de aciertos alta en la TLB, ésta probablemente necesitaría bastantes entradas. Nombre Por su parte, si usamos páginas de 2048 bytes, un programa de cuatro megabytes requerirá 2048 páginas, 128 veces menos espacio que en el caso anterior. El único inconveniente de este tamaño de página es la mayor fragmentación interna, pero estamos hablando, en el peor de los casos, de 2047 bytes desperdiciados, que serían menos de un 0,05% de un programa de tamaño típico de 4 megas. Es decir, irrelevante. Así que la mejor opción es el tamaño de 2048 bytes. 4 (1,25 puntos) Se dispone de un sistema operativo con gestión de memoria virtual por demanda de páginas. El tamaño de la página es de 1KB y el sistema posee 32KB de memoria física disponible para programas de usuario. Supongamos que en un instante dado, un programa que ocupa 9 páginas se carga para su ejecución. Además, es el único proceso en ese momento en el sistema. El sistema le asigna cuatro marcos de memoria. Inicialmente se cargan las páginas 0, 4, 5 y 8 en los marcos 9,3, 8 y 5 respectivamente. a) Dibujar la tabla de páginas para esta situación inicial. ¿Cuál es la dirección física para la dirección virtual (2,50) dada en el formato (página, desplazamiento)? ¿Y para la dirección (5,20)? Explica claramente el proceso de traducción de direcciones. Tabla de páginas 0 1 2 3 4 5 6 7 8 Marco Físico 9 ­ ­ ­ 3 8 ­ ­ 5 Bit de validez v i i i v v i i v Dirección virtual (2,50): Página lógica 2 => De entrada lo primero que se comprobaría es si tenemos un registro en la TLB para dicha página lógica, pero dado que en el enunciado no se nos indica nada acerca de la TLB, ignoraremos este paso. Accedemos a la tabla de páginas (entrada de la página lógica 2) y vemos que el bit de validez indica que dicha página lógica no está cargada en memoria. Por tanto se generará una Nombre excepción de fallo de página que conllevará traer la página desde disco a memoria. A modo de ejemplo, supongamos que el SO decide ubicarla en el marco 5. A partir de ese momento el proceso de traducción continuaría de la siguiente forma: Dirección física del marco 5: 5 × 1024 = 5120 Desplazamiento: 50 Dirección física: 5120 + 50 = 5170 Dirección virtual (5,20): Página lógica 5 => La tabla de páginas indica que está cargada en memoria en el marco 8. Dirección física del marco 8: 8 × 1024 = 8192 Desplazamiento: 20 Dirección física: 8192 + 20 = 8212 b) Si la política de reemplazo de páginas es global y partiendo del estado inicial aquí descrito, calcular los fallos de página que se producen con los algoritmos LRU y segunda oportunidad para la siguiente cadena de referencias del proceso: 7 5 6 1 0 8 3 4 3 3 1 2 8 6 2 3 5 3 4 La situación de partida en relación a la ocupación de memoria es la siguiente: existe un solo proceso cargado en memoria que ocupa 4 marcos, quedando por tanto aún libres 28 marcos de memoria (32Kb de memoria principal con páginas de 1Kb dan 32 marcos de página, de los cuales inicialmente están ocupados 4). Según el enunciado, existe un solo proceso en memoria y la política de reemplazo es global, así que estarán disponibles para dicho proceso todos los 28 marcos libres. Por tanto, para la cadena de referencias dada, únicamente se generarán 5 fallos de página, uno por cada referencia a una nueva página (es conveniente recordar que ya están cargadas 4 páginas de las 9 a las que se hace referencia en dicha cadena). Y además podemos afirmar que esto será así con independencia de la política de sustitución, ya que la memoria libre no se llega a agotar. c) ¿Se te ocurre alguna explicación que justifique la decisión del sistema operativo de cargar inicialmente las páginas 0, 4, 5 y 8, y no por ejemplo las páginas 0, 1, 2 y 3? Una razón podría ser para intentar cargar páginas que el cargador pueda sospechar que se van a necesitar inicialmente al comienzo de la ejecución del proceso y de esta forma generar el menor número de fallos de página al inicio. El cargador, conociendo la estructura del fichero ejecutable podría intentar precargar por ejemplo Nombre las primeras páginas de código y datos. El cargador también puede haberse basado en anteriores ejecuciones del mismo programa. 5 (2 puntos) «Parque Jurásico» consiste en un museo y un parque para hacer rutas safari. Hay m pasajeros y n coches monoplazas. Los pasajeros deambulan por el museo durante un rato y luego hacen cola para dar un paseo en coche por el safari. Cuando hay un coche disponible se monta en él un pasajero y el coche realiza un recorrido programado hasta llegar nuevamente al punto de recogida donde el pasajero debe bajar. Si los n vehículos están todos de paseo por el parque con un pasajero a bordo, entonces el pasajero que quiere un coche se espera; si un coche está listo para cargar pero no hay pasajeros esperando, entonces el coche se espera. Use semáforos para sincronizar los m procesos pasajero con los n procesos coche. Primero mostraremos una solución simple que sólo funciona bajo ciertas restricciones. (nota: para superar esta pregunta, basta con aproximarse a esta primera versión). Variables globales Semaphore coche_libre(0); Semaphore pasajero_a_bordo(0); Semaphore final_de_recorrido(0); Proceso pasajero void Pasajero() { ... DEAMBULA POR EL MUSEO ... // Espera por un coche libre P (coche_libre); // Se sube al coche V (pasajero_a_bordo); // disfruta del paseo // El coche le notifica que se acabó el paseo P (final_de_recorrido); } Proceso coche void Coche() { // hace el recorrido una y otra vez Nombre loop { // Indica que está libre V (coche_libre); // Espera por un pasajero P (pasajero_a_bordo); ... HACE EL RECORRIDO ... // Termina el paseo y se lo indica al pasajero V (final_de_recorrido); } } Como decíamos, esta solución sólo funciona correctamente si los semáforos son FIFO y los coches terminan el recorrido en el mismo orden en el que lo comenzaron. Si un coche A arranca antes que B, pero B finaliza antes el recorrido, la V (final_de_recorrido) puede ser recibida por el pasajero que está montado en el coche A, lo cual es erróneo. Si se quiere evitar estos problemas, hay que elaborar una solución más compleja. A continuación se muestra otro algoritmo que funciona correctamente incluso si los coches no siguen un orden perfecto, y que es independiente de la implementación de los semáforos. Este algoritmo se basa en que los pasajeros y los coches tienen identificadores únicos. Cada pasajero sabe cuál es el identificador de su coche, y viceversa. Dos vectores de semáforos se emplean para el diálogo pasajero­coche al iniciar y terminar el recorrido. Variables globales // semáforo para indicar a los pasajeros que hay algún coche libre Semáforo coche_libre = 0; // identificador del primer coche libre de la fila int primer_coche; // Cerrojo para acceder de forma controlada a "primer_coche" Semaphore cerrojo_primer_coche = 1; // pasajero[i] indica el pasajero que está montado // sobre el coche "i" int pasajero[N]; // semáforos para señalizar a los coches // que pueden empezar el recorrido Nombre Semaphore pasajero_montado[N] = {todos a cero}; // semáforos para señalizar a los pasajeros // que su recorrido terminó Semaphore fin_de_trayecto[M] = {todos a cero}; Proceso pasajero void pasajero (int ID) { // identificador del coche que le toca a este pasajero int mi_coche; ... DEAMBULA POR EL MUSEO ... // espera por un coche libre P (coche_libre); // recoge el identificador del primer coche libre mi_coche = primer_coche; // permite al siguiente coche indicar que está libre V (cerrojo_primer_coche); // informa al coche de que ya se ha montado pasajero[mi_coche] = ID; V (pasajero_montado[mi_coche]); // disfruta del paseo // espera a que el coche finalice el recorrido P (fin_del_trayecto[ID]); } Proceso coche void coche (int ID) { int mi_pasajero; // hace el recorrido una y otra vez loop { // se registra como el primer coche libre de la fila P (cerrojo_primer_coche); primer_coche = ID; // avisa a los pasajeros Nombre V (coche_libre); // espera a que un pasajero se monte y le avise P (pasajero_montado[ID]); mi_pasajero = pasajero[ID]; ... HACE EL RECORRIDO ... // señaliza al pasajero que el recorrido terminó V (fin_del_trayecto[mi_pasajero]); } } 6(2 puntos) La política de «rifa» es una técnica aleatoria de planificación de procesos. Cada proceso dispone de una cantidad de «boletos» numerados. Cuando un proceso entra en CPU, se le otorga una rodaja de tiempo. Cuando expira esta rodaja, o cuando el proceso abandona voluntariamente la CPU, el planificador extrae uno de los números de un «bombo» virtual y escoge para la CPU al proceso que tiene ese número. Cada proceso tiene al menos un boleto y el sorteo no puede quedar desierto. Por ejemplo: el proceso A tiene los números 1 y 2. El proceso B tiene el número 3. El planificador escoge un número al azar entre 1 y 3. Si sale el 1 y el 2, entra el proceso A. Si sale el 3, entra el B. a) ¿Qué efecto tiene darle más o menos boletos a un proceso? Cuantos más boletos tiene un proceso, más probabilidad tiene de ser escogido por el planificador y por tanto gozará, en promedio, de más tiempo de CPU. b) ¿Cuál es el comportamiento del sistema si todos los procesos tienen exactamente un boleto? Si todos los procesos tienen igual cantidad de boletos, la planificación es una especie de Round Robin en el que cada vez que hay un cambio de contexto, entra un proceso al azar. c) ¿Esta técnica puede provocar inanición? La técnica puede en teoría provocar inanición, ya que no hay una garantía absoluta de que un proceso sea escogido para entrar en CPU (en teoría, sus boletos pueden no salir nunca elegidos). No obstante, si el sorteo sigue una distribución aleatoria uniforme, la probabilidad de que cualquier proceso acabe entrando en la CPU tiende al 100%. d) ¿Se puede construir un algoritmo no aleatorio que produzca los mismos efectos que el aquí planteado? Nombre Algo parecido a este método se podría conseguir haciendo una cola de preparados en la que cada proceso apareciera tantas veces como boletos tiene en este algoritmo. También haciendo que cuando un proceso termine su rodaja de tiempo, en lugar de ingresar en el final de la cola de preparados, se insertara en un punto intermedio, más cercano al principio de la cola cuantos más boletos tuviera el proceso.