Memoria Virtual en sistemas operativos

Introducción

Anteriormente en la parte de planificación, dijimos que un proceso tenía que estar entero para que se pudiera ejecutar, o estaba todo entero o estaba swapeado a RAM, era una o la otra.

El problema que trae esto, es que limitamos el tamaño máximo del proceso, límite que pone la RAM, es decir, que si hay un proceso enorme cuyo tamaño la RAM no se banca, no lo puede ejecutar.

Por otro lado, limitamos un poco también el tamaño de multiprogramación. Si tenemos un proceso de 4GB que para ejecutarse necesita de 1GB, estamos ocupando 3GB de memoria que tranquilamente podrían ser usados para poder cargar otros procesos.

Ante este problema, surgieron soluciones.

Primeras soluciones

Las primeras soluciones vinieron del lado del programador, debido a que hace tiempo, antes de que el S.O soporte este problema, no había un soporte para solucionar esto.

Los overlays son parte del código del proceso que no necesariamente hacen falta que se ejecuten para que el proceso se pueda ejecutar, para esto último solo necesitamos la sección principal.

Para bajarlo a tierra podemos pensar como el overlay 1 forma parte de la inicialización del proceso y el overlay 4 se basa en manejo de errores (que si en la ejecución del proceso no llegara a fallar nada, este overlay nunca sería enviado a RAM para ejecutarse).

La idea es ir cargando y descargando en RAM las partes del código del proceso que tengan sentido.

Solución actual

La verdad es que la solución actual es otra. Actualmente toda esa parte que antes se hacía a mano, en donde se le mentía al S.O haciéndole creer que un proceso se podía ejecutar sin estar entero en memoria, ahora nos lo soluciona automáticamente el sistema operativo, y entra en juego la memoria virtual.

El sistema operativo lo que va a hacer es mentirle al proceso, le va a decir que puede referenciar todas las direcciones que quiera, aun no teniendo todas las direcciones cargadas en memoria principal.

¿Y qué pasa si una dirección a la que quiere referenciar no está en memoria? Bueno, básicamente lo que va a hacer el S.O es decirle al proceso que espere (bloquearlo), va a traer esa dirección a memoria, y luego le va a decir al proceso que está todo ok.

Que una dirección tenga que estar en memoria para ejecutar se debe a que toda instrucción debe estar en memoria para poder ejecutarse, por cómo funciona la CPU.Memoria Virtual

Viendo la foto de arriba, podemos decir que los frames que se pueden ejecutar ya, son el frame 0 y el frame 3, que contienen al proceso A y al proceso B, todas las demás instrucciones que quieran ejecutar y no estén en memoria van a tener que ser traídas del disco.

Paginación bajo demanda

Siempre que se habla de memoria virtual, generalmente se va a hablar de paginación.

Las páginas en memoria virtual generalmente van a ser de 4KB, básicamente porque es el tamaño justo para no tener que estar trayendo y llevando mucha información.

El tiempo de carga de los procesos disminuye ya que lo único que tenemos que traer de dichos procesos para que puedan ejecutar es el PCB.

Hasta ahora bien, pero ¿qué pasa si quiero referenciar a una página que no está en memoria principal?, es decir, que su bit de presencia es igual a 0. Bueno, entra en juego lo que se llama “Page Fault” ( PF).

Atención Page Fault (PF)

