GESTION DE MEMORIA Hay dos partes principales en cualquier sistema de gestión de memoria: 1_Protección: para evitar que una tarea acceda a la memoria o datos de otra tarea o del sistema operativo. Y para evitar acceso indebido al hardware. 2_ Traducción de direcciones: para dar flexibilidad al sistema operativo a la hora de asignar memoria a las tareas y de paso "puede" y de echo se utiliza como un mecanismo clave de protección (según veremos mas adelante). TRADUCCION DE DIRECCIONES La memoria "física" de un ordenador es una secuencia de bytes, que empieza en el 0 y termina en....por ejemplo 64 megas. Es decir un "array" de bytes. Cada byte tiene una dirección: el numero que ocupa posicionalmente en la memoria. Esta dirección es conocida como "dirección física". (quiero "olvidarme, que además en los procesadores 386 y superiores de Intel, existe la llamada "segmentación", pero en principio vamos a obviar esto...... a propósito). Las direcciones que va a utilizar un programa (en modo protegido), no son esas. Siempre son traducidas, o mapeadas, en direcciones de memoria física por un mecanismo de "traducción de direcciones". Este mecanismo, nos introduce en el concepto de "dirección virtual". Se llama de esta manera porque no corresponde directamente a posiciones de memoria "física", sino que a través de una "función de mapeado" equivale a una dirección de memoria física. Es decir este mecanismo, al "apuntar" a una dirección de memoria, lo que hace es buscar, por ejemplo en unas tablas internas, la correspondencia entre esta dirección y la dirección real "física" del dato. La traducción de dirección "virtual" a "física" también proporciona protección de memoria, ya que podría disponerse por ejemplo, que ciertas direcciones físicas de memoria no se mapeen desde ninguna dirección virtual. Además de esta protección, en la función de traducción de direcciones, se pueden identificar ciertas direcciones virtuales como "no validas". Esto amplía el mecanismo de protección. Para no tener que generar una dirección física cuando se presenta un dirección virtual no valida, el mecanismo de traducción de direcciones informa de una "excepción", de forma que el software del sistema operativo, puede tomar la acción que considere oportuna. MEMORIA VIRTUAL La memoria virtual es una técnica para proporcionar la ilusión de un espacio de memoria mucho mayor que la memoria física de una maquina. Esta "ilusión" permite que los programas se hagan sin tener en cuenta el tamaño exacto de la memoria física. La ilusión de la memoria virtual está soportada por el mecanismo de traducción de memoria, junto con una gran cantidad de almacenamiento rápido en disco duro. En cualquier momento el espacio de direcciones virtuales, está mapeado de tal forma que una pequeña parte de él, está en memoria real y el resto almacenado en el disco. Debido a que solo la parte de memoria virtual que está almacenada en la memoria principal, es accesible a la CPU, según un programa va ejecutándose, la vecindad de referencias a memoria cambia, necesitando que algunas partes de la memoria virtual se traigan a la memoria principal desde el disco, mientras que otras ya ejecutadas, se pueden volver a depositar en el disco (archivos de paginación).Por ejemplo, un programa de aplicación grande, podría mostrar un menú con muchas funciones. Al seleccionar una función del menú, se ejecutarían varias rutinas especificas de dicha función, pero no se reverenciarían las rutinas de las funciones restantes. En un sistema de memoria virtual, la ejecución de la función seleccionada del menú, se soportaría trayendo el código y los datos para esa función a la memoria principal (si no estuviese ya allí). El resto podría o no sacarse del disco a memoria real. Mientras la memoria física fuese lo bastante grande para contener el código y datos de cualquier función elemental de nuestro menú, el tamaño total de la memoria física, podría ser mucho menor que el tamaño total de nuestro programa. El mecanismo de paginación, también recoge estadísticas de utilización de las partes de memoria virtual que están residentes en memoria real o física. Estas estadísticas ayudan al sistema operativo a decidir que se puede devolver a disco cuando el espacio de memoria principal está muy "achuchado". PROTECCIÓN Hay dos clases de protección usadas en el 386 y superiores (y en general en cualquier CPU, incluso de los grandes sistemas). Habilidad de separar completamente las tareas, al poder dar a cada tarea un espacio de direcciones virtuales diferentes. Esto se hace dando un mapa de traducción de direcciones virtuales diferentes a cada tarea. Los otros mecanismos funcionan dentro de una tarea: para proteger al propio sistema operativo de la intrusión por parte de esa tarea, y al uso de registros especiales del procesador e instrucciones reservadas únicamente al sistema operativo. PROTECCION ENTRE TAREAS Tal y como comentamos, se consigue poniendo cada tarea en un espacio de direcciones virtuales diferentes, al asignar un mapa de traducción virtual a física diferente. Ya que una tarea no puede generar una dirección virtual que se mapee a otra parte de la memoria física usada por otra tarea, las tareas están aisladas unas de otras. El sistema operativo se podría almacenar como una tarea independiente y aislada del resto. Sin embargo el mecanismo de protección del sistema operativo, es diferente y lo veremos mas adelante. Ese mecanismo, permite al sistema operativo, el ser compartido por todas las tareas y accederse dentro de cada una de ellas, mientras que por otra parte lo protege de las aplicaciones. El sistema operativo es compartido por todas las tareas, disponiendo que una parte del mapa de dirección virtual a física sea la misma para todas las tareas y almacenado el sistema operativo en esa parte común del espacio de direcciones virtuales. Esta parte del espacio de direcciones virtuales, que es común a todas las tareas, se llama "espacio de direcciones GLOBAL". La parte de espacio de direcciones virtuales que es única para una tarea simple, se llama "espacio de direcciones LOCAL". Una consecuencia inmediata de tener un espacio de direcciones local diferente para cada tarea, es que la referencia a la "misma" dirección virtual en cada tarea, corresponde a diferentes direcciones "físicas". De esta manera se consigue el aislamiento de tareas. Una referencia, en cambio, en el espacio de direcciones global (donde está el SO), en cualquiera de las tareas se traducirá en la "misma" dirección física para todas las tareas. PROTECCION DENTRO DE UNA TAREA Dentro de una tarea se definen 4 niveles de privilegio de ejecución, para proteger el acceso a parte de la tarea de acuerdo con la sensibilidad de los datos. Los niveles son numerados de 0 a 3, con un cero, el mas privilegiado, y con un 3 el menos privilegiado. El nivel 0 se conoce como nivel de privilegio interno y el 3 como nivel de privilegio externo. Cada segmento de memoria, esta asignado a un nivel de privilegio. Este nivel de privilegio limita el acceso al segmento a los programas con privilegio suficiente. Siempre que un programa intenta acceder a otro segmento (evidentemente de su espacio virtual de direcciones), se comprueba el nivel de privilegio del segmento en el cual se accede, con el nivel de privilegio del segmento al cual queremos acceder. A un programa se le permite acceder a un segmento de datos con un nivel de privilegio igual o menor al segmento en ejecución. Un intento de ir a un nivel superior, es ilegal y provoca una "excepción" para informar del intento de violación al sistema operativo. El uso típico de los niveles de privilegio, es poner el núcleo del sistema operativo en el nivel 0, resto del sistema operativo al nivel 1, y las aplicaciones al nivel 3. Esto deja el nivel 2 para los niveles de software intermedio. Dada esta asignación, el sistema operativo (nivel 0) tiene derecho de pernada sobre el resto de segmentos. El resto del sistema operativo (nivel 1) puede acceder a todos los segmentos excepto el 0. La aplicación de nivel 3, solo puede acceder a sus propios segmentos. Esta asignación, protege al núcleo del sistema operativo de cualquier intruso. SEGMENTACIÓN La segmentación organiza la memoria virtual como una colección de unidades de tamaño variable llamadas segmentos. Los segmentos forman la base del mecanismo de traducción de direcciones virtuales a la dirección lineal (o física). Cada segmento queda definido por tres parámetros: 1) Dirección de base: dirección de comienzo del segmento en el espacio de direcciones lineal. 2) Limite del segmento: el mayor desplazamiento que se puede usar con ese segmento de dirección virtual. 3) Los "atributos" del segmento, que indican por ejemplo, si el segmento se puede leer, escribir o es únicamente de ejecución de programa, y una cosa muy importante: el nivel de privilegio (recordar 0= máximo privilegio, 3 mínimo privilegio). TABLAS DE DESCRIPTORES DE SEGMENTOS Un sistema operativo, guarda una tabla de descriptores global (GDT) y la tabla de descriptores local (LDT) correspondiente a una determinada tarea. Las tablas de descriptores, "también" se almacenan en segmentos. En este caso unos especiales mantenidos por el sistema operativo y referenciados por el HARDWARE directamente. Esto es lo que lo hace importante. Lo maneja ya el hard. Está claro, que estos segmentos "deberían" guardarse en memoria superprotegida, accesible únicamente por el propio sistema operativo. El espacio de direcciones virtual está mapeada en dos secciones iguales: una para la GDT y otra para la LDT. Cuando entra en funcionamiento la MULTITAREA real, la LDT se cambia por la LDT de la nueva tarea, pero la GDT permanece inalterable. Esto es por lo que la mitad del espacio de direcciones virtuales, pertenece al sistema operativo. Esto es lo que se denomina "conmutación de tarea".La LDT, además, contiene descriptores de segmento para los segmentos privados de una tarea. Pero..... varia tareas podrían compartir una LDT común. En este caso el mismo conjunto de segmentos está disponible para todas ellas ya que tienen la misma LDT compartiendo una única GDT. IMPORTANTE:***** Lo anterior es el caso del subsistema de 16 bits de Windows 98. Una sola LDT. Por tanto, un "cuelgue" en una tarea de 16 bits, por desgracia, nos influye en "todo" el resto de tareas de 16 bits. Como bastante parte del núcleo de Windows, por compatibilidad, es de 16 bits, esto implica que el cuelgue de una aplicación de 16 bits, "puede" dejar al sistema totalmente inestable. Dos tareas pueden tener también el mismo descriptor para un segmento compartido en sus LDT respectivas, de esta manera se puede compartir un segmento sin tener que poner su descriptor en la GDT. En este caso, el segmento compartido debe ser tratado con especial atención por el sistema operativo ya que realmente tiene dos descriptores en dos LDT distintas que deben ser actualizados simultáneamente. SELECTORES DE SEGMENTO: Un selector de segmento identifica a un segmento. Aunque sea un poco técnico, diremos que en la arquitectura de nuestro PC, un selector de segmento, es de 16 bites de tamaño y contiene tres subcampos: 1) los dos primeros bits, contienen el llamado RPL (Requested Privilege Level) o nivel de privilegio solicitado. Un valor de 2 bits, puede contener de 0 a 3. Es decir el sistema de protección anteriormente citado. 2) El siguiente BIT, indica un 0 para saber que este selector pertenece a la GDT, o un 1 si pertenece a la LDT, 3) y los otros 13 bits, es un índice (numero de orden) del descriptor en las tablas GDT o LDT en función del contenido del punto anterior. USO DEL CAMPO RPL Cada vez que un programa intenta acceder a un segmento, el nivel de privilegio actual (CPL) se compara con el nivel de privilegio del segmento para determinar si el acceso está permitido. En caso de no estar permitido, disparará una "excepción" y por tanto el sistema operativo tomará control. DESCRIPTORES DE SEGMENTO Bien, cada descriptor de segmento es realmente pequeño. 8 bytes. Y contiene 3 campos: 1) Dirección de base de segmento 2) Limite del segmento 3) Atributos La dirección de base es de 32 bits, para permitir que esa dirección comience en cualquier byte de la memoria lineal (física). El limite, es también de 32 bits, pero únicamente está especificado con 20 bits. Los limites de segmento, pueden ser granulares de byte o granulares de pagina (4 Kb). El tipo de granularidad viene en uno de los 12 bits que quedan en el descriptor. Por tanto, con 20 bits permite la especificación de tamaños de segmento de 1 a 1 Mega si la granularidad es de byte. o tamaños de 4 Kb a 4 Gigabytes en incrementos de 4 Kb. Los segmentos se pueden extender de tamaño, simplemente modificando el "limite" (puede requerir en este caso, mover segmentos dentro del espacio lineal de direcciones). Atributos del segmento En los 12 bits restantes, aparte de la granularidad, existen 2 bits para el nivel de privilegio (de 0 a 3). A este campo se le llama DPL (Descriptor Privilege Level). Otros bits interesantes: ** El BIT de presencia (P): Si está a 1 indica que el descriptor es válido para utilizar en la traducción de direcciones virtuales a direcciones lineales. Si está a 0, el descriptor no es valido, y su uso provocaría una "excepción" ** E BIT "Tipo D". Si es 1 es para un segmento de memoria. Si es 0 es para un segmento del sistema o una "puerta" (lo veremos mas adelante). La LDT es por ejemplo, un segmento del sistema. ** Otros dos bites de "tipo". Sirven para especificar el tipo de memoria. No voy a detallarlo completamente, pero sepamos que aquí se guarda si es por ejemplo de solo lectura, de escritura, si ha sido o no accedido últimamente (importante para saber si se "debe" o no paginar. un segmento muy accedido no debe paginarse a disco)..... etc. Puertas: Bien las puertas, realmente son también descriptores pero con otro formato. Los 48 primeros bits es un puntero completo y además tiene los 16 de atributos. La puerta, es lo único que va a permitir a un programa con un nivel de privilegio mayor, el saltar a las rutinas del sistema operativo que tiene mayor nivel de privilegio. Recordemos que en principio no se puede saltar desde un programa de aplicación, a ninguna rutina del sistema operativo, ya que esto provocaría una "excepción". Por ello se utiliza el mecanismo de la puerta. La puerta tiene la dirección "exacta" de llamada a la rutina del sistema operativo. Y el programa de aplicación "únicamente" puede utilizar esa puerta. PAGINACIÓN La paginación es la otra parte del mecanismo de gestión de memoria con que empezábamos estos artículos. Funciona por debajo de la segmentación para completar los procesos de conversión de dirección virtual a física. El mecanismo de paginación, es HARDWARE. Simplemente se habilita por el sistema operativo en su carga incial, al situar un BIT, en un registro especial de la CPU que es solo accesible desde el sistema operativo. A diferencia de la segmentación que funciona con segmentos de memoria de tamaño variable, la paginación se hace con tamaños de memoria fijos llamado "paginas". La paginación divide el espacio de direcciones lineales en "paginas". Cualquier pagina en el espacio de direcciones lineales se puede mapear sobre otra en el espacio de direcciones físicas. ** Esto ultimo es importante. Hasta ahora hemos hablado de direcciones lineales como direcciones físicas. Pero debemos fijarnos que en el momento en que entra a funcionar el mecanismo de paginación, existe otra indireccion, ya que una dirección lineal puede estar mapeada en cualquier sitio del espacio de direcciones físico. Las CPU s de los PCS actuales que conocemos, utilizan un tamaño de pagina de 4 Kb, y evidentemente "alineados" en intervalos de 4 Kbs. Esto significa que el mecanismo de paginación divide el espacio de direcciones lineal de 2 elevado a 32 bits (4 GB) en 2 elevado a 20 paginas de 2 elevado a 12 bytes cada una (4 Kbs).La función de traducción de dirección lineal a física se extiende para permitir que una dirección lineal se marque como no valida, bien porque esa dirección no está soportada por el sistema operativo, o bien, y esto es lo mas importante, porque corresponde a una pagina que ha sido "paginada" (almacenada) en el disco y no está disponible en ese momento en memoria física. Supongamos, que es el segundo caso. Cualquier referencia de nuestro programa a esa pagina, provocará una excepción llamada "fallo de pagina", lo cual invoca a manejador del sistema operativo encargado de llamar al gestor de memoria para mover la pagina de disco a memoria física. Estas paginas que residen en el disco se llaman pagina no presentes, y se identifican por un atributo de la tabla de paginas llamado el atributo de presente. Con esto terminamos una introducción genérica que es valida para cualquier procesador (con algún matiz del tamaño de pagina, y descriptor). Para cualquier procesador, bien nuestro humilde 486 o Pentium o bien los gigantescos "mainframes" de IBM. Todo el manejo de estos bits de paginación, los mecanismos de excepciones, las tablas de paginas, etc..... las maneja directamente el HARDWARE. El "creador" del sistema operativo, solo tiene que decir si quiere activos estos mecanismos y las direcciones de las tablas de paginas, etc.... El procesador vía hardware se encarga de la actualización de estos datos. Es decir, una vez creadas las GDT del sistema, la tabla de paginación, las puertas y las tablas de interrupciones (o excepciones), el propio hardware se encarga de esto. Igualmente, el cambio de contexto en un cambio de tarea (en multitasking multitarea-) lo realiza mediante hardware el procesador. Otra cosa es que lo anterior (cambio de contexto hardware), esté deshabilitado "a propósito" por Windows y lo haga el "manualmente". Aparentemente y según las malas lenguas, se debe a un error de diseño de Intel en sus primeros procesadores (386) en los cuales fallaba el mecanismo hardware. Por herencia y compatibilidad, Windows ha "heredado" esta malfunción.