Semana 8 — Swapping, VM completa y examen parcial
Objetivos
- Explicar cómo el OS usa el disco para ofrecer más memoria de la que hay: fallo de página (page fault) y espacio de intercambio (swap space).
- Comparar políticas de reemplazo: óptima (OPT), FIFO, aleatoria, LRU y aproximaciones (clock).
- Relacionar todo lo visto con VAX/VMS y Linux.
Sesión A — Swapping · VM completa
Lecturas:
- Cap. 21 Beyond Physical Memory: Mechanisms
- Cap. 22 Beyond Physical Memory: Policies
- Cap. 23 Complete Virtual Memory Systems
- Cap. 24 Resumen de memoria
Temas:
- Bit presente, manejo del fallo de página, marcas de agua (high/low watermark), demonio de intercambio (swap daemon).
- Fallos compulsivos, de capacidad y de conflicto (las 3 C). Anomalía de Belady.
- LRU, algoritmo del reloj (clock) y páginas sucias. Thrashing.
- VAX/VMS: segmentación + paginación, copia en escritura (copy-on-write), demand zeroing.
- Linux: espacio del kernel, páginas enormes (huge pages), page cache, KPTI.
Sesión B — Examen parcial
Temas: semanas 1 a 8 (virtualización de la CPU y de la memoria). Ver exams/README.md.
Laboratorio — Repaso y mem.c
paging-policy.py: preguntas 1–4 del cap. 22.mem.c+vmstat 1: observarsi/soal pedir más memoria de la disponible (dentro del contenedor, limitar condocker run --memory).- Ejercicios tipo examen de traducción de direcciones, resueltos en grupo.
Entregas
- P2 entrega.
- P3 publicado: Tablas de páginas en xv6 (lab
pgtbldel MIT). Entrega en semana 10.
Preguntas de repaso
- Simule a mano OPT, FIFO y LRU para la secuencia
0 1 2 0 1 3 0 3 1 2 1con una caché de 3 páginas. - ¿Por qué LRU exacto es caro de implementar? ¿Qué aproxima el algoritmo del reloj?
- ¿Qué ventaja tiene la copia en escritura para
fork()seguido deexec()?
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.
// memtouch.c — presión de memoria: reserva N MB y los recorre varias veces,
// escribiendo un byte en cada página. Por cada pasada imprime el tiempo, la
// velocidad y cuántos fallos de página hubo (menores: sin disco; mayores:
// hubo que leer la página del disco, por ejemplo desde el swap).
//
// Uso: ./memtouch MB [pasadas] [huge]
// pasadas cuántas veces se recorre la memoria (por defecto 4)
// huge permitir páginas grandes (THP); sin él se piden páginas de
// 4 KB para que cada página cuente como un fallo
//
// Para verlo hacer swap, dentro de un contenedor con poca memoria:
// docker run --rm -it --memory=256m -v "$PWD":/work -w /work sistemas-operativos bash
// ./memtouch 200 & vmstat 1 # cabe: sin si/so
// ./memtouch 400 & vmstat 1 # no cabe: si/so distintos de 0
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/resource.h>
#include <time.h>
#include <unistd.h>
static double now_s(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec + ts.tv_nsec / 1e9;
}
int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "uso: %s MB [pasadas] [huge]\n", argv[0]);
return 1;
}
long mb = atol(argv[1]);
int passes = (argc > 2) ? atoi(argv[2]) : 4;
int huge = (argc > 3) && strcmp(argv[3], "huge") == 0;
long page = sysconf(_SC_PAGESIZE);
size_t size = (size_t)mb << 20;
if (mb <= 0 || passes <= 0) {
fprintf(stderr, "MB y pasadas deben ser positivos\n");
return 1;
}
// mmap (lo mismo que hace malloc con bloques grandes) solo reserva
// direcciones: la memoria física llega con el primer acceso a cada
// página, en un fallo de página.
char *buf = mmap(NULL, size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (buf == MAP_FAILED) {
perror("mmap");
return 1;
}
madvise(buf, size,
huge ? MADV_HUGEPAGE : MADV_NOHUGEPAGE);
printf("PID %d: %ld MB, páginas de %ld bytes, %zu páginas\n", getpid(),
mb, page, size / page);
printf("%7s %9s %9s %10s %10s\n", "pasada", "segundos", "MB/s",
"f.menores", "f.mayores");
fflush(stdout);
struct rusage before, after;
for (int p = 1; p <= passes; p++) {
getrusage(RUSAGE_SELF, &before);
double t0 = now_s();
for (size_t i = 0; i < size; i += page)
buf[i] = (char)p; // queda sucia
double dt = now_s() - t0;
getrusage(RUSAGE_SELF, &after);
printf("%7d %9.3f %9.0f %10ld %10ld\n", p, dt, mb / dt,
after.ru_minflt - before.ru_minflt,
after.ru_majflt - before.ru_majflt);
fflush(stdout);
}
munmap(buf, size);
return 0;
}// paging.c — simulador de políticas de reemplazo de páginas.
//
// Recorre una secuencia de referencias a páginas con una memoria de N marcos
// y dice, para cada referencia, si fue acierto o fallo, qué página se expulsó
// y cómo quedaron los marcos. Sirve para verificar a mano los ejemplos de la
// semana 8 (OPT, FIFO, aleatoria, LRU y reloj).
//
// Uso:
// ./paging [-p política] [-n marcos] [-s semilla] ref ref ...
// ./paging -p all -n 3 ref ... resumen de todas las políticas
// ./paging -p fifo -c 5 ref ... fallos con 1, 2, ..., 5 marcos
// ./paging -p rand -t 10000 ref ... distribución de fallos de RAND
// Políticas: opt, fifo, rand, lru, clock (por defecto: fifo).
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define MAX_FRAMES 64
#define MAX_REFS 4096
#define EMPTY (-1)
enum policy { OPT, FIFO, RAND, LRU, CLOCK, NPOLICIES };
static const char *names[NPOLICIES] = {"opt", "fifo", "rand", "lru", "clock"};
struct sim {
enum policy pol;
int nframes;
int frame[MAX_FRAMES]; // página en cada marco, o EMPTY
long loaded[MAX_FRAMES]; // instante en que entró la página (FIFO)
long used[MAX_FRAMES]; // instante del último uso (LRU)
int refbit[MAX_FRAMES]; // bit de referencia (reloj)
int hand; // manecilla del reloj
};
static int refs[MAX_REFS];
static int nrefs;
static int find(const struct sim *s, int page) {
for (int i = 0; i < s->nframes; i++)
if (s->frame[i] == page)
return i;
return -1;
}
// Elige el marco víctima. Solo se llama cuando no hay marcos libres.
static int victim(struct sim *s, int now) {
int v = 0;
switch (s->pol) {
case OPT: {
// La página cuyo próximo uso está más lejos (o que no se vuelve a
// usar). Empate entre páginas que no se usan más: la más antigua.
long best = -1;
for (int i = 0; i < s->nframes; i++) {
long next = MAX_REFS + 1;
for (int t = now + 1; t < nrefs; t++)
if (refs[t] == s->frame[i]) {
next = t;
break;
}
if (next > best || (next == best && s->loaded[i] < s->loaded[v])) {
best = next;
v = i;
}
}
break;
}
case FIFO: // la que entró primero
for (int i = 1; i < s->nframes; i++)
if (s->loaded[i] < s->loaded[v])
v = i;
break;
case LRU: // la usada hace más tiempo
for (int i = 1; i < s->nframes; i++)
if (s->used[i] < s->used[v])
v = i;
break;
case RAND:
v = rand() % s->nframes;
break;
case CLOCK: // avanzar la manecilla, dando segunda oportunidad
while (s->refbit[s->hand] == 1) {
s->refbit[s->hand] = 0;
s->hand = (s->hand + 1) % s->nframes;
}
v = s->hand;
break;
default:
break;
}
return v;
}
// Espacios que faltan para que s ocupe width columnas (UTF-8: los bytes de
// continuación no ocupan columna).
static int pad(const char *s, int width) {
int cols = 0;
for (; *s; s++)
if ((*s & 0xC0) != 0x80)
cols++;
return cols < width ? width - cols : 0;
}
// Escribe en buf el estado de los marcos, en orden de marco.
static void show_frames(const struct sim *s, char *buf, size_t len) {
size_t off = 0;
off += snprintf(buf + off, len - off, "[");
for (int i = 0; i < s->nframes && off < len; i++) {
const char *sep = (i == 0) ? "" : " ";
if (s->frame[i] == EMPTY)
off += snprintf(buf + off, len - off, "%s-", sep);
else if (s->pol == CLOCK)
off += snprintf(buf + off, len - off, "%s%d%s%d", sep, s->frame[i],
(i == s->hand) ? "<" : ":", s->refbit[i]);
else
off += snprintf(buf + off, len - off, "%s%d", sep, s->frame[i]);
}
if (off < len)
snprintf(buf + off, len - off, "]");
}
// Simula la política sobre refs[]. Si verbose, imprime una fila por
// referencia. Retorna el número de fallos.
static int run(enum policy pol, int nframes, int verbose) {
struct sim s = {.pol = pol, .nframes = nframes, .hand = 0};
int misses = 0, cold = 0;
char seen[MAX_REFS] = {0}; // páginas ya vistas (para fallos compulsivos)
char buf[256];
for (int i = 0; i < nframes; i++)
s.frame[i] = EMPTY;
if (verbose) {
printf("política %s, %d marcos%s\n", names[pol], nframes,
pol == CLOCK ? " (pág:bit, < = manecilla)" : "");
printf("%4s %-14s %7s %s\n", "ref", "resultado", "expulsa", "marcos");
}
for (int t = 0; t < nrefs; t++) {
int page = refs[t];
int f = find(&s, page);
int evicted = EMPTY;
int compulsory = 0, hit = (f >= 0);
if (hit) { // acierto
s.used[f] = t;
s.refbit[f] = 1;
} else { // fallo: buscar un marco libre o elegir una víctima
misses++;
if (page >= 0 && page < MAX_REFS && !seen[page])
compulsory = 1, cold++;
f = find(&s, EMPTY);
if (f < 0) {
f = victim(&s, t);
evicted = s.frame[f];
} else if (pol == CLOCK) {
s.hand = f; // los marcos libres se llenan en orden
}
s.frame[f] = page;
s.loaded[f] = t;
s.used[f] = t;
s.refbit[f] = 1;
if (pol == CLOCK)
s.hand = (f + 1) % nframes;
}
if (page >= 0 && page < MAX_REFS)
seen[page] = 1;
if (verbose) {
show_frames(&s, buf, sizeof(buf));
char ev[16] = "-";
if (evicted != EMPTY)
snprintf(ev, sizeof(ev), "%d", evicted);
const char *what =
hit ? "acierto" : (compulsory ? "fallo (frío)" : "fallo");
printf("%4d %s%*s %7s %s\n", page, what, pad(what, 14), "",
ev, buf);
}
}
if (verbose)
printf("aciertos %d, fallos %d (%d fríos), tasa de aciertos %.1f%%\n",
nrefs - misses, misses, cold,
100.0 * (nrefs - misses) / nrefs);
return misses;
}
static void usage(const char *prog) {
fprintf(stderr,
"uso: %s [-p opt|fifo|rand|lru|clock|all] [-n marcos] "
"[-s semilla] [-c max_marcos] [-t intentos] ref ...\n",
prog);
exit(1);
}
int main(int argc, char *argv[]) {
int nframes = 3, curve = 0, trials = 0, all = 0;
unsigned seed = 1;
enum policy pol = FIFO;
int opt;
while ((opt = getopt(argc, argv, "p:n:s:c:t:")) != -1) {
switch (opt) {
case 'p':
if (strcmp(optarg, "all") == 0) {
all = 1;
break;
}
pol = NPOLICIES;
for (int i = 0; i < NPOLICIES; i++)
if (strcmp(optarg, names[i]) == 0)
pol = i;
if (pol == NPOLICIES)
usage(argv[0]);
break;
case 'n': nframes = atoi(optarg); break;
case 's': seed = (unsigned)atoi(optarg); break;
case 'c': curve = atoi(optarg); break;
case 't': trials = atoi(optarg); break;
default: usage(argv[0]);
}
}
if (nframes < 1 || nframes > MAX_FRAMES || curve > MAX_FRAMES ||
optind >= argc)
usage(argv[0]);
for (int i = optind; i < argc && nrefs < MAX_REFS; i++)
refs[nrefs++] = atoi(argv[i]);
printf("referencias:");
for (int i = 0; i < nrefs; i++)
printf(" %d", refs[i]);
printf("\n");
if (all) { // una línea por política
printf("%d marcos\n%-6s %8s %6s %8s\n", nframes, "", "aciertos",
"fallos", "tasa");
for (int p = 0; p < NPOLICIES; p++) {
srand(seed);
int m = run(p, nframes, 0);
printf("%-6s %8d %6d %7.1f%%\n", names[p], nrefs - m, m,
100.0 * (nrefs - m) / nrefs);
}
} else if (curve > 0) { // fallos según el número de marcos
printf("política %s\n%6s %6s\n", names[pol], "marcos", "fallos");
for (int n = 1; n <= curve; n++) {
srand(seed);
printf("%6d %6d\n", n, run(pol, n, 0));
}
} else if (trials > 0) { // distribución de fallos con RAND
int hist[MAX_REFS + 1] = {0};
double sum = 0;
for (int t = 0; t < trials; t++) {
srand(seed + t);
int m = run(RAND, nframes, 0);
hist[m]++;
sum += m;
}
printf("rand, %d marcos, %d intentos: fallos promedio %.2f\n",
nframes, trials, sum / trials);
printf("%6s %8s\n", "fallos", "veces");
for (int m = 0; m <= nrefs; m++)
if (hist[m] > 0)
printf("%6d %8d\n", m, hist[m]);
} else {
srand(seed);
run(pol, nframes, 1);
}
return 0;
}Compilación
Cómo se compilan y ejecutan los ejemplos.
CFLAGS = -Wall -Wextra -g
PROGS = paging memtouch
# Secuencias de la semana 8
REFS = 0 1 0 2 3 0 1 4 0 3 4 0
BELADY = 0 1 2 3 0 1 4 0 1 2 3 4
all: $(PROGS)
# Verifica los ejemplos de las diapositivas.
examples: paging
./paging -p all -n 3 $(REFS)
for p in opt fifo lru clock; do ./paging -p $$p -n 3 $(REFS); done
./paging -p rand -n 3 -t 10000 $(REFS)
./paging -p fifo -c 5 $(BELADY)
./paging -p lru -c 5 $(BELADY)
clean:
rm -f $(PROGS)
.PHONY: all examples clean