Глубины памяти Windows (часть 1) — Миг, когда виртуальный адрес становится физической RAM: page fault от начала до конца

· · Windows, Управление памятью, VirtualAlloc, Page fault, VAD, Мониторинг производительности

Передача MEM_COMMIT в VirtualAlloc в тот же миг увеличивает Commit. Working Set, однако, не обязательно растёт на ту же величину. Так где же память, которую вы считали выделенной?

Ответ в том, что у большинства страниц ещё нет соответствующей физической RAM. Windows откладывает назначение физической страницы, пока приложение действительно не коснётся страницы. Когда первый доступ заставляет процессор поднять page fault, диспетчер памяти проверяет VAD, PTE, атрибуты защиты и backing store и при необходимости связывает RAM по одной странице.1

Эта статья прослеживает путь, который «первый байт, которого вы коснулись» проходит до физической RAM. Если сначала хотите разобрать смысл цифр вроде Working Set и Commit, см. вводную статью «What Does Windows’ “Memory Usage” Actually Mean? — Correctly Reading Working Set, Private Bytes, Commit, and the Page File». Этот цикл не переопределяет термины оттуда; он копает со стороны механизма, «почему цифра получается такой».

«Глубины памяти Windows» — все 3 части

  1. Часть 1 (эта статья): виртуальные адреса и page fault
    Прослеживаем, когда область, выделенная через VirtualAlloc, получает физическую RAM.
  2. Часть 2: жизнь физической страницы
    Прослеживаем, как страница, покинувшая Working Set, движется через Modified, Standby, Free и Zeroed.
  3. Часть 3: объекты секций и копирование при записи
    Прослеживаем, почему DLL, отображения файлов и разделяемая память могут делить физические страницы.

Вопрос, на который отвечает часть 1, один.

В какой миг закоммиченный виртуальный адрес становится физической RAM?

Предполагаемые читатели — разработчики и эксплуатация, которые хотят понять с механизма использование памяти Windows-приложений, page fault сразу после запуска, 0xC0000005 и цифры VMMap и PerfMon. Предпосылки — Windows 10/11 или актуальный Windows Server, а нужный фон — указатели и основы VirtualAlloc; опыт раскладки битов таблиц страниц или отладчика ядра не нужен. Сложность средняя. Мы используем имена внутренних структур, но не предполагаем недокументированных раскладок, зависящих от конкретной сборки Windows.

1. Сначала вывод

Обычный поток частной памяти в одну строку таков.

Reserve откладывает диапазон виртуальных адресов, Commit учитывает commit charge в лимите commit, чтобы гарантировать будущее место хранения содержимого, а page fault при первом доступе назначает физическую страницу.

Иными словами, MEM_COMMIT — не приказ «выдели RAM прямо сейчас». Документация Microsoft по VirtualAlloc также гарантирует, что начальное содержимое закоммиченной страницы — нули, и объясняет, что фактическая физическая страница не назначается, пока к виртуальному адресу не обратятся.1

При этом неточно утверждать, что «Reserve/Commit пишут только в VAD». На практике Reserve в основном создаёт VAD, представляющий диапазон виртуальных адресов и его атрибуты, а Commit увеличивает Commit Total системы и записывает закоммиченное состояние диапазона. Промежуточные уровни таблицы страниц и отдельные PTE строятся лениво по необходимости, а окончательная привязка к физической RAM обычно происходит при первом доступе.

Commit — не пустое обещание; это общесистемное обещание, что содержимое в будущем можно будет хранить в RAM или подходящем backing store. Точка входа, которая материализует это обещание по одной странице, — page fault.

Что происходит при Reserve, Commit и первом доступеMEM_RESERVE записывает диапазон и атрибуты в VAD, MEM_COMMIT расходует Commit Total чтобы обещать хранение, а page fault при первом доступе назначает физическую страницу и добавляет её в Working Set1. MEM_RESERVE2. MEM_COMMIT3. Первый доступ(Touch)Записать диапазон и атрибуты в VADИзрасходовать Commit Total(физической страницы ещё нет)Page faultПривязать обнулённую физическую страницу к PTEДобавить в Working Set и повторить инструкцию

