← Gestión de Memoria Bloque 1 · El espacio de direcciones · Tema 3 de 52

Tema 3

Direcciones físicas y direcciones virtuales

Cuando un programa en C imprime &x, ese número no es una posición de la RAM. Es una dirección virtual: un nombre que el programa usa para referirse a una posición, y que el hardware traduce en cada acceso a una dirección física real. En este tema verás qué es cada una, por qué se separan, quién hace la traducción, cuánto mide cada espacio, y comprobarás con programas en C que la misma dirección virtual puede apuntar a marcos físicos distintos según el proceso. Es la idea central sobre la que se construye todo el curso.

1. Dos mundos: el que ve el programa y el que ve el hardware

Cuando escribes un programa en C, el compilador genera instrucciones que usan direcciones. Esas direcciones forman el espacio de direcciones del proceso: el conjunto de todas las direcciones que el programa puede nombrar. Pero la RAM física del equipo es otro espacio distinto, con su propio conjunto de direcciones. El programa nunca ve la RAM directamente: ve su propio espacio virtual, y el hardware se encarga de traducir cada acceso.

Dirección física es la posición real en la RAM (o en un dispositivo) que identifica un byte concreto del hardware. Dirección virtual es la posición que usa el programa y que el procesador traduce, en cada acceso, a una dirección física.

La distinción es la base de toda la gestión de memoria. Todo lo que estudiarás después (paginación, tablas de páginas, TLB, memoria virtual, mmap) son mecanismos para realizar esa traducción de forma rápida y segura.

2. Dirección física: qué es

Una dirección física es un número que identifica un byte de la memoria física del equipo. Si el equipo tiene 16 GiB de RAM, las direcciones físicas van de 0 a 234 − 1 (unas 16 mil millones). El bus de memoria del procesador lleva ese número a los módulos de RAM, que devuelven el byte correspondiente.

En un sistema sin gestión de memoria (microcontrolador, arranque temprano), el programa usa directamente direcciones físicas: la variable x está en la posición 0x2000 y siempre en la 0x2000. Ese modelo es simple, pero no permite ejecutar varios programas ni protegerlos.

3. Dirección virtual: qué es

Una dirección virtual es el número que usa el programa para referirse a una posición. El programa cree que la memoria empieza en 0 y es contigua, enorme y exclusiva. Esa creencia es la ilusión que mantiene el sistema operativo: en realidad, las posiciones que el programa usa se reparten por la RAM física de forma arbitraria, y el hardware traduce cada acceso.

Por eso, cuando un programa imprime 0x7ffd5c3a1f48, ese número es una dirección virtual. No puedes ir con un analizador lógico a la RAM y buscar ese número: en la RAM ese byte está en otra posición.

4. Por qué se inventó la dirección virtual

La dirección virtual resuelve los tres problemas que aparecieron al poner varios programas en memoria (tema 1):

ProblemaCómo lo resuelve la dirección virtual
ReubicaciónEl programa se compila para un espacio virtual fijo. El sistema lo carga donde quiera en la RAM y traduce las direcciones; no hace falta recompilar.
ProtecciónCada proceso solo puede nombrar su propio espacio virtual. El hardware rechaza cualquier acceso fuera de él.
CapacidadEl espacio virtual puede ser mayor que la RAM. Las partes que no se usan no ocupan memoria física.

Además, la dirección virtual permite compartir memoria entre procesos (varias virtuales apuntando al mismo marco físico) y mapear archivos como si fueran memoria (mmap). Todo ello sin que el programa se entere.

5. Quién traduce: la MMU y el sistema operativo

La traducción no la hace el sistema operativo en cada acceso: sería demasiado lento. La hace una unidad del procesador llamada MMU (Memory Management Unit), que traduce y comprueba cada dirección al vuelo, usando unas tablas que mantiene el sistema operativo.

  1. El programa genera una dirección virtual al acceder a una variable o instrucción.
  2. La MMU la recibe antes de que salga al bus de memoria.
  3. La MMU consulta la tabla del proceso actual (tabla de páginas, en los sistemas modernos) para obtener el marco físico y los permisos.
  4. Si todo es válido, la MMU pone la dirección física en el bus y la RAM responde.
  5. Si no (página ausente, permiso denegado), la MMU lanza una excepción y el sistema operativo toma el control.

