- Explica la estructura de memoria de los procesos en Linux al nivel de funcionamiento real, detallando paso a paso la relación entre el espacio de direcciones virtuales y la memoria física
- Describe de forma concreta cómo un proceso posee y accede a la memoria, centrándose en mecanismos clave como page tables, VMA, mmap, page fault y CoW
- Presenta cómo observar el estado de memoria por proceso mediante el sistema de archivos
/proc, así como el papel de herramientas avanzadas de diagnóstico como pagemap y kpageflags
- Aborda la optimización de rendimiento y las técnicas de dirty tracking en espacio de usuario mediante funciones recientes del kernel como Transparent Huge Pages (THP), userfaultfd y PAGEMAP_SCAN
- También explica principios de diseño del kernel relacionados con seguridad y rendimiento, como PTI para mitigar Meltdown, flush de TLB y la política W^X, ofreciendo una comprensión integral de la gestión de memoria en Linux
Estructura básica de la memoria de procesos
- Cuando un programa se ejecuta, parece que dispone de una enorme memoria continua, pero en realidad el kernel de Linux la organiza dinámicamente en unidades de páginas
- La CPU consulta las page tables para traducir direcciones virtuales a frames físicos
- Si no existe un mapeo, ocurre un page fault, y el kernel asigna una nueva página o devuelve un error
- Si falta RAM física, el kernel mueve páginas sin uso al disco o elimina páginas de archivo para liberar espacio
/proc es un sistema de archivos virtual construido por el kernel en memoria, que expone el estado de los procesos y del kernel en forma de archivos
Espacio de direcciones y VMA
- Cada proceso tiene un objeto de espacio de direcciones, compuesto internamente por varias VMA (Virtual Memory Area)
- Una VMA es un rango contiguo de direcciones con los mismos permisos (R/W/X) y el mismo backend (memoria anónima o archivo)
- Las page tables son estructuras consultadas por el hardware y almacenan la información de mapeo (PTE) entre páginas virtuales y físicas
- Los cambios en el espacio de direcciones se realizan con tres llamadas al sistema
mmap: crea una nueva región
mprotect: cambia permisos
munmap: elimina un mapeo
- La página es la unidad base de 4 KiB, y algunos sistemas también admiten páginas grandes de 2 MiB y 1 GiB
Ver la composición de memoria con /proc/self/maps
- Con el comando
cat /proc/self/maps se puede revisar el mapa de memoria del proceso
- Aparecen el código, datos y bss del ejecutable, el heap, mapeos anónimos, bibliotecas compartidas, la pila, etc.
- Las regiones
[vdso] y [vvar] son código y datos para llamadas al sistema rápidas mapeados por el kernel
Cómo funciona mmap
mmap no asigna memoria real de inmediato, sino que registra una promesa sobre el espacio de direcciones
- Las páginas se asignan en el momento del primer acceso
- Al mapear archivos,
offset debe estar alineado a página, y acceder más allá del final del archivo provoca SIGBUS
MAP_SHARED se refleja directamente en el archivo, mientras que MAP_PRIVATE crea páginas independientes mediante Copy-on-Write (CoW) al escribir
MAP_FIXED_NOREPLACE mejora la seguridad al fallar si ya existe un mapeo en la dirección indicada
Primer acceso y page fault
- En el primer acceso a un mapeo nuevo, si la CPU no encuentra la entrada en la page table, ocurre un page fault
- El kernel verifica la validez de la dirección, los permisos de acceso y la existencia del recurso
- Si es un mapeo anónimo, asigna una nueva página rellenada con ceros; si es un mapeo de archivo, la lee desde la page cache
- Un minor fault ocurre cuando los datos ya están en RAM; un major fault, cuando hace falta I/O de disco
- La pila está protegida con una guard page, por lo que un acceso demasiado hacia abajo provoca
SIGSEGV
Copy-on-Write de fork() y MAP_PRIVATE
- Al hacer
fork, padre e hijo comparten las mismas páginas físicas, marcadas como solo lectura
- Solo en el momento de escritura se copia una nueva página para mantener la independencia
- Los mapeos de archivo con
MAP_PRIVATE funcionan con el mismo principio
- Opciones relacionadas
vfork: comparte el espacio de direcciones del padre
clone(CLONE_VM): crea un hilo
MADV_DONTFORK, MADV_WIPEONFORK: excluyen el mapeo del proceso hijo o lo inicializan en cero
Cambio de permisos e invalidación del TLB
- Cuando
mprotect cambia los permisos de una página, el kernel divide la VMA y modifica las page tables, y luego realiza la invalidación del TLB
- Según la política W^X, una página no puede ser escribible y ejecutable al mismo tiempo
- El TLB (Translation Lookaside Buffer) es una caché de traducciones recientes de direcciones, y su invalidación provoca una pequeña pausa
Observación detallada mediante /proc
- Con
/proc/<pid>/maps, smaps y smaps_rollup se pueden revisar permisos por región, RSS y uso de HugePage
/proc/<pid>/pagemap ofrece estado por página (presencia, swap, PFN, etc.), aunque el PFN no está disponible para usuarios normales
/proc/kpagecount y /proc/kpageflags muestran el número de mapeos por PFN y propiedades de la página (anónima, de archivo, dirty, etc.)
- Con
mincore y SEEK_DATA/SEEK_HOLE se pueden identificar regiones de datos y huecos en archivos dispersos
- Al combinar
PAGEMAP_SCAN y userfaultfd, es posible implementar dirty tracking en espacio de usuario
Transparent Huge Pages (THP) y mTHP
- THP agrupa automáticamente la memoria de acceso frecuente en páginas grandes (como 2 MiB) para mejorar la eficiencia del TLB
- El hilo
khugepaged fusiona páginas adyacentes
- mTHP admite páginas grandes variables (folio) de distintos tamaños, como 16 KiB y 64 KiB
- En
/proc/self/smaps, AnonHugePages y FilePmdMapped permiten verificar su uso
- La configuración global del sistema se administra en
/sys/kernel/mm/transparent_hugepage/
- Se puede controlar por región con
MADV_HUGEPAGE y MADV_NOHUGEPAGE
Dirty tracking en espacio de usuario
- Con
userfaultfd y PAGEMAP_SCAN se pueden copiar solo las páginas modificadas
- El kernel realiza el escaneo y la protección contra escritura en una sola operación atómica
- Es eficiente para snapshots, live migration y casos similares
Mecanismo de flush del TLB
- En x86, la invalidación del TLB se hace de dos maneras
INVLPG: invalida una sola página
- Recargar la raíz de las page tables para hacer un flush completo
PCID e INVPCID permiten la gestión de etiquetas de TLB por proceso, reduciendo flushes innecesarios
tlb_single_page_flush_ceiling es el umbral con el que el kernel decide entre flush por página o flush completo
Mitigación de Meltdown: Page Table Isolation (PTI)
- Meltdown es una vulnerabilidad en la que datos del kernel pueden quedar expuestos a través de la caché durante la ejecución especulativa
- Linux usa PTI (Page Table Isolation) para separar los espacios de direcciones de usuario y kernel
- Al entrar, cambia
CR3 para usar page tables exclusivas del kernel
- Aprovecha
PCID para minimizar el flush del TLB
- Está activado por defecto y puede deshabilitarse con
nopti
Procedimiento seguro del kernel para cambiar mapeos
- Al modificar un mapeo, el orden es
- Procesar las reglas de caché
- Modificar las page tables
- Invalidar el TLB
- Los mapeos internos del kernel (
vmap, vmalloc) también sincronizan caché y TLB antes y después de I/O
- Algunas arquitecturas requieren flush de la instruction cache después de copiar código
Estructura de pila y llamadas en x86
- En modo de 64 bits se usan los registros RIP, RSP y RBP, y la pila crece hacia abajo
- Según el ABI System V AMD64, los argumentos se pasan en RDI, RSI, RDX, RCX, R8 y R9, y el valor de retorno en RAX
- El modo usuario es ring 3, el kernel es ring 0, y las system calls e interrupciones cambian entre ambos mediante gates
Situaciones de error y diagnóstico
mmap → EINVAL: error de alineación en el offset del archivo
mmap → ENOMEM: falta de espacio virtual o límite de overcommit
- Acceso a un mapeo de archivo →
SIGBUS: acceso más allá de EOF
mprotect(PROT_EXEC) → EACCES: montaje noexec o política W^X
- Aumento de RSS después de
fork(): copia de páginas por CoW
- Sobrescribir un mapeo existente con
MAP_FIXED → se recomienda MAP_FIXED_NOREPLACE
Checklist práctico
- Para asegurar memoria de inmediato:
mmap + PROT_READ|PROT_WRITE + MAP_PRIVATE|MAP_ANONYMOUS
- Al generar código: mantener W^X, usar
mprotect(PROT_READ|PROT_EXEC)
- Al mapear archivos: alinear
offset a página, no acceder más allá de EOF
- Si hay muchos page faults: usar
MADV_WILLNEED o acceso anticipado
- Para analizar uso de memoria:
/proc/<pid>/smaps_rollup → /proc/<pid>/maps
- En
fork de procesos grandes: considerar CoW y usar exec en el hijo
- En entornos sensibles a la latencia: observar THP/mTHP,
mlock y el comportamiento del TLB
Aún no hay comentarios.