Рис. 1: Reserve, Commit и Touch — разные события. Физическая RAM привязывается только на последнем шаге, при первом доступе.

2. Три реестра, которые ведут виртуальную страницу

Чтобы понять путь от виртуального адреса к физической RAM, нужно различать три вида реестров, которые ведёт Windows.

Реестр Единица Роль
VAD Диапазон виртуальных адресов Ведёт, что представляет область, Reserve/Commit, защиту и соответствие секции
Таблица страниц / PTE Виртуальная страница Представляет текущий перевод на физическую страницу или нереализованное состояние
База PFN Физическая страница Отслеживает владение, ссылки и состояние каждой страницы RAM

VAD держит сведения о диапазоне, PTE — о виртуальной странице, база PFN — о физической странице. Обработчик page fault сверяет их, чтобы решить, можно ли продолжить доступ.

Три реестра от виртуального адреса к физической RAMВиртуальный адрес ведёт VAD с гранулярностью диапазона и PTE с гранулярностью виртуальной страницы, а база PFN отслеживает физическую страницу — цель перевода PTE — с гранулярностью физической страницыОценить Reserve/Commit и защитуДействительный переводВиртуальный адресVAD(реестр диапазонов)PTE(реестр виртуальных страниц)База PFN(реестр физических страниц)Страница физической RAM

Рис. 2: Три реестра разной гранулярности. Обработка ошибки сверяет VAD и PTE, затем отражает результат на стороне PFN.

Главные герои этой статьи — VAD и PTE. Базу PFN мы рассмотрим со стороны физической страницы во второй части.

3. Reserve, Commit и Touch — разные события

3.1. Reserve — заявить адрес

Сначала зарезервируйте непрерывный диапазон виртуальных адресов в 256 МиБ.

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

На этом этапе произошло лишь то, что адрес отложен в виртуальном пространстве процесса, чтобы другие выделения не могли использовать этот диапазон. MEM_RESERVE не назначает физическое хранилище ни в RAM, ни в файле подкачки.1

Поскольку у 64-разрядного процесса огромное виртуальное пространство, практично сначала сделать Reserve большого диапазона и позже Commit только нужных частей.

3.2. Commit — обещать, что содержимое можно сохранить

Затем сделайте Commit зарезервированного диапазона.

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

При успехе растёт обещанный объём, который отражается в Commit Total системы — и обычно в Private Bytes процесса. Даже так 256 МиБ физических страниц не выстраиваются сразу. Обычные страницы остаются физически неназначенными до первого доступа.12

Так в чём смысл Commit? В том, что когда система не может взять обещание, она может вернуть отказ в момент Commit, а не посреди использования памяти.

3.3. Touch — когда становится нужна физическая страница

Наконец, следующее присваивание впервые пишет в первую страницу.

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

Процессор пытается перевести виртуальный адрес в физический, но у PTE ещё нет действительного перевода на физическую страницу. Здесь возникает page fault.

Диспетчер памяти, получивший управление, считает это «первым доступом к закоммиченной записываемой частной странице», получает обнулённую физическую страницу, привязывает её к PTE и добавляет в Working Set. Затем он повторяет неудавшуюся инструкцию записи.

С точки зрения приложения это простое присваивание, но внутри управление входит в ядро посреди присваивания, назначается физическая страница, и выполнение возвращается к той же инструкции.

4. VAD — реестр диапазонов виртуального пространства

VAD означает Virtual Address Descriptor, и Windows ведёт используемые диапазоны адресов процесса как дерево VAD. Командой WinDbg !vad можно осмотреть начальный и конечный VPN, Commit, атрибуты защиты, Private/Mapped, Control Area и другое.3

