Interioridades de la memoria de Windows (1.ª parte) — El instante en que una dirección virtual se convierte en RAM física: el ciclo completo de un fallo de página

· Actualizado el: · · Windows, Gestión de memoria, VirtualAlloc, Fallo de página, VAD, Monitoreo de rendimiento

Al pasar MEM_COMMIT a VirtualAlloc, el Commit aumenta en ese mismo instante. Sin embargo, el Working Set no aumenta necesariamente en la misma medida. Entonces, ¿dónde está la memoria que supuestamente se reservó?

La respuesta es que «la mayoría de las páginas todavía no tienen RAM física correspondiente». Windows retrasa la asignación de páginas físicas hasta que la aplicación realmente toca la página. Cuando el primer acceso hace que la CPU genere un fallo de página, el administrador de memoria examina el VAD, la PTE, los atributos de protección y el backing store, y vincula la RAM página por página cuando es necesario.1

En este artículo seguimos el camino que recorre ese «primer byte tocado» hasta llegar a la RAM física. Si primero quiere aclarar el significado de cifras como Working Set o Commit, consulte el artículo introductorio «Qué representa el “uso de memoria” de Windows — cómo leer correctamente Working Set, Private Bytes, Commit y el archivo de paginación». Esta serie no vuelve a definir la terminología usada allí, sino que profundiza desde el lado mecánico en «por qué esa cifra llega a ser la que es».

Las tres partes de «Interioridades de la memoria de Windows»

  1. 1.ª parte (este artículo): direcciones virtuales y fallos de página
    Seguimos cuándo una región reservada con VirtualAlloc obtiene RAM física.
  2. 2.ª parte: el ciclo de vida de una página física
    Seguimos cómo una página que sale del Working Set se mueve entre Modified, Standby, Free y Zeroed.
  3. 3.ª parte: objetos de sección y copy-on-write
    Seguimos por qué los DLL, las asignaciones de archivos y la memoria compartida pueden compartir páginas físicas.

La 1.ª parte responde a una sola pregunta.

¿En qué instante una dirección virtual ya en Commit se convierte en RAM física?

El público objetivo son desarrolladores y responsables de operación que quieren entender, desde el mecanismo mismo, el uso de memoria de las aplicaciones Windows, los fallos de página justo después del arranque, 0xC0000005 y las cifras de VMMap o PerfMon. El entorno previsto es Windows 10/11 o una versión actual de Windows Server, y los conocimientos previos necesarios son punteros y los fundamentos de VirtualAlloc; no hace falta experiencia con la disposición de bits de la tabla de páginas ni con el depurador de kernel. El nivel es intermedio. Se mencionan nombres de estructuras internas, pero no se asume ningún diseño no documentado que dependa de una compilación concreta de Windows.

1. La conclusión primero

El flujo habitual de la memoria privada se puede resumir en una sola línea.

Reserve reserva un rango de direcciones virtuales, Commit cobra una cuota de compromiso que garantiza un destino de almacenamiento futuro, y el fallo de página del primer acceso asigna la página física.

Es decir, MEM_COMMIT no es una orden que diga «reserve RAM ahora mismo». La propia documentación de VirtualAlloc de Microsoft garantiza que el contenido inicial de una página en Commit es cero, pero explica que la página física real no se asigna hasta que se accede a la dirección virtual.1

Aunque tampoco es del todo exacto decir que «Reserve/Commit solo escriben en el VAD». En realidad, Reserve crea un VAD que representa principalmente el rango de direcciones virtuales y sus atributos, y Commit incrementa el Commit Total del sistema y registra el estado de compromiso del rango. Los niveles intermedios de la tabla de páginas y cada PTE individual se construyen de forma diferida cuando se necesitan, y el enlace final con la RAM física ocurre normalmente en el primer acceso.

Commit no es una promesa vacía: es la promesa de todo el sistema de que en el futuro podrá conservar ese contenido en RAM o en el backing store adecuado. La puerta de entrada que materializa esa promesa página por página es el fallo de página.

