Преодолевая границы Windows: объекты USER и GDI (часть 2)

В прошлый раз я рассказал об ограничениях и о том, как измерить степень использования одного из двух ключевых ресурсов диспетчера окон – объектах USER. На этот раз я собираюсь затронуть другой ключевой ресурс – объекты GDI. Как всегда, перед прочтением этой статьи я рекомендую прочитать предыдущие мои публикации, поскольку некоторые из ограничений, связанных с ресурсами USER и GDI, основаны на ограничениях, о которых я уже рассказывал. Вот полный список моих статей из серии «Преодолевая границы Windows»:

Объекты GDI

Объекты GDI представляют собой ресурсы интерфейса графического устройства, такие как шрифты, точечные рисунки, кисти, перья и контексты устройств (поверхности конструктора). Как и в случае с объектами USER, диспетчер окон ограничивает процессы использованием не более чем 10000 объектов GDI, что вы можете проверить с помощью Testlimit с параметром -g:

clip_image001

Чтобы увидеть, сколько объектов GDI использует процесс, вы можете обратиться к странице Performance в диалоговом окне свойств процесса в Process Explorer, а также добавить колонку GDI Objects в Process Explorer, чтобы просмотреть параметры использования объектов GDI для процессов:

clip_image002

Как и в случае с объектами USER, 16-разрядная функциональная совместимость означает, что объекты GDI имеют 16-разрядные идентификаторы, ограничивая их количество 65535 экземплярами на сеанс. Вот как выглядит рабочий стол после того, как Testlimit достиг этого ограничения на 64-разрядной системе Windows Vista:

clip_image003

Обратите внимание, что кнопка «Пуск» находится в левом нижнем углу, тогда как остальная часть панели задач находится в верхней части экрана. Рабочий стол стал черным, а боковая панель потеряла большинство своих цветов. У вас это может выглядеть иначе, но вы все равно можете увидеть, какие забавные вещи начинают происходить в таких случаях, лишая вас возможности нормально взаимодействовать с рабочим столом. Вот как стал выглядеть экран, когда я нажал на кнопку «Пуск»:

clip_image004

В отличие от объектов USER, объекты GDI не распределяются из куч рабочего стола; вместо этого на системах Windows XP и Windows Server 2003, на которых не установлены Terminal Services, они распределяются из общего выгружаемого пула; на всех других системах они распределяются из пула сеансов, для каждого сеанса в отдельности.

Команда отладчика ядра "!vm 4" отображает общую информацию о виртуальной памяти, включая информацию о сеансе в конце. На системе Windows XP она показывает, что выгружаемый пул сеанса не использован:

clip_image005

Те же результаты отображаются и на Windows Server 2003 без установленных Terminal Services:

clip_image006

Поэтому ограничением по памяти для объектов GDI служит ограничение выгружаемого пула, как я описывал в своей предыдущей статье, "Преодолевая границы Windows: выгружаемый и невыгружаемый пулы". Однако, когда на той же системе Windows Server 2003 установлены Terminal Services, в информации об использовании пула ненулевого сеанса вы можете увидеть, что объекты GDI берутся из пула сеанса:

clip_image007

В приведенных выше результатах работы команды "!vm 4" также показаны максимальный размер выгружаемого пула сеанса и размер пула сеанса, но на Windows Vista и в последующих системах максимум выгружаемого пула сеанса и размер пространства сеанса не отображаются, поскольку они являются переменными величинами. Показатели использования выгружаемого пула сеанса на этих системах ограничиваются либо объемом адресного пространства, до которого они могут вырасти, либо пределом выделения системных ресурсов, в зависимости от того, какой параметр будет меньше. Вот результаты выполнения этой команды на системе Windows 7, отображающие текущие показатели использования выгружаемого пула сеансов:

clip_image008

Вполне ожидаемо, что главный интерактивный сеанс, Session 1, использует большую часть выгружаемого пула сеанса.

Вы можете использовать инструмент Testlimit с параметром "-g 0", чтобы увидеть, что происходит, когда заканчивается место для хранения объектов GDI. Число, указываемое вами после -g является размером объектов точечных рисунков GDI, выделяемых Testlimit, при этом число 0 указывает Testlimit выделять настолько большие объекты, насколько это вообще возможно. Вот результаты работы Testlimit на 32-разрядной системе Windows XP:

clip_image009

Чтобы увидеть распределение объектов GDI по тегам пулов в Windows XP или Windows Server 2003 без установленных Terminal Services, можно использовать утилиту Poolmon из Windows Driver Kit (WDK). Вот как выглядят результаты работы Poolmon, после того, как Testlimit исчерпал выгружаемый пул в системе Windows XP. Результаты отсортированы по количеству выделенных байт (нажмите "b" в окне Poolmon, чтобы сделать такую сортировку), из которых видно, что Gh05 является тегом для объектов точечных рисунков на Windows Server 2003:

clip_image010

На системе Windows Server 2003 с установленными Terminal Services и на Windows Vista и выше, чтобы определить, какую сессию вы хотите увидеть, можно использовать Poolmon с параметром /s. Вот результат работы Testlimit на системе Windows Server 2003 с установленными Terminal Services:

clip_image011

Команда "poolmon /s1" показывает теги, имеющие наибольшие показатели выделяемого объема для Session 1. Вы можете увидеть на верху списка тег Gh15, и чего следует, что в данном случае для выделения точечных рисунков используется другой тег пула:

clip_image012

Обратите внимание на то, что Testlimit смог выделить приблизительно 58 Мб под данные точечных рисунков (это число не входит во внутренние издержки GDI для точечных рисунков) в системе Windows XP, и только 10 Мб – в системе Windows Server 2003. Такая разница объясняется тем, что объем пула сессии в системе Windows Server 2003 Terminal Server составляет всего 32 Мб, о чем говорит объем памяти, который отобразила Poolmon для тега Gh15. Результаты выполнения команды "!vm 4" подтверждают, что пул сеанса для Session1 был полностью использован и все последующие попытки выделить объекты GDI из пула сеанса терпели неудачу:

clip_image013

Также можно воспользоваться командой отладчика ядра !poolused, чтобы посмотреть на показатели использования пула сеанса. Для начала, переключитесь на нужный сеанс с помощью команды .process с параметром /p и адресом объекта процесса, подключенного к сеансу. Чтобы увидеть, какие процессы выполняются в определенном сеансе, используйте команду !sprocess. Вот результат выполнения команды !poolmon в той же самой системе Windows Server 2003, где опция "с" команды сортирует результаты по числу выделенных байт:

clip_image014

К сожалению, нет никакого общедоступного сопоставления между тегами кучи диспетчера окон и объектами, которые они представляют, однако команда отладчика ядра !poolused использует файл triage.ini из директории установки отладчика для отображения более наглядной информации о тегах. Эта команда сообщает, что Gh15 – это GDITAG_HMGR_SPRITE_TYPE, что не слишком понятно для пользователя, однако для других тегов эти данные приводятся в более понятной форме.

К счастью, большинство ошибок объектов GDI и USER связаны с превышением определенным процессом ограничения в 10000 объектов на процесс, так что нет надобности в проведении более подробного исследования, ставящего целью выяснить, какой процесс ответственен за исчерпание пула сеанса или выделение объектов GDI.

В следующий раз я рассмотрю системные элементы таблицы страниц (System PTE) – другой ключевой ресурс, у которого есть ограничения, которые могут быть превышены, особенно в сеансах Remote Desktop в системах Windows Server 2003.