Gestión de memoria en el kernel de Linux: Mecanismos de asignación y fragmentación

Objetivos fundamentales de la gestión de memoria del kernel

La gestión de memoria del kernel persigue tres metas esenciales: asignación eficiente para el núcleo y los procesos de usuario, minimización de la fragmentación del sistema y un mapeo flexible entre memoria física contigua y no contigua para adaptarse a diferentes requisitos de hardware y software.

El kernel divide la memoria física en marcos de página, típicamente de 4KB. Todos los mecanismos de gestión se construyen sobre esta unidad básica.

Sistema Buddy: Asignación y defragmentación de marcos de página

El Sistema Buddy aborda la fragmentación externa administrando los marcos de página en bloques de tamaño potencia de dos. Su estructura de datos central es el arreglo free_area, donde cada índice k contiene una lista enlazada de bloques libres compuestos por 2^k marcos.

Al solicitar memoria, el sistema busca un bloque del tamaño adecuado. Si no está disponible, descompone recursivamente un bloque más grande en dos "compañeros" (buddies), entregando uno al solicitante y colocando el otro en la lista libre correspondiente. La liberación invierte el proceso: se verifica si el compañero del bloque liberado también está libre, y de ser así, se fusionan para formar un bloque de mayor orden, propagando la fusión hacia arriba hasta que ya no sea posible o se alcence el orden máximo.

Flujo de asignación y liberación

// Ejemplo de asignación de un bloque de 4 marcos (orden 2)
#include <linux/mm.h>

struct page *bloque_asignado = alloc_pages(GFP_KERNEL, 2);
if (!bloque_asignado) {
    printk(KERN_ERR "Fallo en la asignación\n");
    return -ENOMEM;
}

// Uso de la memoria...
void *direccion_virtual = page_address(bloque_asignado);

// Liberación del bloque
__free_pages(bloque_asignado, 2);

Slab Allocator: Eficiancia para objetos pequeños

El asignador Slab optimiza la asignación de objetos pequeños (menores a un marco de página) al pre-cachear instancias de tipos específicos. Evita la sobrecarga y el desperdicio de usar el Sistema Buddy para cada pequeña estructura de datos.

Utiliza una jerarquía de cachés (kmem_cache), slabs y objetos. Un slab es un conjunto de marcos de página que contiene objetos de un tipo específico y tamaño. Cada slab mantiene una lista de objetos libres. La asignación (kmem_cache_alloc) toma un objeto de esta lista, y la liberación (kmem_cache_free) lo devuelve.

Se pueden crear cachés personalizadas con funciones constructoras y destructoras para inicializar y limpiar objetos automáticamente durante su ciclo de vida.

// Definición y uso de un caché Slab personalizado
#include <linux/slab.h>

struct mi_estructura {
    int id;
    unsigned long tiempo;
};

struct kmem_cache *mi_caché;
mi_caché = kmem_cache_create("caché_mi_estructura",
                             sizeof(struct mi_estructura), 0,
                             SLAB_HWCACHE_ALIGN, NULL);

// Asignación de un objeto
struct mi_estructura *obj = kmem_cache_alloc(mi_caché, GFP_KERNEL);
if (obj) {
    obj->id = 123;
    obj->tiempo = jiffies;
}

// Liberación y destrucción
kmem_cache_free(mi_caché, obj);
kmem_cache_destroy(mi_caché);

Asignación Contigua vs. No Contigua

El kernel soporta dos modos principales de asignación de memoria virtual para el espacio del kernel:

  • Asignación contigua (alloc_pages): Proporciona memoria física contigua. Ideal para controladores de dispositivos que requieren acceso directo a buffers de DMA. Es administrada por el Sistema Buddy.
  • Asignación no contigua (vmalloc): Asigna marcos de página físicamente dispersos y los mapea a un rango contiguo de direcciones virtuales mediante la manipulación de las tablas de páginas. Es más flexible para bloques grandes, pero con un costo de rendimiento debido al posible cache misses y a la gestión de la tabla de páginas.
// Comparación de los dos métodos
void *memoria_contigua = (void *)__get_free_pages(GFP_KERNEL, 2); // 16 KB
void *memoria_vm = vmalloc(16 * 1024); // 16 KB

// La memoria_contigua es físicamente contigua.
// La memoria_vm es virtualmente contigua, pero físicamente dispersa.

vfree(memoria_vm);
free_pages((unsigned long)memoria_contigua, 2);

Page Cache: Mejorando el rendimiento de E/S de archivos

El Page Cache almacena en memoria los datos de archivos recientemente accedidos desde el disco. Está vinculado a un archivo a través de la estructura address_space dentro de su inodo. Utiliza un árbol radix (page_tree) para mapear rápidamente los desplazamientos de archivo a marcos de página en la caché.

Las operaciones de lectura primero buscan en la caché. Si hay un acierto (cache hit), los datos se devuelven directamente desde la memoria. Si hay un fallo (cache miss), se lee del disco y se almacena en la caché. Las escrituras modifican las páginas en la caché, que se marcan como "sucias". Un hilo del kernel se encarga periódicamente de escribir (volcar) las páginas sucias de vuelta al disco.

Detección y prevención de fugas de memoria

Una fuga de memoria ocurre cuando la memoria asignada no se libera correctamente, agotando los recursos del sistema con el tiempo. Las causas comunes incluyen olvidar llamar a kfree, kmem_cache_free o vfree, o no manejar las rutas de error de excepción en el código.

Las herramientas de diagnóstico son esenciales:

  • slabtop: Muestra el uso de los cachés Slab en tiempo real. Un aumento persistente en el conteo de objetos de un caché después de la carga/descarga de un módulo indica una posible fuga.
  • /proc/meminfo y vmstat -s: Proporcionan estadísticas del sistema de memoria, incluida la cantidad total de memoria ocupada por el slab allocator.

El principio de corrección fundamental es: quien asigna, debe liberar. El manejo de errores debe asegurar que toda la memoria asignada exitosamente sea liberada antes de que la función salga por una ruta de error.

// Ejemplo corregido con manejo de errores robusto
void *ptr1 = NULL;
void *ptr2 = NULL;

ptr1 = kmalloc(1024, GFP_KERNEL);
if (!ptr1) goto error;

ptr2 = kmalloc(2048, GFP_KERNEL);
if (!ptr2) goto error_liberar_ptr1;

// ... lógica normal ...

kfree(ptr2);
error_liberar_ptr1:
kfree(ptr1);
error:
return; // ptr2 ya fue liberado o nunca se asignó; ptr1 se libera si fue asignado.

Etiquetas: kernel-linux gestion-memoria sistema-socios asignador-slab cache-paginas

Publicado el 7-9 05:52