Qué sucede en Reserve, Commit y el primer accesoMEM_RESERVE registra el rango y los atributos en el VAD, MEM_COMMIT consume el Commit Total para prometer el almacenamiento, y el fallo de página del primer acceso asigna la página física y la añade al Working Set1. MEM_RESERVE2. MEM_COMMIT3. Primer acceso(Touch)Registra el rango y los atributos en el VADConsume el Commit Total(aún sin página física)Fallo de páginaVincula una página física puesta a cero a la PTELa añade al Working Set y reejecuta la instrucción

Figura 1: Reserve, Commit y Touch son sucesos distintos. La RAM física se vincula recién en ese último primer acceso.

2. Los tres registros que siguen la página virtual

Para entender el flujo desde la dirección virtual hasta la RAM física, hay que distinguir los tres tipos de registros que mantiene Windows.

Registro Unidad Función
VAD Rango de direcciones virtuales Gestiona de qué es la región, Reserve/Commit, la protección y la correspondencia con la sección
Tabla de páginas / PTE Página virtual Representa la traducción a la página física actual o el estado sin materializar
Base de datos PFN Página física Rastrea la propiedad, las referencias y el estado de cada página de RAM

El VAD contiene información sobre el rango, la PTE sobre la página virtual y la base de datos PFN sobre la página física. El controlador de fallos de página cruza esta información para decidir si el acceso puede continuar.

Los tres registros entre la dirección virtual y la RAM físicaLa dirección virtual la gestiona el VAD por rango y la PTE por página virtual, y la página física a la que apunta la PTE la rastrea la base de datos PFN por página físicaEvalúa Reserve/Commit y protecciónTraducción válidaDirección virtualVAD(registro de rangos)PTE(registro de páginas virtuales)Base de datos PFN(registro de páginas físicas)Página de RAM física

Figura 2: Tres registros de distinta granularidad. El procesamiento del fallo cruza el VAD y la PTE, y refleja el resultado en el lado del PFN.

Los protagonistas de este artículo son el VAD y la PTE. La base de datos PFN la abordaremos en la 2.ª parte desde el lado de la página física.

3. Reserve, Commit y Touch son sucesos distintos

3.1. Reserve — reservar la dirección

Primero, reservemos un rango contiguo de direcciones virtuales de 256 MiB.

void* base = VirtualAlloc(
    nullptr,
    256ull * 1024 * 1024,
    MEM_RESERVE,
    PAGE_NOACCESS);

En este punto lo único que ha ocurrido es que se ha reservado una dirección en el espacio virtual del proceso para impedir que este rango se use en otra asignación. MEM_RESERVE no asigna almacenamiento físico ni en la RAM ni en el archivo de paginación.1

Como los procesos de 64 bits disponen de un espacio virtual enorme, resulta práctico diseñar el sistema reservando primero un rango grande con Reserve y haciendo Commit más adelante solo de la parte que realmente se necesita.

3.2. Commit — prometer que se podrá guardar

A continuación, hagamos Commit del rango ya reservado.

void* committed = VirtualAlloc(
    base,
    256ull * 1024 * 1024,
    MEM_COMMIT,
    PAGE_READWRITE);

Si tiene éxito, aumentan el Commit Total del sistema y la cantidad prometida que normalmente se refleja en Private Bytes del proceso. Aun así, no aparecen de golpe 256 MiB de páginas físicas. Las páginas normales permanecen sin asignación física hasta el primer acceso.12

Entonces, ¿qué sentido tiene Commit? Que si el sistema no puede asumir la promesa, puede devolver el fallo en el momento del Commit en lugar de en mitad del uso de la memoria.

3.3. Touch — cuando se necesita la página física

Por último, con la siguiente asignación escribimos por primera vez en la página inicial.

static_cast<unsigned char*>(base)[0] = 1;

La CPU intenta traducir la dirección virtual a una dirección física, pero la PTE todavía no tiene una traducción válida a una página física. Aquí se produce el fallo de página.

El administrador de memoria, que recibe el control, determina que se trata de «un primer acceso a una página privada en Commit y con permiso de escritura», reserva una página física puesta a cero, la vincula a la PTE y la añade al Working Set. A continuación, hace que se vuelva a ejecutar la instrucción de escritura que había fallado.