Типичные сведения, которые записывает VAD, включают следующее.

  • Начало и конец диапазона адресов
  • Вид, например Private, Mapped или Image
  • Состояние Reserve/Commit
  • Защита вроде чтения, записи, исполнения и копирования при записи
  • Соответствие файлу или секции
  • Особые атрибуты вроде сторожевых страниц

Причина вести по диапазонам — эффективность. 256 МиБ — это 65 536 страниц по 4 КиБ. Вместо того чтобы заранее строить полную структуру учёта для каждой страницы, меньше траты держать в VAD «этот непрерывный диапазон — одна резервация» и материализовать страницы по мере надобности.

4.1. Найти VAD не значит гарантировать восстановление

«Если это в VAD, ошибка разрешается; если нет — нарушение доступа» удобно как начальное объяснение, но упрощает. Даже когда VAD найден, обычный доступ нельзя продолжить в таких случаях, как следующие.

  • Только Reserve, и целевая страница не закоммичена
  • PAGE_NOACCESS
  • Запись в страницу только для чтения
  • Исполнение инструкции со страницы без исполнения
  • Первое касание сторожевой страницы
  • Касание вне действительного диапазона секции

Наоборот, даже если PTE недействительна, при законном доступе по программному состоянию VAD и PTE это можно разрешить как demand-zero, восстановление Transition, подкачку или CoW. Точнее ответ: судить вместе VAD, PTE, атрибуты защиты и тип доступа.

5. Таблицы страниц и TLB

Указатель, который держит приложение, — виртуальный адрес. Чтобы процессор обратился к RAM, он должен перевести номер виртуальной страницы в номер физической. Эта иерархическая таблица перевода — таблица страниц, а листовая запись — PTE (Page Table Entry).

Действительная PTE концептуально держит PFN, защиту чтения/записи/исполнения, разрешение пользовательского режима, Accessed/Dirty и подобное. Фактическая раскладка битов зависит от процессора и версии Windows.

Обходить таблицу страниц каждый раз было бы слишком медленно, поэтому процессор кэширует недавние переводы в TLB (Translation Lookaside Buffer). Перевод адреса идёт в таком порядке.

  1. Если в TLB есть перевод и доступ соответствует этой защите, используется этот результат.
  2. Если в TLB нет перевода, процессор обходит таблицу страниц.
  3. Если есть действительная PTE и защита тоже совпадает, она регистрируется в TLB и выполнение продолжается.
  4. Если действительного перевода нет или есть нарушение защиты, управление переходит к точке входа page fault. Проверка защиты выполняется и когда перевод пришёл из TLB.

Как показывает этот поток, промах TLB и page fault — разные вещи. Если единственная проблема в том, что в TLB нет перевода, а PTE действительна, происходит только обход таблицы страниц. Наоборот, даже если в TLB есть перевод, нарушение защиты вроде записи в страницу только для чтения или исполнения инструкции на странице без исполнения идёт к точке входа page fault. Поэтому запись в страницу CoW может дать ошибку, даже когда перевод уже в кэше.

Поток перевода адреса и точка входа page faultДаже если в TLB есть перевод, несовпадение защиты идёт к точке входа page fault. Если в TLB нет перевода, обходится таблица страниц; действительная PTE с совпадающей защитой регистрируется в TLB и выполнение продолжается, а недействительный перевод или нарушение защиты идут к точке входа page faultДаСовпадаетНарушение защитыНетДаНедействительно или нарушение защитыОбращение к памятиЕсть ли в TLB перевод?Совпадает ли доступ с защитой?Продолжить с этим переводомК точке входа page faultОбход таблицы страницДействительная PTE и защита тоже совпадает?Зарегистрировать в TLB и продолжить(без ошибки)

Рис. 3: Промах TLB можно разрешить обходом таблицы страниц. Управление идёт к page fault, когда перевод недействителен или есть нарушение защиты; нарушение защиты возникает и при попадании в TLB.

5.1. Недействительная PTE — не просто пустое место

