Académique Documents
Professionnel Documents
Culture Documents
El manejo de la memoria es uno de los temas que interesan a los programadores, diseadores e implementadores de lenguajes. Tpicamente los lenguajes contienen varias caractersticas o restricciones que podran ser explicadas solamente por las intenciones de los diseadores, para permitir que una u otra tcnica de almacenamiento sea usada. Por ejemplo, en Fortran existe la restriccin de efectuar solamente llamadas no recursivas a subprogramas. Este tipo de llamadas podra ser permitido en Fortran sin efectuar algn cambio en la sintaxis, pero su implementacin requerira un stack en tiempo de corrida de puntos de retorno de las subrutinas, que es una estructura que requiere de almacenamiento dinmico. As, Fortran puede manejar solamente almacenamiento esttico. Otros lenguajes, como Pascal, estn diseados para permitir el manejo de almacenamiento basado en stacks. Tambin hay lenguajes como LISP, que permiten la recoleccin de basura en la memoria. Cada diseo de lenguaje normalmente permite el uso de ciertas tcnicas de manejo de memoria. Los detalles del mecanismo, y su representacin en hardware y software son tareas del implementador. Veamos ahora como la mayora de los programas y sus elementos requieren almacenamiento durante la ejecucin del programa.
Temporales en evaluacin de expresiones. Se requiere un espacio de almacenamiento temporal para los resultados intermedios de una evaluacin. Por ejemplo, en la evaluacin de la expresin (x + y) * (u + v), el resultado de la primera suma debe ser almacenado temporalmente mientras el resto de la evaluacin se completa. Temporales en el paso de parmetros Cuando un subprograma es llamado, una lista de los parmetros en turno debe de ser evaluada y los resultados almacenados temporalmente hasta que sea completada la evaluacin de la lista. Cuando la evaluacin de algn parmetro de la lista requiera llamadas a funciones recursivas, existe la posibilidad de que se emplee un nmero ilimitado de almacenamientos temporales sucesivos. Buffers de entrada y salida Los buffers sirven como reas de almacenamiento temporal donde los datos son almacenados durante el tiempo de transferencia fsica hacia el almacenamiento externo. Generalmente cientos de localidades son reservadas para los buffers. Datos miscelneos del sistema En casi todos los lenguajes, se requiere almacenamiento para diversos datos propios del sistema, por ejemplo tablas, estados de informacin para entrada y salida, as como para la recoleccin de basura y contadores de referencia. Adems de los elementos antes mencionados, es necesario considerar las principales operaciones que requieren almacenamiento en memoria: Llamadas a subprogramas y retorno de operaciones. Las llamadas a subprogramas, ambientes de referencia y otros datos utilizados en subprogramas son la mayora de operaciones que requieren almacenamiento. La ejecucin del retorno desde un subprograma usualmente requiere la liberacin del espacio de almacenamiento utilizado durante la ejecucin. Creacin y destruccin de operaciones de estructuras de datos. Si el lenguaje posee operaciones que permiten la creacin de nuevas estructuras de datos en puntos arbitrarios durante la ejecucin de un programa (no solamente cuando inicia algn subprograma), entonces estas operaciones normalmente requieren disponer de espacio aparte del utilizado cuando inicia el subprograma. Ejemplos de esto son las instrucciones new en pascal o malloc en C. Adems el lenguaje debe proveer una operacin explcita de destruccin como dispose en pascal o free en C. Insercin y borrado de operaciones de componentes Si el lenguaje proporciona operaciones de insercin y borrado de estructura de datos, para que estas operaciones puedan ser implementadas, se podra requerir de operaciones de almacenamiento y liberacin del espacio.
Hay tres aspectos bsicos del almacenamiento: Asignacin inicial Al principio de la ejecucin cada pieza de almacenamiento debe ser reservada para usarse o estar disponible. Si est libre inicialmente, debe de poder ser reservada dinmicamente durante la ejecucin del programa. Cualquier sistema de almacenamiento requiere de alguna tcnica para mantener un registro del espacio libre, as como mecanismos para su uso, dependiendo de las necesidades durante la ejecucin. Recuperacin El almacenamiento que ha sido asignado y usado subsecuentemente debera de ser recuperado por el administrador de almacenamiento para su nuevo uso. El proceso de recuperacin puede ser simple, como el reposicionamiento del apuntador de stack, o complejo, como la recuperacin de basura. Compactacin y nuevo uso. En el mejor de los casos, el espacio de almacenamiento recuperado podra estar inmediatamente listo para su nuevo uso. Otras veces podra ser necesaria la compactacin, que es la construccin de grandes bloques libres a partir de piezas pequeas de almacenamiento. El nuevo uso normalmente involucra las mismas tcnicas que la asignacin inicial.
En este caso, todo lo que se necesita para el control del almacenamiento es un simple apuntador de stack (stack pointer). Este siempre apunta a la parte alta del stack, donde se localiza la siguiente palabra libre de almacenamiento. Todo el espacio usado yace debajo de la direccin sealada por el stack pointer. Cuando un bloque de k locaciones es requerido, el apuntador simplemente se mueve k locaciones ms adelante. Si un bloque de k locaciones es liberado, el apuntador se mueve k locaciones hacia abajo. Por esto, la compactacin de la memoria ocurre automticamente. Cuando un subprograma es llamado, un nuevo record es creado en el tope del stack. La mayora de las implementaciones de Pascal estn basadas en un stack central de registros de activacin de subprogramas, junto con un rea estticamente reservada que contiene los programas del sistema y los segmentos de cdigo de subprogramas. En la siguiente figura, en (a) se muestra la activacin tpica de un subprograma en Pascal. En (b) se muestra la organizacin de la memoria durante la ejecucin.
El uso del stack en LISP es algo diferente. Las llamadas a subprogramas (funciones) son estrictamente anidadas y un stack podra ser usada para mantener los registros de activacin. Cada registro de activacin contiene un punto de retorno y temporales para la evaluacin de expresiones y la transmisin de parmetros. Los ambientes de referencia locales podran ser tambin colocados en el mismo stack, pero el programador puede manipular estas asociaciones. Por lo tanto, normalmente estos son almacenados en un stack separado, representada como una lista ligada, llamada la A-lista. Una lista de memoria usada por LISP se muestra en la siguiente figura.
La divisin del almacenamiento en un rea reservada estticamente, en otra rea reservada por un stack, y un rea reservada por un heap no solamente es utilizada por Pascal y LISP. Otros lenguajes como Ada, C y Prolog tambin hacen esta divisin tripartita de la memoria.
Estructura de la lista de espacio libre. Las reas sombreadas representan elementos que fueron reservados y no liberados
Recuperacin: Contadores de referencia y recoleccin de basura: La forma ms sencilla de recuperacin es la del retorno explcito. Cuando un elemento que estaba en uso est disponible para nuevo uso, este debe ser explcitamente identificado como free y retornado a la lista de espacio libre. Cuando los elementos son utilizados por el sistema, cada rutina del sistema es responsable de liberar el espacio. El retorno explcito es la tcnica natural para administrar el almacenamiento en el heap, pero desafortunadamente no siempre es posible emplearla. Hay dos viejos problemas que la inutizan: garbage y dangling references. dangling references: si una estructura se destruye antes de que todos los accesos a la estructura hayan sido destruidos, cualquier trayectoria restante se convierte en una referencia dangling. Garbage: si el ltimo acceso a la estructura es destruido sin que la estructura misma sea destruida, entonces la estructura se convierte en garbage. En el contexto de la administracin de almacenamiento en heap, una referencia dangling es un apuntador a un elemento que ha sido retornado a la lista de espacio libre, y cuyo espacio podra ser utilizado para cualquier otro propsito. Un elemento garbage es uno que est disponible para su nuevo uso, pero que como no se encuentra en la lista de espacio libre, se convierte en espacio inaccesible. Si la basura se acumula, el almacenamiento disponible es gradualmente reducido hasta que el programa no puede continuar su ejecucin por falta de espacio libre conocido. Las referencias dangling pueden causar un caos. Si un programa intenta modificar una estructura, que ha sido liberada, a travs de una referencia dangling, los contenidos de un elemento de la lista de espacio libre pueden ser modificados inadvertidamente. Si esta modificacin sobrescribe el apuntador que liga a este elemento con el siguiente
elemento libre de la lista, todo el resto de la lista podra volverse inaccesible. Peor an, un intento posterior para reservar espacio usando el apuntador de la lista en el elemento sobrescrito podra dar lugar a resultados impredecibles, por ejemplo, un pedazo de un programa ejecutable podra ser declarado como free space y luego modificado. El retorno explcito de heaps facilita la creacin de garbage y dangling references. Ejemplos en C: Garbage: int *p, *q; /* p y q son apuntadores a enteros */ ... p=malloc(sizeof(int)); /* se almacena un entero */ p = q; /* se pierde la direccin de p */ dangling: int *p, *q; ... p=malloc(sizeof(int)); q = p; free(p); /* p y q son apuntadores a enteros */ /* se almacena un entero */ /* se copia a q la direccin de p */ /* se libero el espacio apuntado por q */
El sistema enfrenta las mismas dificultades que el usuario. En LISP. Por ejemplo, las listas ligadas son un tipo de estructura de datos bsico. Una de las operaciones primitivas de LISP es cdr, la cual, dado un apuntador a un elemenrto en la lista ligada, retorna un apuntador al siguiente elemento de la lista. (Ver la siguiente figura)
El siguiente elemento al que fue originalmente apuntado podra haber sido ser liberado por la operacin cdr, si el nico apuntador hacia este es el determinado por cdr . Si cdr no retorna el elemento a la lista de espacio libre, este se convierte en basura. Sin embargo, si cdr manda el elemento a la lista de elementos libres y existen otros apuntadores hacia l, entonces estos apuntadores se convierten en dangling references. Si no existe una forma de determinar cuando estos otros apuntadores existen, entonces cdr se convierte en un generador potencial de garbage o de dangling references. Existen otras alternativas para la solucin de estos problemas en el retorno explicito, una es la de los reference counts y otra la de los garbage colectors. Reference counts. El uso de un contador de referencia genera un requerimiento mayor de espacio. El contador de referencia indica cuantos apuntadores a un elemento existen. Cuando un elemento es colocado en el espacio libre, su contador de referencia se inicializa en 1. Cuando un se crea un nuevo apuntador al elemento, el contador se incrementa en uno. Cuando ste apuntador es destruido, el contador de referencia se decrementa en uno. Cuando el contador de referencia de un elemento llega a cero, el elemento es libre y regresa a la lista de espacio libre. Los contadores de referencia permiten evitar las referencias dangling y a generacin de garbage en la mayora de los casos. Consideremos nuevamente la instruccin cdr de LISP. Si cada elemento de la lista contiene un un contador de referencias, entonces es simple para la operacin cdr evitar las
dificultades previas. cdr simplemente debe de substraer 1 del contador de referencias del elemento originalmente apuntado por su output. Cuando el programador utiliza la instruccin erase o free, el contador de referencia tambin provee proteccin a la informacin. Si se emplea la sentencia free, el contador de referencia se decrementa en 1. Si la cuenta llega a cero la estructura regresa a la lista de espacio libre; si por el contrario, el contador de referencia es mayor a cero, esto indica que la estructura sigue siendo accesible y entonces la sentencia free no tiene efecto. El principal problema se que genera al utilizar al contador de referencia es el costo que hay que pagar para mantenerlo. Dado que la accin de incrementar y decrementar es llevada a cabo continuamente, la eficiencia de la ejecucin disminuye. Supongamos, por ejemplo, que hay que asignar a un apuntador P, el valor de un apuntador Q: int *P, *Q; P=2; P=Q; Sin el contador de referencia solo era necesario copiar el valor de Q en P. Ahora se tienen que llevar a cabo varias tareas: 1. Acceder al elemento apuntado por P y decrementar su contador en 1. 2. Verificar el resultado de la operacin anterior. Si el resultado es cero, regresar la estructura al espacio libre. 3. Copiar la liga al valor apuntado por Q en P. 4. Acceder al elemento apuntado por Q e incrementar el contador de referencia por 1. Como se puede ver, esto incrementa substancialmente el costo de esta simple operacin. Cualquier operacin similar que pueda crear o destruir apuntadores debe de modificar los contadores de referencias tambin. Adems, existe un costo extra de almacenamiento para mantener los contadores de referencias. Sin embargo, esta tcnica es popular en los sistemas de procesamiento paralelo. Recoleccin de basura. El principal problema que genera la acumulacin de basura es la cantidad de espacio que queda inutilizable conforme la ejecucin del programa avanza. La filosofa del modelo de recoleccin de basura es simple: cuando el espacio libre es ocupado totalmente y es necesario ms espacio para almacenar, el cmputo se suspende temporalmente y un proceso de recoleccin de basura tiene lugar. Este identifica los elementos inservibles en la estructura y los regresa al espacio libre. Una vez liberado el espacio, el cmputo se reanuda y el proceso de recoleccin de basura no se vuelve a activar hasta que el espacio libre se haya agotado. El proceso de recoleccin de basura se lleva a cabo en dos fases: Marcado. En ste proceso cada elemento activo dentro de la estructura es marcado. Esta marca pone el bit del colector de basura en apagado.
Barrido. Mediante un proceso secuencial se examinan todos los elementos de la estructura, siendo mandados al espacio libre todos aquellos que posean encendido el bit de recoleccin de basura; al mismo tiempo que se lleva a cabo el barrido todos los elementos, sus bits de barrido son reestablecidos. La parte relativa al marcado es la ms difcil del proceso, debido a que como el espacio libre se encuentra agotado, cada elemento dentro de la estructura se encuentra activado, por tanto deber ser analizado en el proceso. Desafortunadamente, la revisin de cada elemento no puede arrojar como resultado si el elemento debe ser enviado al espacio libre o no, adems un apuntador de un elemento a otro dentro de nuestra estructura no precisa que ste elemento se encuentre activo. Para identificar los elementos que se encuentran activos, se buscan ligas que salgan de nuestra estructura, stos elementos se pueden considerar activos, adems de los que tengan ligas con elementos activos. Asignacin de almacenamiento en LISP La relacin entre el stack y el heap en LISP se muestra en la siguiente figura. En esta figura, se supone que: El almacenamiento en el heap contiene 15 elementos, de los cuales 9 estn actualmente en la lista de espacio libre. (a) Las definiciones siguientes han sido introducidas por el usuario: (defun f1(x y z) (cons x f2,y,z))) (defun f2(v w) (cons v w)) La ejecucin de la expresin (f1 a (b c) (d e)) tiene lugar como sigue: 1. Se invoca f1 y los argumentos x y z se agregan al stack usando las 9 entradas de heap disponibles en la lista de espacio libre (b) 2. Se invoca f2 con apuntadores a sus argumentos v y w (c) 3. La lista de espacio libre est vaca. El recolector de basura marca primero los elementos a los que se apunta desde el stack y luego, en un segundo paso, pone todos los elementos restantes en la lista de libres. (d) 4. Se calcula el valor de f2 y se pone en el stack (e) 5. Se calcula el valor de f1 y se pone en el stack (f). El sistema de LISP mostrara automticamente este resultado al usuario 6. Todos los elementos del cmputo son ahora basura. Cuando la lista libre vuelva a estar vaca, sern recuperados por la prxima recoleccin de basura.
Reservacin de espacio en el stack y en el heap para LISP Para realizar este proceso de marcado se asumen las siguientes caractersticas: 1. Ningn elemento activo deber ser alcanzado desde fuera de la estructura por un cambio de apuntadores. 2. Deber ser posible localizar cualquier apuntador externo que apunte hacia algn elemento de nuestra estructura.
3. Debe ser posible identificar cuando un elemento dentro de la estructura no tiene apuntadores procedentes de otro elemento dentro de la misma.
variable no se puede determinar de una manera simple donde termina un bloque y donde comienza el siguiente. La solucin que se presenta es el uso de un indicador entero que marque el tamao del bloque, el cul se deber localizar despus del bit de barrido, y as la recoleccin de basura se podra llevar acabo mediante la lectura de esta informacin. Durante esta fase de verificacin se puede ejecutar una rutina de compactacin que permita mantener la mayor cantidad de bloques vacos juntos. Si combinamos sta tcnica de recoleccin de basura con una compactacin completa podremos eliminar el uso de las listas de espacio vaco, sustituyndolas por el uso de un solo apuntador al principio del espacio libre. Compactacin y el problema de la fragmentacin de memoria. Un problema con el que nos enfrentamos al emplear bloques de tamao variable es la fragmentacin de la memoria. Cuando se comienza el trabajo contamos con un gran bloque de espacio libre, sin embargo cuando continuamos trabajando, la memoria se fragmenta en pequeas piezas generadas por el almacenamiento y borrado de elementos. Al final de este proceso se da el caso de que no podemos alojar bloques de N palabras debido a que no tenemos suficiente espacio libre en forma consecutiva. Sin embargo, puede ocurrir que al contar el espacio libre que se tiene en la lista de espacio libre, podemos comprobar que hay mas espacio libre del que requerimos. Para solucionar este problema se emplea la compactacin de los bloques de memoria libres. Existen dos tipos de compactacin posibles: Compactacin parcial: Cuando los bloques activos no pueden ser movidos, solo los bloques adyacentes contenidos en la lista de espacio libre puede ser compactada. Compactacin total: Si los bloques activos en la estructura pueden ser movidos, todos los bloques activos son recorridos al inicio del stack de almacenamiento, dejando el espacio libre en el bloque final. La compactacin total requiere que cuando un bloque activo es movido, todos los apuntadores a ese bloque apunten ahora a la nueva localidad.