Desde la aplicación solo se ve una simple asignación, pero internamente se ejecuta un proceso que entra en el kernel en mitad de la asignación, asigna la página física y vuelve a la misma instrucción.

4. VAD — el registro de rangos del espacio virtual

VAD son las siglas de Virtual Address Descriptor, y Windows gestiona los rangos de direcciones en uso de un proceso como un árbol de VAD. Con el comando !vad de WinDbg se pueden consultar la VPN inicial y final, el Commit, los atributos de protección, si es Private o Mapped, el Control Area, entre otros datos.3

La información representativa que contiene un VAD es la siguiente.

  • El inicio y el final del rango de direcciones
  • El tipo: Private, Mapped, Image, etc.
  • El estado de Reserve/Commit
  • La protección: lectura, escritura, ejecución, copy-on-write, etc.
  • La correspondencia con un archivo o una sección
  • Atributos especiales como las páginas de guarda

La razón de gestionar por rangos es la eficiencia. 256 MiB equivalen a 65 536 páginas de 4 KiB. En lugar de crear desde el principio una estructura de gestión completa para todas las páginas, resulta menos costoso mantener en el VAD la información de que «este rango contiguo es una sola reserva» e ir concretando las páginas a medida que se necesitan.

4.1. Encontrar el VAD no garantiza poder recuperarse

La explicación de que «si está en el VAD, el fallo se resuelve, y si no está, hay una infracción de acceso» es útil como punto de partida, pero es una simplificación excesiva. Aunque se encuentre el VAD, en casos como los siguientes no se puede continuar con un acceso normal.

  • Solo está en Reserve y la página en cuestión no está en Commit
  • Es PAGE_NOACCESS
  • Se escribió en una página de solo lectura
  • Se ejecutó una instrucción desde una página no ejecutable
  • Se tocó una página de guarda por primera vez
  • Se accedió fuera del rango válido de la sección

En cambio, aunque la PTE sea inválida, si el estado de software del VAD y de la PTE indican que el acceso es legítimo, puede resolverse como demand zero, recuperación desde Transition, paginación de entrada o CoW. Dicho con precisión, la respuesta es que se evalúan en conjunto el VAD, la PTE, los atributos de protección y el tipo de acceso.

5. La tabla de páginas y el TLB

Los punteros que maneja una aplicación son direcciones virtuales. Para que la CPU acceda a la RAM, debe traducir el número de página virtual a un número de página física. Esa tabla jerárquica de traducción es la tabla de páginas, y su entrada final es la PTE (Page Table Entry).

Conceptualmente, una PTE válida contiene el PFN, la protección de lectura/escritura/ejecución, si permite modo usuario, los bits Accessed/Dirty, entre otra información. La disposición real de los bits depende de la CPU y de la versión de Windows.

Ahora bien, recorrer la tabla de páginas cada vez sería demasiado lento, así que la CPU almacena en caché las traducciones recientes en el TLB (Translation Lookaside Buffer). La traducción de direcciones avanza en este orden.

  1. Si el TLB tiene la traducción y el acceso cumple su protección, se usa ese resultado.
  2. Si el TLB no tiene la traducción, la CPU recorre la tabla de páginas.
  3. Si hay una PTE válida y también cumple la protección, se registra en el TLB y se continúa.
  4. Si no hay una traducción válida o hay una violación de protección, se pasa a la entrada del fallo de página. La comprobación de protección también se realiza cuando la traducción viene del TLB.

Como se ve en este flujo, un fallo de TLB y un fallo de página son cosas distintas. Si simplemente no hay traducción en el TLB pero la PTE es válida, el proceso termina solo con el recorrido de la tabla de páginas. Al contrario, incluso con una traducción en el TLB, una violación de protección como escribir en una página de solo lectura o ejecutar una instrucción en una página no ejecutable avanza hacia la entrada del fallo de página. Por eso una escritura en una página CoW puede provocar un fallo aunque la traducción ya esté en caché.