Даже недействительная PTE не пуста. По программному состоянию недействительной PTE Windows различает такие случаи, как следующие.

  • Страница demand-zero, которая никогда не материализовалась
  • Страница Transition, оставшаяся в RAM
  • Разделяемая страница, ссылающаяся на Prototype PTE
  • Частная страница, сохранённая в файле подкачки
  • Нарушение защиты или недействительная область

Задача процессора — лишь решить «это не обычный действительный перевод» и передать ядру; смысл дальше даёт диспетчер памяти.

6. Page fault от начала до конца

Проследим первую запись в закоммиченную частную страницу в шесть этапов.

  1. Процессор пытается писать.
    Он проверяет TLB и таблицу страниц, но у целевой PTE нет действительного PFN.
  2. Процессор поднимает page fault.
    Он передаёт ядру виртуальный адрес ошибки, тип чтения/записи/исполнения, пользователь/ядро и то, проблема в отсутствии перевода или в нарушении защиты.
  3. Диспетчер памяти проверяет VAD и PTE.
    Он решает, закоммичена ли страница, совпадает ли защита и что из demand-zero, Transition, разделяемой, подкачки, CoW или исключения применимо.
  4. Если это demand-zero, получают обнулённую физическую страницу.
    Вновь выданная страница должна быть нулевой, чтобы не утекли данные другого процесса.
  5. Обновляются сведения учёта PTE и PFN.
    В PTE задаются PFN и защита, физическая страница становится Active и добавляется в Working Set процесса.
  6. Неудавшаяся инструкция повторяется.
    Поскольку ошибка разрешилась нормально, исключение пользовательского режима не доставляется, и приложение продолжает присваивание как обычно.

События ETW page fault также записывают Transition, Demand Zero, Copy-on-Write, Guard Page, Hard Page Fault и Access Violation как разные виды.4

Итак, page fault с самого начала не слово, означающее «аномалию». Это общая точка входа, чтобы ОС решила, когда процессор не смог перевести обычным путём.

Ветвление разрешений page faultДиспетчер памяти судит VAD, PTE, атрибуты защиты и тип доступа и направляет к demand-zero, повторному подключению страницы ещё в RAM, жёсткой ошибке из backing store, копированию при записи, уведомлению сторожевой страницы или исключениюПервый доступЕщё в RAMНужно чтение с дискаЗапись CoWСторожевая страницаНеразрешимоВозникает page faultОценить VAD, PTE, защиту, типDemand-zero(мягкая)Подключить из Standby(мягкая)Жёсткая ошибка(дисковый ввод-вывод)Скопировать и заменить PTEСнять сторож и уведомитьИсключение(0xC0000005 и т. д.)

Рис. 4: Ошибки, входящие через одну точку входа, расходятся на шесть исходов в зависимости от оценки. Подробности сторожевых страниц — в разделе 9.

7. Demand-zero — мягкая ошибка, которая не читает диск

Demand-zero — характерная мягкая ошибка, возникающая при первом касании закоммиченной частной страницы. Документация Microsoft по Working Set также приводит «процесс впервые ссылается на выделенную виртуальную страницу» как пример мягкой ошибки.5

У demand-zero такие свойства.

  • Не нужно читать исходные данные с диска
  • Начальное содержимое — нули
  • Привязывается доступная физическая страница
  • Растут Working Set и накопительный Page Fault Count
  • Одна эта обработка не увеличивает Memory\\Pages Input/sec

Поэтому всплеск Page Faults/sec сразу после запуска сам по себе не значит, что хранилище — узкое место.

Компромисс отложенного выделения тоже стоит разобрать. Если вы сделали Commit 256 МиБ и реально используете только 8 МиБ, оставить остальные 248 МиБ вне RAM разумно. Взамен первый доступ несёт стоимость обработки ошибки. Для работы, чувствительной к задержке, есть проект, который касается каждой страницы до старта, чтобы заранее вызвать ошибку (prefault), но это компромисс, заранее увеличивающий резидентность в RAM.

