Gestión de memoria (I)

Última actualización de la serie: Septiembre 15, 2020

Introducción


En un sistema Linux, todo proceso tiene asociado un espacio de memoria virtual gestionado por el sistema operativo en cooperación con hardware específico de la CPU (concretamente, la unidad de manejo de memoria o MMU en sus siglas inglesas).

La arquitectura x86-64 [1] define un espacio virtual con direcciones de 64 bits, de los cuales las implementaciones actuales habilitan sólo los 48 bits menos significativos para direccionamiento. Esto proporciona un tamaño máximo de 256 TiB (248 bytes) de espacio virtual --a comparar con los 4 GiB (232 bytes) de la arquitectura x86--, de un máximo teórico de 16 EiB (264 bytes). La figura inferior muestra la estructura (enormemente simplificada) del espacio de memoria virtual asignado a un proceso cualquiera en una plataforma Linux de 64 bits, dividido en áreas de propósito bien definido [2]. A saber, y en orden creciente de direcciones en memoria:


  • El segmento .text (o código del programa, con inicio en la dirección 0x00400000), conteniendo las instrucciones ejecutables. Le siguen el segmento .data (conteniendo variables estáticas inicializadas) y el segmento .bss (variables estáticas sin inicializar).
  • El área de almacenamiento libre (free store), cuyo tamaño varía en tiempo de ejecución como resultado del uso de expresiones new y delete.
  • Una región de memoria intermedia que contiene el código y los datos de las bibliotecas compartidas con otros procesos (e.g., la biblioteca estándar del lenguaje).
  • La pila del usuario (user stack), empleada para la ejecución de funciones. Su tamaño varía dinámicamente en el transcurso del programa, si bien de forma mucho más ordenada y predecible que el free store, según el esquema LIFO (Last In, First Out).
  • El núcleo del sistema operativo (kernel), es decir, la parte del sistema operativo que siempre reside en memoria, responsable de facilitar el acceso seguro al hardware de la computadora.

Por motivos de seguridad, las distribuciones actuales de Linux y otros sistemas operativos utilizan por defecto la técnica ASLR (Address Space Layout Randomization [3]) para introducir aleatoriedad en la ubicación de las áreas de datos claves del proceso. Así, por ejemplo, pueden introducirse bloques de memoria sin utilizar de tamaño aleatorio entre el segmento .bss y el inicio del free store, o al fondo de la pila del usuario.

Este espacio de memoria virtual se divide en distintas páginas virtuales, a la vez que la memoria física se divide en marcos de página de igual tamaño. El sistema operativo transfiere entonces las páginas del proceso a los marcos de página libres en memoria principal según sea necesario, siguiéndose un estricto control en la asignación de las direcciones de memoria física. Múltiples procesos (muchos de los cuales pueden ser más extensos que la propia memoria física) pueden ejecutarse de esta forma simultáneamente.

Este procedimiento de paginación simplifica enormemente la gestión de la memoria. Así, por ejemplo, el sistema puede alojar bloques contiguos de memoria virtual en bloques desperdigados aleatoriamente en la memoria principal. El sistema operativo puede, asimismo, direccionar múltiples procesos a las mismas páginas de memoria física con el fin de permitir la compartición de bibliotecas (e.g., la biblioteca estándar del lenguaje C++) y el núcleo del sistema. La figura inferior proporciona un esquema virtual de este proceso:


En esta nueva serie de posts analizaremos múltiples aspectos relativos a la gestión eficiente de la memoria, dando respuesta a múltiples cuestiones prácticas: ¿Cómo proteger nuestro código de posibles corrupciones del user stack? ¿Cuándo preferir el free store frente a la pila para el almacenamiento de variables? ¿Qué impacto tiene sobre la eficiencia de nuestro programa la fragmentación del free store? ¿Cómo conseguir un código robusto que impida la fuga de recursos?

En particular, nuestro estudio dará pie a la introducción de los punteros inteligentes std::unique_ptr<>, std::shared_ptr<> y std::weak_ptr<> facilitados por los estándares C++11 y posteriores (véase esta serie de artículos para más detallles).


Referencias bibliográficas:
  1. AMD64 Developer Guides & Manuals - https://developer.amd.com/resources/developer-guides-manuals/
  2. Bryant R. E. and O'Hallaron D. R., 'Computer Systems: A Programmer's Perspective', 2nd Edition. Addison-Wesley (2010).
  3. ASLR technique - https://en.wikipedia.org/wiki/Address_space_layout_randomization

No hay comentarios:

Publicar un comentario