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/freey 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:
- Cap. 12 Diálogo sobre memoria
- Cap. 13 The Abstraction: Address Spaces · código
- Cap. 14 Interlude: Memory API
Temas:
- Código, heap y pila (stack). Direcciones virtuales vs. físicas.
malloc,free,calloc,realloc.brk/sbrkymmap.- 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:
- Cap. 15 Mechanism: Address Translation
- Cap. 16 Segmentation
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
- Lectura guiada de
kernel/proc.cen xv6-riscv:struct proc,scheduler(),sched(),yield(),kfork(), y de cómo la interrupción del temporizador llama ayield()enkernel/trap.c. - Agregar un
printken el planificador y observar el orden de ejecución con varios procesos. lottery.pyymulti.py: preguntas seleccionadas de los cap. 9 y 10.- Arrancar P2.
Entregas
- P2 publicado: Planificador por lotería en xv6-riscv. Entrega en semana 8.
Preguntas de repaso
- 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?
- ¿Qué problema de base y límite resuelve la segmentación? ¿Qué problema nuevo introduce?
- 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
Los programas que usan los apuntes y la clase.
// 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.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.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.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.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.
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