Nombre Titulación SISTEMAS OPERATIVOS 1

Anuncio
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.
Descargar