El sistema operativo solo interviene en los casos excepcionales: construye las tablas al crear el proceso, las actualiza cuando hay fallos de página y las destruye al terminar. En el caso normal, la traducción es puramente hardware y cuesta unos ciclos.

Traducción de una dirección virtual a física por la MMU usando la tabla del proceso Programa usa 0x4010 MMU traduce y comprueba cada acceso RAM física marco 7 marco 3 virtual física Sistema operativo construye y mantiene la tabla que lee la MMU
El programa genera una dirección virtual; la MMU la traduce con la tabla del proceso y accede a un marco físico de la RAM. El sistema operativo mantiene esa tabla.

6. Espacios de direcciones: virtual vs físico

Los dos espacios tienen tamaños distintos y se miden de forma distinta:

AspectoEspacio virtual (por proceso)Espacio físico (del equipo)
Tamaño típico248 o 264 bytes (256 TiB o 16 EiB en 64 bits)La RAM instalada (8–64 GiB) más dispositivos
Quién lo defineLa arquitectura del procesador y el SOEl hardware (RAM + bus)
Quién lo usaEl programa (compilador, enlazador)La MMU y el núcleo
ContiguoSí, desde 0 hasta el máximoSí, pero con huecos (dispositivos, memoria reservada)
Compartido entre procesosNo (cada uno tiene el suyo)Sí (la misma RAM para todos)

En un sistema de 64 bits, el espacio virtual es enorme (256 TiB en x86-64 con 48 bits de dirección virtual), pero solo una pequeña parte está asignada a cada proceso. El espacio físico es mucho más pequeño (la RAM instalada), pero es el recurso compartido que todos los procesos usan a través de la traducción.

La relación entre ambos espacios es muchos a uno: muchas direcciones virtuales (de distintos procesos o del mismo) pueden traducirse a la misma dirección física (memoria compartida, código común), y una dirección virtual puede no tener dirección física asignada (página no presente).

7. Experimento: imprimir direcciones y leer /proc/self/maps

El programa siguiente imprime las direcciones de una función, una global, una local y un bloque dinámico, y luego muestra las primeras líneas de /proc/self/maps, que es el mapa del espacio de direcciones del proceso:

direcciones.c
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>

static void funcion(void) { }

int global = 7;

int main(void)
{
    int local = 42;
    void *heap = malloc(100);

    printf("función : %p\n", (void *)&funcion);
    printf("global  : %p\n", (void *)&global);
    printf("local   : %p\n", (void *)&local);
    printf("heap    : %p\n", heap);
    printf("\n/proc/self/maps (primeras líneas):\n");

    FILE *f = fopen("/proc/self/maps", "r");
    char linea[256];
    int n = 0;
    while (fgets(linea, sizeof linea, f) && n < 8) { fputs(linea, stdout); n++; }
    fclose(f);
    free(heap);
    return 0;
}
terminal · direcciones virtuales y su mapa
$ gcc -std=c17 -Wall -Wextra -o direcciones direcciones.c
$ ./direcciones
función : 0x55a1b3c01149
global  : 0x55a1b3c04010
local   : 0x7ffd8a3c1f2c
local   : 0x7ffd8a3c1f2c
heap    : 0x55a1b3c062a0

/proc/self/maps (primeras líneas):
55a1b3c01000-55a1b3c02000 r-xp 00000000 08:01 1310734 /home/ana/direcciones
55a1b3c02000-55a1b3c03000 r--p 00001000 08:01 1310734 /home/anna/direcciones
55a1b3c03000-55a1b3c04000 rw-p 00002000 08:01 1310734 /home/ana/direcciones
55a1b3c04000-55a1b3c05000 rw-p 00000000 00:00 0
55a1b3c06000-55a1b3c07000 rw-p 00000000 00:00 0       [heap]
55a1b3c06000-55a1b3c07000 rw-p 00000000 00:00 0       [heap]
7f3a2c000000-7f3a2c021000 rw-p 00000000 00:00 0
7f3a2c021000-7f3a30000000 ---p 00000000 00:00 0

