Examen de septiembre de 2006

 

Ejercicio 1

Sobre la pereza y los sistemas operativos (si hay que ir se va, pero ir para nada es tontería). Se pretende estudiar diversas estrategias “perezosas” usadas en los sistemas operativos, analizando en qué situaciones pueden ser beneficiosas con respecto a las estrategias “no perezosas” y en cuáles no lo son o incluso pueden ser perjudiciales.

a)      [Puntuación: 1/10] Una técnica perezosa en la gestión de memoria es la paginación por demanda. Suponiendo que hay suficiente memoria física, explique en qué situaciones es beneficiosa, en cuanto a una ejecución más eficiente del proceso afectado, comparándola con el uso de la solución no perezosa, y en cuáles perjudicial y por qué motivo, en caso de que pueda serlo.

b)      [Puntuación: 1,5/10] ¿Qué otra técnica perezosa se usa en la gestión de memoria? Para esa técnica, responda a la misma pregunta que en el apartado anterior.

c)      [Puntuación: 1,5/10] Durante el arranque, Linux almacena en un determinado marco una página de sólo lectura llena de ceros, que se mantendrá así todo el tiempo, y que se usa para implementar una técnica perezosa en la gestión de páginas de una región anónima (es decir, sin fichero asociado). Explique cómo sería esta técnica e identifique en qué situaciones puede ser beneficiosa y en cuáles perjudicial, si es que puede serlo.

d)      Dado que muchos programas no usan números en coma flotante y la mayoría de los sistemas operativos tampoco, se puede plantear una técnica perezosa a la hora de guardar y restaurar el contenido de los registros de este tipo del procesador. A continuación se plantea una serie de preguntas sobre esta idea suponiendo que se usa un sistema operativo cuyo código no utiliza aritmética en coma flotante.

d1)  [Puntuación: 0,5/10] Explique razonadamente si sería necesario salvaguardar los registros de coma flotante al comenzar una llamada al sistema y restaurarlos al terminar ésta.

d2)  [Puntuación: 1,5/10] Explique razonadamente si puede ser necesario salvaguardar y restaurar los registros de coma flotante ante el cambio entre dos threads de distinto proceso. ¿Y si se trata de dos threads del mismo proceso? ¿Y en caso de que sean dos threads/procesos de núcleo?

d3)  [Puntuación: 1/10] ¿Qué valor inicial deben tener los registros de coma flotante para un nuevo proceso creado con fork? ¿Y cuál para un nuevo thread?

d4)  [Puntuación: 3/10] Para ayudar a gestionar esta política perezosa, el procesador Pentium incluye un mecanismo con las siguientes características: hay un campo denominado TS en un registro de control del procesador (cr0), accesible sólo en modo privilegiado, de manera que si TS vale 1 y se usa una instrucción que accede a los registros de coma flotante se produce una excepción denominada “Dispositivo no disponible”. En caso de que TS valga 0, no se produce ninguna excepción al acceder a dichos registros.

Plantee un mecanismo perezoso basado en este esquema que intente minimizar la necesidad de salvaguardar y restaurar los registros de coma flotante, especificando qué campos se añadirían al BCP del proceso, qué rutinas del sistema operativo habría que modificar y qué código habría que incluir en las mismas. Asimismo, analice cuándo el uso de este mecanismo puede ser beneficioso y cuándo perjudicial, en caso de que pueda serlo.

 

Solución

a) En un sistema sin memoria virtual, a la hora de activar la ejecución de un programa, hay que traer a memoria toda la imagen del proceso. Con la técnica de la paginación por demanda, esa operación de carga en memoria se puede diferir hasta que el proceso vaya accediendo a las distintas páginas de su mapa. Si nos fijamos solamente en el aspecto del rendimiento en la ejecución del programa, como plantea el enunciado, la técnica de memoria virtual es beneficiosa puesto que sólo se traen a memoria las páginas requeridas. Este beneficio es particularmente significativo cuando se accede a un número relativamente pequeño de páginas del mapa. El peor caso aparecerá si se acceden a todas las páginas del mapa. En ese caso, ambas soluciones requerirían cargar en memoria todas las páginas, pero en la paginación por demanda habría una sobrecarga adicional por las activaciones del sistema operativo causadas por los sucesivos fallos de página.

 

b) En el sistema de gestión de memoria se utilizan otras técnicas de carácter perezoso. En esta solución se ha elegido la técnica del copy-on-write (COW), que permite optimizar el duplicado de una región. Cuando un proceso requiere un duplicado de una región privada asociada normalmente a otro proceso (ya sea las regiones privadas del proceso padre en una llamada fork o las regiones privadas de un ejecutable, o de una biblioteca dinámica, cuando dos o más procesos lo comparten), en vez de realizar el duplicado, se comparte la región retardando la copia de cada página hasta que ésta se modifique por parte de alguno de los procesos. Esta técnica será tanto más ventajosa cuantas menos páginas de la región sean modificadas por los procesos involucrados. El peor caso correspondería con una situación en la que todos los procesos afectados modifican todas las páginas de la región. Nótese que podría parecer que en este caso el resultado en cuanto a eficiencia es igual que el que se obtiene con la solución no perezosa (es decir, la duplicación directa), ya que el número de copias es el mismo. Sin embargo, con el COW se producen activaciones del sistema operativo cada vez que un proceso modifica por primera vez una página de la región (con una región de p páginas que usan P procesos puede haber hasta P × p activaciones del sistemas operativo por fallos de tipo COW) con la sobrecarga correspondiente.

