08
Memoria

Swapping, VM completa y examen parcial

Virtualización de la memoria

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:

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

  1. paging-policy.py: preguntas 1–4 del cap. 22.
  2. mem.c + vmstat 1: observar si/so al pedir más memoria de la disponible (dentro del contenedor, limitar con docker run --memory).
  3. Ejercicios tipo examen de traducción de direcciones, resueltos en grupo.

Entregas

Preguntas de repaso

  1. Simule a mano OPT, FIFO y LRU para la secuencia 0 1 2 0 1 3 0 3 1 2 1 con una caché de 3 páginas.
  2. ¿Por qué LRU exacto es caro de implementar? ¿Qué aproxima el algoritmo del reloj?
  3. ¿Qué ventaja tiene la copia en escritura para fork() seguido de exec()?

Código de la semana

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

Ejemplos (2)Compilación (1)

Ejemplos

Los programas que usan los apuntes y la clase.

memtouch.cDescargar memtouch.c
// 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.cDescargar paging.c
// 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.

MakefileDescargar Makefile
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