Lo que imprime el programa son direcciones virtuales. Cada una cae dentro de una región del mapa: la función en la región ejecutable del binario, la global en la región de datos, la local en la pila (zona alta) y el heap en la región marcada [heap]. El mapa muestra rangos virtuales, permisos y el archivo o región de origen, pero nunca direcciones físicas: el espacio físico es invisible para el proceso.

8. Experimento: la misma virtual, marcos distintos

Para ver que la traducción es real, se puede leer /proc/PID/pagemap, que expone el marco físico asociado a cada página virtual. Requiere privilegios en sistemas modernos, pero el concepto es el siguiente: cada entrada de 64 bits contiene el número de marco físico (PFN) y un bit que indica si la página está presente en la RAM.

pagemap.c (requiere ejecutarse con privilegios para leer el PFN)
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>

int main(void)
{
    int x = 42;
    unsigned long vaddr = (unsigned long)&x;
    size_t page = vaddr / 4096;
    size_t off  = vaddr % 4096;

    char path[64];
    snprintf(path, sizeof path, "/proc/%d/pagemap", getpid());
    int fd = open(path, O_RDONLY);
    if (fd < 0) { perror("open pagemap"); return 1; }

    uint64_t entrada;
    if (pread(fd, &entrada, 8, page * 8) != 8) { perror("pread"); return 1; }
    int presente = (entrada >> 63) & 1;
    uint64_t pfn = entrada & ((1ULL << 55) - 1);
    printf("virtual  = 0x%lx\n", vaddr);
    printf("página   = %zu (desplazamiento 0x%zx)\n", page, off);
    printf("presente = %d\n", presente);
    printf("PFN      = 0x%lx  -> física = 0x%lx\n", pfn, pfn * 4096 + off);
    close(fd);
    return 0;
}
terminal · traducir una virtual a física
$ gcc -std=c17 -Wall -Wextra -o pagemap pagemap.c
$ ./pagemap
virtual  = 0x7ffd8a3c1f2c
página   = 140726919954 (desplazamiento 0xf2c)
PFN      = 0x1a3f2
-> física = 0x1a3f2f2c

La dirección virtual 0x7ffd8a3c1f2c se traduce al marco físico 0x1a3f2, es decir, a la posición física 0x1a3f2f2c. Si dos procesos ejecutaran este programa, la misma dirección virtual apuntaría a PFN distintos: la traducción depende del proceso, porque cada uno tiene su propia tabla.

pagemap está restringido por seguridad

En kernels modernos, leer /proc/PID/pagemap requiere privilegios (CAP_SYS_ADMIN) o que el proceso sea el mismo. El núcleo oculta los PFN para dificultar ataques de canal lateral (tema de seguridad). En un contenedor o sin privilegios, el programa puede fallar con EPERM.

9. Ventajas de la virtualización de direcciones

Separar las direcciones virtuales de las físicas no es solo un truco: aporta propiedades que hacen posible el sistema moderno:

10. Probar con un simulador

Este simulador traduce una dirección virtual a física usando una tabla de páginas editable. El espacio virtual tiene 16 páginas de 4096 bytes (0x0000–0xFFFF) y la RAM tiene 16 marcos. Edita los marcos en la tabla y observa cómo cambia la traducción. También puedes provocar un fallo marcando una página como no presente.

Simulador · traducción virtual → física

CampoValor
Número de página virtual
Desplazamiento
Marco físico asignado
Bit presente
Dirección física resultante

Tabla de páginas (edita el marco o desmarca "presente"):

11. Mirar la traducción en Linux

Linux expone el mapa virtual de un proceso y, con privilegios, la traducción a marcos físicos:

terminal · el espacio virtual y la traducción
$ cat /proc/self/maps | head -20
55a1b3c01000-55a1b3c02000 r-xp 00000000 08:01 1310734 /home/ana/prog
55a1b3c02000-55a1b3c03000 r--p 00001000 08:01 1310734 /home/ana/prog
55a1b3c03000-55a1b3c04000 rw-p 00002000 08:01 1310734 /home/ana/prog
55a1b3c04000-55a1b3c05000 rw-p 00000000 00:00 0
55a1b3c06000-55a1b3c07000 rw-p 00000000 00:00 0       [heap]
7ffd8a3a3000-7ffd8a3c4000 rw-p 00000000 00:00 0       [stack]
7ffd8a3f6000-7ffd8a3f8000 r-xp 00000000 00:00 0       [vdso]
$ getconf PAGESIZE
4096
$ grep -E 'Virtual|Physical|Swap' /proc/meminfo | head
...
$ sudo cat /proc/$$/pagemap | head -c 64 | xxd
(salida binaria de 8 bytes por página)

12. Resumen

13. Preguntas de repaso

Intenta responder cada una antes de abrir la respuesta.

1. ¿Qué es una dirección física y quién la genera?

Es la posición real de un byte en la RAM (o en un dispositivo). La genera el hardware al acceder a la memoria; el programa no puede nombrarla directamente.

2. ¿Qué es una dirección virtual?

Es la posición que usa el programa para referirse a una zona de memoria. El procesador la traduce en cada acceso a una dirección física usando las tablas del proceso.

3. ¿Por qué un programa no puede leer la memoria de otro?

Porque cada proceso tiene su propio espacio de direcciones virtuales y la MMU rechaza cualquier acceso fuera de él. La dirección virtual del otro proceso no existe en el espacio del primero.

4. ¿Interviene el sistema operativo en cada acceso a memoria?

No. La traducción la hace la MMU en hardware. El núcleo solo interviene en los casos excepcionales (página ausente, permiso denegado, creación y destrucción de procesos).

5. ¿Qué muestra /proc/PID/maps?

El mapa del espacio de direcciones virtuales del proceso: rangos de direcciones, permisos, desplazamiento en el archivo de origen y el archivo o región (código, datos, heap, pila, mmap). No muestra direcciones físicas.

6. ¿Qué es un PFN?

El número de marco físico (page frame number): el índice del bloque de 4096 bytes en la RAM al que se traduce una página virtual. Se obtiene de /proc/PID/pagemap.

7. ¿Por qué el espacio virtual puede ser mayor que la RAM?

Porque no toda dirección virtual tiene un marco físico asignado. Solo se asigna RAM a las páginas que se usan (paginación por demanda), y el resto puede estar en disco o no existir.

8. ¿Qué ventaja da la dirección virtual para la compartición?

Permite que varias direcciones virtuales (de uno o varios procesos) apunten al mismo marco físico, compartiendo código o datos sin duplicarlos en la RAM.

Para practicar

  1. Compila y ejecuta direcciones.c varias veces. ¿Cambia la dirección del heap entre ejecuciones? ¿Y la de la pila? Relaciona con ASLR.
  2. Modifica direcciones.c para imprimir también la dirección de una variable estática y de un bloque obtenido con mmap. ¿En qué región de /proc/self/maps cae cada una?
  3. Ejecuta pagemap.c con privilegios (sudo) y sin ellos. ¿Qué error aparece sin privilegios?
  4. Ejecuta dos instancias de pagemap.c y compara los PFN de la variable x. ¿Son iguales o distintos? ¿Por qué?
  5. Usa pmap -x $$ en tu shell y localiza las regiones de código, datos, heap y pila. Compara con /proc/self/maps.
  6. Modifica el simulador para que una página mapee al mismo marco que otra. ¿Qué significa eso en términos de compartición?
  7. Investiga con readelf -l un binario propio: ¿qué segmentos (LOAD) aparecen y a qué direcciones virtuales se cargan?