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

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

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

Сеансы, рабочие станции и рабочие столы

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

Понятие «сеанс» впервые было представлено в терминальных службах (ныне «службах удаленного рабочего стола») в Windows NT 4 Terminal Server Edition, где понятия физического дисплея, клавиатуры и мыши виртуализировались для каждого пользователя, удаленно совершившего интерактивный вход в систему, и основной функционал терминальных служб был встроен в Windows 2000 Server. В Windows XP сеансы были усилены для создания механизма быстрого переключения пользователей (Fast User Switching, FUS), который позволяет переключаться между несколькими интерактивными логинами при одном физическом дисплее, клавиатуре и мыши.

Таким образом, сеанс может быть связан: с физическим дисплеем и устройствами ввода, подключенными к системе; с логическим дисплеем и устройствами ввода, как они представляются с помощью клиентского приложения Remote Desktop; или находиться в отключенном состоянии, как это происходит, когда вы выходите из сеанса с помощью FUS или через отключение соединения Remote Desktop Client, не завершая при этом сеанс.

Каждый процесс однозначно связан с определенным сеансом, что можно увидеть, добавив колонку Session в Sysinternals Process Explorer. Этот экран, на котором я свернул дерево процессов, чтобы показать только процессы, не имеющие родителей, сделан из системы Remote Desktop Services (RDS – ранее Terminal Server Services), которая имеет четыре активных сеанса: сеанс 0 является выделенным сеансом, в котором выполняются системные процессы на Windows Vista и выше; сеанс 1 – это сеанс, в котором я пишу эту статью; сеанс 2 – это сеанс другой учетной записи пользователя, под которой я одновременно совершил вход из другой системы; и наконец, сеанс 3 является одним из сеансов, которые создает Remote Desktop Services для того, чтобы быть готовой к следующим интерактивным входам в систему:

clip_image001

Так как каждый процесс связан с определенным сеансом, и операционной системе обычно необходимо иметь доступ к данным текущего сеанса процесса, Windows определяет представление к данным сеанса процесса в виртуальном адресном пространстве процесса. Таким образом, когда система переключается между потоками различных процессов, она также переключает адресные пространства через переключение текущего представления сеанса. Например, когда процесс Csrss.exe сеанса 0 является текущим процессом, сопоставления адресного пространства включают в себя системное адресное пространство (в которое включены все адресные пространства процессов), адресное пространство Csrss и адресное пространство нулевого сеанса. Область памяти, сопоставляющая данные сеанса, также известна как «пространство представления сеанса» (Session View Space) или просто «пространство сеанса» (Session Space). Когда система переключается на поток из процесса Explorer сеанса 1, соответственно изменяются сопоставления, и когда она переключается на поток Блокнота, пространство сеанса 1 остается сопоставленным:

clip_image002

Обратите внимание, что этот рисунок не совсем точно отражает ситуацию с 32-разрядной Windows Vista и выше, потому что динамическое системное адресное пространство подразумевает, что пространство сеанса не обязательно будет смежным ему и может расти или сужаться, если это необходимо на этих системах.

Следующими понятием является рабочий стол – объект, определенный диспетчером окна для представления виртуального дисплея, который включает в себя окна, связанные с этим рабочим столом (обратите внимание, что данное определение отличается от того, которое имеет рабочий стол Explorer, являющийся пользовательской папкой с ярлыками и другими объектами, помещенными туда пользователем). Рабочий стол, заданный по умолчанию, имеет название «Default», однако приложения могут создавать дополнительные рабочие столы и переключать подключение с логическим дисплеем; утилита Sysinternals Desktops может создавать до четырех виртуальных рабочих столов, между которыми может переключаться пользователь.

И наконец, чтобы обеспечить поддержку нескольких виртуальных дисплеев, которые связанны с одним единственным экземпляром диспетчера окон, последний определяет объект Window Station. Этот объект связан с определенным сеансом, а сеанс может иметь несколько объектов Window Station, но у каждого сеанса может быть только один интерактивный объект Window Station, называемый Winsta0, который может соединиться с физическим или логическим дисплеем, клавиатурой и мышью; другие объекты Window Station по существу являются «бездисплейными» и поддерживаются исключительно для того, чтобы изолировать процессы, которые ожидают обслуживания диспетчером окон, но не требуют для себя дисплея. Например, система создает неинтерактивные Window Station для каждой служебной учетной записи, с которыми она связывает выполнение процессов этой учетной записи, потому что службам Windows не нужно отображать пользовательский интерфейс.