8. Мягкие и жёсткие ошибки

8.1. Мягкие ошибки

Мягкая ошибка — ошибка, которую можно разрешить без чтения из backing store. Типичные примеры включают следующее.

  • Demand-zero
  • Повторное подключение страницы, оставшейся на Standby/Transition
  • Подключение разделяемой страницы, которая в Working Set другого процесса
  • Подключение предварительно выбранной страницы
  • Копирование при записи, исходная страница которого резидентна

Остаётся стоимость процессора на переход в ядро, блокировки, обновления PTE/PFN, согласованность TLB и тому подобное, но ожидания хранилища нет.5

8.2. Жёсткие ошибки

С другой стороны, когда нужной страницы нигде в RAM нет и её надо читать из backing store, это жёсткая ошибка. Источник чтения — не только файл подкачки.

  • Частная страница, вытесненная в файл подкачки
  • Файл, отображённый в память
  • Образ EXE или DLL
  • Файл данных, на который ссылается кэш файлов

События ETW HardFault включают FileObject, ReadOffset и ByteCount, так что можно проследить фактический источник чтения.6

Поэтому Hard Fault = чтение pagefile.sys неверно.

Когда нужно чтение из backing store, запрос входит в стек ввода-вывода Windows. Поток IRP и выдачи/завершения разобран в «The Depths of Windows I/O (Part 1)», а стык с кэшем файлов — в «The Depths of Windows I/O (Part 4)». Если страница в RAM, диспетчер памяти может вернуться сам; если нет, он выдаёт ввод-вывод и ждёт поток с ошибкой до завершения.

9. Неразрешимая ошибка становится исключением

Ошибка, которую после проверки VAD и PTE нельзя разрешить как законное выделение, подкачку или CoW, доставляется в пользовательский режим как исключение.

Типичный случай — STATUS_ACCESS_VIOLATION, код исключения 0xC0000005. Он возникает при чтении, записи или исполнении недействительного адреса; первый параметр исключения указывает тип доступа, второй — нарушающий адрес.7

Типичные шаблоны включают следующее.

  • Чтение NULL, освобождённого адреса или адреса за пределами массива
  • Запись в страницу только для чтения
  • Исполнение инструкции со страницы, которую DEP/NX сделал неисполняемой
  • Касание зарезервированного, но не закоммиченного диапазона

У PAGE_GUARD смысл чуть другой. Это одноразовое уведомление о доступе: оно поднимает STATUS_GUARD_PAGE_VIOLATION и используется, например, для роста стека.8

Обычное отложенное выделение, подкачка, CoW, уведомление сторожа и итоговое нарушение доступа с точки зрения процессора сходятся в одной точке входа page fault. Исход решает сочетание VAD, PTE, атрибутов защиты и типа доступа.

10. Посмотрите сами

Поток до сих пор можно наблюдать на своей машине. Следующая программа на C++ делает Reserve 256 МиБ, Commit, пишет по байту в каждую страницу и в конце Release. На каждом этапе она ждёт Enter, чтобы вы могли наблюдать изменения в VMMap и 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");
}

Из командной строки x64 Native Tools Visual Studio можно собрать следующей командой.

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

10.1. На что смотреть в VMMap

VMMap — инструмент, который показывает зарезервированную виртуальную память, Commit, Working Set, Private и Shareable по типам.9 Ожидаемые изменения на каждом этапе таковы.

Этап Ожидаемое изменение
Reserve Address Space Size растёт, но Commit/WS не растут на ту же величину
Commit Private Commit растёт примерно на 256 МиБ
Touch Working Set и Private WS заметно растут, растёт и Fault Count
Release Целевой диапазон исчезает, Commit и WS падают

Фактические числа зависят от среды выполнения, продуктов безопасности, давления на память и момента наблюдения. Смотрите в какую сторону сдвинулись числа между этапами, а не сошлись ли они ровно в 256 МиБ.