Proceso de traducción de dirección lógica a dirección física

  1. En primer lugar, la CPU va a ejecutar una instrucción (que está en memoria) que referencie a una página.
  2. Para ejecutar esta instrucción, el S.O va a buscar esa página en la TLB: si la TLB la tiene, se produce un hit y se pasa a tener la dirección física para a posteriori poder buscar el dato que queremos, es el mejor escenario; en cambio, si no la tiene, se va a tener que ir a buscarla a la RAM, especificando más, a la tabla de páginas correspondiente al proceso que contiene a dicha página.
  3. Una vez encontrada en la tabla de páginas, si la presencia de dicha página está en 1 (es decir, que está en memoria principal) entonces ya tengo la dirección física, por lo cual lo único que hago es agregar la entrada a la TLB, ya que probablemente la vuelva a referenciar a la brevedad. Si la presencia está en 0, se lanza una interrupción de Page Fault, la cual va a atender el S.O, como vimos en las primeras unidades.
  4. Si cuando atendemos la interrupción estamos fuera del espacio de direcciones, se termina el proceso o devuelve error. Por otro lado, si la página es válida, es decir, está en SWAP (disco), necesito asegurarme de que hay lugar para la página, entonces busco un frame libre.
  5. Si hay un frame libre, se dispara la operación de lectura (asumimos que estamos usando DMA) y se marca el frame como ocupado (en el bitmap aviso que ese frame ya está ocupado). Una vez que tengo la información cargada se marca esa página como presente (bit de presencia = 1) y se agrega a la TLB para que me quede actualizada. Si no hay un frame libre, se elige un frame víctima, es decir, vamos a aplicar un algoritmo + una política de sustitución de páginas, que nos van a decir cuál es una buena víctima para sacarle el frame y con cuál criterio lo voy a sacar, cuál es el más apropiado para sacar. Esta víctima va a ser la que probablemente menos se vaya a referenciar a posteriori.
  6. Si el frame de víctima que elijo no tiene nada modificado (M = 0), el S.O va a marcar el frame como libre y le va a avisar a la página del proceso que se estaba ejecutando que su presencia va a pasar a ser 0, debido a la quita del frame. Por otro lado, si el frame de víctima que elijo tiene algo que estaba modificado (M = 1), es decir que fue modificado en runtime, lo cual es muy raro porque por lo general no se va a modificar, se va a tener que disparar una operación de escritura (lo cual lleva tiempo) para a posteriori hacer lo mismo que hicimos cuando el bit de modificado era igual a 0 (M = 0).

Cabe recalcar de este último ítem, que siempre se va a querer optar por el camino del frame no modificado, el camino más corto, ya que disparar una operación de escritura consume más tiempo. El algoritmo que usemos siempre va a tratar de elegir un frame que no haya sido modificado.

Asignación / Sustitución de frames

Local Fija:

Las víctimas a elegir van a estar dentro del conjunto de número fijo de frames de un proceso

Local Dinámica:

Las víctimas a elegir van a estar dentro del conjunto de número dinámico de frames de un proceso

Global Dinámica:

Cualquier frame de cualquier proceso puede ser elegido como víctima

Global Fija:

Esta combinación no tiene sentido, ya que si yo le robo un frame a un proceso hago que este decrezca y que el “ladrón” crezca, entonces ya no sería fija, es un absurdo.

Thrashing

El problema del ejemplo A es que para ejecutarse necesita de 3 frames y solo se le asigna 2, nunca va a poder ejecutarse la instrucción, ya que como la sustitución es local fija siempre se va a estar reemplazando así mismo los frames.

Las consecuencias del ejemplo B son peores que las del ejemplo A.

Situaciones que se pueden presentar en memoria virtual

Acordémonos primero cuando vimos en el ítem 6 del proceso de las traducciones lógicas a físicas, que en una parte puede que el frame víctima que se eligió esté modificado.

Teniendo en cuenta esto, comenzamos diciendo que hay algo llamado “pool de marcos”,

¿qué es esto? Es una cantidad de n marcos que siempre están libres.

Siguiendo esta línea, supongamos que tenemos dos procesos, el proceso A y el proceso B. En un momento, el proceso A necesita un frame, entonces lo saca del proceso B. Una vez que elige ese frame, vemos que está modificado, entonces el proceso B no se lo puede dar ya, ya que primero tiene que swapear a disco para guardar la información que contiene y luego dárselo.

Ante esto, lo que va a hacer el S.O es decirle al proceso A que ese frame está listo y se lo va a dar. ¿Pero pará, no me dijiste que primero el proceso B tiene que guardar primero la información en disco? Claro que sí, lo que pasa es que ese frame que el S.O le da al proceso A, es uno que forma parte de este pool de marcos. Lo que hace el S.O es agarrar un frame del pool de marcos que no va a estar modificado y se lo va a dar al proceso que lo necesite, entonces se va a ahorrar el tener que esperar a que se ejecute esa interrupción de operación de escritura para a posteriori darle el frame a A, se lo va a dar ya. Una vez que esa interrupción termine y obtengamos el frame ya no modificado, este mismo va a entrar al pool de marcos para que pueda ser usado por otro proceso que lo pueda llegar a necesitar.

La idea de que el S.O haga esto es reducir el overhead que se produce al tener que esperar que B vaya a guardar su información para luego liberarle el frame a A.El problema que puede suceder acá es el siguiente: el proceso A tiene una página cargada en el frame 11 y escribe sobre ella, inmediatamente después el proceso G pide un frame, y el

S.O desaloja el frame 11 que lo tenía A, entonces, lo que puede pasar es que ese pedido llegue a ejecutarse cuando el frame 11 ya fue desalojado del proceso A y agregado al proceso G, por tener A una sustitución global de frames, y que el frame 11 que ahora corresponde a G se pise con lo que A quería poner.