En el sistema de gestión de memoria se utilizan otras técnicas de carácter perezoso como el uso de esquemas sin preasignación de swap, aunque no se analizan en esta solución. Por otro lado, hay que resaltar que, a diferencia de lo que ocurre en otros niveles de la jerarquía de memoria, como en la memoria cache, el uso de la escritura diferida en la memoria virtual no se puede considerar como una técnica perezosa, puesto que es la única solución factible. El uso de escritura inmediata no es admisible en este nivel de la jerarquía por razones evidentes (un fallo de página y una escritura en disco por cada escritura en memoria es absolutamente inviable).

 

c) Con la técnica planteada en el enunciado, cuando hay un primer fallo de página que corresponde con una región de tipo anónima, en vez de buscar un marco libre y rellenarlo con ceros por motivos de seguridad, se hace que el proceso haga referencia a esta página llena de ceros y de sólo lectura. Aplicando la idea del COW, se espera a que se produzca una primera modificación para reservar un marco independiente y rellenarlo con ceros.

Como es fácil apreciar, esta técnica es efectiva cuando hay páginas en una región anónima que nunca se modifican. Por otro lado, el peor caso se produce cuando un proceso provoca primero un fallo por un acceso de lectura a la página, que causa que se haga referencia al marco común lleno de ceros, y un luego un fallo de COW por un acceso de escritura, que genera la reserva del marco libre y su rellenado con ceros. Con una solución no perezosa, aunque se habrían hecho las mismas operaciones sobre la memoria, sólo habría existido una activación del sistema operativo, que correspondería con el primer acceso de lectura.

 

d1) Dado que el código del sistema operativo no modifica los registros, no es necesario salvarlos al producirse una activación del mismo. Dado que sólo se modifican estos registros desde código de usuario, la salvaguarda se puede diferir hasta que se produzca un cambio de contexto, como se verá en el próximo apartado, pero no al entrar y salir de una llamada.

 

d2) Cada thread debe de mantener su propia copia de los registros y, por tanto, en caso de que los use, habrá que salvarlos y restaurarlos cada vez que esté involucrado en un cambio de contexto. Esto es aplicable tanto a los threads del mismo proceso como de distintos.

En cuanto a los procesos/threads de núcleo, al ejecutar sólo código del sistema operativo, no usan los registros de coma flotante. En consecuencia, no se requiere salvar ni restaurar estos registros cuando se produce un cambio de contexto entre este tipo de procesos.

 

d3) Cuando se produce la llamada fork, el proceso creado es un duplicado del proceso padre y, por ello, el contenido de sus registros debe de ser una copia de los del padre. Nótese que si se puede determinar que el padre no ha usado los registros, no sería necesario hacer esta copia puesto que el valor de estos registros no es significativo.

En cuanto a la creación de un thread, en este caso también el valor inicial será una copia de los registros de coma flotante asociados al thread que realiza la operación de creación, siempre que su valor sea significativo.

 

d4) A continuación, se plantea una posible estrategia similar a la usada en Linux. La estrategia consiste en controlar dos aspectos de cada proceso en cuanto a su uso de los registros de coma flotante. Por un lado, es necesario saber si el proceso ha usado estos registros durante su último turno de ejecución. Si es así, habrá que salvarlos antes de que los pueda usar otro proceso. Por otro lado, se requiere conocer si el proceso ha usado alguna vez los registros de coma flotante durante su ejecución. En caso afirmativo, cada vez que vuelva a usarlos habrá que restaurar su valor. Acto seguido, pasamos a describir la propuesta planteada.

 

 

 

                               Si (pactual->Utiliza_Flot)  

restaurar registros de coma flotante de BCP

TS=0; // para evitar más excepciones

pactual->Mod_Flot=TRUE;

pactual->Utiliza_Flot=TRUE;

 

Si (pprevio ->Mod_Flot) // pprevio es el proceso que va a dejar la CPU

salvar registros de coma flotante en BCP

TS=1; // para que el primer acceso cause una excepción

pprevio->Mod_Flot=FALSE;

resto del código del c. de contexto (guardar registros generales, etc.)

 

 

Este esquema será beneficioso para procesos que no usen los registros de coma flotante (nos ahorramos salvarlos y restaurarlos) o que los utilicen pocas veces (se minimizan salvaguardas y restauraciones). Sin embargo, puede ser perjudicial para un proceso que siempre que ejecuta usa los registros de coma flotante, ya que no nos ahorramos ninguna operación y tenemos la sobrecarga de una activación adicional del sistema operativo (correspondiente a la excepción) por cada turno de ejecución del proceso.

 

Por último, hay que resaltar que se podría plantear una solución más perezosa retardando la salvaguarda de los registros de un proceso hasta que realmente otro proceso los usara (es decir, la salvaguarda se realizaría en el tratamiento de la excepción en vez de en el cambio de contexto). Esta solución retarda la salvaguarda hasta el último momento, lo que minimiza el número de veces que se realiza esta operación. Sin embargo, complica un poco la gestión, puesto que hay que acordarse de qué proceso fue el último que los usó para salvarlos en su BCP (pero quizás ese proceso ya haya terminado por entonces…).