Red de conocimiento informático - Problemas con los teléfonos móviles - Análisis detallado de la gestión de memoria STL

Análisis detallado de la gestión de memoria STL

La gestión de la memoria en STL es muy particular. Este artículo toma SGI STL como ejemplo para analizar las ideas de diseño de su gestión de la memoria. También es un resumen del contenido relevante del "Análisis del código fuente STL" del profesor Hou Jie.

En primer lugar, en general, el configurador de espacio STL se divide en dos niveles: el configurador de espacio de primer nivel se usa para invocar solicitudes de memoria grandes y el configurador de segundo nivel se usa para invocar solicitudes de memoria pequeñas. .

El configurador de espacio de primer nivel proporciona tres funciones: allocate(), deallocate() y reallocate() externamente para que las utilicen los usuarios, y define internamente las funciones oom_allocate() y oom_reallocate() para procesar Condición de memoria insuficiente .

La función deallocate() llamará directamente a la función free para liberar la memoria sin preocuparse por otros problemas, y se centrará en cómo las funciones allocate() y realocate() responden a la memoria insuficiente.

La función allocate() primero llama a la función malloc para obtener memoria. En caso de memoria insuficiente, la función devolverá el puntero nulo NULL. Cuando la función malloc devuelva NULL, llamará a oom_allocate(. ) funcionan para intentar liberar algo de memoria y aplicarla nuevamente.

Este es el mecanismo de almacenamiento en búfer proporcionado por el asignador de espacio de primer nivel. El asignador de primer nivel define un puntero de función que apunta a una función definida por el usuario, porque solo el usuario sabe qué memoria se puede liberar para liberar espacio. Si el puntero de función no especifica una función, se generará una excepción bad_alloc directamente. esta vez. Si se especifica un puntero de función, la función se llamará hasta que se solicite suficiente memoria. Esta es la llamada función de falta de memoria y su funcionamiento es como se muestra en la figura.

El interno. El funcionamiento de la función reasignar es similar a la función asignar, excepto que malloc se reemplaza por realloc y oom_allocate se reemplaza por oom_reallocate.

El asignador de memoria secundaria es responsable de gestionar la memoria pequeña

Cuando se solicita una gran cantidad de memoria pequeña, la partición de memoria completa se dividirá por un lado, de modo que cuando una Si se solicita nuevamente una memoria mayor, es posible que no haya particiones lo suficientemente largas. Por otro lado, una gran cantidad de intervalos pequeños inflarán las estructuras de datos utilizadas por el sistema operativo para rastrear el estado de la memoria.

La estrategia adoptada por el asignador de memoria de segunda etapa es reservar un bloque grande de memoria cuando solicita memoria pequeña por primera vez, y luego, cuando solicite memoria pequeña en el futuro, comenzará directamente desde el Última memoria grande solicitada. La parte solicitada se asigna en la memoria del bloque y ya no se solicita al sistema.

Del mismo modo, el asignador de espacio secundario proporciona las interfaces estándar allocate(), deallocate() y realocate(). Antes de presentar estas tres interfaces, primero introduzcamos algunos términos que encontrará a continuación.

1. Bloque de memoria, a veces también llamado bloque

Un bloque de memoria es una pequeña porción de memoria, su tamaño es múltiplo de 8, hasta 128Bytes, es decir, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 114, 122 y 128 bloques de memoria. En los primeros 8 bytes de cada bloque, se almacena la dirección del siguiente bloque disponible, formando así una lista enlazada de bloques

2. matriz freelist

La matriz freelist es una matriz contiene 16 elementos, cada elemento es un puntero al primer bloque en la lista de bloques

3. Grupo de memoria

El grupo de memoria es un bloque de memoria grande. Hay tres parámetros: inicio. dirección, dirección final y tamaño. El tamaño del grupo de memoria = dirección final - dirección inicial

En el estado inicial, el grupo de memoria está vacío, el bloque de memoria no existe y la matriz de lista libre contiene todos los punteros nulos.

Partimos de este estado para analizar cómo funciona el mecanismo.

Cuando la memoria solicitada tiene más de 128 bytes, se reenviará directamente al configurador de primer nivel de la solicitud de memoria.

Cuando la memoria solicitada no supera los 128 bytes, suponga que la memoria solicitada es de n bytes

1. Calcule (n + 7)/7 para obtener un valor entero i, es decir, índice del elemento de lista libre

2. Acceda al elemento de lista libre ubicado en i. En este momento, el elemento es NULL y no apunta a ningún bloque disponible. En este momento, ajuste n hacia arriba a un múltiplo. de 8 y llame a la función de recarga

3. La función de la función de recarga es reponer los bloques de memoria para la lista libre. Estos bloques de memoria se obtienen del grupo de memoria a través de la función chunk_alloc. se obtienen cada vez

La función chunk_alloc devuelve un bloque de memoria con una longitud de nobjs*n. La función de recarga necesita dividir este bloque de memoria continuo en un solo bloque de memoria y establecer una relación de enlace con la lista enlazada.

Cuando la memoria es suficiente, el primer bloque de memoria se devolverá al usuario y la relación de enlace se construirá a partir del segundo bloque de memoria, como se muestra en la figura

> En el caso de memoria insuficiente, si solo se asigna un bloque de memoria, el bloque de memoria se entrega directamente al usuario y la lista libre no se actualiza

Si hay menos de 20, la memoria obtenida sigue siendo utilizado para construir la relación de enlace.

Si no se obtiene un fragmento, las condiciones de falta de memoria se manejan de la misma manera que el asignador de primer nivel porque la función chunk_alloc llama internamente al asignador de primer nivel para llenar el grupo de memoria.