Flujo de la traducción de direcciones y la entrada al fallo de páginaAunque el TLB tenga la traducción, si no cumple la protección se entra en el fallo de página. Si el TLB no la tiene, se recorre la tabla de páginas; si la PTE es válida y cumple la protección, se registra en el TLB y continúa; si es inválida o hay violación de protección, se entra en el fallo de páginaCumpleViolación de protecciónNoInválida o violación de protecciónAcceso a memoria¿El TLB tiene la traducción?¿Cumple la protección?Continúa con esa traducciónHacia la entrada del fallo de páginaRecorrido de la tabla de páginas¿PTE válida y protección conforme?Se registra en el TLB y continúa(sin fallo)

Figura 3: Un fallo de TLB se puede resolver con el recorrido de la tabla de páginas. Se avanza hacia el fallo de página cuando la traducción es inválida o hay una violación de protección, y esta última también puede ocurrir con un acierto de TLB.

5.1. Una PTE inválida no es simplemente un espacio vacío

Aunque se hable de una PTE inválida, eso no significa que su contenido esté vacío. Windows distingue, a partir del estado de software de una PTE inválida, casos como los siguientes.

  • Una página de demand zero que nunca se ha materializado
  • Una página en Transition que sigue en RAM
  • Una página compartida que referencia una Prototype PTE
  • Una página privada guardada en el archivo de paginación
  • Una violación de protección o una región inválida

El trabajo de la CPU llega hasta determinar que «no es una traducción válida normal» y entregarlo al kernel; interpretar el significado a partir de ahí corresponde al administrador de memoria.

6. El ciclo completo de un fallo de página

Sigamos en seis etapas una primera escritura en una página privada en Commit.

  1. La CPU intenta escribir.
    Consulta el TLB y la tabla de páginas, pero la PTE correspondiente no tiene un PFN válido.
  2. La CPU genera un fallo de página.
    Entrega al kernel la dirección virtual que falló, el tipo de acceso (lectura, escritura o ejecución), si es modo usuario o kernel, y si es por ausencia de traducción o por violación de protección.
  3. El administrador de memoria examina el VAD y la PTE.
    Decide si está en Commit, si cumple la protección y si se trata de demand zero, Transition, compartida, paginación de entrada, CoW o una excepción.
  4. Si es demand zero, obtiene una página física puesta a cero.
    Para no filtrar datos de otros procesos, cualquier página nueva que se entregue debe estar en cero.
  5. Actualiza la PTE y la información de gestión del PFN.
    Configura el PFN y la protección en la PTE, marca la página física como Active y la añade al Working Set del proceso.
  6. Reejecuta la instrucción que había fallado.
    Como se resolvió con normalidad, no llega ninguna excepción en modo usuario y la aplicación continúa como si fuera una asignación normal.

Los eventos de fallo de página de ETW también registran Transition, Demand Zero, Copy-on-Write, Guard Page, Hard Page Fault y Access Violation como tipos distintos.4

Es decir, «fallo de página» no significa «anomalía» desde el principio. Es la entrada común mediante la cual, cuando la CPU no puede traducir por la vía normal, se le pide al sistema operativo que decida qué hacer.

Ramificación de las resoluciones del fallo de páginaEl administrador de memoria evalúa el VAD, la PTE, los atributos de protección y el tipo de acceso, y deriva hacia demand zero, la reconexión de una página que sigue en RAM, un fallo duro desde el backing store, copy-on-write, la notificación de página de guarda o una excepciónPrimer accesoSigue en RAMRequiere leer discoEscritura CoWPágina de guardaSin resoluciónSe produce el fallo de páginaEvalúa VAD, PTE, protección y tipoDemand zero(suave)Reconexión desde Standby, etc.(suave)Fallo duro(E/S de disco)Copia y sustituye la PTERetira la guarda y notificaExcepción(0xC0000005, etc.)

Figura 4: Un fallo que entra por la misma puerta se ramifica, según el resultado del juicio, en seis desenlaces distintos. Los detalles de la página de guarda se tratan en el apartado 9.

7. Demand zero — el fallo suave que no lee el disco

Demand zero es el fallo suave representativo que ocurre al tocar por primera vez una página privada en Commit. La propia explicación de Microsoft sobre Working Set cita como ejemplo de fallo suave el caso en que «un proceso hace referencia por primera vez a una página virtual ya asignada».5