Вы можете увидеть объекты Window Station, связанные с нулевым сеансом, просмотрев пространство имен Object Manager в папке \Windows с помощью утилиты Sysinternals Winobj (просмотр этой папки требует наличия прав администратора). Здесь вы можете увидеть объект Window Station для Microsoft Windows Search Service, который создается, чтобы фильтровать результаты поиска, Window Station для каждой из трех встроенных служебных учетных записей (System, Network Service и Local Service) и интерактивный объект Window Station нулевого сеанса:

clip_image003

В папке Sessions в пространстве имен Object Manager можно видеть объекты Window Station, связанные с другими сеансами. Вот лишь один из них, интерактивная станция WinSta0, связанная с моим сеансом входа в систему:

clip_image004

Диаграмма отображает связи между сеансами, объектами Window Station и рабочими столами для системы, в которой один пользователь вошел под сеансом 1 на физической консоли, а другой зашел под сеансом 2 через подключение к удаленному рабочему столу, в котором пользователь запустил утилиту виртуальных рабочих столов и переключился на отображение Desktop1.

clip_image005

Помимо связи с определенными сеансами, процессы связаны с определенным объектом Window Station и рабочим столом, хотя процессы могут переключаться между ними обоими, а потоки могут переключать между рабочими столами. Таким образом, каждая связь процесса может быть представлена в виде иерархического пути (например, так «Session 1\WinSta0\Default»). В большинстве случаев можно косвенно определить, к какому объекту Window Station или рабочему столу подключен процесс, просмотрев его таблицу дескрипторов в окне дескрипторов Process Explorer, чтобы увидеть имена открытых им объектов. Нижний снимок экрана таблицы дескрипторов процесса Explorer показывает, что данный процесс подключен к рабочему столу Default через объект WinSta0 сеанса 1:

clip_image006

Объекты USER

Вооружившись фундаментальными понятиями, давайте для начала обратим внимание на объекты USER. Объекты USER получили свое название благодаря тому, что они представляют элементы пользовательского интерфейса, такие как рабочие столы, окна, меню, курсоры, значки и таблицы с сочетаниями клавиш. Несмотря на то, что объекты USER связаны с определенным рабочим столом, одни должны быть доступны из всех рабочих столов сеанса, например, для того, чтобы позволить процессу на одном рабочем столе зарегистрировать сочетание клавиш, которое может быть использовано на любом из них. По этой причине диспетчер окон относит рассматриваемые идентификаторы объектов USER к объекту Window Station.

Базовое ограничение, накладываемое диспетчером окон, состоит в том, что ни один процесс не может создать более 10000 объектов USER. Это ограничение – попытка воспрепятствовать исчерпыванию ресурсов, связанных с объектами USER, которое может происходить либо из-за того, что используются алгоритмы, допускающие создание чрезмерного числа объектов, либо потому, что имеет место утечка объектов, которые создаются и не удаляются после того, как в них отпадает надобность. Вы можете легко проверить наличие данного ограничения, запустив утилиту Sysinternals Testlimit с параметром -u, который указывает Testlimit создать столько объектов USER, сколько она может:

clip_image007

Диспетчер окон отслеживает количество объектов USER, которые может разместить процесс, что можно увидеть, добавив колонку USER Objects в отображение Process Explorer. Этот скриншот показывает, что системные (например, Lsass.exe, Local Security Authority Subsystem) и служебные (например, Svchost) процессы Windows не могут размещать объекты USER, поскольку они не имеют пользовательского интерфейса:

clip_image008

На странице Performance диалогового окна свойств процесса в Process Explorer отображается число объектов USER, размещенных процессом:

clip_image009

Фундаментальное ограничение на количество объектов USER появилось потому, что их идентификаторы в первых версиях Windows являлись 16-разрядными, как и сама Windows. Когда в последующие версии была добавлена поддержка 32-разрядной адресации, идентификаторы USER продолжали оставаться ограниченными 16-разрядными значениями, чтобы 16-разрядные процессы могли взаимодействовать с окнами и другими объектами USER, созданными 32-разрядными процессами. Таким образом, 65535 (2^16) является ограничением на общее число объектов USER, которые могут быть созданы в одном сеансе (и, по историческим причинам, окна должны иметь четные идентификаторы, так что максимально в одном сеансе может быть создано 32768 окон). Вы можете проверить наличие этого ограничения, запуская несколько копий Testlimit с параметром -u до тех пор, пока это будет возможно. Предполагая, что процессы, которые уже запущены, не используют слишком много объектов, у вас должно получиться запустить 7 копий, из которых первые шесть разместят по 10000 объектов и последняя разместит количество объектов, равное разнице между количеством уже размещенных объектов и 65535:

clip_image010

Прежде чем делать это, убедитесь, что вы готовы выполнить принудительную перезагрузку вашей системы, поскольку в результате можно потерять управление над рабочим столом. Многие операции, даже такие, как открытие меню завершения работы Windows из меню Пуск, требуют для себя объекты USER, и когда невозможно будет больше выделять эти объекты, система будет вести себя причудливым образом. После того, как объекты USER были исчерпаны, я не смог даже завершить процесс Notepad, нажав на кнопку закрытия приложения в его меню.

Пока что я говорил только об ограничениях, связанных с абсолютным числом объектов USER, которые может разместить процесс или рабочая станция, но есть и другие ограничения, связанные с дисковым пространством, используемым под сами объекты USER. Каждый рабочий стол имеет свою собственную область памяти, называемую кучей рабочего стола (от англ. desktop heap), из которой выделено большинство объектов USER, созданных на рабочем столе. Поскольку кучи хранятся в пространстве сеанса и 32-битное адресное пространство накладывает ограничение на объем адресного пространства режима ядра, размеры куч рабочего стола ограничены довольно малым объемом. Они также различаются по размеру в зависимости от типа рабочего стола, для которого они предназначены, и в зависимости от разрядности системы (32-х или 64-х битная).

В своих статьях «Обзор кучи рабочего стола» и «Куча рабочего стола, ч. 2» из блога NT Debugging Мэтью Джастис (Matthew Justice) проделал великолепную работу по документированию размеров куч рабочего стола вплоть до Windows Vista SP1. В данной таблице собранны данные о размере куч рабочего стола для различных версий Windows вплоть до Windows Server 2008 R2:

Версия ОС

Interactive Desktop

Non-Interactive Desktop

Winlogon Desktop

Disconnect Desktop

Windows XP (32-бит)

3 MB

512 KB

128 KB

64 KB

Windows Server 2003 (32-бит)

3 MB

512 KB

128 KB

64 KB

Windows Server 2003 (64-бит)

20 MB

768 KB

192 KB

96 KB

Windows Vista/Windows Server 2008 (32-бит)

12 MB

512 KB

128 KB

64 KB

Windows Vista/Windows Server 2008 (64-бит)

20 MB

768 KB

192 KB

96 KB

Windows 7 (32-бит)

12 MB

512 KB

128 KB

64 KB

Windows 7/Windows Server 2008 R2 (64-бит)

20 MB

768 KB

192 KB

96 KB

Здесь стоит отметить, что у первоначального выпуска 32-разрядной Windows Vista размер интерактивной кучи равен 3 Мб, как и у предыдущих 32-разрядных версий Windows. После ее выхода телеметрия показа, что некоторым пользователям иногда не хватает этого объема, возможно потому, что они запускают много приложений в системе с большим объемом памяти, так что в SP1 это значение было увеличено до 12 Мб. Также возможно переопределить стандартные размеры кучи рабочего стола с помощью настройки системного реестра, описанной в статье Мэтью.

В версиях Windows, предшествующих Windows Vista, можно использовать инструмент Microsoft Desktop Heap Monitor, чтобы увидеть размер куч рабочего стола, и какая часть из них используется. По результату работы этого инструмента в системе с 32-разрядной Windows XP видно, что было использовано только 5,6% от кучи (172 Кб) интерактивного рабочего стола Default.

clip_image011

Данный инструмент не был обновлен для Windows Vista, поскольку использование большего размера кучи рабочего стола в новых версиях Windows означает, что объем кучи рабочего стола крайне редко будет исчерпываться, прежде чем будут достигнуты другие ограничения объектов USER. Однако, вы можете использовать Testlimit с параметрами -u и -i, чтобы увидеть, как ведет себя система при истощении кучи рабочего стола. Данная комбинация параметров Testlimit создает структуры данных класса window, у которых есть 4 Кб дополнительного дискового пространства, до тех пор, пока они не вызовут ошибку. Вот результат работы Testlimit, запущенной сразу после того, как я сделал снимок экрана вывода Desktop Heap Monitor. 2823 Кб и 172 Кб, которые, согласно Desktop Heap Monitor, уже были выделены, в сумме дают приблизительно 3 Мб:

clip_image012

Хотя нет никакого способа для определения используемого объема куч на новых системах, диспетчер окон записывает событие в системный лог событий каждый раз, когда происходит истощение кучи, что может помочь при определении причин ошибок диспетчера окон:

clip_image013

В этой статье я рассказал об ограничения объектов USER. Во второй части мы обсудим ограничения, связанные с объектами GDI диспетчера окон.