05
Memoria

Espacios de direcciones, traducción y segmentación

Virtualización de la memoria

Semana 5 — Espacios de direcciones, traducción y segmentación

Objetivos

  • Explicar la abstracción de espacio de direcciones y sus metas: transparencia, eficiencia y protección.
  • Usar correctamente malloc/free y reconocer errores comunes de memoria.
  • Traducir direcciones con base y límite (base and bounds) y con segmentación.

Sesión A — Espacios de direcciones · API de memoria

Lecturas:

Temas:

  • Código, heap y pila (stack). Direcciones virtuales vs. físicas.
  • malloc, free, calloc, realloc. brk/sbrk y mmap.
  • Errores típicos: olvidar reservar, no inicializar, fugas (memory leaks), punteros colgantes (dangling pointers), doble free.

Sesión B — Traducción de direcciones · Segmentación

Lecturas:

Temas:

  • Relocalización dinámica (dynamic relocation) con registros base y límite; MMU.
  • Rol del OS: asignar memoria, guardar y restaurar base/límite, manejar excepciones.
  • Segmentación: un par base/límite por segmento, crecimiento hacia abajo de la pila, segmentos compartidos y bits de protección.
  • Fragmentación externa (external fragmentation).

Laboratorio — xv6 por dentro: el planificador

  1. Lectura guiada de kernel/proc.c en xv6-riscv: struct proc, scheduler(), sched(), yield(), kfork(), y de cómo la interrupción del temporizador llama a yield() en kernel/trap.c.
  2. Agregar un printk en el planificador y observar el orden de ejecución con varios procesos.
  3. lottery.py y multi.py: preguntas seleccionadas de los cap. 9 y 10.
  4. Arrancar P2.

Entregas

Preguntas de repaso

  1. Con base = 16 KB y límite = 4 KB, ¿a qué dirección física corresponde la dirección virtual 1 KB? ¿Y 5 KB?
  2. ¿Qué problema de base y límite resuelve la segmentación? ¿Qué problema nuevo introduce?
  3. Escriba un programa con una fuga de memoria y muestre cómo la detecta valgrind.

Código de la semana

Compilar dentro del contenedor del curso con make o gcc -Wall.

Ejemplos (5)Compilación (1)

Ejemplos

Los programas que usan los apuntes y la clase.

doublefree.cDescargar doublefree.c
// doublefree.c — liberar dos veces el mismo bloque.
// Sin valgrind, glibc suele abortar: "free(): double free detected".
//   valgrind ./doublefree
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static void cleanup(char *buf) {
    free(buf);
}

int main(void) {
    char *buf = malloc(32);
    if (buf == NULL)
        return 1;
    strcpy(buf, "hola");
    printf("%s\n", buf);
    cleanup(buf);
    free(buf);   // segundo free del mismo bloque
    return 0;
}
layout.cDescargar layout.c
// layout.c — dónde viven el código, los datos, el heap y la pila.
// Imprime direcciones (virtuales) de cada región del espacio de direcciones.
//   ./layout              (con ASLR: cambian en cada ejecución)
//   setarch -R ./layout   (sin ASLR: siempre las mismas)
#define _DEFAULT_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>

int global_init = 42;   // datos inicializados (.data)
int global_zero;        // datos en cero (.bss)

int main(void) {
    int local = 7;                       // en la pila
    int *small = malloc(16);             // heap (vía brk)
    int *big = malloc(1 << 20);          // 1 MiB: glibc usa mmap
    void *brk_end = sbrk(0);             // fin actual del heap

    printf("código  main()       %p\n", (void *)main);
    printf("datos   global_init  %p\n", (void *)&global_init);
    printf("bss     global_zero  %p\n", (void *)&global_zero);
    printf("heap    malloc(16)   %p\n", (void *)small);
    printf("heap    sbrk(0)      %p\n", brk_end);
    printf("mmap    malloc(1MiB) %p\n", (void *)big);
    printf("pila    local        %p\n", (void *)&local);

    free(big);
    free(small);
    return 0;
}
leak.cDescargar leak.c
// leak.c — fuga de memoria (memory leak).
// Reserva 10 bloques de 64 bytes y nunca los libera.
//   valgrind --leak-check=full ./leak
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static char *make_label(int i) {
    char *s = malloc(64);
    if (s == NULL)
        exit(1);
    snprintf(s, 64, "etiqueta %d", i);
    return s;
}

int main(void) {
    for (int i = 0; i < 10; i++) {
        char *label = make_label(i);
        printf("%s\n", label);
        // falta free(label): el puntero se pierde en cada vuelta
    }
    return 0;
}
uaf.cDescargar uaf.c
// uaf.c — uso después de liberar (use-after-free).
// p queda como puntero colgante (dangling pointer) tras free().
//   valgrind ./uaf
#include <stdio.h>
#include <stdlib.h>

struct account {
    int id;
    int balance;
};

static void close_account(struct account *a) {
    free(a);   // libera, pero el llamador conserva el puntero
}

int main(void) {
    struct account *a = malloc(sizeof(*a));
    if (a == NULL)
        return 1;
    a->id = 1;
    a->balance = 100;
    close_account(a);
    printf("saldo: %d\n", a->balance);   // lectura de memoria liberada
    return 0;
}
uninit.cDescargar uninit.c
// uninit.c — lectura de memoria no inicializada.
// malloc() no pone la memoria en cero (calloc() sí).
//   valgrind --track-origins=yes ./uninit
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int n = 8;
    int *v = malloc(n * sizeof(int));
    if (v == NULL)
        return 1;
    for (int i = 0; i < n - 1; i++)   // error: falta v[n-1]
        v[i] = i;

    int sum = 0;
    for (int i = 0; i < n; i++)
        sum += v[i];
    if (sum > 28)                      // la decisión depende de basura
        printf("suma grande: %d\n", sum);
    else
        printf("suma: %d\n", sum);
    free(v);
    return 0;
}

Compilación

Cómo se compilan y ejecutan los ejemplos.

MakefileDescargar Makefile
CFLAGS = -Wall -Wextra -g
PROGS  = layout leak uaf uninit doublefree
BUGS   = leak uaf uninit doublefree
VG     = valgrind --leak-check=full --track-origins=yes

all: $(PROGS)

# Ejecuta cada programa con errores bajo valgrind. Algunos abortan
# sin valgrind (doublefree), por eso se ignora el código de salida.
check: $(BUGS)
	@for p in $(BUGS); do echo "=== $$p ==="; $(VG) ./$$p; true; done

clean:
	rm -f $(PROGS) vgcore.*

.PHONY: all check clean