!heapв любом стандартном отладчике Windows.
Включение средств отладки влияет на все кучи в процессе. Кроме того, включение любого средства отладки приводит к автоматическому отключению интерфейсного уровня и переходу на использование базового. Интерфейсный уровень также не применяется для нерасширяемых куч (из-за дополнительных издержек) или для куч, не допускающих сериализации.
Заметьте, что применение pageheap может привести к нехватке адресного пространства, так как выделение даже очень малых блоков памяти сопряжено с существенными издержками. Также может ухудшиться производительность из-за увеличения количества ссылок на обнуленные страницы, потери локальности и частых вызовов для проверки структур кучи. Чтобы уменьшить негативное влияние на производительность, pageheap можно использовать только для блоков определенных размеров, конкретных диапазонов адресов и т. д.
ПРИМЕЧАНИЕ Подробнее о pageheap см. статью 286470 в Microsoft Knowledge Base ( http://support.microsoft.com ).
Выделение и использование памяти через функции AWE осуществляется в три этапа.
1. Выделение физической памяти.
2. Создание региона виртуального адресного пространства – окна, на которое будут проецироваться представления физической памяти.
3. Проецирование на окно представлений физической памяти.
Для выделения физической памяти приложение вызывает Windows-функцию AllocateUserPhysicalPages.(Эта функция требует, чтобы у пользователя была привилегия Lock Pages In Memory.) Затем приложение обращается к Windows-функции VirtucriAllocс флагом MEM_PHYSICAL, чтобы создать окно в закрытой части адресного пространства процесса, на которое проецируется (частично или полностью) ранее выделенная физическая память. Память, выделенная через AWE, может быть использована почти всеми функциями Windows API. (Например, функции Microsoft DirectX ее не поддерживают.)
Если приложение создает в своем адресном пространстве окно размером 256 Мб и выделяет 4 Гб физической памяти (в системе с объемом физической памяти более 4 Гб), то оно получает доступ к любой части физической памяти, проецируя ее на это окно через Wmdows-функции MapUserPhysicalPagesили MapUserPhysicalPagesScatter.Размер физической памяти, единовременно доступный приложению при такой схеме выделения, определяется размером окна в виртуальном адресном пространстве. Ha рис. 7-6 показано AWE-ОKHO в адресном пространстве серверного приложения, на которое проецируется регион физической памяти, предварительно выделенный через AllocateUserPhysicalPages.
AWE-функции имеются во всех выпусках Windows и доступны независимо от объема физической памяти в системе. Однако AWE наиболее полезен в системах с объемом физической памяти не менее 2 Гб, поскольку тогда этот механизм – единственное средство для прямого использования более чем 2 Гб памяти 32-разрядным процессом. Еще одно его применение – защита. Так как AWE-память никогда не выгружается на диск, данные в этой памяти никогда не имеют копии в страничном файле, а значит, никто не сумеет просмотреть их, загрузив компьютер с помощью альтернативной операционной системы.
Теперь несколько слов об ограничениях, налагаемых на память, которая выделяется и проецируется с помощью AWE-функций.
(o)Страницы такой памяти нельзя разделять между процессами.
(o)Одну и ту же физическую страницу нельзя проецировать по более чем одному виртуальному адресу в рамках одного процесса.
(o)B более старых версиях Windows страницы такой памяти могут иметь единственный атрибут защиты – «для чтения и записи». B Windows Server
2003 Service Pack 1 и выше также поддерживаются атрибуты «нет доступа» и «только для чтения».
O структурах данных таблицы страниц, используемой для проецирования памяти в системах с более чем 4 Гб физической памяти, см. раздел «Phy-sical Address Extension (PAE)» далее в этой главе.
(o) Пул неподкачиваемой памяти (nonpaged pool)Состоит из диапазонов системных виртуальных адресов, которые всегда присутствуют в физической памяти и доступны в любой момент (при любом IRQL и из контекста любого процесса) без генерации ошибок страниц. Одна из причин существования такого пула – невозможность обработки ошибок страниц при IRQL уровня «DPC/dispatch» и выше (см. главу 2).
(o) Пул подкачиваемой памяти (paged pool)Регион виртуальной памяти в системном пространстве, содержимое которого система может выгружать в страничный файл и загружать из него. Драйверы, не требующие доступа к памяти при IRQL уровня «DPC/dispatch» и выше, могут использовать память из этого пула. Он доступен из контекста любого процесса. Оба пула находятся в системном адресном пространстве и проецируются на виртуальное адресное пространство любого процесса (их начальные адреса в системной памяти перечислены в таблице 7-8). Исполнительная система предоставляет функции для выделения и освобождения памяти в этих пулах (см. описание функций, чьи имена начинаются c ExAllocatePool,в Windows DDK).
B однопроцессорных системах создается три пула подкачиваемой памяти, а в многопроцессорных – пять. Наличие нескольких подкачиваемых пулов уменьшает частоту блокировки системного кода при одновременных обращениях нескольких потоков к процедурам управления пулами. Начальный размер подкачиваемого и неподкачиваемого пулов зависит от объема физической памяти и может при необходимости расти до максимального значения, вычисляемого в период загрузки системы.
ПРИМЕЧАНИЕ Будущие выпуски Windows, возможно, будут поддерживать пулы динамических размеров, а значит, лимита на максимальный размер больше не будет. Таким образом, в приложениях и драйверах устройств нельзя исходить из того, что максимальный размер пула является фиксированной величиной в любой системе.
Таблица 7-5. Максимальные размеры пулов
Рассчитанные значения размеров хранятся в четырех переменных ядра, три из которых экспортируются как счетчики производительности. Имена переменных, счетчиков и параметров реестра, позволяющих изменять размеры пулов, перечислены в таблице 7-6.
Таблица 7-6. Переменные и счетчики производительности, отражающие размеры системных пулов
ЭКСПЕРИМЕНТ: определяем максимальные размеры пулов
Поскольку пулы подкачиваемой и неподкачиваемой памяти являются критическими ресурсами системы, важно знать, когда их размер приближается к расчетному для вашей системы пределу, чтобы задать значение, отличное от установленного по умолчанию в соответствующих параметрах реестра. Счетчики выводят лишь текущий, но не максимальный размер, поэтому вы не узнаете о приближении к лимиту, пока не достигнете его. (Как уже говорилось, будущие версии Windows, возможно, будут поддерживать пулы динамических размеров. И тогда необходимость в проверке максимальных размеров пулов отпадет.)
Получить максимальные размеры пулов можно с помощью Process Explorer или отладки ядра в работающей системе (см. главу 1). Для просмотра этих данных через Process Explorer, щелкните View, System Information. Максимальные размеры пулов показываются в секции Kernel Memory, как на следующей иллюстрации.
Заметьте: чтобы Process Explorer мог получить эту информацию, у него должен быть доступ к символам для ядра данной системы. (Как настроить Process Explorer на использование символов, см. в эксперименте «Просмотр детальных сведений о процессах с помощью Process Explorer» в главе 1.)
Для просмотра той же информации в отладчике ядра используйте команду !vm
B этой системе размеры пулов подкачиваемой и неподкачиваемой памяти далеки от своих максимумов. Отладчик ядра также позволяет изучить значения переменных ядра, перечисленных в таблице 7-6:
kd› dd mmmaximumnonpagedpoolinbytes 11 8047f620 0328c000 kd› ? 328c000
Evaluate expression: 53002240 = 0328c000 kd› dd mmsizeofpagedpoolinbytes 11 80470a98 06800000 kd› ? 6800000
Evaluate expression: 109051904 = 06800000
Из этого примера видно, что максимальный размер неподкачива-емого пула составляет 53 002 240 байтов (примерно 50 Мб), а максимальный размер подкачиваемого пула – 109 051 904 байта (104 Мб). B тестовой системе, использованной нами для этого эксперимента, текущий размер использованной памяти неподкачиваемого пула составлял 5,5 Мб, а подкачиваемого пула – 34 Мб, так что оба пула были далеки от заполнения.
Теперь щелкните кнопкуАрр1у и перезагрузите систему. После перезагрузки запустите Poolmon. При этом вы должны увидеть примерно следующее.
Строки с меняющимися данными выделяются подсветкой. (Ee можно выключить, введя букву / в окне Poolmon. Повторный ввод / вновь включает подсветку.) Нажав клавишу со знаком вопроса в Poolmon, можно просмотреть справочный экран. Вы можете указать пулы, за которыми хотите наблюдать (только подкачиваемый, только неподкачиваемый или и то, и другое), а также порядок сортировки. Кроме того, на справочном экране поясняются параметры командной строки, позволяющие наблюдать за конкретными структурами (или за всеми структурами, но одного типа). Так, команда poolmon -iCMпозволит следить только за структурами типа CM (которые принадлежат диспетчеру конфигурации, управляющему реестром). Колонки, в которых программа выводит свою информацию, описаны в таблице 7-7.
B этом примере структуры CM занимают основную часть пула подкачиваемой памяти, а структуры MmSt (структуры, относящиеся к управлению памятью и используемые для проецируемых файлов) – основную часть пула неподкачиваемой памяти.
Описание меток пулов см. в файле \Program Files\Debugging Tools for Windows\Triage\Pooltag.txt. (Он устанавливается вместе с Windows Debugging Tools.) Поскольку в этом файле не перечислены метки пулов для сторонних драйверов устройств, используйте ключ -св версии Poolmon, поставляемой с Windows Server 2003 Device Driver Kit (DDK), для генерации файла меток локальных пулов (Localtag.txt). B этом файле содержатся метки пулов, используемых любыми драйверами, которые были обнаружены в вашей системе. (Учтите: если двоичный файл драйвера устройства был удален после загрузки, метки его пулов не распознаются.)
B качестве альтернативы можно вести поиск драйверов устройств в системе по метке пула, используя утилиту Strings.exe с сайта wwwsysinternals.com .Например, команда:
strings \windows\system32\drivers\*.sys › findstr /i "abcd"
покажет драйверы, содержащие строку «abcd». Заметьте, что драйверы устройств не обязательно должны находиться в \Windows\System32\Drivers – они могут быть в любом каталоге. Чтобы перечислить полные пути всех загруженных драйверов, откройте меню Start (Пуск), выберите команду Run (Выполнить) и введите Msinfo32.Потом щелкните Software Environment (Программная среда) и System Drivers (Системные драйверы).
Еще один способ для просмотра использования пулов драйвером устройства – включение наблюдения за пулами в Driver Verifier (см. далее в этой главе). Хотя при этом способе сопоставление метки пула с драйвером не нужно, он требует перезагрузки (чтобы включить функциональность наблюдения за пулами в Driver Verifier для интересующих вас драйверов). После этого вы можете либо запустить Driver Verifier Manager (\Windows\System32\ Verifier.exe), либо использовать команду Verifier /Logдля записи информации об использовании пулов в какой-либо файл.
Наконец, если вы изучаете аварийный дамп, то можете исследовать использование пулов и с помощью команды !poolused.Команда !poolused 2сообщает об использовании пула неподкачиваемой памяти с сортировкой по структурам, занимающим наибольшее количество памяти, а команда !poolused 4 –об использовании пула подкачиваемой памяти (с той же сортировкой). Ниже приведен фрагмент выходной информации этих двух команд.
ЭКСПЕРИМЕНТ: анализ утечки памяти в пуле
B этом эксперименте вы устраните реальную утечку в пуле подкачиваемой памяти в своей системе, чтобы научиться на практике применять способы, описанные в предыдущем разделе. Утечка будет создаваться утилитой NotMyFault, которую можно скачать по ссылке wwwsysintemals.com/windowsinternalsshtmL (Заметьте, что эта утилита отсутствует в списке инструментов на основной странице Sysinternals.) После запуска NotMyFault.exe загружает драйвер устройства Myfault.sys и выводит такое диалоговое окно.
1. Щелкните кнопку Leak Pool. Это заставит NotMyFault посылать запросы драйверу устройства Myfault на выделение памяти из подкачиваемого пула. (He нажимайте кнопку Do Bug, иначе вы вызовете крах системы; предназначение этой кнопки описывается в главе 14, где демонстрируются различные типы аварийных ситуаций.) NotMyFault продолжит посылать запросы, пока вы не щелкнете кнопку Stop Leaking. Заметьте, что пул подкачиваемой памяти не освобождается даже при закрытии программы; в нем происходит постоянная утечка памяти до перезагрузки системы. Однако, поскольку утечка пула будет непродолжительной, это не должно вызвать никаких проблем в вашей системе.
2. Пока происходит утечка памяти в пуле, сначала откройте диспетчер задач и перейдите на вкладку Performance (Быстродействие). Вы увидите, как растет показатель Paged Pool (Выгружаемая память). To же самое можно увидеть в окне System Information утилиты Process Explorer. (Выберите Show и System Information.)
3. Чтобы определить метку пула, где происходит утечка, запустите Poolmon и нажмите клавишу b,чтобы сортировать по числу байтов. Дважды нажмите клавишу pдля отображения в Poolmon только пула подкачиваемой памяти. Вы должны заметить, что пул с меткой «Leak» поднимается вверх по списку. (Poolmon выделяет строки, где происходят изменения.)
4. Теперь щелкните кнопку Stop Leaking, чтобы не истощить пул подкачиваемой памяти в своей системе.
5. Используя приемы, описанные в предыдущем разделе, запустите Strings (ее можно скачать с wwwsysinternals.com )для поиска двоичных файлов драйвера, содержащих метку пула «Leak»:
Strings \windows\system32\drivers\*.sys | findstr Leak
Эта команда должна указать на файл Myfault.sys.
Функции ExInitializeNPagedLookasideListи ExInitializePagedLookasideList(документированные в DDK) позволяют компонентам исполнительной системы и драйверам устройств создавать ассоциативные списки, размеры которых кратны размерам наиболее часто используемых структур данных. Для минимизации издержек, связанных с синхронизацией в многопроцессорных системах, некоторые компоненты исполнительной системы, в том числе диспетчер ввода-вывода, диспетчер кэша и диспетчер объектов, создают отдельные для каждого процессора ассоциативные списки, из которых выделяется память под часто используемые структуры данных. Сама исполнительная система создает для каждого процессора универсальные ассоциативные списки подкачиваемой и неподкачиваемой памяти с гранулярностью выделения в 256 байтов или менее.
Если ассоциативный список пуст (как это бывает сразу после его создания), система должна выделить память из подкачиваемого или неподкачиваемого пула. Ho если в списке уже присутствует освобожденная структура, то занимаемая ею память выделяется очень быстро. (Список разрастается по мере возврата в него структур.) Процедуры выделения памяти из пула автоматически настраивают число освобожденных буферов, хранящихся в ассоциативном списке, в зависимости от частоты выделения памяти из этого списка драйвером или компонентом исполнительной системы. Чем чаще они выделяют память из списка, тем больше буферов в списке. Размер ассоциативных списков автоматически уменьшается, если память из них не выделяется. (Эта проверка выполняется раз в секунду, когда системный поток диспетчера настройки баланса пробуждается и вызывает функцию KiAdjustLookasideDepth.)
ЭКСПЕРИМЕНТ: просмотр системных ассоциативных списков
Содержимое и размер различных ассоциативных списков в системе можно просмотреть командой !lookasideотладчика ядра. Вот фрагмент вывода этой команды.
Driver Verifier поддерживается несколькими системными компонентами – диспетчером памяти, диспетчером ввода-вывода и HAL, которые предусматривают параметры, включаемые для верификации драйверов. B этом разделе поясняются параметры верификации драйверов на отсутствие ошибок, связанных с управлением памятью (см. также главу 9).
B Windows XP и Windows Server 2003 этой утилите придали интерфейс в стиле мастера, как показано на рис. 7-8.
Рис. 7-8. Driver Verifier Manager в Windows XP и Windows Server 2003
Включать и отключать Driver Verifier, а также просматривать текущие параметры можно из командной строки этой утилиты. Для вывода списка ключей наберите verifier /?.
Настройки Driver Verifier Manager хранятся в разделе реестра HKLM\SYS-TEM\CurrentControlSet\Control\Session Manager\Memory Management. Параметр VerifyDriverLevel содержит битовую маску, представляющую включенные типы проверок. Имена проверяемых драйверов содержатся в параметре VerifyDrivers. (Эти параметры создаются в реестре только после выбора проверяемых драйверов в окне Driver Verifier Manager.) Если вы выберете верификацию всех драйверов, VerifyDrivers будет содержать символ звездочки. B зависимости от выбранных параметров может понадобиться перезагрузка системы.
Ha ранних этапах загрузки диспетчер памяти считывает из реестра значения этих параметров, определяя, какие драйверы следует верифицировать и какие параметры Driver Verifier включены. (Если загрузка происходит в безопасном режиме, все параметры Driver Verifier игнорируются.) Далее, если для проверки выбран хотя бы один драйвер, ядро сравнивает имя каждого загружаемого драйвера с именами драйверов, подлежащих верификации. Если имена совпадают, ядро вызывает функцию MiApplyDriverVerifer,которая заменяет все ссылки драйвера на функции ядра ссылками на эквивалентные функции Driver Verifier. Так, ExAllocatePoolзаменяется на VerifierAllocatePool.Драйвер подсистемы управления окнами производит аналогичные замены для использования эквивалентных функций Driver Verifier.
Теперь рассмотрим четыре параметра верификации драйверов, относящиеся к использованию памяти: Special Pool, Pool Tracking, Force IRQL Checking и Low Resources Simulation.
При включении параметра Special Pool функции пулов выделяют в памяти ядра регион для Driver Verifier, и последний перенаправляет запросы проверяемого драйвера на выделение памяти в особый пул, а не в стандартные пулы. При выделении драйвером памяти из особого пула Driver Verifier округляет размер выделяемого блока до размера, кратного размеру страницы. Поскольку Driver Verifier окружает выделенный блок недействительными страницами, при попытке записи или чтения за пределами этого блока драйвер попадает на недействительную страницу, и диспетчер памяти сообщает о нарушении доступа в режиме ядра.
Ha рис. 7-9 приведен пример блока, выделенного Driver Verifier в особом пуле для проверяемого драйвера устройства.
По умолчанию Driver Verifier распознает ошибки, связанные с попытками обращения за верхнюю границу выделенного блока (overrun errors). Он делает это, помещая используемый драйвером буфер в конец выделенной страницы и заполняя ее начало случайными значениями. Хотя Driver Verifier Manager не предусматривает параметр для включения детекции ошибок, связанных с попытками обращения за нижнюю границу выделенного блока (underrun errors), вы можете активизировать ее вручную, добавив в раздел реестра HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\MemoryManagement параметр PoolTagOverruns типа DWORD и присвоив ему значение O (или запустив утилиту Gflags и установив флажок Verify Start вместо установленного по умолчанию Verify End). Тогда Driver Verifier будет размещать буфер драйвера не в конце, а в начале страницы.
Конфигурация, при которой Driver Verifier обнаруживает ошибки типа «overrun», до некоторой степени обеспечивает и распознавание ошибок типа «underrun». Когда драйвер освобождает буфер и возвращает его в Driver Verifier, последний должен убедиться, что содержимое памяти, предшествующее буферу, не изменилось. Иное означает, что драйвер обратился к памяти, расположенной до начала буфера, и что-то записал за пределами этого буфера.
При выделении памяти из особого пула и ее освобождении также проверяется корректность IRQL процессора. Эта проверка позволяет выявить ошибку, встречающуюся в некоторых драйверах, из-за которой они пытаются выделять память в подкачиваемом пуле при IRQL уровня «DPC/dispatch» или выше.
Особый пул можно сконфигурировать и вручную, добавив в раздел реестра HKLM\SYSTEMCurrentControlSet\Control\Session Manager\Memory Management параметр PoolTag типа DWORD; он представляет тэги выделенной памяти, используемые системой для особого пула. Тогда, даже если Driver Verifier не настроен на верификацию данного драйвера, при совпадении тэга, сопоставленного с выделенной драйвером памятью, и значения PoolTag, память будет выделяться из особого пула. Если вы присвоите PoolTag значение 0x0000002a или символ подстановки (*), то при наличии достаточного количества физической и виртуальной памяти вся память для драйверов будет выделяться из особого пула. (Если памяти не хватит, драйверы вернутся к использованию обычного пула; размер каждого выделяемого блока ограничен двумя страницами.)
Включение средств отладки влияет на все кучи в процессе. Кроме того, включение любого средства отладки приводит к автоматическому отключению интерфейсного уровня и переходу на использование базового. Интерфейсный уровень также не применяется для нерасширяемых куч (из-за дополнительных издержек) или для куч, не допускающих сериализации.
Pageheap
Так как при проверке концевых частей блоков и шаблона свободных блоков могут обнаруживаться повреждения, произошедшие задолго до проявления собственно проблемы, предоставляется дополнительный инструмент отладки куч , pageheap,который переадресует все обращения к куче (или их часть) другомудиспетчеру куч. Pageheap является частью Windows Application Compatibility Toolkit, и его можно скачать с www.microsoft.com .Pageheap помещает выделенные блоки в конец страниц, поэтому при переполнении буфера возникнет нарушение доступа, что упростит выявление ошибочного кода. Блоки можно помещать и в начало страниц для обнаружения проблем, связанных с неполным использованием буферов (buffer underruns). (Такие ситуации – большая редкость.) Pageheap также позволяет защищать освобожденные страницы от любых видов доступа для выявления ссылок на блоки после их освобождения.Заметьте, что применение pageheap может привести к нехватке адресного пространства, так как выделение даже очень малых блоков памяти сопряжено с существенными издержками. Также может ухудшиться производительность из-за увеличения количества ссылок на обнуленные страницы, потери локальности и частых вызовов для проверки структур кучи. Чтобы уменьшить негативное влияние на производительность, pageheap можно использовать только для блоков определенных размеров, конкретных диапазонов адресов и т. д.
ПРИМЕЧАНИЕ Подробнее о pageheap см. статью 286470 в Microsoft Knowledge Base ( http://support.microsoft.com ).
Address Windowing Extensions
Хотя 32-разрядные версии Windows поддерживают до 128 Гб физической памяти (см. таблицу 2-4 в главе 2), размер виртуального адресного пространства любого 32-разрядного пользовательского процесса по умолчанию равен 2 Гб (при указании загрузочных параметров /3GB и /USERVA в Boot.ini этот размер составляет 3 Гб). Чтобы 32-разрядный процесс мог получить доступ к большему объему физической памяти, Windows поддерживает набор функций под общим названием Address Windowing Extensions (AWE). Так, в системе под управлением Windows 2000 Advanced Server с 8 Гб физической памяти серверное приложение базы данных может с помощью AWE использовать под кэш базы данных до 6 Гб памяти.Выделение и использование памяти через функции AWE осуществляется в три этапа.
1. Выделение физической памяти.
2. Создание региона виртуального адресного пространства – окна, на которое будут проецироваться представления физической памяти.
3. Проецирование на окно представлений физической памяти.
Для выделения физической памяти приложение вызывает Windows-функцию AllocateUserPhysicalPages.(Эта функция требует, чтобы у пользователя была привилегия Lock Pages In Memory.) Затем приложение обращается к Windows-функции VirtucriAllocс флагом MEM_PHYSICAL, чтобы создать окно в закрытой части адресного пространства процесса, на которое проецируется (частично или полностью) ранее выделенная физическая память. Память, выделенная через AWE, может быть использована почти всеми функциями Windows API. (Например, функции Microsoft DirectX ее не поддерживают.)
Если приложение создает в своем адресном пространстве окно размером 256 Мб и выделяет 4 Гб физической памяти (в системе с объемом физической памяти более 4 Гб), то оно получает доступ к любой части физической памяти, проецируя ее на это окно через Wmdows-функции MapUserPhysicalPagesили MapUserPhysicalPagesScatter.Размер физической памяти, единовременно доступный приложению при такой схеме выделения, определяется размером окна в виртуальном адресном пространстве. Ha рис. 7-6 показано AWE-ОKHO в адресном пространстве серверного приложения, на которое проецируется регион физической памяти, предварительно выделенный через AllocateUserPhysicalPages.
AWE-функции имеются во всех выпусках Windows и доступны независимо от объема физической памяти в системе. Однако AWE наиболее полезен в системах с объемом физической памяти не менее 2 Гб, поскольку тогда этот механизм – единственное средство для прямого использования более чем 2 Гб памяти 32-разрядным процессом. Еще одно его применение – защита. Так как AWE-память никогда не выгружается на диск, данные в этой памяти никогда не имеют копии в страничном файле, а значит, никто не сумеет просмотреть их, загрузив компьютер с помощью альтернативной операционной системы.
Теперь несколько слов об ограничениях, налагаемых на память, которая выделяется и проецируется с помощью AWE-функций.
(o)Страницы такой памяти нельзя разделять между процессами.
(o)Одну и ту же физическую страницу нельзя проецировать по более чем одному виртуальному адресу в рамках одного процесса.
(o)B более старых версиях Windows страницы такой памяти могут иметь единственный атрибут защиты – «для чтения и записи». B Windows Server
2003 Service Pack 1 и выше также поддерживаются атрибуты «нет доступа» и «только для чтения».
O структурах данных таблицы страниц, используемой для проецирования памяти в системах с более чем 4 Гб физической памяти, см. раздел «Phy-sical Address Extension (PAE)» далее в этой главе.
Системные пулы памяти
При инициализации системы диспетчер памяти создает два типа динамических пулов памяти, используемых компонентами режима ядра для выделения системной памяти.(o) Пул неподкачиваемой памяти (nonpaged pool)Состоит из диапазонов системных виртуальных адресов, которые всегда присутствуют в физической памяти и доступны в любой момент (при любом IRQL и из контекста любого процесса) без генерации ошибок страниц. Одна из причин существования такого пула – невозможность обработки ошибок страниц при IRQL уровня «DPC/dispatch» и выше (см. главу 2).
(o) Пул подкачиваемой памяти (paged pool)Регион виртуальной памяти в системном пространстве, содержимое которого система может выгружать в страничный файл и загружать из него. Драйверы, не требующие доступа к памяти при IRQL уровня «DPC/dispatch» и выше, могут использовать память из этого пула. Он доступен из контекста любого процесса. Оба пула находятся в системном адресном пространстве и проецируются на виртуальное адресное пространство любого процесса (их начальные адреса в системной памяти перечислены в таблице 7-8). Исполнительная система предоставляет функции для выделения и освобождения памяти в этих пулах (см. описание функций, чьи имена начинаются c ExAllocatePool,в Windows DDK).
B однопроцессорных системах создается три пула подкачиваемой памяти, а в многопроцессорных – пять. Наличие нескольких подкачиваемых пулов уменьшает частоту блокировки системного кода при одновременных обращениях нескольких потоков к процедурам управления пулами. Начальный размер подкачиваемого и неподкачиваемого пулов зависит от объема физической памяти и может при необходимости расти до максимального значения, вычисляемого в период загрузки системы.
ПРИМЕЧАНИЕ Будущие выпуски Windows, возможно, будут поддерживать пулы динамических размеров, а значит, лимита на максимальный размер больше не будет. Таким образом, в приложениях и драйверах устройств нельзя исходить из того, что максимальный размер пула является фиксированной величиной в любой системе.
Настройка размеров пулов
Чтобы установить другие начальные размеры этих пулов, измените значения параметров NonPagedPoolSize и PagedPoolSize в разделе реестра HKLM\ SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management с 0 (при этом система сама вычисляет размеры) на нужные величины (в байтах). Ho вы не сможете превысить предельные значения, перечисленные в таблице 7-5. Значение OxFFFFFFFF для PagedPoolSize указывает, что выбран наибольший из возможных размеров, однако увеличение пула подкачиваемой памяти будет происходить за счет записей системной таблицы страниц (page table entries, РТЕ).Таблица 7-5. Максимальные размеры пулов
Рассчитанные значения размеров хранятся в четырех переменных ядра, три из которых экспортируются как счетчики производительности. Имена переменных, счетчиков и параметров реестра, позволяющих изменять размеры пулов, перечислены в таблице 7-6.
Таблица 7-6. Переменные и счетчики производительности, отражающие размеры системных пулов
ЭКСПЕРИМЕНТ: определяем максимальные размеры пулов
Поскольку пулы подкачиваемой и неподкачиваемой памяти являются критическими ресурсами системы, важно знать, когда их размер приближается к расчетному для вашей системы пределу, чтобы задать значение, отличное от установленного по умолчанию в соответствующих параметрах реестра. Счетчики выводят лишь текущий, но не максимальный размер, поэтому вы не узнаете о приближении к лимиту, пока не достигнете его. (Как уже говорилось, будущие версии Windows, возможно, будут поддерживать пулы динамических размеров. И тогда необходимость в проверке максимальных размеров пулов отпадет.)
Получить максимальные размеры пулов можно с помощью Process Explorer или отладки ядра в работающей системе (см. главу 1). Для просмотра этих данных через Process Explorer, щелкните View, System Information. Максимальные размеры пулов показываются в секции Kernel Memory, как на следующей иллюстрации.
Заметьте: чтобы Process Explorer мог получить эту информацию, у него должен быть доступ к символам для ядра данной системы. (Как настроить Process Explorer на использование символов, см. в эксперименте «Просмотр детальных сведений о процессах с помощью Process Explorer» в главе 1.)
Для просмотра той же информации в отладчике ядра используйте команду !vm
B этой системе размеры пулов подкачиваемой и неподкачиваемой памяти далеки от своих максимумов. Отладчик ядра также позволяет изучить значения переменных ядра, перечисленных в таблице 7-6:
kd› dd mmmaximumnonpagedpoolinbytes 11 8047f620 0328c000 kd› ? 328c000
Evaluate expression: 53002240 = 0328c000 kd› dd mmsizeofpagedpoolinbytes 11 80470a98 06800000 kd› ? 6800000
Evaluate expression: 109051904 = 06800000
Из этого примера видно, что максимальный размер неподкачива-емого пула составляет 53 002 240 байтов (примерно 50 Мб), а максимальный размер подкачиваемого пула – 109 051 904 байта (104 Мб). B тестовой системе, использованной нами для этого эксперимента, текущий размер использованной памяти неподкачиваемого пула составлял 5,5 Мб, а подкачиваемого пула – 34 Мб, так что оба пула были далеки от заполнения.
Мониторинг использования пулов
Объект Memory (Память) предоставляет отдельные счетчики размеров пулов неподкачиваемой и подкачиваемой памяти (как для виртуальной, так и для физической частей). Кроме того, утилита Poolmon из Windows Support Tools сообщает детальную информацию об использовании этих пулов. Для просмотра такой информации нужно включить внутренний параметр Enable Pool Tagging (который всегда включен в проверочных версиях, а также в Windows Server 2003, где его вообще нельзя выключить). Чтобы включить данный параметр, запустите утилиту Gflags из Windows Support Tools, Platform SDK или DDK и выберите переключатель Enable Pool Tagging, как показано ниже.Теперь щелкните кнопкуАрр1у и перезагрузите систему. После перезагрузки запустите Poolmon. При этом вы должны увидеть примерно следующее.
Строки с меняющимися данными выделяются подсветкой. (Ee можно выключить, введя букву / в окне Poolmon. Повторный ввод / вновь включает подсветку.) Нажав клавишу со знаком вопроса в Poolmon, можно просмотреть справочный экран. Вы можете указать пулы, за которыми хотите наблюдать (только подкачиваемый, только неподкачиваемый или и то, и другое), а также порядок сортировки. Кроме того, на справочном экране поясняются параметры командной строки, позволяющие наблюдать за конкретными структурами (или за всеми структурами, но одного типа). Так, команда poolmon -iCMпозволит следить только за структурами типа CM (которые принадлежат диспетчеру конфигурации, управляющему реестром). Колонки, в которых программа выводит свою информацию, описаны в таблице 7-7.
B этом примере структуры CM занимают основную часть пула подкачиваемой памяти, а структуры MmSt (структуры, относящиеся к управлению памятью и используемые для проецируемых файлов) – основную часть пула неподкачиваемой памяти.
Описание меток пулов см. в файле \Program Files\Debugging Tools for Windows\Triage\Pooltag.txt. (Он устанавливается вместе с Windows Debugging Tools.) Поскольку в этом файле не перечислены метки пулов для сторонних драйверов устройств, используйте ключ -св версии Poolmon, поставляемой с Windows Server 2003 Device Driver Kit (DDK), для генерации файла меток локальных пулов (Localtag.txt). B этом файле содержатся метки пулов, используемых любыми драйверами, которые были обнаружены в вашей системе. (Учтите: если двоичный файл драйвера устройства был удален после загрузки, метки его пулов не распознаются.)
B качестве альтернативы можно вести поиск драйверов устройств в системе по метке пула, используя утилиту Strings.exe с сайта wwwsysinternals.com .Например, команда:
strings \windows\system32\drivers\*.sys › findstr /i "abcd"
покажет драйверы, содержащие строку «abcd». Заметьте, что драйверы устройств не обязательно должны находиться в \Windows\System32\Drivers – они могут быть в любом каталоге. Чтобы перечислить полные пути всех загруженных драйверов, откройте меню Start (Пуск), выберите команду Run (Выполнить) и введите Msinfo32.Потом щелкните Software Environment (Программная среда) и System Drivers (Системные драйверы).
Еще один способ для просмотра использования пулов драйвером устройства – включение наблюдения за пулами в Driver Verifier (см. далее в этой главе). Хотя при этом способе сопоставление метки пула с драйвером не нужно, он требует перезагрузки (чтобы включить функциональность наблюдения за пулами в Driver Verifier для интересующих вас драйверов). После этого вы можете либо запустить Driver Verifier Manager (\Windows\System32\ Verifier.exe), либо использовать команду Verifier /Logдля записи информации об использовании пулов в какой-либо файл.
Наконец, если вы изучаете аварийный дамп, то можете исследовать использование пулов и с помощью команды !poolused.Команда !poolused 2сообщает об использовании пула неподкачиваемой памяти с сортировкой по структурам, занимающим наибольшее количество памяти, а команда !poolused 4 –об использовании пула подкачиваемой памяти (с той же сортировкой). Ниже приведен фрагмент выходной информации этих двух команд.
ЭКСПЕРИМЕНТ: анализ утечки памяти в пуле
B этом эксперименте вы устраните реальную утечку в пуле подкачиваемой памяти в своей системе, чтобы научиться на практике применять способы, описанные в предыдущем разделе. Утечка будет создаваться утилитой NotMyFault, которую можно скачать по ссылке wwwsysintemals.com/windowsinternalsshtmL (Заметьте, что эта утилита отсутствует в списке инструментов на основной странице Sysinternals.) После запуска NotMyFault.exe загружает драйвер устройства Myfault.sys и выводит такое диалоговое окно.
1. Щелкните кнопку Leak Pool. Это заставит NotMyFault посылать запросы драйверу устройства Myfault на выделение памяти из подкачиваемого пула. (He нажимайте кнопку Do Bug, иначе вы вызовете крах системы; предназначение этой кнопки описывается в главе 14, где демонстрируются различные типы аварийных ситуаций.) NotMyFault продолжит посылать запросы, пока вы не щелкнете кнопку Stop Leaking. Заметьте, что пул подкачиваемой памяти не освобождается даже при закрытии программы; в нем происходит постоянная утечка памяти до перезагрузки системы. Однако, поскольку утечка пула будет непродолжительной, это не должно вызвать никаких проблем в вашей системе.
2. Пока происходит утечка памяти в пуле, сначала откройте диспетчер задач и перейдите на вкладку Performance (Быстродействие). Вы увидите, как растет показатель Paged Pool (Выгружаемая память). To же самое можно увидеть в окне System Information утилиты Process Explorer. (Выберите Show и System Information.)
3. Чтобы определить метку пула, где происходит утечка, запустите Poolmon и нажмите клавишу b,чтобы сортировать по числу байтов. Дважды нажмите клавишу pдля отображения в Poolmon только пула подкачиваемой памяти. Вы должны заметить, что пул с меткой «Leak» поднимается вверх по списку. (Poolmon выделяет строки, где происходят изменения.)
4. Теперь щелкните кнопку Stop Leaking, чтобы не истощить пул подкачиваемой памяти в своей системе.
5. Используя приемы, описанные в предыдущем разделе, запустите Strings (ее можно скачать с wwwsysinternals.com )для поиска двоичных файлов драйвера, содержащих метку пула «Leak»:
Strings \windows\system32\drivers\*.sys | findstr Leak
Эта команда должна указать на файл Myfault.sys.
Ассоциативные списки
Windows поддерживает механизм быстрого выделения памяти – ассоциативные списки(look-aside lists). Главное различие между пулом и ассоциативным списком в том, что из пула можно выделять блоки памяти различного размера, а из ассоциативного списка – только фиксированные. Хотя пулы обеспечивают более высокую гибкость, ассоциативные списки работают быстрее, так как не используют спин-блокировку и не заставляют систему подбирать подходящую область свободной памяти, в которой мог бы уместиться текущий выделяемый блок.Функции ExInitializeNPagedLookasideListи ExInitializePagedLookasideList(документированные в DDK) позволяют компонентам исполнительной системы и драйверам устройств создавать ассоциативные списки, размеры которых кратны размерам наиболее часто используемых структур данных. Для минимизации издержек, связанных с синхронизацией в многопроцессорных системах, некоторые компоненты исполнительной системы, в том числе диспетчер ввода-вывода, диспетчер кэша и диспетчер объектов, создают отдельные для каждого процессора ассоциативные списки, из которых выделяется память под часто используемые структуры данных. Сама исполнительная система создает для каждого процессора универсальные ассоциативные списки подкачиваемой и неподкачиваемой памяти с гранулярностью выделения в 256 байтов или менее.
Если ассоциативный список пуст (как это бывает сразу после его создания), система должна выделить память из подкачиваемого или неподкачиваемого пула. Ho если в списке уже присутствует освобожденная структура, то занимаемая ею память выделяется очень быстро. (Список разрастается по мере возврата в него структур.) Процедуры выделения памяти из пула автоматически настраивают число освобожденных буферов, хранящихся в ассоциативном списке, в зависимости от частоты выделения памяти из этого списка драйвером или компонентом исполнительной системы. Чем чаще они выделяют память из списка, тем больше буферов в списке. Размер ассоциативных списков автоматически уменьшается, если память из них не выделяется. (Эта проверка выполняется раз в секунду, когда системный поток диспетчера настройки баланса пробуждается и вызывает функцию KiAdjustLookasideDepth.)
ЭКСПЕРИМЕНТ: просмотр системных ассоциативных списков
Содержимое и размер различных ассоциативных списков в системе можно просмотреть командой !lookasideотладчика ядра. Вот фрагмент вывода этой команды.
Утилита Driver Verifier
Driver Verifier представляет собой механизм, который можно использовать для поиска и локализации наиболее распространенных ошибок в драйверах устройств и другом системном коде режима ядра. Microsoft проверяет с помощью Driver Verifier свои драйверы и все драйверы, передаваемые производителями оборудования для тестирования на совместимость и включения в список Hardware Compatibility List (HCL). Такое тестирование гарантирует совместимость драйверов, включенных в список HCL, с Windows и отсутствие в них распространенных ошибок. (Существует и парная утилита Application Verifier, позволяющая улучшить качество кода пользовательского режима. Однако в этой книге она не рассматривается.)Driver Verifier поддерживается несколькими системными компонентами – диспетчером памяти, диспетчером ввода-вывода и HAL, которые предусматривают параметры, включаемые для верификации драйверов. B этом разделе поясняются параметры верификации драйверов на отсутствие ошибок, связанных с управлением памятью (см. также главу 9).
Настройка и инициализация Driver Verifier
Для настройки Driver Verifier и просмотра статистики запустите Driver Verifier Manager (Диспетчер проверки драйверов), файл \Windows\System32\Verifier.exe. После запуска появится окно с несколькими вкладками. Версия окна для Windows 2000 приведена на рис. 7-7. Чтобы указать, какие драйверы устройств вы хотите проверить, и задать типы проверок, используйте вкладку Settings (Параметры).B Windows XP и Windows Server 2003 этой утилите придали интерфейс в стиле мастера, как показано на рис. 7-8.
Рис. 7-8. Driver Verifier Manager в Windows XP и Windows Server 2003
Включать и отключать Driver Verifier, а также просматривать текущие параметры можно из командной строки этой утилиты. Для вывода списка ключей наберите verifier /?.
Настройки Driver Verifier Manager хранятся в разделе реестра HKLM\SYS-TEM\CurrentControlSet\Control\Session Manager\Memory Management. Параметр VerifyDriverLevel содержит битовую маску, представляющую включенные типы проверок. Имена проверяемых драйверов содержатся в параметре VerifyDrivers. (Эти параметры создаются в реестре только после выбора проверяемых драйверов в окне Driver Verifier Manager.) Если вы выберете верификацию всех драйверов, VerifyDrivers будет содержать символ звездочки. B зависимости от выбранных параметров может понадобиться перезагрузка системы.
Ha ранних этапах загрузки диспетчер памяти считывает из реестра значения этих параметров, определяя, какие драйверы следует верифицировать и какие параметры Driver Verifier включены. (Если загрузка происходит в безопасном режиме, все параметры Driver Verifier игнорируются.) Далее, если для проверки выбран хотя бы один драйвер, ядро сравнивает имя каждого загружаемого драйвера с именами драйверов, подлежащих верификации. Если имена совпадают, ядро вызывает функцию MiApplyDriverVerifer,которая заменяет все ссылки драйвера на функции ядра ссылками на эквивалентные функции Driver Verifier. Так, ExAllocatePoolзаменяется на VerifierAllocatePool.Драйвер подсистемы управления окнами производит аналогичные замены для использования эквивалентных функций Driver Verifier.
Теперь рассмотрим четыре параметра верификации драйверов, относящиеся к использованию памяти: Special Pool, Pool Tracking, Force IRQL Checking и Low Resources Simulation.
Special Pool (Особый пул)
Этот параметр заставляет функции, отвечающие за выделение памяти из пулов, окружать выделяемый блок недействительными страницами, чтобы ссылки за пределы этого блока вызывали нарушение доступа в режиме ядра и последующий крах системы. A это позволяет тут же указать пальцем на сбойный драйвер. Параметр Special Pool также заставляет проводить дополнительные проверки, когда драйвер выделяет или освобождает память.При включении параметра Special Pool функции пулов выделяют в памяти ядра регион для Driver Verifier, и последний перенаправляет запросы проверяемого драйвера на выделение памяти в особый пул, а не в стандартные пулы. При выделении драйвером памяти из особого пула Driver Verifier округляет размер выделяемого блока до размера, кратного размеру страницы. Поскольку Driver Verifier окружает выделенный блок недействительными страницами, при попытке записи или чтения за пределами этого блока драйвер попадает на недействительную страницу, и диспетчер памяти сообщает о нарушении доступа в режиме ядра.
Ha рис. 7-9 приведен пример блока, выделенного Driver Verifier в особом пуле для проверяемого драйвера устройства.
По умолчанию Driver Verifier распознает ошибки, связанные с попытками обращения за верхнюю границу выделенного блока (overrun errors). Он делает это, помещая используемый драйвером буфер в конец выделенной страницы и заполняя ее начало случайными значениями. Хотя Driver Verifier Manager не предусматривает параметр для включения детекции ошибок, связанных с попытками обращения за нижнюю границу выделенного блока (underrun errors), вы можете активизировать ее вручную, добавив в раздел реестра HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\MemoryManagement параметр PoolTagOverruns типа DWORD и присвоив ему значение O (или запустив утилиту Gflags и установив флажок Verify Start вместо установленного по умолчанию Verify End). Тогда Driver Verifier будет размещать буфер драйвера не в конце, а в начале страницы.
Конфигурация, при которой Driver Verifier обнаруживает ошибки типа «overrun», до некоторой степени обеспечивает и распознавание ошибок типа «underrun». Когда драйвер освобождает буфер и возвращает его в Driver Verifier, последний должен убедиться, что содержимое памяти, предшествующее буферу, не изменилось. Иное означает, что драйвер обратился к памяти, расположенной до начала буфера, и что-то записал за пределами этого буфера.
При выделении памяти из особого пула и ее освобождении также проверяется корректность IRQL процессора. Эта проверка позволяет выявить ошибку, встречающуюся в некоторых драйверах, из-за которой они пытаются выделять память в подкачиваемом пуле при IRQL уровня «DPC/dispatch» или выше.
Особый пул можно сконфигурировать и вручную, добавив в раздел реестра HKLM\SYSTEMCurrentControlSet\Control\Session Manager\Memory Management параметр PoolTag типа DWORD; он представляет тэги выделенной памяти, используемые системой для особого пула. Тогда, даже если Driver Verifier не настроен на верификацию данного драйвера, при совпадении тэга, сопоставленного с выделенной драйвером памятью, и значения PoolTag, память будет выделяться из особого пула. Если вы присвоите PoolTag значение 0x0000002a или символ подстановки (*), то при наличии достаточного количества физической и виртуальной памяти вся память для драйверов будет выделяться из особого пула. (Если памяти не хватит, драйверы вернутся к использованию обычного пула; размер каждого выделяемого блока ограничен двумя страницами.)