07
Memoria

TLB y tablas de páginas avanzadas

Virtualización de la memoria

Semana 7 — TLB y tablas de páginas avanzadas

Objetivos

  • Explicar cómo la TLB acelera la traducción y qué pasa en un fallo de TLB (TLB miss).
  • Reducir el tamaño de las tablas de páginas con páginas grandes, enfoques híbridos y tablas multinivel (multi-level page tables).

Sesión A — TLB

Lecturas:

Temas:

  • Algoritmo de control con TLB. Localidad espacial y temporal.
  • TLB manejada por hardware (x86) vs. por software (MIPS).
  • Cambios de contexto: vaciar la TLB (flush) vs. ASID.
  • Política de reemplazo de entradas. Ejemplo de una entrada de TLB real (MIPS R4000).

Sesión B — Tablas de páginas avanzadas

Lecturas:

Temas:

  • Páginas más grandes y su costo.
  • Híbrido paginación + segmentación.
  • Tablas multinivel: directorio de páginas (page directory), traducción en dos y más niveles.
  • Tablas invertidas (inverted page tables). Intercambio tiempo/espacio.

Laboratorio — Paginación y medición de la TLB

  1. paging-linear-translate.py: preguntas 1–5 del cap. 18.
  2. Tarea de código del cap. 19: programa tlb.c que mide el costo de acceso por página al recorrer N páginas; graficar y estimar el tamaño de la TLB.
  3. paging-multilevel-translate.py: preguntas 1–3 del cap. 20.

Preguntas de repaso

  1. Un arreglo de 10 enteros cruza 3 páginas. ¿Cuántos aciertos y fallos de TLB hay al recorrerlo la primera vez? ¿Y la segunda?
  2. ¿Por qué una tabla de dos niveles ahorra memoria cuando el espacio de direcciones es disperso (sparse)?
  3. ¿Cuántos accesos a memoria cuesta un fallo de TLB con una tabla de tres niveles?

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.

ptcalc.cDescargar ptcalc.c
// ptcalc.c — calculadora de tablas de páginas multinivel.
//
// Uso:   ./ptcalc bits_va tamaño_página tamaño_pte dirección...
//
// Con los parámetros de la máquina calcula cuántos niveles hacen falta para
// que cada nodo de la tabla quepa en una página, separa cada dirección en sus
// índices y compara cuánta memoria ocupa la tabla lineal frente a la
// multinivel si el proceso solo usa las páginas de las direcciones dadas.
//
// Ejemplos:
//   ./ptcalc 32 4096 4 0x00403A7C              x86 de 32 bits (10/10/12)
//   ./ptcalc 39 4096 8 0x80604567              RISC-V Sv39 (9/9/9/12)
//   ./ptcalc 15 32 1 0x611c                    simulador del cap. 20 (5/5/5)
#include <stdio.h>
#include <stdlib.h>

#define MAXLEVELS 8
#define MAXADDRS  64

static int log2_exact(unsigned long x) {
    int n = 0;
    while ((1UL << n) < x)
        n++;
    return (1UL << n) == x ? n : -1;
}

static void print_size(unsigned long long bytes) {
    if (bytes >= (1ULL << 30))
        printf("%llu GB", bytes >> 30);
    else if (bytes >= (1ULL << 20))
        printf("%llu MB", bytes >> 20);
    else if (bytes >= (1ULL << 10))
        printf("%llu KB", bytes >> 10);
    else
        printf("%llu B", bytes);
}