Demand zero tiene las siguientes características.

  • No hace falta leer los datos originales del disco
  • El contenido inicial es cero
  • Vincula una página física disponible
  • El Working Set y el Page Fault Count acumulado aumentan
  • Si solo ocurre este proceso, Memory\\Pages Input/sec no aumenta

Por eso, aunque Page Faults/sec se dispare justo después del arranque, eso por sí solo no significa que el almacenamiento esté saturado.

Repasemos también las ventajas y desventajas de la asignación diferida. Si se hace Commit de 256 MiB pero en realidad solo se usan 8 MiB, es razonable que la asignación diferida no coloque en RAM los 248 MiB restantes. A cambio, el primer acceso carga el coste del procesamiento del fallo. En procesos con requisitos estrictos de latencia existe también el diseño de tocar cada página de antemano para provocar un prefallo (prefault) antes de empezar, pero esa es una decisión que intercambia esa ventaja por aumentar de antemano la cantidad de memoria residente en RAM.

8. Fallos suaves y fallos duros

8.1. Fallos suaves

Un fallo suave es aquel que se puede resolver sin una operación de E/S de lectura al backing store. Estos son los casos representativos.

  • Demand zero
  • La reconexión de una página que permanece en Standby/Transition
  • La conexión de una página compartida presente en el Working Set de otro proceso
  • La conexión de una página ya leída por adelantado (prefetch)
  • Copy-on-write cuando la página original ya está residente

Conlleva costes de CPU como la transición al kernel, los bloqueos, la actualización de la PTE/PFN y la coherencia del TLB, pero no hay espera de almacenamiento.5

8.2. Fallos duros

En cambio, un fallo duro es cuando la página necesaria no está en ningún lugar de la RAM y hay que leerla del backing store. En este caso, el origen de la lectura no se limita al archivo de paginación.

  • Una página privada expulsada al archivo de paginación
  • Un archivo mapeado en memoria
  • La imagen de un EXE o un DLL
  • Un archivo de datos al que hace referencia la caché de archivos

El evento HardFault de ETW incluye FileObject, ReadOffset y ByteCount, lo que permite rastrear el origen real de la lectura.6

Por lo tanto, Hard Fault no equivale a “se leyó pagefile.sys”.

Cuando se necesita leer del backing store, la solicitud entra en la pila de E/S de Windows. El flujo de la IRP y su emisión y finalización se tratan en «Interioridades de la E/S de Windows (1.ª parte)», y su confluencia con la caché de archivos, en «Interioridades de la E/S de Windows (4.ª parte)». Si la página está en RAM, se puede volver solo con el administrador de memoria, pero si no lo está, hay que emitir la E/S y hacer esperar al hilo que provocó el fallo hasta que se complete.

9. Los fallos que no se pueden resolver se convierten en excepciones

Un fallo que, tras examinar el VAD y la PTE, no se puede resolver como una asignación legítima, una paginación de entrada o un CoW, llega al modo usuario como una excepción.

El caso representativo es STATUS_ACCESS_VIOLATION, con el código de excepción 0xC0000005. Se produce al leer, escribir o ejecutar una dirección inválida; el primer parámetro de la excepción indica el tipo de acceso y el segundo, la dirección que infringió la protección.7

Los patrones típicos en los que se produce son los siguientes.

  • Leer una dirección NULL, ya liberada o fuera de un arreglo
  • Escribir en una página de solo lectura
  • Ejecutar una instrucción desde una página no ejecutable por DEP/NX
  • Tocar un rango en Reserve que no está en Commit

Cabe señalar que PAGE_GUARD tiene un significado un poco distinto. Es un mecanismo que notifica el acceso una sola vez, genera STATUS_GUARD_PAGE_VIOLATION y se usa, por ejemplo, para la expansión de la pila.8

La asignación diferida normal, la paginación de entrada, el CoW, la notificación de guarda y, en última instancia, la infracción de acceso: vistos desde la CPU, todos convergen en la misma entrada del fallo de página. Lo que decide el desenlace es la combinación del VAD, la PTE, los atributos de protección y el tipo de acceso.

