Gestión de memoria dinámica

pinguinoslinux4En cualquier sistema informático, la memoria es un recurso escaso. No importa de cuánta memoria dispongamos, siempre nos parece poco. No hace mucho, 256MB de RAM eran suficientes, pero 2GB de RAM es el mínimo requerimiento común y sensato, incluso para sistemas de sobremesa, con servidores que disponen, de muchos más. Desde las primeras versiones del sistema operativo,  aquellos que se ejecutan al estilo UNIX han seguido un enfoque muy claro sobre la gestión de memoria, que Linux, al implementar la especificación X/Open, ha adoptado.Las aplicaciones Linux, salvo por algunas especializadas que aparecen con el programa, no tienen permiso para acceder directamente a la memoria física, Puede que eso sea lo que le parece a la aplicación, pero lo que realmente está viendo es una ilusión cuidadosamente controlada.

Distribución de memoria sencilla

Para distribuir la memoria se usa la llamada malloc de la biblioteca estándar C:

memoria_ultimaPuede distribuir una gran cantidad de memoria en la mayoría de sistemas Linux. Veamos un programa sencillo  que rechazará a los programas antiguos basados en MS-DOS, porque no pueden acceder a la memoria que está fuera de los ordenadores con mapa de memoria base de 640K.

Distribución de memoria sencilla

Escriba el siguiente programa, y guárdelo como, memory1.c:

memory1

Cuando ejecutemos el programa obtendremos la salida siguiente:

memory1_salida

Cómo funciona

memory2

memory3

La razón de todo esto es que Linux usa enteros de 32 bits y los indicadores de 32 bits para señalar una memoria, lo cual le permite especificar hasta 4Gb. Esta capacidad de dirigirse directamente con un indicador de 32 bits, sin necesidad de registros de segmentos u otros trucos, se denomina modelo de memoria plano de 32 bits. Este modelo se usa también en las versiones de 32 bits de Windows XP y Vista. Sin embargo, nunca debería confiar en los enteros de 32 bits, ya que cada vez hay más versiones de Linux de 64 bits.

Distribuyendo mucha memoria

Tras ver cómo excede Linux las limitaciones del modelo de memoria MS-DOS, vamos a hacerlo más difícil aún. El siguiente programa solicita que se distribuya más memoria de la que dispone el programa físicamente. Es posible que espere que malloc empiece a fallar dentro de poco, cuando sobrepase la cantidad de memoria real presente, ya que el núcleo y todos los procesos de ejecución están usando parte de la memoria.

Solicitar toda la memoria física

Con el programa que llamaremos, memory2.c, vamos a solicitar más memoria que la memoria física del sistema. Debería ajustar la definición PHY_MEM_MEGS dependiendo de su máquina física:

memory2_listado

En esta ocasión la salida muestra:

memory2_salida

Cómo funciona

El núcleo de Linux gestiona la memoria asignada de la aplicación. Cada vez que el programa solicita memoria o intenta leer o escribir en la memoria asignada, el núcleo de Linux toma el control y decide cómo gestionar la respuesta.

El núcleo mueve los datos y el código de memoria entre la memoria física y el espacio de intercambio de manera que cada vez que lea o escriba en la memoria, los datos parecerán estar siempre en la memoria física, fuese cual fuese su ubicación real antes de intentar acceder a ellos.

ramTécnicamente, Linux ejecuta un sistema de memoria virtual de paginación de demanda. Toda la memoria que ven los programas de usuario es virtual, es decir, realmente no existe en la dirección física que utiliza el programa. Linux decide toda la memoria en páginas, normalmente 4096 bytes por página. Cuando un programa intenta acceder a la memoria, se realiza un cambio de memoria virtual a física, a pesar de que el modo de ejecución y el tiempo depende del hardware que se esté usando. Cuando se realiza el acceso a la memoria que no está físicamente presente, se provoca un fallo de página y el control se transmite al núcleo.