int main(int argc, char *argv[]) {
    if (argc < 5) {
        fprintf(stderr, "uso: %s bits_va tamaño_página tamaño_pte dirección...\n", argv[0]);
        return 1;
    }
    int va_bits = atoi(argv[1]);
    unsigned long page = strtoul(argv[2], NULL, 0);
    unsigned long pte = strtoul(argv[3], NULL, 0);
    int off_bits = log2_exact(page);
    int pte_bits = log2_exact(pte);
    if (va_bits < 1 || va_bits > 64 || off_bits < 0 || pte_bits < 0 || pte > page) {
        fprintf(stderr, "parámetros inválidos (página y PTE deben ser potencias de 2)\n");
        return 1;
    }

    // Geometría: una página de tabla guarda page/pte entradas, así que cada
    // nivel consume idx_bits bits del VPN. El nivel de arriba se queda con lo
    // que sobra (puede tener menos bits).
    int vpn_bits = va_bits - off_bits;
    int idx_bits = off_bits - pte_bits;
    int levels = (vpn_bits + idx_bits - 1) / idx_bits;
    if (levels > MAXLEVELS) {
        fprintf(stderr, "demasiados niveles\n");
        return 1;
    }
    int bits[MAXLEVELS]; // bits[0] = nivel de arriba (el directorio)
    for (int l = 0; l < levels; l++)
        bits[l] = idx_bits;
    bits[0] = vpn_bits - idx_bits * (levels - 1);

    printf("VA de %d bits, páginas de %lu B, PTE de %lu B\n", va_bits, page, pte);
    printf("offset: %d bits   VPN: %d bits   %lu PTE por página\n", off_bits, vpn_bits,
           page / pte);
    printf("niveles: %d   bits por índice:", levels);
    for (int l = 0; l < levels; l++)
        printf(" %d", bits[l]);
    printf("\n");
    printf("tabla lineal: 2^%d PTE x %lu B = ", vpn_bits, pte);
    print_size((1ULL << vpn_bits) * pte);
    printf("\n\n");

    // Separar cada dirección en sus índices.
    int n = 0;
    unsigned long long vpn[MAXADDRS];
    for (int a = 4; a < argc && n < MAXADDRS; a++, n++) {
        unsigned long long va = strtoull(argv[a], NULL, 0);
        if (va_bits < 64 && (va >> va_bits) != 0) {
            fprintf(stderr, "%s no cabe en %d bits\n", argv[a], va_bits);
            return 1;
        }
        vpn[n] = va >> off_bits;
        printf("0x%llx -> ", va);
        int shift = vpn_bits;
        for (int l = 0; l < levels; l++) {
            shift -= bits[l];
            printf("L%d=0x%llx ", levels - 1 - l, (vpn[n] >> shift) & ((1ULL << bits[l]) - 1));
        }
        printf("offset=0x%llx\n", va & (page - 1));
    }

    // ¿Cuántas páginas de tabla hacen falta para mapear solo esas páginas?
    // En el nivel l hay un nodo por cada prefijo distinto del VPN de largo
    // (bits de los niveles anteriores). El directorio de arriba siempre existe.
    unsigned long long nodes = 0;
    int prefix = 0;
    for (int l = 0; l < levels; l++) {
        unsigned long long seen[MAXADDRS];
        int count = 0;
        for (int i = 0; i < n; i++) {
            unsigned long long p = prefix ? vpn[i] >> (vpn_bits - prefix) : 0;
            int dup = 0;
            for (int j = 0; j < count; j++)
                dup |= (seen[j] == p);
            if (!dup)
                seen[count++] = p;
        }
        if (count == 0)
            count = 1;
        printf("%snivel %d: %d página(s)\n", l == 0 ? "\n" : "", levels - 1 - l, count);
        nodes += count;
        prefix += bits[l];
    }
    printf("multinivel: %llu páginas x %lu B = ", nodes, page);
    print_size(nodes * page);
    printf("\n");
    printf("fallo de TLB: %d accesos a la tabla + 1 al dato = %d accesos a memoria\n",
           levels, levels + 1);
    return 0;
}
run-tlb.shDescargar run-tlb.sh
#!/bin/bash
# run-tlb.sh — ejecuta ./tlb para 1, 2, 4, ... páginas, con páginas de 4 KB
# y pidiendo páginas grandes (THP), y muestra los resultados en una tabla.
# La última columna dice cuántos KB quedaron de verdad en páginas grandes:
# una región de menos de 2 MB no puede usarlas.
#
# Uso:   ./run-tlb.sh [máximo_de_páginas]     (por defecto 32768)
set -e
MAX=${1:-32768}
ACCESSES=10000000   # accesos por medición: ~10^7 para que el reloj sea preciso

printf "%7s %10s %8s %8s %10s\n" "páginas" "memoria" "ns 4KB" "ns THP" "KB en THP"
p=1
while [ "$p" -le "$MAX" ]; do
    trials=$(( ACCESSES / p ))
    [ "$trials" -lt 10 ] && trials=10
    read -r _ small _ < <(./tlb "$p" "$trials")
    read -r _ big hugekb < <(./tlb "$p" "$trials" huge)
    printf "%7d %7d KB %8s %8s %10s\n" "$p" $(( p * 4 )) "$small" "$big" "$hugekb"
    p=$(( p * 2 ))
done

Compilación

Cómo se compilan y ejecutan los ejemplos.

MakefileDescargar Makefile
CFLAGS = -Wall -Wextra -g
PROGS  = tlb ptcalc
# Las soluciones aún no publicadas (code/.ocultos) no están en el repositorio:
# se omiten los programas sin fuente y sus líneas en las pruebas.
PROGS := $(foreach p,$(PROGS),$(if $(wildcard $(p).c),$(p)))
RUN    = $(if $(wildcard $(1).c),./$(1),@: sin $(1).c)

all: $(PROGS)

# -O2 reduce el costo del bucle; los accesos son volatile y no se eliminan.
tlb: CFLAGS += -O2

# Tabla de tiempos por acceso (tarda alrededor de un minuto).
bench: tlb
	./run-tlb.sh

clean:
	rm -f $(PROGS)

.PHONY: all bench clean