• Суммарный размер системного рабочего набора Эти данные можно увидеть, выбрав объект Memory (Память) и счетчик Cache Bytes (Байт кэш-памяти). Как поясняется в разделе «Системный рабочий набор», суммарный размер системного рабочего набора определяется не только размером кэша, но и подмножеством пула подкачиваемой памяти и объемом резидентного кода операционной системы и драйверов, находящегося в этом рабочем наборе.
• Размер пула неподкачиваемой памяти Для просмотра этого значения выберите счетчик Memory: Pool Nonpaged Bytes (Память: Байт в невыгружаемом страничном пуле).
• Размер списков простаивающих, свободных и обнуленных страниц Общий размер этих списков сообщает счетчик Memory: Available Bytes (Память: Доступно байт). Если вы хотите узнать размер каждого из списков, используйте команду /memusage отладчика ядра.
Теперь на графике (или в отчете) должна присутствовать информация обо всей физической памяти, кроме двух элементов, о которых счетчики производительности не сообщают:
• неподкачиваемого кода операционной системы и драйверов;
• списка модифицированных страниц и списка модифицированных, но не записываемых страниц (modified-no-write paging list). Хотя размеры двух последних списков легко узнать с помощью команды !memusage отладчика ядра, выяснить размер резидентного кода операционной системы и драйверов не так просто.
Сервисы диспетчера памяти
Диспетчер памяти предоставляет набор системных сервисов для выделения и освобождения виртуальной памяти, разделения памяти между процессами, проецирования файлов в память, сброса виртуальных страниц на диск, получения информации о диапазоне виртуальных страниц, изменения атрибутов защиты виртуальных страниц и блокировки в памяти.
Как и другие сервисы исполнительной системы, сервисы управления памятью требуют при вызове передачи описателя того процесса, с виртуальной памятью которого будут проводиться операции. Таким образом, вызывающая программа может управлять как собственной памятью, так и памятью других процессов (при наличии соответствующих прав). Например, если один процесс порождает другой, у первого по умолчанию остается право на манипуляции с виртуальной памятью второго. Впоследствии родительский процесс может выделять и освобождать память, считывать и записывать в нее данные через сервисы управления виртуальной памятью, передавая им в качестве аргумента описатель дочернего процесса. Подсистемы используют эту возможность для управления памятью своих клиентских процессов; она же является ключевой для реализации отладчиков, так как им нужен доступ к памяти отлаживаемого процесса для чтения и записи.
Большинство этих сервисов предоставляется через Windows API. B него входят три группы прикладных функций управления памятью: для операций со страницами виртуальной памяти (Virtualxxx), проецирования файлов в память (CreateFileMapping,MapViewOfFile) и управления кучами (Heapxxx, а также функции из старых версий интерфейса — Localxxx и Globalxxx).
Диспетчер памяти поддерживает такие сервисы, как выделение и освобождение физической памяти, блокировка страниц в физической памяти для передачи данных другим компонентам исполнительной системы режима ядра и драйверам устройств через DMA. Имена этих функций начинаются с префикса Mm. Кроме того, существуют процедуры поддержки исполнительной системы, имена которых начинаются с Ex. He являясь частью диспетчера памяти в строгом смысле этого слова, они применяются для выделения и освобождения памяти из системных куч (пулов подкачиваемой и неподкачиваемой памяти), а также для манипуляций с ассоциативными списками. Мы затронем эту тематику в разделе «Системные пулы памяти» далее в этой главе.
Несмотря на упоминание Windows-функций управления памятью в режиме ядра и выделения памяти для драйверов устройств, мы будем рассматривать не столько интерфейсы и особенности их программирования, сколько внутренние принципы работы этих функций. Полное описание доступных функций и их интерфейсов см. в документации Platform SDK и DDK.
Большие и малые страницы
Виртуальное адресное пространство делится на единицы, называемые страницами. Это вызвано тем, что аппаратный блок управления памятью транслирует виртуальные адреса в физические по страницам. Поэтому страница — наименьшая единица защиты на аппаратном уровне. (Различные параметры защиты страниц описываются в разделе «Защита памяти» далее в этой главе.) Страницы бывают двух размеров: малого и большого. Реальный размер зависит от аппаратной платформы (см. таблицу 7–1).
Преимущество больших страниц — скорость трансляции адресов для ссылок на другие данные в большой странице. Дело в том, что первая ссылка на любой байт внутри большой страницы заставляет аппаратный ассоциативный буфер трансляции (translation look-aside buffer, TLB) (см. раздел «Ассоциативный буфер трансляции» далее в этой главе) загружать в свой кэш информацию, необходимую для трансляции ссылок на любые другие байты в этой большой странице. При использовании малых страниц для того же диапазона виртуальных адресов требуется больше элементов TLB, что заставляет чаще обновлять элементы по мере трансляции новых виртуальных адресов. A это в свою очередь требует чаще обращаться к структурам таблиц страниц при ссылках на виртуальные адреса, выходящие за пределы данной малой страницы. TLB — очень маленький кэш, и поэтому большие страницы обеспечивают более эффективное использование этого ограниченного ресурса.
Чтобы задействовать преимущества больших страниц в системах с достаточным объемом памяти (см. минимальные размеры памяти в таблице 7–2), Windows проецирует на такие страницы базовые образы операционной системы (Ntoskrnl.exe и Hal.dll) и базовые системные данные (например, начальную часть пула неподкачиваемой памяти и структуры данных, описывающие состояние каждой страницы физической памяти). Windows также автоматически проецирует на большие страницы запросы объемного ввода-вывода (драйверы устройств вызывают MmMapIoSpace), если запрос удовлетворяет длине и выравниванию для большой страницы. Наконец, Windows разрешает приложениям проецировать на такие страницы свои образы, закрытые области памяти и разделы, поддерживаемые страничным файлом (pagefile-backed sections). (См. описание флага MEM_LARGE_PAGE функции VirtualAlloc.) Вы можете указать, чтобы и другие драйверы устройств проецировались на большие страницы, добавив многострочный параметр реестра HKLMSYSTEMCurrentControlSetControlSession ManagerMemory ManagementLargePageDrivers и задав имена драйверов как отдельные строки с нулем в конце.
Один из побочных эффектов применения больших страниц заключается в следующем. Так как аппаратная защита памяти оперирует страницами как наименьшей единицей, то, если на большой странице содержатся код только для чтения и данные для записи/чтения, она должна быть помечена как доступная для чтения и записи, т. е. код станет открытым для записи. A значит, драйверы устройств или другой код режима ядра мог бы в результате скрытой ошибки модифицировать код операционной системы или драйверов, изначально предполагавшийся только для чтения, и не вызвать нарушения доступа к памяти. Однако при использовании малых страниц для проецирования ядра части NTOSKRNL.EXE и HAL.DLL только для чтения будут спроецированы именно как страницы только для чтения. Хотя это снижает эффективность трансляции адресов, зато при попытке драйвера устройства (или другого кода режима ядра) модифицировать доступную только для чтения часть операционной системы произойдет немедленный крах с указанием на неверную инструкцию. Поэтому, если вы подозреваете, что источник ваших проблем связан с повреждением кода ядра, включите Driver Verifier — это автоматически отключит использование больших страниц.
Резервирование и передача страниц
Страницы в адресном пространстве процесса могут быть свободными (free), зарезервированными (reserved) или переданными (committed). Приложения могут резервировать (reserve) адресное пространство и передавать память (commit) зарезервированным страницам по мере необходимости. Резервировать страницы и передавать им память можно одним вызовом. Эти сервисы предоставляются через Windows-функции VirtualAlloc и VirtualAllocEx.
Резервирование адресного пространства позволяет потоку резервировать диапазон виртуальных адресов для последующего использования. Попытка доступа к зарезервированной памяти влечет за собой нарушение доступа, так как ее страницы не спроецированы на физическую память.
При попытке доступа адреса переданных страниц в конечном счете транслируются в допустимые адреса страниц физической памяти. Переданные страницы могут быть закрытыми (не предназначенными для разделения с другими процессами) или спроецированными на представление объекта-раздела (на которое в свою очередь могут проецировать страницы другие процессы).