El núcleo de Linux comprueba la dirección a la que se accede y, si se trata de una dirección legal para el programa, determina qué página de la memoria física poner a disposición. Después la asigna, si nunca ha sido escrita antes, o, si se ha almacenado en el disco en el espacio de intercambio, lee la página de memoria que contiene los datos en la memoria física (posiblemente sacando fuera del disco a una página existente). Después, tras trazar la dirección virtual de la memoria para que se corresponda con la dirección física, le permite al programa de usuario que continúe. Las aplicaciones Linux no tienen que preocuparse por esa actividad, ya que toda la ejecución está oculta en el núcleo.

Finalmente, cuando la aplicación agota tanto la memoria física como el espacio de intercambio, o cuando se excede el tamaño máximo de la pila, el núcleo rechaza la petición de memoria y puede cerrar el programa.

mistery_nota2

La importancia para el programador es que Linux es muy bueno gestionando memoria permitiendo a las aplicaciones usar grandes cantidades de memoria e incluso grandes bloques de memoria independientes. Sin embargo, no debe olvidar que asignar dos bloques de memoria no tendrá como resultado un bloque de memoria al que se pueda acceder de manera continua, sino que obtendremos los dos bloques de memoria independiente solicitados.

Uno de los problemas más comunes con los programas C que usan memoria asignada dinámicamente es escribir detrás del final en el bloque asignado. Cuando ocurre esto, el programa no terminará de manera inmediata, pero, probablemente, haya eliminado algunos datos usados internamente por las rutinas de la biblioteca malloc.

 Normalmente, el resultado es que las futuras llamadas a malloc fallarán, y no porque no exista memoria para asignar, sino porque se han modificado las estructuras de las memorias. Resulta difícil seguir la pista a estos problemas, y cuanto antes se detecte el error, más oportunidades tendremos de localizar la causa.

Abuso de memoria

En este ejercicio, asignará memoria y después intentará escribir una vez llegado al final, llame al programa, memory4.c.
memory4_listado

La salida muestra la queja del abuso que hemos practicado:

memory4_salida

Cómo funciona

El sistema de gestión de memoria  de Linux ha protegido al resto del sistema de este abuso de memoria. Para comprobar que un programa con un comportamiento inadecuado (este mismo) no puede dañar a otros programas, Linux lo ha finalizado.

Cada programa en ejecución en un sistema Linux ve su propio mapa de memoria, que es distinto que el de cualquier otro programa. Solamente el sistema operativo sabe cómo se organiza la memoria física, y no sólo la gestiona para los programas del usuario, sino que también protege a los programas del usuario entre sí.

El indicador nulo

A diferencia de MS-DOS, y más en la tónica de las nuevas versiones de Windows, los sistemas de Linux modernos, son muy protectores a la hora de escribir o leer la dirección que señala un indicador nulo, aunque el comportamiento real depende de la implementación.

Acceso a un indicador nulo

Vamos a ver qué ocurre cuando accedemos a un indicador nulo en el programa que crearemos y llamaremos memory5.c:

 

memory5_listado

La salida mostrada sería:

indicador_nulo

Cómo funciona

indicador_nuloexplicacion

Este caso se utilizó el estilo de la biblioteca GNU “C”. Volvámoslo a intentar sin usar la biblioteca GNU “C”. Llamaremos a este programa memory5b.c:

memory5b_listado

La salida sería:

memory5b_salida

Al intentar leer directamente desde la ubicación cero, no existe ninguna biblioteca libc GNU entre nosotros y el núcleo en este momento, y se termina el programa. Debemos tener en cuenta que algunas versiones UNIX sí permiten  leer a partir de la ubicación cero, pero Linux no.

 Liberación de memoria

Por ahora, hemos asignado memoria y hemos esperado que, al finalizar el programa, no se perdiese la memoria usada. Afortunadamente, el sistema de gestión de memoria de Linux es capaz de asegurar con toda fiabilidad que la memoria se vuelve a enviar al sistema cuando se finaliza un programa. Sin embargo, la mayoría de programas no quieren asignar parte de la memoria, la usan durante un breve periodo de tiempo, y después salen. El uso más común es usar la memoria dinámicamente, según las necesidades.

