1. El problema: no todos los fallos son iguales
En el tema 29 vimos que un fallo de página ocurre cuando se accede a una página no presente. Pero hay dos tipos: el fallo menor (la página ya está en la RAM) y el fallo mayor (hay que leer del disco). La diferencia en costo es de 1000×. Un programa con 1000 fallos menores tarda ~10 ms; con 1000 fallos mayores, ~10 s.
Saber cuántos fallos de cada tipo tiene un programa es esencial para optimizarlo.
2. Repaso: fallo de página
Un fallo de página ocurre cuando la MMU detecta que una página no está presente (P=0) y lanza una excepción. El SO carga la página en la RAM y reanuda la instrucción (tema 29). La diferencia entre menor y mayor está en dónde está la página antes del fallo.
3. Fallo menor (minor page fault)
Un fallo menor ocurre cuando la página ya está en la RAM pero no está mapeada en el proceso. El SO solo necesita actualizar la tabla de páginas, sin leer del disco.
Casos típicos:
- Primera escritura en una página anónima: la página se pone a cero en la RAM (no se lee del disco).
- Copy-on-write (fork): la página se comparte entre padre e hijo hasta que uno escribe; entonces se copia.
- Páginas de bibliotecas compartidas: ya cargadas por otro proceso.
- Páginas de código del ejecutable: ya cargadas por el cargador.
Costo: ~1-10 microsegundos (actualizar la tabla + posiblemente poner a cero la página).
Los fallos menores son normales y esperados: ocurren en la primera escritura a cada página.
4. Fallo mayor (major page fault)
Un fallo mayor ocurre cuando la página no está en la RAM y hay que leerla del disco:
- La página está en el swap: se intercambió a disco por falta de RAM.
- La página es de un archivo mapeado (mmap): no se ha cargado aún.
- La página es de código o datos del ejecutable: no se ha cargado (carga por demanda).
Costo: ~1-10 milisegundos (lectura del disco). Es 1000× más lento que un fallo menor.
Los fallos mayores son señal de un problema: indican que el sistema está leyendo del disco con frecuencia.
5. Comparación detallada
| Aspecto | Fallo menor | Fallo mayor |
|---|---|---|
| La página está en la RAM | Sí | No |
| Requiere lectura del disco | No | Sí |
| Costo típico | 1-10 µs | 1-10 ms |
| Frecuencia esperada | Alta (normal) | Baja (debería ser rara) |
| Causa típica | Primera escritura, COW, bibliotecas compartidas | Swap, mmap de archivo, carga de código |
| Señal de problema | No | Sí (hiperpaginación o carga ineficiente) |
6. Medir con getrusage
La función getrusage(RUSAGE_SELF, &ru) devuelve estadísticas del proceso, incluyendo el número de fallos menores (ru_minflt) y mayores (ru_majflt).
#include <sys/resource.h>
#include <stdio.h>
struct rusage ru;
getrusage(RUSAGE_SELF, &ru);
printf("Fallos menores: %ld\\n", ru.ru_minflt);
printf("Fallos mayores: %ld\\n", ru.ru_majflt);printf("Fallos mayores: %ld\\n", ru.ru_majflt);
7. Otros métodos de medición
Además de getrusage, hay otras formas de medir los fallos:
| Herramienta | Qué mide | Cómo se usa |
|---|---|---|
| perf stat | Fallos por programa (page-faults, major-faults). | perf stat -e page-faults,major-faults ./prog |
| /proc/vmstat | Contadores globales del sistema (pgfault, pgmajfault). | grep pgfault /proc/vmstat |
| /proc/PID/stat | Fallos del proceso (minflt, majflt en campos 10 y 12). | cat /proc/PID/stat |
| Valgrind | Fallos de página y acceso a memoria. | valgrind ./prog |
8. Experimento en C
El programa siguiente mide los fallos menores y mayores de distintas operaciones:
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <sys/resource.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
static void medir(const char *nombre)
{
struct rusage ru;
getrusage(RUSAGE_SELF, &ru);
printf("%-22s minflt=%ld majflt=%ld\\n", nombre, ru.ru_minflt, ru.ru_majflt);
}
int main(void)
{
medir("inicio");
/* 1. malloc sin tocar: no genera fallos */
char *p = malloc(1 << 20);
medir("tras malloc");
/* 2. Tocar las páginas: fallos menores */
for (size_t i = 0; i < (1 << 20); i += 4096) p[i] = 1;
medir("tras tocar");
/* 3. mmap de archivo: fallos mayores al leer */
int fd = open("/etc/passwd", O_RDONLY);
char *m = mmap(NULL, 4096, PROT_READ, MAP_PRIVATE, fd, 0);
for (int i = 0; i < 4096; i++) (void)m[i];
medir("tras mmap leer");
munmap(m, 4096); close(fd);
free(p);
medir("final");
return 0;
}
$ gcc -std=c17 -Wall -Wextra -o fallos fallos.c
$ ./fallos
inicio minflt=12 majflt=0
tras malloc minflt=12 majflt=0
tras tocar minflt=268 majflt=0
tras mmap leer minflt=269 majflt=1
final minflt=269 majflt=1
Los resultados muestran:
- malloc sin tocar: no genera fallos (solo reserva espacio).
- Tocar las páginas: 256 fallos menores (1 MiB / 4 KiB = 256 páginas), sin fallos mayores.
- mmap de archivo: 1 fallo menor (mapear) + 1 fallo mayor (leer del disco).
9. Interpretar los resultados
Los fallos menores son normales y esperados: ocurren en la primera escritura a una página. Los fallos mayores son los que hay que vigilar: indican que se está leyendo del disco.
Un programa bien diseñado tiene muchos fallos menores y pocos (o cero) fallos mayores. Muchos fallos mayores indican:
- El sistema está usando swap (hiperpaginación).
- Se están mapeando archivos grandes sin prefetch.
- La RAM es insuficiente para la carga de trabajo.
La relación minflt/majflt es un indicador de salud: si majflt crece, hay un problema de memoria.
10. Cómo reducir los fallos menores
- Reutilizar memoria: evitar crear y destruir datos repetidamente.
- Pool de memoria: pre-asignar bloques y reutilizarlos.
- Evitar fork innecesario: fork genera COW y fallos menores al escribir.
- Memoria compartida: usar shm para evitar duplicar páginas.
11. Cómo reducir los fallos mayores
- Prefetching: leer datos por adelantado (madvise con MADV_WILLNEED).
- Memoria suficiente: asegurar que la RAM sea suficiente para la carga de trabajo.
- Evitar swap: desactivar el swap o usar mlock para páginas críticas.
- Archivos mapeados: usar madvise para indicar el patrón de acceso (MADV_SEQUENTIAL, MADV_RANDOM).
- Leer secuencialmente: el kernel prefetcha automáticamente en accesos secuenciales.
12. Ejemplo numérico: costo total
Un programa tiene 10000 fallos menores y 50 fallos mayores. Costos típicos: fallo menor = 5 µs, fallo mayor = 5 ms.
- Costo de fallos menores: 10000 × 5 µs = 50 ms.
- Costo de fallos mayores: 50 × 5 ms = 250 ms.
- Costo total: 300 ms.
Los fallos mayores (solo 50) contribuyen 5× más al tiempo total que los 10000 fallos menores. Esto ilustra por qué los fallos mayores son los que hay que vigilar.
13. Probar con un simulador
Este simulador clasifica un escenario como fallo menor o mayor y explica por qué.
Simulador · clasificar fallos
14. En Linux
$ grep -E 'pgfault|pgmajfault' /proc/vmstat
$ perf stat -e page-faults,major-faults ./fallos
/proc/vmstat: contadores globales de fallos (pgfault, pgmajfault).perf stat -e page-faults,major-faults: cuenta por programa.getrusage: mide en el propio programa (ru_minflt, ru_majflt)./proc/PID/stat: campos 10 (minflt) y 12 (majflt).
15. Resumen
- Los fallos menores se resuelven sin disco (página ya en RAM).
- Los fallos mayores requieren leer del disco (swap o archivo).
- Los fallos menores son normales; los mayores hay que vigilarlos.
getrusage,perfy/procmiden los fallos.- Prefetching, reutilización y memoria suficiente reducen los fallos.
- Unos pocos fallos mayores pueden dominar el tiempo total.
16. Preguntas de repaso
Intenta responder cada una antes de abrir la respuesta.
1. ¿Qué es un fallo menor?
Un fallo en el que la página está en la RAM pero no mapeada en el proceso.
2. ¿Qué es un fallo mayor?
Un fallo que requiere leer la página desde el disco.
3. ¿Cuál es más costoso?
El fallo mayor: miles de veces más lento (ms vs µs).
4. ¿Qué campos de getrusage miden los fallos?
ru_minflt (menores) y ru_majflt (mayores).
5. ¿Qué indica un alto número de fallos mayores?
Que el sistema está usando swap o leyendo archivos del disco con frecuencia.
6. ¿Cómo se reduce un fallo mayor por mmap?
Con madvise (MADV_WILLNEED) para prefetch, o leyendo secuencialmente.
7. ¿Por qué malloc sin tocar no genera fallos?
Porque solo reserva espacio de direcciones; la RAM se entrega al tocar.
8. ¿Qué herramienta cuenta los fallos por programa en Linux?
perf stat -e page-faults,major-faults.
9. ¿Por qué la primera escritura en una página anónima es un fallo menor?
Porque la página se pone a cero en la RAM sin leer del disco.
10. ¿Cómo se interpretan los fallos con madvise?
MADV_WILLNEED provoca fallos mayores anticipados (prefetch); MADV_DONTNEED libera las páginas.
Para practicar
- Modifica
fallos.cpara medir los fallos de un programa que lee un archivo de 10 MiB con mmap. - Compara los fallos de un programa con y sin prefetch (madvise).
- Usa
perf statpara contar los fallos de./fallos. - Explica por qué la primera escritura en una página anónima es un fallo menor.
- Investiga cómo Linux distingue entre fallo menor y mayor en el manejador.
- Dibuja el flujo de un fallo mayor, incluyendo la lectura del disco.
- Compara el tiempo de un fallo menor y uno mayor con mediciones reales.
- Calcula el costo total para 5000 fallos menores y 20 mayores con costos de 5 µs y 5 ms.
- Explica cómo madvise con MADV_SEQUENTIAL afecta a los fallos mayores.
- Investiga qué es el read-ahead del kernel y cómo reduce los fallos mayores.