10.2. Разделить мягкое и жёсткое в PerfMon

В PerfMon поместите следующие счётчики на одну временную ось.

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

Process\\Page Faults/sec включает и мягкие, и жёсткие ошибки. Memory\\Pages Input/sec же — число страниц, прочитанных с диска, чтобы разрешить жёсткие ошибки.10

На этапе Touch этой программы Page Faults/sec должен скакнуть, а Pages Input/sec — почти не вырасти. Вновь закоммиченные страницы материализуются через demand-zero, поэтому исходные данные с диска читать не нужно.

Когда несколько процессов делят одно имя, номера PerfMon вроде process#1 могут меняться после перезапуска. Сверяйте со счётчиком, показывающим PID, или идентифицируйте по PID через Process V2 или ETW/WPA.

11. Три неверных прочтения, которых стоит избегать на практике

11.1. «Commit вырос, значит это утечка RAM»

Commit — обещанный объём содержимого, которое нужно хранить; нетронутые страницы могут не быть резидентными в RAM. Чтобы судить об утечке, смотрите временной ряд Private Bytes, разбивку выделений и то, возвращается ли цифра к базовой линии после окончания обработки.

11.2. «Page Faults/sec высок, значит диск медленный»

Мягкие ошибки не включают дисковый ввод-вывод. Разделяйте Page Faults/sec, Pages Input/sec и ожидание хранилища и при необходимости прослеживайте исходный файл и стек событиями ETW HardFault.

11.3. «Очистка Working Set исправит утечку»

Удаление страницы из Working Set не освобождает Commit и владение. Страница переходит в Standby или Modified и позже снова входит через ошибку. Исправить утечку должен распределитель через VirtualFree, освобождение кучи, уничтожение объекта и тому подобное.

Куда уходит эта снятая физическая страница, мы проследим во второй части.

12. Кратко

  • MEM_RESERVE откладывает диапазон виртуальных адресов, но не назначает физическую область в RAM или файле подкачки.1
  • MEM_COMMIT расходует Commit и гарантирует, что содержимое можно будет хранить в будущем, но обычная физическая страница не назначается до первого доступа.12
  • VAD — реестр диапазонов, PTE — реестр виртуальных страниц, база PFN — реестр физических страниц.
  • Промах TLB — не page fault. Если PTE действительна, его разрешает один обход таблицы страниц.
  • Demand-zero, восстановление Transition и подключение разделяемой страницы — мягкие ошибки, разрешимые без дискового ввода-вывода.5
  • Если нужно чтение из файла подкачки, DLL, EXE или отображённого файла, это жёсткая ошибка.6
  • Если проверка VAD, PTE и атрибутов защиты не может разрешить ошибку, вы получаете исключение вроде 0xC0000005.7
  • Для оценки производительности не смотрите только Page Faults/sec; смотрите Pages Input/sec, Available, Working Set, Private Bytes и ожидание хранилища на одной временной оси.

Продолжение — часть 2, «Жизнь физической страницы: пять списков и правда о файле подкачки».

После того как обещание Commit стало физической страницей, мы проследим, куда она уходит, покинув Working Set, по базе PFN и спискам страниц.

Похожие статьи

Смежные области консультирования

KomuraSoft LLC занимается расследованиями использования памяти приложений Windows, нарушений доступа, задержек запуска, подкачки и дефектов нативного кода.