Aquí hay varios parámetros a los que debemos prestar atención

1. El tamaño total de la memoria solicitada - tamaño*nobjs, aquí representado por total_bytes

2 Grupo de memoria El espacio restante en - representa bytes_left

Si total_bytes es menor que bytes_left, elimine directamente total_bytes de memoria y actualice el estado del grupo de memoria al mismo tiempo

Si el. el espacio restante en el grupo de memoria es insuficiente. Para aplicar tantos bloques, es decir, tamaño

Si no se puede proporcionar ni siquiera un bloque, entonces debe "rellenar" el grupo de memoria.

Antes de agregar agua, primero debe "rellenar" la mayor cantidad de memoria posible para el grupo de memoria. Antes de agregar agua, primero debemos recolectar el agua restante en el grupo de memoria sin desperdiciarla y agregarla a la lista libre. Los pasos específicos son determinar el índice de la lista libre en función del tamaño de la memoria restante. Debido a que cada bloque de memoria es múltiplo de 8, también se divide según un múltiplo de 8 cuando se tacha, por lo que la memoria generada. debe poder formar un bloque de memoria. Después de tener el bloque de memoria, busque la ubicación adecuada de la lista libre y agregue el bloque de memoria a la lista libre. En este momento, puede comenzar a "agregar agua".

Primero, debemos determinar "cuánta agua agregar". ", que es El grupo de memoria está lleno de agua

1. Después de agregar "agua", la cantidad de memoria solicitada debe satisfacerse, es decir, mayor que total_bytes

2. Después agregando "agua", el tamaño del grupo de memoria debe ser mayor que el tamaño anterior

SGI STL es un nuevo sistema que se puede utilizar para muchos propósitos.

El número de selecciones SGI STL es

2 × total_bytes + heap_size >> 4

heap_size es la suma acumulada de los pools anteriores, aquí se trata como variable adicional para adaptarse al hecho de que a medida que aumenta el número de adiciones de "agua", el número de adiciones de "agua" aumenta cada vez.

"

Después de determinar cuánta agua agregar, la función malloc obtendrá la memoria

Si la adquisición es exitosa, el estado de la piscina se actualizará y se llamará a chunk_malloc de forma recursiva porque el grupo está lleno La siguiente adquisición puede ir directamente a la memoria especificada

Si la adquisición no tiene éxito, se llamará a chunk_malloc de forma recursiva

Si no se obtiene tanta memoria

Primero, se itera la lista libre, si hay un bloque libre mayor que el tamaño en la lista libre, agregue el bloque al grupo y recurra

Tenga en cuenta que la iteración aquí no comienza desde. la primera lista libre e iterar una por una, pero desde Comenzando con el tamaño, determine el tamaño de la lista libre y luego llame a chunk_malloc para determinar el tamaño. Comenzando desde el punto de partida, determine el índice correspondiente en la lista libre, y si el índice lo hace. no contiene un bloque libre, aumente el tamaño en 8 bytes, es decir, verifique la siguiente lista libre, hasta que se verifiquen las listas libres posteriores, si no se encuentran bloques libres durante el proceso, regresará inmediatamente y ya no atravesará

Si no se encuentran suficientes bloques libres al recorrer la lista libre, el grupo de memoria y la recursividad

p>

Si no se encuentran suficientes bloques libres, los grupos de memoria y la recursividad al recorrer la lista libre

Si no se encuentran suficientes bloques libres al recorrer la lista libre

entonces nosotros. El problema solo se puede resolver confiando en el controlador de falta de memoria establecido por el usuario en el primer nivel. configurador.Este controlador se pasará al configurador de espacio de primer nivel, y luego el configurador de espacio de primer nivel obtendrá correctamente la memoria. En este momento, el grupo de memoria se actualizará y será recursivo, o la actualización fallará y luego. se lanza una excepción.

El proceso de liberación de memoria es relativamente simple. La memoria asignada por el configurador secundario no será liberada por la función libre ni se agregará al grupo de memoria. a la lista enlazada de freelist para su próximo uso. Esta es una operación de lista enlazada simple y no se explicará en detalle.

En freelist, cada elemento es un obj* y la estructura de obj es como se muestra en. Como se muestra en la figura

¿Por qué se usa esta estructura aquí?

Primero considere su función. Es un nodo de lista vinculada de un bloque de memoria y necesita registrar la dirección actual. bloque de memoria y el siguiente La dirección del bloque de memoria, cada dirección es un puntero de 8 bytes, usar struct requiere 16 bytes, mientras que usar estructura union solo requiere 8 bytes

Los primeros 8 bytes de cada uno. bloque de memoria, hay un objeto obj, que almacena la dirección del siguiente bloque de memoria. Cuando se usa free_list_link para desreferenciar el puntero, el intervalo válido son estos 8 bytes client_data es una matriz de longitud 1 con un solo elemento, que es. memoria. El primer byte del bloque. Defina una variable para este byte y diríjala, y obtendrá la dirección del bloque actual. Aquí, se utiliza la forma de matriz en lugar de definir directamente char. El propósito es devolver directamente client_data como la primera dirección de la matriz. No es necesario llamar al operador de direccionamiento. Cuando se devuelve el bloque de memoria, se devuelve client_data. No se requiere conversión de tipo. Simplemente cambie directamente en la unión. El cambio de estado no cambiará el contenido de los primeros 8 bytes, pero una vez entregado el bloque de memoria, la pérdida de los primeros 8 bytes no es importante. Cuando se agrega un bloque de memoria a la lista libre, el valor de los primeros ocho bytes se restablecerá para garantizar la validez de los datos.