8 La Administración de Memoria en Sistemas Operativos
La administración de memoria constituye uno de los pilares fundamentales en el diseño y operación de sistemas operativos modernos. Su objetivo principal radica en optimizar el uso de la memoria física y virtual, garantizando que los procesos accedan a los recursos necesarios mientras se mantiene la estabilidad del sistema. Este campo ha evolucionado desde enfoques rudimentarios, como el monitor residente, hasta técnicas sofisticadas como la partición dinámica, cada una abordando desafíos específicos de fragmentación, seguridad y eficiencia. En este material, exploraremos las políticas clásicas de administración de memoria, analizando su funcionamiento, ventajas y limitaciones en contextos reales.
8.1 Evolución Histórica de la Gestión de Memoria
8.1.1 Del Monitor Residente a la Multiprogramación
Los primeros sistemas operativos, como el monitor residente, surgieron en la década de 1950 para automatizar la carga de tareas en entornos batch. Este software primitivo residía permanentemente en memoria, coordinando la ejecución secuencial de programas mediante tarjetas de control. Aunque revolucionario para su época, presentaba serias límitaciones:
- Ausencia de concurrencia: Solo un proceso ocupaba memoria en cada instante.
- Gestión manual: El operador debía agrupar tareas compatibles para evitar conflictos de recursos.
- Inflexibilidad: La memoria se dividía estáticamente entre el sistema y los programas de usuario.
Estos obstáculos motivaron el desarrollo de esquemas más avanzados, como la multiprogramación, que permitió ejecutar múltiples procesos simultáneamente mediante la asignación dinámica de memoria.
8.2 Políticas Clásicas de Administración de Memoria
8.2.1 Monitor Residente: La Cuna de la Gestión
El monitor residente operaba bajo un principio de separación estricta entre el espacio del sistema y el usuario. Utilizaba un registro de “cerca” (fence register) para delimitar ambas zonas, verificando cada acceso a memoria mediante hardware o software.
Ejemplo didáctico: Imaginemos una biblioteca donde el bibliotecario (monitor) ocupa siempre las primeras estanterías. Los usuarios (programas) solo pueden acceder a las secciones posteriores, y cualquier intento de entrar al área reservada genera una alerta.
Limitaciones:
- Fragmentación interna: Si el programa del usuario requería menos espacio que la partición asignada, el excedente se desperdiciaba.
- Rigidez: La ubicación del monitor no podía modificarse durante la ejecución, dificultando la escalabilidad.
8.2.2 Partición Estática: Organización Predefinida
En este esquema, la memoria se divide en regiones de tamaño fijo antes de la ejecución de procesos. Cada partición alberga un único programa, y el sistema operativo emplea algoritmos simples para asignarlas:
- Primer ajuste (First-fit): Asigna la primera partición libre que cumpla con el tamaño requerido.
- Mejor ajuste (Best-fit): Busca la partición más pequeña que pueda contener al proceso.
- Peor ajuste (Worst-fit): Utiliza la partición más grande disponible, dejando espacio residual para futuros procesos.
Caso de estudio: Un sistema con particiones de 64 KB, 128 KB y 256 KB. Si un proceso de 100 KB solicita memoria:
- Primer ajuste: Ocupa la partición de 128 KB, desperdiciando 28 KB.
- Mejor ajuste: Usa la misma partición de 128 KB.
- Peor ajuste: Asigna la de 256 KB, generando 156 KB no utilizados.
Problemas inherentes:
- Fragmentación interna: Espacio no utilizado dentro de particiones asignadas.
- Subutilización: Procesos pequeños ocupan particiones grandes, reduciendo la capacidad de multiprogramación.
8.2.3 Partición Dinámica: Flexibilidad con Costos
Para superar las limitaciones de la partición estática, se introdujo la asignación dinámica, donde las particiones se crean en tiempo de ejecución según las necesidades de cada proceso. Este enfoque requiere estructuras de datos complejas (tablas de huecos libres) y algoritmos de coalescencia para fusionar espacios adyacentes liberados.
Mecanismo operativo:
- Solicitud de memoria: El proceso indica cuánto espacio necesita.
- Búsqueda de hueco: El sistema identifica un bloque libre usando algoritmos como:
- Next-fit: Continúa buscando desde la última asignación.
- Buddy system: Divide la memoria en bloques de potencias de dos para facilitar la coalescencia.
- Asignación y actualización: Se marca el espacio como ocupado y se actualizan las tablas de gestión.
8.3 Comparativa entre Enfoques
Aspecto | Monitor Residente | Partición Estática | Partición Dinámica |
---|---|---|---|
Flexibilidad | Baja | Moderada | Alta |
Fragmentación | Interna | Interna | Externa |
Complejidad | Simple | Moderada | Alta |
Multiprogramación | No soportada | Limitada | Soportada |
Ejemplos históricos | IBM 1401 | OS/360 | UNIX |
Tabla 1: Características comparativas de políticas de gestión de memoria
8.3.1 Paginación
La paginación es una técnica de gestión de memoria utilizada por los sistemas operativos que permite dividir un programa en bloques de tamaño fijo llamados páginas, los cuales pueden ubicarse en diferentes partes de la memoria física, sin necesidad de estar contiguos.
Antes de ejecutar un programa, el sistema realiza los siguientes pasos:
- Calcula el número de páginas necesarias, según el tamaño total del programa.
- Identifica los marcos de memoria disponibles, que son bloques de igual tamaño en la memoria física.
- Carga cada página del programa en uno de esos marcos disponibles.
Este enfoque permite que un programa sea cargado en regiones dispersas de la memoria, evitando la necesidad de bloques contiguos y reduciendo la fragmentación externa.
No obstante, este método requiere un mecanismo de seguimiento para mantener el control sobre dónde se ha almacenado cada página. Ese mecanismo es la tabla de páginas, la cual permite traducir direcciones lógicas (generadas por el programa) en direcciones físicas (utilizadas por la memoria real).
La paginación también es la base de la virtualización de la memoria, una técnica esencial en los sistemas modernos que permite a cada proceso creer que dispone de su propio espacio de memoria continuo, aunque en realidad este espacio esté repartido en bloques dispersos e incluso parcialmente almacenado en disco.
Gracias a esto, los sistemas pueden ejecutar programas que exceden el tamaño de la memoria física, mejorar el aislamiento entre procesos y optimizar el uso de los recursos disponibles.
8.3.2 Segmentación
La segmentación es un método de gestión de memoria donde un programa se divide en partes lógicas de diferente tamaño, llamadas segmentos. A diferencia de la paginación —donde todo se divide en bloques iguales— en segmentación cada parte puede tener un tamaño distinto, según la función que cumpla en el programa.
Un programa no es una sola cosa continua: normalmente está compuesto por varias secciones, como:
- El programa principal (la función principal o main)
- Varias subrutinas o funciones auxiliares
- Un bloque de datos globales
- Una pila para las llamadas de funciones
Cada una de estas partes se puede convertir en un segmento. Por ejemplo:
- Segmento 0: código principal (main)
- Segmento 1: subrutina A
- Segmento 2: subrutina B
- Segmento 3: datos globales
- Segmento 4: pila
Cada segmento se carga en una parte de la memoria principal, pero no necesariamente uno al lado del otro. El sistema operativo lleva un registro de cada segmento mediante una tabla de segmentos, que guarda dos datos importantes:
- La dirección base: dónde empieza ese segmento en memoria.
- El límite: cuánto ocupa (es decir, su tamaño).
8.3.3 Carga dinámica
La carga dinámica es una técnica que permite a un programa cargar en memoria ciertas partes de su código solo cuando realmente las necesita durante la ejecución.
Por ejemplo, una aplicación que incluye una función para exportar archivos en diferentes formatos puede decidir cargar la biblioteca necesaria para "Exportar a PDF"
solo si el usuario elige esa opción. Hasta ese momento, esa parte del programa ni siquiera está en memoria.
En sistemas operativos como Linux, esto se puede implementar usando funciones como dlopen()
y dlsym()
, que permiten cargar y utilizar componentes de forma dinámica. Esto le da al programa mayor flexibilidad, permitiendo incluso extender su comportamiento en tiempo de ejecución, como ocurre en programas que cargan plugins o módulos personalizados.
En resumen, la carga dinámica retrasa el momento en que ciertos componentes se incorporan al programa en ejecución, mejorando el uso de memoria y permitiendo estructuras más modulares.
8.3.4 Enlace dinámico
El enlace dinámico es un proceso que ocurre cuando se carga el programa en memoria, y consiste en vincular el programa con las bibliotecas compartidas que necesita, sin que esas bibliotecas estén incluidas directamente dentro del archivo ejecutable.
Los ejecutables contienen referencias a bibliotecas externas (como archivos .dll
en Windows o .so
en Linux), que el sistema operativo se encarga de buscar y conectar cuando se ejecuta el programa.
Este mecanismo permite que varios programas compartan una misma copia de una biblioteca en memoria, lo que ahorra recursos. Además, si se actualiza o corrige una biblioteca, todos los programas que la utilizan se benefician automáticamente, sin necesidad de recompilarlos.