Глубины памяти Windows (часть 1) — момент, когда виртуальный адрес становится физической ОЗУ: ошибка страницы от начала до конца
· Обновлено: · Go Komura · Windows, Управление памятью, VirtualAlloc, Ошибка страницы, VAD, Мониторинг производительности
История изменений (1 обновлений, последнее 31 Aug 2026)
Журнал изменений этой статьи. Там, где версия до правки была заархивирована, она остаётся доступной для чтения по постоянной ссылке с DOI.
- Русский текст переписан по текущему навыку технического перевода как полный перевод японского оригинала.
- Первая публикация
Цитирование статьи(DOI (зарегистрированный архив): 10.5281/zenodo.22176086)
Приведённые ниже DOI относятся к ранее зарегистрированным архивным версиям, которые могут отличаться от текущего текста. Для ссылки на текущий текст используйте URL этой страницы.
Go Komura (2026). Глубины памяти Windows (часть 1) — момент, когда виртуальный адрес становится физической ОЗУ: ошибка страницы от начала до конца. KomuraSoft LLC. https://comcomponent.com/ru/blog/windows-memory-internals-page-fault/
- DOI (зарегистрированный архив)
- 10.5281/zenodo.22176086
- DOI (последняя зарегистрированная версия)
- 10.5281/zenodo.22176087
Если передать MEM_COMMIT в VirtualAlloc, Commit увеличивается сразу. Но рабочий набор (Working Set) не обязан вырасти на ту же величину. Тогда где память, которую вы только что «выделили»?
Ответ: у большинства страниц ещё нет соответствующей физической ОЗУ. Windows откладывает назначение физической страницы, пока приложение действительно не обратится к ней. Когда при первом доступе процессор вызывает ошибку страницы, диспетчер памяти проверяет VAD, PTE, атрибуты защиты и резервное хранилище (backing store) и при необходимости привязывает ОЗУ по одной странице.1
В этой статье проследим путь от «первого затронутого байта» до физической ОЗУ. Если сначала нужно разобраться, что означают цифры вроде Working Set и Commit, начните с вводной статьи «Как читать «использование памяти» в Windows: Working Set, Private Bytes, Commit и файл подкачки». Этот цикл не переопределяет термины оттуда: он разбирает механизм и отвечает на вопрос, почему получается именно такая цифра.
«Глубины памяти Windows» — все 3 части
- Часть 1 (эта статья): виртуальные адреса и ошибка страницы
Прослеживаем, когда область, выделенная черезVirtualAlloc, получает физическую ОЗУ. - Часть 2: жизнь физической страницы
Прослеживаем, как страница, вышедшая из Working Set, движется через Modified, Standby, Free и Zeroed. - Часть 3: объекты секций и копирование при записи
Прослеживаем, почему DLL, отображения файлов и разделяемая память могут делить физические страницы.
Вопрос, на который отвечает часть 1, один.
В какой момент виртуальный адрес в состоянии Commit становится физической ОЗУ?
Кому адресована статья — разработчикам и специалистам по эксплуатации, которые хотят понять из механизма использование памяти Windows-приложений, ошибки страниц сразу после запуска, 0xC0000005 и цифры VMMap и PerfMon. Среда — Windows 10/11 или актуальный Windows Server. Предполагаемые знания — указатели и основы VirtualAlloc; раскладка битов таблицы страниц и опыт с отладчиком ядра не нужны. Сложность средняя. Мы используем имена внутренних структур, но не опираемся на недокументированные раскладки, зависящие от конкретной сборки Windows.
На схеме сплошная линия обозначает отношение, которое выполняется всегда, а пунктирная — условное отношение (условия указаны в пояснении к каждому отношению на странице сведений). Полный список отношений (всего 14, с доказательствами и степенью уверенности) и определения основных понятий собраны на странице сведений карты знаний (на японском). Данные: JSON-LD / Turtle
1. Сначала вывод
Обычный путь частной памяти в одну строку таков.
Reserve резервирует диапазон виртуальных адресов, Commit расходует commit charge, чтобы гарантировать место для будущего содержимого, а ошибка страницы при первом доступе назначает физическую страницу.
Иначе говоря, MEM_COMMIT — это не команда «выдели ОЗУ прямо сейчас». Документация Microsoft по VirtualAlloc тоже гарантирует, что начальное содержимое страниц в состоянии Commit — нули, и при этом поясняет, что фактическая физическая страница не назначается, пока не будет обращения к виртуальному адресу.1
Но и утверждение «Reserve и Commit только пишут в VAD» неточно. На практике Reserve в основном создаёт VAD, который описывает диапазон виртуальных адресов и его атрибуты. Commit увеличивает Commit Total системы и фиксирует состояние Commit для диапазона. Промежуточные уровни таблицы страниц и отдельные PTE строятся отложенно, когда понадобятся. Окончательная привязка к физической ОЗУ обычно происходит при первом доступе.
Commit — не пустая формальность, а общесистемное обязательство, что содержимое в будущем можно будет удерживать в ОЗУ или в подходящем резервном хранилище. Точка входа, которая материализует это обязательство по одной странице, — ошибка страницы.
flowchart TB
accTitle: Что происходит при Reserve, Commit и первом доступе
accDescr: MEM_RESERVE записывает диапазон и атрибуты в VAD, MEM_COMMIT расходует Commit Total, чтобы обещать хранение, а ошибка страницы при первом доступе назначает физическую страницу и добавляет её в Working Set
reserve["1. MEM_RESERVE"] --> commit["2. MEM_COMMIT"]
commit --> touch["3. Первый доступ (Touch)"]
reserve -.-> vad["Диапазон и атрибуты записываются в VAD"]
commit -.-> charge["Расход Commit Total (физической страницы ещё нет)"]
touch --> fault["Ошибка страницы"]
fault --> zero["Привязать обнулённую физическую страницу к PTE"]
zero --> ws["Добавить в Working Set и повторить инструкцию"]
Рис. 1: Reserve, Commit и Touch — разные события. Физическая ОЗУ привязывается только на последнем шаге, при первом доступе.
2. Три учёта, которые ведут виртуальную страницу
Чтобы понять путь от виртуального адреса к физической ОЗУ, нужно различать три структуры учёта, которые ведёт Windows.
| Учёт | Единица | Роль |
|---|---|---|
| VAD | Диапазон виртуальных адресов | Что это за область, Reserve/Commit, защита, соответствие секции |
| Таблица страниц / PTE | Виртуальная страница | Текущая трансляция на физическую страницу или нематериализованное состояние |
| База данных PFN | Физическая страница | Владение, ссылки и состояние каждой страницы ОЗУ |
VAD хранит сведения о диапазоне, PTE — о виртуальной странице, база данных PFN — о физической странице. Обработчик ошибки страницы сверяет их, чтобы решить, можно ли продолжить доступ.
flowchart TB
accTitle: Три учёта от виртуального адреса к физической ОЗУ
accDescr: Виртуальный адрес ведёт VAD с гранулярностью диапазона и PTE с гранулярностью виртуальной страницы, а база данных PFN отслеживает физическую страницу — цель трансляции PTE — с гранулярностью физической страницы
va["Виртуальный адрес"] --> vad["VAD (учёт диапазонов)"]
va --> pte["PTE (учёт виртуальных страниц)"]
vad -.->|Оценить Reserve/Commit и защиту| pte
pte -->|Действительная трансляция| pfn["База данных PFN (учёт физических страниц)"]
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 не выделяет физическое хранилище ни в ОЗУ, ни в файле подкачки.1
У 64-разрядного процесса виртуальное пространство огромно, поэтому практично сначала зарезервировать большой диапазон и позже выполнить 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 ещё нет действительной трансляции на физическую страницу. Здесь возникает ошибка страницы.
Диспетчер памяти, получив управление, определяет это как «первый доступ к частной странице в состоянии Commit с правом записи», берёт обнулённую физическую страницу, привязывает её к 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, и целевая страница не в состоянии Commit
PAGE_NOACCESS- Запись в страницу только для чтения
- Исполнение инструкции со страницы без права исполнения
- Первое обращение к сторожевой странице
- Обращение вне действительного диапазона секции
Наоборот, даже если PTE недействительна, при законном доступе по программному состоянию VAD и PTE это можно разрешить как demand-zero, восстановление Transition, подкачку страницы или CoW. Точный ответ: нужно оценивать совместно VAD, PTE, атрибуты защиты и тип доступа.
5. Таблицы страниц и TLB
Указатель в приложении — виртуальный адрес. Чтобы процессор обратился к ОЗУ, номер виртуальной страницы нужно транслировать в номер физической. Иерархическая таблица этой трансляции — таблица страниц, а листовая запись — PTE (Page Table Entry).
Действительная PTE концептуально содержит PFN, защиту на чтение, запись и исполнение, разрешение пользовательского режима, Accessed/Dirty и подобное. Фактическая раскладка битов зависит от процессора и версии Windows.
Обходить таблицу страниц каждый раз было бы слишком медленно, поэтому процессор кэширует недавние трансляции в TLB (Translation Lookaside Buffer). Трансляция адреса идёт в таком порядке.
- Если в TLB есть трансляция и доступ соответствует этой защите, используется этот результат.
- Если в TLB нет трансляции, процессор обходит таблицу страниц.
- Если есть действительная PTE и защита тоже совпадает, она регистрируется в TLB и выполнение продолжается.
- Если действительной трансляции нет или есть нарушение защиты, управление переходит на вход обработчика ошибки страницы. Проверка защиты выполняется и тогда, когда трансляция пришла из TLB.
Как показывает этот поток, промах TLB и ошибка страницы — разные вещи. Если единственная проблема в том, что в TLB нет трансляции, а PTE действительна, происходит только обход таблицы страниц. Наоборот, даже если в TLB есть трансляция, нарушение защиты вроде записи в страницу только для чтения или исполнения инструкции на странице без права исполнения идёт на вход обработчика ошибки страницы. Поэтому запись в страницу CoW может вызвать ошибку, даже когда трансляция уже в кэше.
flowchart TB
accTitle: Поток трансляции адреса и вход в обработчик ошибки страницы
accDescr: Даже если в TLB есть трансляция, несовпадение защиты идёт на вход обработчика ошибки страницы. Если в TLB нет трансляции, обходится таблица страниц; действительная PTE с совпадающей защитой регистрируется в TLB и выполнение продолжается, а недействительная трансляция или нарушение защиты идут на вход обработчика ошибки страницы
access["Обращение к памяти"] --> tlb{"Есть ли в TLB трансляция?"}
tlb -->|Да| perm{"Совпадает ли доступ с защитой?"}
perm -->|Совпадает| go["Продолжить с этой трансляцией"]
perm -->|Нарушение защиты| entry["На вход обработчика ошибки страницы"]
tlb -->|Нет| walk["Обход таблицы страниц"]
walk --> valid{"Действительная PTE и защита тоже совпадает?"}
valid -->|Да| register["Зарегистрировать в TLB и продолжить (без ошибки)"]
valid -->|Недействительно или нарушение защиты| entry
Рис. 3: Промах TLB можно разрешить обходом таблицы страниц. Управление идёт к ошибке страницы, когда трансляция недействительна или есть нарушение защиты; нарушение защиты возникает и при попадании в TLB.
5.1. Недействительная PTE — не просто пустое место
Даже недействительная PTE не пуста. По программному состоянию недействительной PTE Windows различает такие случаи, как следующие.
- Страница demand-zero, которая никогда не материализовалась
- Страница Transition, оставшаяся в ОЗУ
- Разделяемая страница, ссылающаяся на Prototype PTE
- Частная страница, сохранённая в файле подкачки
- Нарушение защиты или недействительная область
Задача процессора — лишь решить «это не обычная действительная трансляция» и передать управление ядру; смысл дальше задаёт диспетчер памяти.
6. Ошибка страницы от начала до конца
Проследим первую запись в частную страницу в состоянии Commit в шесть этапов.
- Процессор пытается писать.
Он проверяет TLB и таблицу страниц, но у целевой PTE нет действительного PFN. - Процессор вызывает ошибку страницы.
Он передаёт ядру виртуальный адрес ошибки, тип чтения, записи или исполнения, пользовательский режим или ядро и то, в чём проблема: нет трансляции или нарушение защиты. - Диспетчер памяти проверяет VAD и PTE.
Он решает, страница ли в состоянии Commit, совпадает ли защита, и что из demand-zero, Transition, разделяемой страницы, подкачки, CoW или исключения применимо. - Если это demand-zero, получают обнулённую физическую страницу.
Вновь выданная страница должна быть нулевой, чтобы не утекли данные другого процесса. - Обновляются сведения учёта PTE и PFN.
В PTE задаются PFN и защита, физическая страница становится Active и добавляется в Working Set процесса. - Неудавшаяся инструкция выполняется повторно.
Поскольку ошибка разрешилась нормально, исключение пользовательского режима не доставляется, и приложение продолжает присваивание как обычно.
События ETW об ошибках страниц также записывают Transition, Demand Zero, Copy-on-Write, Guard Page, Hard Page Fault и Access Violation как разные виды.4
Итак, ошибка страницы с самого начала не слово, означающее «аномалию». Это общая точка входа, чтобы ОС приняла решение, когда процессор не смог выполнить трансляцию обычным путём.
flowchart LR
accTitle: Ветвление исходов ошибки страницы
accDescr: Диспетчер памяти оценивает VAD, PTE, атрибуты защиты и тип доступа и направляет к demand-zero, повторному подключению страницы ещё в ОЗУ, жёсткой ошибке из резервного хранилища, копированию при записи, уведомлению сторожевой страницы или исключению
faultIn["Возникает ошибка страницы"] --> judge["Оценить VAD, PTE, защиту, тип"]
judge -->|Первый доступ| dz["Demand-zero (мягкая)"]
judge -->|Ещё в ОЗУ| soft["Подключить из Standby (мягкая)"]
judge -->|Нужно чтение с диска| hard["Жёсткая ошибка (дисковый ввод-вывод)"]
judge -->|Запись CoW| cow["Скопировать и заменить PTE"]
judge -->|Сторожевая страница| guard["Снять PAGE_GUARD и уведомить"]
judge -->|Неразрешимо| av["Исключение (0xC0000005 и т. д.)"]
Рис. 4: Ошибки, входящие через одну точку входа, расходятся на шесть исходов в зависимости от оценки. Подробности сторожевых страниц — в разделе 9.
7. Demand-zero — мягкая ошибка, которая не читает диск
Demand-zero — характерная мягкая ошибка страницы при первом обращении к частной странице в состоянии Commit. Документация Microsoft по Working Set также приводит «процесс впервые обращается к выделенной виртуальной странице» как пример мягкой ошибки.5
У demand-zero такие свойства.
- Не нужно читать исходные данные с диска
- Начальное содержимое — нули
- Привязывается доступная физическая страница
- Растут Working Set и накопительный Page Fault Count
- Одна эта обработка не увеличивает
Memory\\Pages Input/sec
Поэтому всплеск Page Faults/sec сразу после запуска сам по себе не значит, что хранилище стало узким местом.
Компромисс отложенного выделения тоже стоит разобрать. Если вы сделали Commit на 256 МиБ и реально используете только 8 МиБ, оставить остальные 248 МиБ вне ОЗУ разумно. Цена — стоимость обработки ошибки при первом доступе. В коде, чувствительном к задержке, иногда заранее касаются каждой страницы (prefault), но это сознательный обмен: резидентный объём в ОЗУ растёт раньше.
8. Мягкие и жёсткие ошибки страниц
8.1. Мягкие ошибки
Мягкая ошибка страницы — ошибка, которую можно разрешить без чтения из резервного хранилища. Типичные примеры включают следующее.
- Demand-zero
- Повторное подключение страницы, оставшейся на Standby/Transition
- Подключение разделяемой страницы, которая в Working Set другого процесса
- Подключение предварительно прочитанной страницы
- Копирование при записи, исходная страница которого резидентна
Остаётся стоимость процессора на переход в ядро, блокировки, обновления PTE/PFN, согласованность TLB и тому подобное, но ожидания хранилища нет.5
8.2. Жёсткие ошибки
С другой стороны, когда нужной страницы нигде в ОЗУ нет и её надо читать из резервного хранилища, это жёсткая ошибка страницы. Источник чтения — не только файл подкачки.
- Частная страница, вытесненная в файл подкачки
- Файл, отображённый в память
- Образ EXE или DLL
- Файл данных, на который ссылается кэш файлов
События ETW HardFault включают FileObject, ReadOffset и ByteCount, так что можно проследить фактический источник чтения.6
Поэтому Hard Fault ≠ «прочитали pagefile.sys».
Когда нужно чтение из резервного хранилища, запрос входит в стек ввода-вывода Windows. Поток IRP и выдачи/завершения разобран в «Глубины ввода-вывода Windows (часть 1) — архитектура ввода-вывода Windows и IRP», а стык с кэшем файлов — в «Глубины ввода-вывода Windows (часть 4) — диспетчер кэша: когда ваш WriteFile доходит до диска». Если страница уже в ОЗУ, диспетчер памяти может вернуться сам. Если нет, он выдаёт запрос ввода-вывода и удерживает поток с ошибкой до завершения.
9. Неразрешимая ошибка становится исключением
Ошибка, которую после проверки VAD и PTE нельзя разрешить как законное выделение, подкачку страницы или CoW, доставляется в пользовательский режим как исключение.
Типичный случай — STATUS_ACCESS_VIOLATION, код исключения 0xC0000005. Он возникает при чтении, записи или исполнении недействительного адреса; первый параметр исключения указывает тип доступа, второй — адрес нарушения.7
Типичные шаблоны включают следующее.
- Чтение NULL, освобождённого адреса или адреса за пределами массива
- Запись в страницу только для чтения
- Исполнение инструкции со страницы, которую DEP/NX сделал неисполняемой
- Обращение к зарезервированному диапазону, для которого не выполнен Commit
У PAGE_GUARD смысл чуть другой. Это одноразовое уведомление о доступе: оно поднимает STATUS_GUARD_PAGE_VIOLATION и используется, например, для роста стека.8
Обычное отложенное выделение, подкачка страницы, CoW, уведомление сторожа и итоговое нарушение доступа с точки зрения процессора сходятся в одной точке входа ошибки страницы. Исход решает сочетание 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(<процесс>)\\Page Faults/secMemory\\Pages Input/secMemory\\Page Reads/secMemory\\Available MBytesProcess(<процесс>)\\Working Set - PrivateProcess(<процесс>)\\Private Bytes
Process\\Page Faults/sec включает и мягкие, и жёсткие ошибки. Memory\\Pages Input/sec же — число страниц, прочитанных с диска, чтобы разрешить жёсткие ошибки.10
На этапе Touch этой программы Page Faults/sec должен скакнуть, а Pages Input/sec — почти не вырасти. Страницы, для которых только что выполнен Commit, материализуются через demand-zero, поэтому исходные данные с диска читать не нужно.
Если процессов с одним именем несколько, номера PerfMon вроде process#1 могут смениться после перезапуска. Сверяйте со счётчиком, который показывает PID, или идентифицируйте по PID через Process V2 или ETW/WPA.
11. Три неверных прочтения, которых стоит избегать на практике
11.1. «Вырос Commit — значит утечка ОЗУ»
Commit — обещанный объём содержимого, которое нужно хранить; страницы, к которым ещё не было Touch, могут не быть резидентными в ОЗУ. Чтобы судить об утечке, смотрите временной ряд 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резервирует диапазон виртуальных адресов, но не выделяет физическую область в ОЗУ или файле подкачки.1MEM_COMMITрасходует Commit и гарантирует, что содержимое можно будет хранить в будущем, но обычная физическая страница не назначается до первого доступа.12- VAD — учёт диапазонов, PTE — учёт виртуальных страниц, база данных PFN — учёт физических страниц.
- Промах TLB — не ошибка страницы. Если 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 и спискам страниц.
Связанные статьи
- Как читать «использование памяти» в Windows: Working Set, Private Bytes, Commit и файл подкачки
- Глубины ввода-вывода Windows (часть 1) — архитектура ввода-вывода Windows и IRP
- Глубины ввода-вывода Windows (часть 4) — диспетчер кэша: когда ваш WriteFile доходит до диска
- Как читать аварийные дампы в WinDbg + SOS — практика анализа после сбора
- Сбор дампов сбоев Windows — введение: WER, ProcDump, WinDbg
Смежные области консультаций
KomuraSoft LLC занимается расследованием использования памяти приложений Windows, нарушений доступа, задержек запуска, подкачки и анализом дефектов в нативном коде.
- Разработка Windows-приложений
- Исследование дефектов и анализ первопричин
- Использование и миграция существующих активов
- Связаться с нами
Справочные ссылки
-
Microsoft Learn, VirtualAlloc function. О том, что
MEM_RESERVEрезервирует диапазон виртуальных адресов без выделения физического хранилища;MEM_COMMITрасходует commit charge против общей памяти и файла подкачки системы; начальное содержимое страницы в состоянии Commit — нули; и фактическая физическая страница не назначается, пока к ней не обратятся. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 -
Microsoft Learn, PERFORMANCE_INFORMATION structure. О том, что
CommitTotal— текущее число страниц Commit системы, аCommitLimit— верхняя граница, до которой можно выполнить Commit без расширения файла подкачки. ↩ ↩2 -
Microsoft Learn, !vad (WinDbg). О том, что
!vadпоказывает дерево VAD и позволяет посмотреть начальный и конечный VPN, Commit, Mapped/Private, атрибуты защиты, Control Area и другое. ↩ -
Microsoft Learn, PageFault_TypeGroup1 class. О том, что ETW различает и записывает Transition Fault, Demand Zero Fault, Copy-on-Write, Guard Page Fault, Hard Page Fault и Access Violation. ↩
-
Microsoft Learn, Working Set. О том, что мягкая ошибка разрешима без обращения к резервному хранилищу и возникает из Working Set другого процесса, Transition, demand-zero при первом обращении и тому подобного. ↩ ↩2 ↩3
-
Microsoft Learn, PageFault_HardFault class. О том, что событие HardFault включает FileObject, ReadOffset, ByteCount, VirtualAddress и идентификатор потока, так что источник чтения можно проследить. ↩ ↩2
-
Microsoft Learn, Access Violation C0000005. О том, что
0xC0000005возникает при чтении, записи или исполнении недействительного адреса памяти, а параметры исключения указывают тип доступа и адрес нарушения. ↩ ↩2 -
Microsoft Learn, Creating Guard Pages. О том, что
PAGE_GUARDдаёт одноразовое уведомление о доступе к странице и поднимаетSTATUS_GUARD_PAGE_VIOLATION. ↩ -
Microsoft Learn, VMMap - Sysinternals. О том, что VMMap раскладывает виртуальную память в состоянии Commit по типам и показывает Working Set каждого типа и подробную карту адресов. ↩
-
Microsoft Learn, Performance Analysis of Logs (PAL) Tool. О том, что
Memory\\Pages Input/sec— число страниц, прочитанных с диска для разрешения жёстких ошибок страниц. ↩
Похожие статьи
Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.
Глубины памяти Windows (часть 2) — жизненный цикл физической страницы: пять списков и файл подкачки
Разбираем, как связаны база данных PFN, Standby, Modified, сжатие памяти и файл подкачки, и куда уходит физическая страница, когда её уби...
Как читать «использование памяти» в Windows: Working Set, Private Bytes, Commit и файл подкачки
Столбец «Память» в диспетчере задач, Working Set, Private Bytes и Commit — это разные величины. Разбираем связь виртуальной и физической ...
Глубины памяти Windows (часть 3) — объекты секций и копирование при записи: как устроены DLL и проецирование файлов
Разбираем объекты секций, проецирование образа и данных, общий кэш и копирование при записи: как DLL и общая память делят одни и те же фи...
Как ярлык Windows находит перемещённый файл? — Местоположение файла и его идентичность — разные вещи
Почему ярлык по-прежнему открывает перемещённый файл? Windows ищет цель не только по сохранённому пути, но и по идентификаторам отслежива...
Нужно ли по-прежнему «безопасно извлекать» USB-накопитель? — взгляд через быстрое удаление и кэш записи
Копирование закончилось — можно ли сразу вынуть USB-накопитель? Разбираем кэш записи и разницу между «быстрым удалением» и «повышенной пр...
Связанные темы
Эти страницы показывают тему статьи в более широком контексте услуг и решений.
Технические темы Windows
Раздел о разработке Windows, расследовании сбоев и использовании существующих активов.
Услуги по этой теме
Статья напрямую связана со следующими услугами.
Разработка приложений для Windows
Бизнес-приложения, интеграция оборудования и средства связи — от требований до разработки.
Частые вопросы
Вопросы, которые часто возникают при консультациях по теме статьи.
- Вызов VirtualAlloc с MEM_COMMIT сразу выделяет ОЗУ?
- В обычной частной памяти в момент Commit расходуется запас commit системы, но соответствующая физическая страница не назначается до первого доступа. Страница, к которой впервые обратились записью, получает физическую страницу во время обработки ошибки demand-zero.
- Означает ли ошибка страницы сбой или проблему с производительностью?
- Нет. Мягкие ошибки без дискового ввода-вывода — demand-zero или возврат страницы из Standby — это нормальная работа. Для оценки производительности смотрите не только Page Faults/sec, но и Pages Input/sec, ожидание хранилища и Available MBytes.
- Промах TLB и ошибка страницы — это одно и то же?
- Нет, это разные вещи. Даже если в TLB нет трансляции, при действительной PTE процессор просто обходит таблицу страниц и заново регистрирует трансляцию. На вход обработчика ошибки страницы он переходит, когда PTE недействительна или есть нарушение защиты.
- Если диапазон адресов есть в VAD, нарушения доступа быть не может?
- Не обязательно. Помимо наличия VAD диспетчер памяти оценивает Reserve или Commit, защиту на чтение, запись и исполнение, сторожевые страницы, состояние PTE и другое. Если ошибку нельзя разрешить, возникает исключение вроде 0xC0000005.
- Высокий Page Faults/sec означает нехватку ОЗУ?
- По одному этому показателю не скажешь. Page Faults/sec включает и большое число мягких ошибок. Нужно сопоставить его на одной временной оси с Memory\Pages Input/sec, Memory\Page Reads/sec, Available MBytes и временем ожидания диска.
Об авторе
Страница с профилем автора статьи.
Го Комура
Представитель KomuraSoft LLC
Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.