10. Compruébelo usted mismo

El flujo descrito hasta aquí se puede observar directamente. El siguiente programa en C++ reserva 256 MiB, hace Commit, escribe 1 byte en cada página y finalmente hace Release. En cada etapa espera a que se pulse Intro, de modo que puede observar los cambios con VMMap y PerfMon.

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <psapi.h>

#include <cstdio>
#include <cstdlib>

#pragma comment(lib, "Psapi.lib")

constexpr SIZE_T kSize = 256ull * 1024 * 1024;

void PrintMemory(const char* stage)
{
    PROCESS_MEMORY_COUNTERS_EX c{};
    c.cb = sizeof(c);
    if (!GetProcessMemoryInfo(
            GetCurrentProcess(),
            reinterpret_cast<PROCESS_MEMORY_COUNTERS*>(&c),
            sizeof(c))) {
        std::printf("GetProcessMemoryInfo failed: %lu\n", GetLastError());
        return;
    }

    std::printf(
        "%-10s WS=%zu MiB  Private=%zu MiB  Faults=%lu\n",
        stage,
        c.WorkingSetSize / 1024 / 1024,
        c.PrivateUsage / 1024 / 1024,
        c.PageFaultCount);
}

void Pause(const char* message)
{
    PrintMemory(message);
    std::puts("Press Enter...");
    (void)std::getchar();
}

int main()
{
    SYSTEM_INFO si{};
    GetSystemInfo(&si);
    std::printf("PID=%lu, page=%lu bytes\n",
                GetCurrentProcessId(), si.dwPageSize);

    void* base = VirtualAlloc(nullptr, kSize, MEM_RESERVE, PAGE_NOACCESS);
    if (!base) {
        std::fprintf(stderr, "Reserve failed: %lu\n", GetLastError());
        return EXIT_FAILURE;
    }
    Pause("reserved");

    if (!VirtualAlloc(base, kSize, MEM_COMMIT, PAGE_READWRITE)) {
        std::fprintf(stderr, "Commit failed: %lu\n", GetLastError());
        VirtualFree(base, 0, MEM_RELEASE);
        return EXIT_FAILURE;
    }
    Pause("committed");

    auto* bytes = static_cast<volatile unsigned char*>(base);
    for (SIZE_T offset = 0; offset < kSize; offset += si.dwPageSize) {
        bytes[offset] = 1;
    }
    Pause("touched");

    if (!VirtualFree(base, 0, MEM_RELEASE)) {
        std::fprintf(stderr, "Release failed: %lu\n", GetLastError());
        return EXIT_FAILURE;
    }
    Pause("released");
}

Desde el x64 Native Tools Command Prompt de Visual Studio, se puede compilar con el siguiente comando.

cl /std:c++20 /EHsc /W4 memory_fault_demo.cpp

10.1. Qué observar en VMMap

VMMap es una herramienta que muestra por categorías la memoria virtual reservada, el Commit, el Working Set, lo Private y lo Shareable.9 Los cambios esperados en cada etapa son los siguientes.

Etapa Cambio esperado
Reserve El Size de Address Space aumenta, pero Commit/WS no aumenta en la misma medida
Commit El Commit de Private aumenta en unos 256 MiB
Touch El Working Set y el Private WS aumentan considerablemente, y el Fault Count también sube
Release El rango desaparece, y el Commit y el WS bajan

Las cifras reales varían según el runtime, los productos de seguridad, la presión de memoria y el momento de la observación. No se fije en si llega exactamente a 256 MiB, sino en hacia dónde se mueve entre una etapa y otra.

10.2. Distinguir entre suave y duro en PerfMon

En PerfMon, coloque los siguientes contadores en el mismo eje temporal.

  • Process(<objetivo>)\\Page Faults/sec
  • Memory\\Pages Input/sec
  • Memory\\Page Reads/sec
  • Memory\\Available MBytes
  • Process(<objetivo>)\\Working Set - Private
  • Process(<objetivo>)\\Private Bytes

Process\\Page Faults/sec incluye tanto los fallos suaves como los duros. En cambio, Memory\\Pages Input/sec es el número de páginas leídas del disco para resolver fallos duros.10

