1.Внутреннее устройство Windows (гл. 1-4)
Шрифт:
1. Скачайте файл Testlimit.zip по только что упомянутой ссылке и раз-архивируйте его в какой-нибудь каталог.
2. Запустите Process Explorer, щелкните View, затем System Information. Обратите внимание на текущий и максимальный размеры пула подкачиваемой памяти. (Для вывода максимальных размеров пулов Process Explorer нужно настроить на доступ к символам для образа ядра, Ntoskrnl.exe.) Пусть эта информация отображается, когда вы запустите программу Testlimit.
3. Откройте окно командной строки.
4. Запустите программу Testlimit с ключом ~h (для этого введите testlimit — h). Когда Testlimit не удастся открыть очередной описатель, она сообщит общее число созданных ею описателей. Если это значение окажется меньше, чем около 16 миллионов, то, вероятно, вы исчерпали пул подкачиваемой
5. Закройте окно командной строки; это приведет к завершению процесса Testlimit и автоматическому закрытию всех открытых описателей.
Как показано на рис. 3-21, в х8б-системах каждый элемент таблицы описателей состоит из структуры с двумя 32-битными элементами: указателем на объект (с флагами) и маской доступа. B 64-разрядных системах каждый элемент имеет размер 12 байтов и состоит из 64-битного указателя на заголовок объекта и 32-битной маски доступа (маски доступа описываются в главе 8).
B Windows 2000 первый 32-битный элемент содержит указатель на заголовок объекта и четыре флага. Поскольку заголовки объектов всегда выравниваются по границе 8 байтов, в качестве флагов используются младшие 3 бита и один старший. Старший бит является флагом блокировки (lock). Когда диспетчер объектов транслирует описатель в указатель на объект, он блокирует соответствующую запись на время трансляции. Так как все объекты находятся в системном адресном пространстве, старший бит указателя на объект устанавливается в 1. (Гарантируется, что адреса всегда будут превышать 0x80000000 даже на системах, запускаемых с ключом загрузки /3GB.) Так что, пока элемент таблицы описателей не заблокирован, диспетчер объектов может сбросить старший бит в 0. При блокировке элемента таблицы диспетчер объектов устанавливает этот бит и получает корректное значение указателя. Диспетчер блокирует всю таблицу описателей (используя специальную блокировку, сопоставляемую с каждым процессом), только если процесс создает новый описатель или закрывает существующий. B Windows XP и Windows Server 2003 флаг блокировки хранится в младшем бите указателя на объект. A флаг, который в Windows 2000 хранился в этом бите, теперь хранится в ранее зарезервированном бите маски доступа.
Первый флаг указывает, имеет ли право вызывающая программа закрывать данный описатель. Второй определяет, получат ли процессы, созданные данным процессом, копию этого описателя. (Как уже отмечалось, наследование описателя можно указать при его создании или позже, через Windows-функцию SetHandleInformation. Этот флаг тоже можно задать вызовом Set-HandleInformation) Третий задает, будет ли генерироваться сообщение аудита при закрытии объекта. (Этот флаг не предоставляется в Windows и предназначен для внутреннего использования диспетчером объектов.)
Системным компонентам и драйверам устройств зачастую нужно открывать описатели объектов, доступа к которым у приложений пользовательского режима нет. Это достигается созданием описателей в таблице описателей ядра (kernel handle table) (внутреннее имя — ObpKernelHandleTable). Описатели в этой таблице доступны только в режиме ядра в контексте любого процесса. Это значит, что функции режима ядра могут ссылаться на эти описатели из контекста любого процесса без ущерба для производительности. Диспетчер объектов распознает ссылки на описатели в таблице описателей ядра, когда старший бит в них установлен, т. е. когда в таких ссылках содержатся значения, превышающие 0x80000000. B Windows 2000 таблица описателей ядра является независимой таблицей описателей, но в Windows XP и Windows Server 2003 она служит и таблицей описателей для процесса System.
ЭКСПЕРИМЕНТ: просмотр таблицы описателей с помощью отладчика ядра
Команда !bandle отладчика ядра допускает следующие аргументы:
!handle ‹индекс_описателя› ‹флаги› ‹идентификатор_процесса›
Индекс описателя определяет элемент в таблице описателей (0 — вывод всех описателей). Индекс первого описателя равен 4, второго — 8 и т. д. Например, введя !bandle 4,
Вы можете указывать флаги, являющиеся битовыми масками, где бит 0 означает, что нужно вывести лишь информацию из элемента таблицы, бит 1 — показать не только используемые, но и свободные описатели, а бит 2 — сообщить информацию об объекте, на который ссылается описатель. Следующая команда выводит полную информацию о таблице описателей в процессе с идентификатором 0x408.
Открывая файл, нужно указать, для чего это делается — для чтения или записи. Если вы попытаетесь записать что-нибудь в файл, открытый для чтения, то получите ошибку. Аналогичным образом действует и исполнительная система: когда процесс создает объект или открывает описатель существующего объекта, он должен указывать набор желательных прав доступа (desired access rights), сообщая тем самым, что именно он собирается делать с объектом. Процесс может запросить либо набор стандартных прав доступа (чтение, запись, выполнение), применимых ко всем объектам, либо специфические права доступа, различные для объектов разного типа. Так, в случае объекта «файл» процесс может запросить права на удаление файла или дозапись, а в случае объекта «поток» — права на приостановку потока или его завершение.
Когда процесс открывает описатель объекта, диспетчер объектов вызывает так называемый монитор состояния защиты* (security reference monitor), часть подсистемы защиты, работающую в режиме ядра, и посылает ему уведомление о наборе желательных для процесса прав доступа. Монитор состояния защиты проверяет, разрешает ли дескриптор защиты объекта запрашиваемый тип доступа. Если да, монитор состояния защиты возвращает процессу набор предоставленных прав доступа (granted access rights), информацию о которых диспетчер объектов сохраняет в создаваемом им описателе объекта. Как подсистема защиты определяет, кто и к каким объектам может получать доступ, рассматривается в главе 8.
* Ha самом деле этот компонент представляет собой нечто вроде монитора запросов к подсистеме защиты. — Прим. перев.
После этого всякий раз, когда потоки процесса используют описатель, диспетчер объектов может быстро проверить, соответствует ли набор предоставленных прав доступа, хранящихся в описателе, действиям, которые намеревается выполнить вызванный потоками сервис объекта. Так, если вызывающая программа запросила доступ для чтения к объекту «раздел», а затем вызывает сервис для записи в этот объект, последний сервис не выполняется.
Объекты бывают двух типов: временные (temporary) и постоянные (permanent). Большинство объектов временные, т. е. они хранятся, пока используются, и освобождаются, как только необходимость в них отпадает. Постоянные объекты существуют до тех пор, пока они не освобождаются явным образом. Поскольку большинство объектов временные, остальная часть этого раздела будет посвящена тому, как диспетчер объектов реализует хранение объектов в памяти (object retention), т. е. сохранение временных объектов лишь до тех пор, пока они используются, с их последующим удалением. Так как для доступа к объекту все процессы пользовательского режима должны сначала открыть его описатель, диспетчер объектов может легко отслеживать, сколько процессов и даже какие именно из них используют объект. Учет описателей является одним из механизмов, реализующих хранение объектов в памяти. Этот механизм двухфазный. Первая фаза называется хранением имен (name retention) и контролируется числом открытых описателей объекта. Каждый раз, когда процесс открывает описатель объекта, диспетчер увеличивает значение счетчика открытых описателей в заголовке объекта. По мере того как процессы завершают использование объекта и закрывают его описатели, диспетчер уменьшает значение этого счетчика. Когда счетчик обнуляется, диспетчер удаляет имя объекта из своего глобального пространства имен. После этого новые процессы уже не смогут открывать описатели данного объекта.