Справочные ссылки

  1. Microsoft Learn, VirtualAlloc function. О том, что MEM_RESERVE резервирует диапазон виртуальных адресов без назначения физического хранилища; MEM_COMMIT учитывает commit charge против общей памяти и файла подкачки системы; начальное содержимое закоммиченной страницы — нули; и фактическая физическая страница не назначается, пока к ней не обратятся.  2 3 4 5 6

  2. Microsoft Learn, PERFORMANCE_INFORMATION structure. О том, что CommitTotal — текущее число страниц Commit системы, а CommitLimit — верхняя граница, которую можно закоммитить без расширения файла подкачки.  2

  3. Microsoft Learn, !vad (WinDbg). О том, что !vad показывает дерево VAD и позволяет осмотреть начальный и конечный VPN, Commit, Mapped/Private, атрибуты защиты, Control Area и другое. 

  4. Microsoft Learn, PageFault_TypeGroup1 class. О том, что ETW различает и записывает Transition Fault, Demand Zero Fault, Copy-on-Write, Guard Page Fault, Hard Page Fault и Access Violation. 

  5. Microsoft Learn, Working Set. О том, что мягкая ошибка разрешима без обращения к backing store и возникает из Working Set другого процесса, Transition, demand-zero при первой ссылке и тому подобного.  2 3

  6. Microsoft Learn, PageFault_HardFault class. О том, что событие HardFault включает FileObject, ReadOffset, ByteCount, VirtualAddress и идентификатор потока, так что источник чтения можно проследить.  2

  7. Microsoft Learn, Access Violation C0000005. О том, что 0xC0000005 возникает при чтении, записи или исполнении недействительного адреса памяти, а параметры исключения указывают тип доступа и нарушающий адрес.  2

  8. Microsoft Learn, Creating Guard Pages. О том, что PAGE_GUARD даёт одноразовое уведомление о доступе к странице и поднимает STATUS_GUARD_PAGE_VIOLATION

  9. Microsoft Learn, VMMap - Sysinternals. О том, что VMMap раскладывает закоммиченную виртуальную память по типам и показывает Working Set каждого типа и подробную карту адресов. 

  10. Microsoft Learn, Performance Analysis of Logs (PAL) Tool. О том, что Memory\\Pages Input/sec — число страниц, прочитанных с диска для разрешения жёстких page fault. 

Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.

Глубины виртуализации Windows (часть 3) — виртуальные машины, которые загружаются за секунды: почему WSL2, Windows Sandbox и контейнеры такие лёгкие

Почему WSL2 и Windows Sandbox стартуют за секунды и ощущаются такими лёгкими? Статья разбирает механизмы — от динамического базового обра...

Эти страницы показывают тему статьи в более широком контексте услуг и решений.

Статья напрямую связана со следующими услугами.

Частые вопросы

Вопросы, которые часто возникают при консультациях по теме статьи.

Передача MEM_COMMIT в VirtualAlloc сразу выделяет RAM?
В обычной частной памяти Commit расходует запас commit системы, но соответствующая физическая страница не назначается до первого доступа. Страница, впервые затронутая записью, получает физическую страницу во время обработки demand-zero fault.
Означает ли page fault, что что-то не так или есть проблема с производительностью?
Нет. Мягкие ошибки без дискового ввода-вывода — demand-zero или возврат страницы из Standby — это нормальная работа. Для оценки производительности смотрите не только Page Faults/sec, но и Pages Input/sec, ожидание хранилища и Available MBytes.
Промах TLB и page fault — это одно и то же?
Это разное. Даже если в TLB нет перевода, при действительной PTE таблицы страниц процессор просто обходит таблицу и заново регистрирует перевод. К точке входа page fault он переходит, когда PTE недействительна или есть нарушение защиты.
Если диапазон адресов есть в VAD, нарушения доступа быть не может?
Не обязательно. Помимо наличия VAD диспетчер памяти оценивает Reserve и Commit, защиту чтения/записи/исполнения, сторожевые страницы, состояние PTE и другое. Если ошибку нельзя разрешить, вы получаете исключение вроде 0xC0000005.
Высокий Page Faults/sec означает, что системе не хватает RAM?
По одному этому не скажешь. Page Faults/sec включает и большое число мягких ошибок. Нужно сопоставить его на одной временной оси с Memory\Pages Input/sec, Memory\Page Reads/sec, Available MBytes и временем ожидания диска.

Об авторе

Страница с профилем автора статьи.

Го Комура

Представитель KomuraSoft LLC

Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.

Публичные ссылки

Вернуться в блог