En la etapa Touch de este programa, aunque Page Faults/sec se dispare, Pages Input/sec no debería aumentar mucho. Como las páginas recién puestas en Commit se materializan mediante demand zero, no hace falta leer los datos originales del disco.

Cabe señalar que, cuando hay varios procesos con el mismo nombre, números como process#1 en PerfMon pueden cambiar al reiniciar. Confírmelo con el contador que muestra el PID, o identifique el proceso mediante el PID con Process V2 o ETW/WPA.

11. Tres malas lecturas que evitar en la práctica

11.1. «El Commit aumentó, así que hay una fuga de RAM»

Commit es la cantidad prometida para conservar el contenido, y una página que no se ha tocado todavía puede no estar residente en RAM. Para determinar si hay una fuga, observe la evolución de Private Bytes en el tiempo, el desglose de las asignaciones y si vuelve al valor de referencia después de procesar.

11.2. «Page Faults/sec es alto, así que el disco va lento»

Los fallos suaves no implican E/S de disco. Observe por separado Page Faults/sec, Pages Input/sec y la espera de almacenamiento, y si es necesario, rastree hasta el archivo de origen y la pila con el evento HardFault de ETW.

11.3. «Vaciar el Working Set arregla la fuga»

Sacar una página del Working Set no libera el Commit ni la propiedad. La página simplemente pasa a Standby o Modified, y más tarde vuelve mediante un fallo. Para corregir una fuga, quien hizo la asignación debe llamar a VirtualFree, liberar el montón o destruir el objeto, entre otras acciones.

Adónde va esa página física una vez retirada lo seguiremos en la 2.ª parte.

12. Resumen

  • MEM_RESERVE reserva un rango de direcciones virtuales, pero no asigna región física ni en la RAM ni en el archivo de paginación.1
  • MEM_COMMIT consume Commit y garantiza que en el futuro se podrá conservar el contenido, pero la página física normal no se asigna hasta el primer acceso.12
  • El VAD es el registro de rangos, la PTE el de páginas virtuales y la base de datos PFN el de páginas físicas.
  • Un fallo de TLB no es un fallo de página. Si hay una PTE válida, se resuelve solo con el recorrido de la tabla de páginas.
  • Demand zero, la recuperación desde Transition y la conexión de páginas compartidas son fallos suaves que se resuelven sin E/S de disco.5
  • Si hace falta leer del archivo de paginación, un DLL, un EXE o un archivo mapeado, es un fallo duro.6
  • Si al examinar el VAD, la PTE y los atributos de protección no se puede resolver, se produce una excepción como 0xC0000005.7
  • Para evaluar el rendimiento, no se fije solo en Page Faults/sec: observe en el mismo eje temporal Pages Input/sec, Available, Working Set, Private Bytes y la espera de almacenamiento.

La continuación es la 2.ª parte, «El ciclo de vida de una página física: las cinco listas y la verdad sobre el archivo de paginación».

Tras convertir la promesa de Commit en una página física, seguiremos, a partir de la base de datos PFN y de las listas de páginas, adónde va esa página cuando sale del Working Set.

Artículos relacionados

Áreas de consultoría relacionadas

KomuraSoft LLC se ocupa de la investigación del uso de memoria de aplicaciones Windows, las infracciones de acceso, la lentitud en el arranque, la paginación y el análisis de defectos en código nativo.