Los programas que usan la memoria según se requiera deberían mandar de nuevo la que no se ha utilizado al gestor de memoria malloc  a través de la llamada free. Esto permite que se vuelvan a unir los bloques separados y que la biblioteca malloc pueda cuidar de la memoria, en vez de que sea la aplicación la que se ocupe de gestionarla. Si un programa en ejecución (proceso) usa y después libera memoria, esa memoria libre sigue asignada al proceso. En verdad, Linux está gestionando los bloques de memoria que el programador está usando como un conjunto de páginas físicas, normalmente con 4Kb cada una, en la memoria. Si embargo, si no se está usando una página de memoria, el gestor de memoria de Linux será capaz de moverla de la memoria física al espacio de intercambio (denominado paginación), donde tendrá poca influencia en el uso de recursos. Si el programa intenta acceder a los datos que se encuentran dentro  de la página de memoria que se ha transmitido a un espacio de intercambio, Linux suspenderá el programa durante un breve periodo de tiempo, devolviendo nuevamente la página de memoria desde el espacio de intercambio a la memoria física, y permitiendo después que el programa continúe como si los datos hubiesen estado siempre en la memoria.

liberacion

La llamada a free debería realizarse únicamente con un indicador de la memoria asignada mediante una llamada a malloc, calloc o realloc. Trataremos en breve estas nuevas llamadas.

Liberación de memoria

Seguimos con otro programa que llamaremos memory6.c:

memory6_listado

La salida sería:

memory6_salida

Cómo funciona

memory6_explicacion

memory6_nota

Otras funciones de distribución de la memoria

calloc y realloc son otras funciones de distribución de memoria que no se usan tan a menudo como malloc y free.

Los prototipos son:

calloc_realloc_sintaxisAunque calloc asigna memoria que se puede liberar mediante free, dispone de unos parámetros distintos de malloc: asigna memoria para una matriz de estructuras y necesita el número de elemento y el tamaño de cada elemento como sus parámetros.

 La memoria asignada se completa con ceros, y si calloc tiene éxito, se envía un indicador del primer elemento. Al igual que malloc, no se garantiza que las llamadas posteriores envíen un espacio contiguo, de manera que puede alargar una matriz creada por calloc mediante una sencilla llamada a calloc y esperando que la segunda llamada envíe la memoria junto con la enviada por la primera llamada.

La función realloc modifica el tamaño de un bloque de memoria que ha sido asignado previamente. Se transmite un indicador a la memoria previamente asignada por malloc, calloc o realloc y se modifica su tamaño a petición. La función realloc puede tener que mover los datos para conseguirlo, por eso resulta tan importante asegurar que una veza que se ha vuelto a ubicar a la memoria (realloc) se usa siempre el indicador nuevo y no se intenta acceder nunca a la memoria usando los indicadores configurados previamente, cuando se llamó a realloc.

Otro problema que hay que controlar es que realloc envíe un indicador nulo si no ha sido capaz de modificar el tamaño de la memoria. Esto significa que en algunas aplicaciones debería evitar escribir código del tipo:

my_ptr = malloc(BLOCK_SIZE);
....
my_ptr = realloc(my_ptr, BLOCK_SIZE * 10);

Si realloc falla, enviará un indicador nulo, my_ptr señalará nulo, y ya no se podrá tener acceso a la memoria original asignada con malloc a través de my_ptr. Le podría beneficiar, por tanto,  solicitar la nueva memoria en primer ligar con malloc y después copiar los datos del bloque antiguo al nuevo usando memcpy antes de liberar (free) al bloque antiguo. Cuando se produzca un error, la aplicación podrá conservar el acceso a los datos almacenados en el bloque de memoria original, quizás mientras se gestiona una finalización limpia del programa.

atras2

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s