La gestión de memoria (también gestión de memoria dinámica , asignación de almacenamiento dinámico o asignación de memoria dinámica ) es una forma de gestión de recursos aplicada a la memoria de la computadora . El requisito esencial de la gestión de memoria es proporcionar formas de asignar dinámicamente porciones de memoria a los programas cuando estos lo soliciten y liberarla para su reutilización cuando ya no sea necesaria. Esto es fundamental para cualquier sistema informático avanzado en el que pueda estar en marcha más de un proceso en cualquier momento. [1]
Se han ideado varios métodos que aumentan la eficacia de la gestión de la memoria. Los sistemas de memoria virtual separan las direcciones de memoria utilizadas por un proceso de las direcciones físicas reales, lo que permite la separación de procesos y aumenta el tamaño del espacio de direcciones virtuales más allá de la cantidad disponible de RAM mediante paginación o intercambio a almacenamiento secundario . La calidad del administrador de memoria virtual puede tener un efecto importante en el rendimiento general del sistema . El sistema permite que una computadora parezca que tiene más memoria disponible que la que está físicamente presente, lo que permite que varios procesos la compartan.
En algunos sistemas operativos , por ejemplo Burroughs/Unisys MCP , [2] y OS/360 y sucesores , [3] la memoria es administrada por el sistema operativo. [nota 1] En otros sistemas operativos, por ejemplo los sistemas operativos tipo Unix , la memoria es administrada a nivel de aplicación.
La gestión de memoria dentro de un espacio de direcciones generalmente se clasifica como gestión de memoria manual o gestión de memoria automática.
La tarea de satisfacer una solicitud de asignación consiste en localizar un bloque de memoria no utilizada de tamaño suficiente. Las solicitudes de memoria se satisfacen asignando porciones de un gran conjunto [nota 2] de memoria llamado montón [nota 3] o almacén libre . En un momento dado, algunas partes del montón están en uso, mientras que otras están "libres" (sin usar) y, por lo tanto, disponibles para futuras asignaciones. En el lenguaje C, se llama a la función que asigna memoria del montón malloc
y se llama a la función que toma la memoria previamente asignada y la marca como "libre" (para ser utilizada en futuras asignaciones) free
. [nota 4]
Varios problemas complican la implementación, como la fragmentación externa , que surge cuando hay muchos espacios pequeños entre los bloques de memoria asignados, lo que invalida su uso para una solicitud de asignación. Los metadatos del asignador también pueden inflar el tamaño de asignaciones (individualmente) pequeñas. Esto se suele gestionar mediante la fragmentación . El sistema de gestión de memoria debe realizar un seguimiento de las asignaciones pendientes para garantizar que no se superpongan y que nunca se "pierda" memoria (es decir, que no haya " fugas de memoria ").
El algoritmo específico de asignación de memoria dinámica implementado puede afectar significativamente el rendimiento. Un estudio realizado en 1994 por Digital Equipment Corporation ilustra los costos operativos que implican una variedad de asignadores. La longitud de ruta de instrucciones promedio más baja requerida para asignar una sola ranura de memoria fue 52 (medida con un perfilador de nivel de instrucción en una variedad de software). [1]
Dado que la ubicación precisa de la asignación no se conoce de antemano, se accede a la memoria de forma indirecta, normalmente a través de una referencia de puntero . El algoritmo específico utilizado para organizar el área de memoria y asignar y desasignar fragmentos está interconectado con el núcleo y puede utilizar cualquiera de los siguientes métodos:
La asignación de bloques de tamaño fijo, también llamada asignación de grupo de memoria, utiliza una lista libre de bloques de memoria de tamaño fijo (a menudo todos del mismo tamaño). Esto funciona bien para sistemas integrados simples en los que no es necesario asignar objetos grandes, pero sufre de fragmentación , especialmente con direcciones de memoria largas. Sin embargo, debido a la sobrecarga significativamente reducida, este método puede mejorar sustancialmente el rendimiento de los objetos que necesitan asignación y desasignación frecuentes, por lo que se utiliza a menudo en los videojuegos .
En este sistema, la memoria se asigna en varios grupos de memoria en lugar de solo uno, donde cada grupo representa bloques de memoria de un tamaño de una determinada potencia de dos , o bloques de alguna otra progresión de tamaño conveniente. Todos los bloques de un tamaño particular se guardan en una lista enlazada ordenada o árbol y todos los bloques nuevos que se forman durante la asignación se agregan a sus respectivos grupos de memoria para su uso posterior. Si se solicita un tamaño menor que el disponible, se selecciona el tamaño más pequeño disponible y se divide. Se selecciona una de las partes resultantes y el proceso se repite hasta que se completa la solicitud. Cuando se asigna un bloque, el asignador comenzará con el bloque más pequeño lo suficientemente grande como para evitar romper bloques innecesariamente. Cuando se libera un bloque, se compara con su compañero. Si ambos están libres, se combinan y se colocan en la lista de bloques amigos de tamaño correspondientemente mayor.
Este mecanismo de asignación de memoria preasigna fragmentos de memoria adecuados para albergar objetos de un determinado tipo o tamaño. [5] Estos fragmentos se denominan cachés y el asignador solo tiene que realizar un seguimiento de una lista de ranuras de caché libres. La construcción de un objeto utilizará cualquiera de las ranuras de caché libres y la destrucción de un objeto agregará una ranura nuevamente a la lista de ranuras de caché libres. Esta técnica alivia la fragmentación de la memoria y es eficiente ya que no hay necesidad de buscar una porción adecuada de memoria, ya que cualquier ranura abierta será suficiente.
Muchos sistemas tipo Unix , así como Microsoft Windows, implementan una función llamada alloca
para asignar dinámicamente memoria de pila de una manera similar a la basada en heap malloc
. Un compilador normalmente la traduce a instrucciones en línea que manipulan el puntero de pila. [6] Aunque no hay necesidad de liberar manualmente la memoria asignada de esta manera, ya que se libera automáticamente cuando la función que se llamó alloca
retorna, existe un riesgo de desbordamiento. Y dado que alloca es una expansión ad hoc vista en muchos sistemas pero nunca en POSIX o el estándar C, su comportamiento en caso de un desbordamiento de pila no está definido.
Existe una versión más segura de alloca llamada _malloca
, que informa errores, en Microsoft Windows. Requiere el uso de _freea
. [7] gnulib proporciona una interfaz equivalente, aunque en lugar de lanzar una excepción SEH en caso de desbordamiento, delega a malloc cuando se detecta un tamaño demasiado grande. [8] Se puede emular una característica similar utilizando contabilidad manual y comprobación de tamaño, como en los usos de alloca_account
en glibc. [9]
La gestión adecuada de la memoria en una aplicación es un problema difícil y se han ideado varias estrategias diferentes para gestionar la memoria.
En muchas implementaciones de lenguajes de programación, el entorno de ejecución del programa asigna automáticamente memoria en la pila de llamadas para las variables locales no estáticas de una subrutina , llamadas variables automáticas , cuando se llama a la subrutina, y libera automáticamente esa memoria cuando se sale de la subrutina. Las declaraciones especiales pueden permitir que las variables locales conserven valores entre invocaciones del procedimiento, o pueden permitir que otras subrutinas accedan a las variables locales. La asignación automática de variables locales hace posible la recursión , hasta una profundidad limitada por la memoria disponible.
La recolección de basura es una estrategia para detectar automáticamente la memoria asignada a objetos que ya no se pueden utilizar en un programa y devolver esa memoria asignada a un grupo de ubicaciones de memoria libres. Este método contrasta con la administración de memoria "manual", en la que un programador codifica explícitamente las solicitudes de memoria y las liberaciones de memoria en el programa. Si bien la recolección de basura automática tiene las ventajas de reducir la carga de trabajo del programador y evitar ciertos tipos de errores de asignación de memoria, la recolección de basura requiere sus propios recursos de memoria y puede competir con el programa de aplicación por el tiempo del procesador.
El conteo de referencias es una estrategia para detectar que un programa ya no puede utilizar la memoria, manteniendo un contador que indica cuántos punteros independientes apuntan a la memoria. Siempre que un nuevo puntero apunta a una parte de la memoria, el programador debe aumentar el contador. Cuando el puntero cambia de lugar, o cuando ya no apunta a ninguna zona o se ha liberado, el contador debe disminuir. Cuando el contador llega a cero, la memoria debe considerarse no utilizada y liberada. Algunos sistemas de conteo de referencias requieren la participación del programador y otros son implementados automáticamente por el compilador. Una desventaja del conteo de referencias es que pueden desarrollarse referencias circulares que provoquen una fuga de memoria. Esto se puede mitigar añadiendo el concepto de "referencia débil" (una referencia que no participa en el conteo de referencias, pero que recibe una notificación cuando la zona a la que apunta ya no es válida) o combinando el conteo de referencias y la recolección de basura.
Un grupo de memoria es una técnica de desasignación automática de memoria en función del estado de la aplicación, como el ciclo de vida de una solicitud o transacción. La idea es que muchas aplicaciones ejecuten grandes fragmentos de código que pueden generar asignaciones de memoria, pero que haya un punto en la ejecución en el que se sepa que todos esos fragmentos ya no son válidos. Por ejemplo, en un servicio web, después de cada solicitud, el servicio web ya no necesita ninguna de las memorias asignadas durante la ejecución de la solicitud. Por lo tanto, en lugar de realizar un seguimiento de si se hace referencia a la memoria o no, la memoria se asigna de acuerdo con la solicitud o la etapa del ciclo de vida con la que está asociada. Cuando esa solicitud o etapa ha pasado, toda la memoria asociada se desasigna simultáneamente.
La memoria virtual es un método para disociar la organización de la memoria del hardware físico. Las aplicaciones operan en la memoria a través de direcciones virtuales . Cada intento de la aplicación de acceder a una dirección de memoria virtual en particular da como resultado que la dirección de memoria virtual se traduzca a una dirección física real . [10] De esta manera, la adición de memoria virtual permite un control granular sobre los sistemas de memoria y los métodos de acceso.
En los sistemas de memoria virtual, el sistema operativo limita el acceso de un proceso a la memoria. Esta función, denominada protección de memoria , se puede utilizar para impedir que un proceso lea o escriba en la memoria que no le está asignada, lo que evita que el código malicioso o que funcione mal en un programa interfiera en el funcionamiento de otro.
Aunque la memoria asignada a procesos específicos normalmente está aislada, a veces los procesos necesitan poder compartir información. La memoria compartida es una de las técnicas más rápidas para la comunicación entre procesos .
La memoria se clasifica generalmente según la velocidad de acceso en almacenamiento primario y almacenamiento secundario . Los sistemas de gestión de memoria, entre otras operaciones, también se encargan del traslado de información entre estos dos niveles de memoria.
Un sistema operativo administra varios recursos en el sistema informático. El subsistema de memoria es el elemento del sistema que administra la memoria. El subsistema de memoria combina el recurso de memoria de hardware y el software del sistema operativo MCP que administra el recurso.
El subsistema de memoria gestiona la memoria física y la memoria virtual del sistema (ambas forman parte del recurso de hardware). La memoria virtual amplía la memoria física utilizando espacio adicional en un dispositivo periférico, normalmente un disco. El subsistema de memoria es responsable de mover código y datos entre la memoria principal y la virtual en un proceso conocido como superposición. Burroughs fue la primera implementación comercial de la memoria virtual (aunque desarrollada en la Universidad de Manchester para la computadora Atlas de Ferranti) e integró la memoria virtual con el diseño del sistema del B5000 desde el principio (en 1961) sin necesidad de una unidad de gestión de memoria externa (MMU). [11] : 48
El subsistema de memoria es responsable de mapear las solicitudes lógicas de bloques de memoria a porciones físicas de memoria (segmentos) que se encuentran en la lista de segmentos libres. Cada bloque asignado se administra por medio de un descriptor de segmento [12] , una palabra de control especial que contiene metadatos relevantes sobre el segmento, incluyendo dirección, longitud, tipo de máquina y el bit p o bit de 'presencia' que indica si el bloque está en la memoria principal o necesita ser cargado desde la dirección dada en el descriptor.
Los descriptores son esenciales para brindar seguridad a la memoria, de modo que las operaciones no puedan desbordar o desbordar por debajo del bloque al que se hace referencia (comúnmente conocido como desbordamiento de búfer). Los descriptores en sí mismos son palabras de control protegidas que no se pueden manipular, excepto para elementos específicos del sistema operativo MCP (habilitado por la directiva de bloque UNSAFE en NEWP ).
Donald Knuth describe un sistema similar en la Sección 2.5 'Asignación de almacenamiento dinámico' de 'Algoritmos fundamentales' .
IBM System/360 no admite memoria virtual. [nota 5] El aislamiento de memoria de los trabajos se logra opcionalmente mediante claves de protección , asignando a cada trabajo una clave de almacenamiento diferente, 0 para el supervisor o 1–15. La gestión de memoria en OS/360 es una función del supervisorGETMAIN
. El almacenamiento se solicita mediante la macro y se libera mediante la FREEMAIN
macro, lo que da como resultado una llamada al supervisor ( SVC ) para realizar la operación.
En OS/360 los detalles varían dependiendo de cómo se genera el sistema , por ejemplo, para PCP , MFT , MVT .
En OS/360 MVT, la subasignación dentro de la región de un trabajo o el Área de cola del sistema (SQA) compartida se basa en subgrupos , áreas de un tamaño múltiplo de 2 KB (el tamaño de un área protegida por una clave de protección). Los subgrupos se numeran del 0 al 255. [13] Dentro de una región, a los subgrupos se les asigna la protección de almacenamiento del trabajo o la clave del supervisor, la clave 0. Los subgrupos 0 a 127 reciben la clave del trabajo. Inicialmente, solo se crea el subgrupo cero y todas las solicitudes de almacenamiento del usuario se satisfacen desde el subgrupo 0, a menos que se especifique otro en la solicitud de memoria. Los subgrupos 250 a 255 se crean mediante solicitudes de memoria del supervisor en nombre del trabajo. A la mayoría de estos se les asigna la clave 0, aunque algunos obtienen la clave del trabajo. Los números de subgrupo también son relevantes en MFT, aunque los detalles son mucho más simples. [14] MFT utiliza particiones fijas redefinibles por el operador en lugar de regiones dinámicas y PCP solo tiene una única partición.
Cada subgrupo se asigna mediante una lista de bloques de control que identifican los bloques de memoria asignados y libres dentro del subgrupo. La memoria se asigna encontrando un área libre de tamaño suficiente o asignando bloques adicionales en el subgrupo, hasta el tamaño de la región del trabajo. Es posible liberar toda o parte de un área de memoria asignada. [15]
Los detalles para OS/VS1 son similares [16] a los de MFT y MVT; los detalles para OS/VS2 son similares a los de MVT, excepto que el tamaño de página es de 4 KiB. Tanto para OS/VS1 como para OS/VS2, el área de cola del sistema (SQA) compartida no es paginable.
En MVS, el espacio de direcciones [17] incluye un área compartida paginable adicional, el Área de almacenamiento común (CSA), y dos áreas privadas adicionales, el área de cola del sistema local no paginable (LSQA) y el área de trabajo del sistema paginable (SWA). Además, las claves de almacenamiento 0 a 7 están reservadas para su uso por parte de código privilegiado.