Referencias

  1. Microsoft Learn, VirtualAlloc function. Sobre cómo MEM_RESERVE reserva un rango de direcciones virtuales sin asignar almacenamiento físico, cómo MEM_COMMIT cobra una cuota de compromiso contra la memoria y el archivo de paginación de todo el sistema, cómo el contenido inicial de una página en Commit es cero, y cómo la página física real no se asigna hasta que se accede a ella.  2 3 4 5 6

  2. Microsoft Learn, PERFORMANCE_INFORMATION structure. Sobre cómo CommitTotal es el número actual de páginas en Commit del sistema y CommitLimit es el límite máximo de Commit posible sin ampliar el archivo de paginación.  2

  3. Microsoft Learn, !vad (WinDbg). Sobre cómo !vad muestra el árbol de VAD y permite consultar la VPN inicial y final, el Commit, si es Mapped o Private, los atributos de protección, el Control Area, entre otros datos. 

  4. Microsoft Learn, PageFault_TypeGroup1 class. Sobre cómo ETW distingue y registra Transition Fault, Demand Zero Fault, Copy-on-Write, Guard Page Fault, Hard Page Fault y Access Violation. 

  5. Microsoft Learn, Working Set. Sobre cómo un fallo suave se puede resolver sin acceder al backing store, y cómo ocurre por el Working Set de otro proceso, por Transition o por demand zero en la primera referencia, entre otros casos.  2 3

  6. Microsoft Learn, PageFault_HardFault class. Sobre cómo el evento HardFault incluye FileObject, ReadOffset, ByteCount, VirtualAddress y Thread ID, lo que permite rastrear el origen de la lectura.  2

  7. Microsoft Learn, Access Violation C0000005. Sobre cómo 0xC0000005 se produce al leer, escribir o ejecutar una dirección de memoria inválida, y cómo los parámetros de la excepción indican el tipo de acceso y la dirección que infringió la protección.  2

  8. Microsoft Learn, Creating Guard Pages. Sobre cómo PAGE_GUARD proporciona una notificación de un solo uso del acceso a la página y genera STATUS_GUARD_PAGE_VIOLATION

  9. Microsoft Learn, VMMap - Sysinternals. Sobre cómo VMMap descompone la memoria virtual en Commit por tipo y muestra el Working Set y el mapa de direcciones detallado de cada tipo. 

  10. Microsoft Learn, Performance Analysis of Logs (PAL) Tool. Sobre cómo Memory\\Pages Input/sec es el número de páginas leídas del disco para resolver fallos de página duros. 

Artículos recientes con las mismas etiquetas para profundizar en temas cercanos.

Estas páginas sitúan el tema en un contexto más amplio de servicios y decisiones.

El artículo está directamente relacionado con los siguientes servicios.

Preguntas frecuentes

Preguntas habituales en las consultas sobre el tema del artículo.

¿Se reserva RAM en el momento en que VirtualAlloc hace MEM_COMMIT?
En la memoria privada habitual, el Commit consume la capacidad de compromiso del sistema, pero la página física correspondiente no se asigna hasta el primer acceso. La página que se toca por primera vez mediante una escritura obtiene su página física durante el procesamiento de un fallo de demand zero.
¿Un fallo de página significa que hay una anomalía o un problema de rendimiento?
No. Los fallos suaves que no implican E/S de disco, como el demand zero o la recuperación desde Standby, son comportamiento normal. Para evaluar el rendimiento no basta con Page Faults/sec: hay que observarlo junto con Pages Input/sec, la espera de almacenamiento y Available MBytes.
¿Un fallo de TLB es lo mismo que un fallo de página?
Son cosas distintas. Aunque el TLB no tenga la traducción, si la PTE de la tabla de páginas es válida, la CPU simplemente recorre la tabla y vuelve a registrar la traducción. Solo se entra en el fallo de página cuando la PTE es inválida o hay una violación de protección.
¿Si el VAD tiene el rango de direcciones, se descarta una infracción de acceso?
No necesariamente. Además de la existencia del VAD, el administrador de memoria evalúa si el rango está en Reserve o en Commit, los atributos de protección de lectura, escritura y ejecución, las páginas de guarda y el estado de la PTE, entre otros factores. Si no puede resolverse, se produce una excepción como 0xC0000005.
¿Un valor alto de Page Faults/sec indica falta de RAM?
Eso solo no permite concluirlo. Page Faults/sec también incluye un gran volumen de fallos suaves. Es necesario correlacionarlo, en el mismo eje temporal, con Memory\Pages Input/sec, Memory\Page Reads/sec, Available MBytes y el tiempo de espera de disco.

Perfil del autor

Página de presentación del autor del artículo.

Go Komura

Representante de KomuraSoft LLC

Especializado en desarrollo de software para Windows, consultoría técnica e investigación de fallos, sobre todo en proyectos con sistemas existentes y errores difíciles de reproducir.

Volver al blog