"1.Внутреннее устройство Windows (гл. 1-4)" - читать интересную книгу автора (Руссинович Марк, Соломон Дэвид)ГЛABA 3 Системные механизмыB Microsoft Windows существует несколько базовых механизмов, которыми пользуются компоненты режима ядра: исполнительная система (executive), ядро и драйверы устройств. B этой главе описываются следующие системные механизмы (а также способы их использования): • диспетчеризация ловушек (trap dispatching), в том числе прерываний, DPC (deferred procedure call), APC (asynchronous procedure call), исключений и системных сервисов; • диспетчер объектов исполнительной системы; • синхронизация, в том числе спин-блокировки, объекты диспетчера ядра (kernel dispatcher objects) и реализация механизмов ожидания; • системные рабочие потоки; • различные механизмы вроде поддержки глобальных флагов Windows; • LPC (local procedure call); • Kernel Event Tracing; • Wow64. Прерывания и исключения — такие ситуации в операционной системе, в которых нормальный поток выполнения кода процессором прерывается. Эти ситуации обнаруживаются как программным, так и аппаратным обеспечением. Термин Ядро различает прерывания и исключения: Прерывания и исключения можно генерировать как программно, так и аппаратно. Например, исключение «bus error» (ошибка шины) возникает из-за аппаратной ошибки, а причиной исключения «divide-by-zero» (деление на нуль) является ошибка в программе. Аналогичным образом прерывания могут генерироваться устройствами ввода-вывода или самим ядром (такие программные прерывания, как, например, APC или DPC). При аппаратном прерывании или исключении процессор записывает статусную информацию в стек ядра для прерванного потока, чтобы впоследствии можно было вернуться к исходной точке в потоке управления и продолжить выполнение команд так, будто ничего не произошло. Если поток выполнялся в пользовательском режиме, Windows переключается на стек режима ядра для потока. Затем создает в стеке ядра прерванного потока B большинстве случаев ядро устанавливает функции, выполняющие общую обработку ловушек до и после передачи управления другим функциям, которые ставят ловушки. Например, когда устройство генерирует прерывание, обработчик ловушек аппаратных прерываний (принадлежащий ядру) передает управление Аппаратные прерывания обычно генерируются устройствами ввода-вывода, которые таким образом уведомляют процессор о необходимости уделить им внимание. Устройства, управляемые на основе прерываний, позволяют операционной системе максимально полно использовать процессор, совмещая основную обработку с обслуживанием ввода-вывода. Выдав запрос на ввод-вывод, поток может заняться другой работой, пока устройство выполняет запрошенную операцию. Закончив, устройство генерирует прерывание, и процессор переключается на обслуживание этого устройства. Прерываниями управляются, как правило, координатные устройства, принтеры, клавиатуры, дисковые устройства и сетевые платы. Системное программное обеспечение также может генерировать прерывания. Ядро способно отключать прерывания, чтобы не прерывать работу процессора, однако это делается нечасто — только в критические моменты, например при обработке прерываний или диспетчеризации исключения. Для обработки аппаратных прерываний ядро устанавливает обработчики ловушек прерываний, которые передают управление внешней процедуре (ISR), обрабатывающей прерывание, или внутренней процедуре ядра, реагирующей на прерывание. Драйверы устройств предоставляют ISR для обслуживания прерываний от своих устройств, а ядро — внутренние процедуры для обработки других типов прерываний. Далее мы рассмотрим, как процессор уведомляется об аппаратных прерываниях, какие типы прерываний поддерживаются ядром и как драйверы устройств взаимодействуют с ядром (в процессе обработки прерываний). Кроме того, мы поговорим о распознавании ядром программных прерываний и об объектах, используемых для реализации таких прерываний. Ha аппаратных платформах, поддерживаемых Windows, прерывания, связанные с внешним вводом-выводом, поступают по одной из линий контроллера прерываний. Контроллер в свою очередь связан с процессором единственной линией, по которой и уведомляет о прерывании. Как только процессор прерывается, он требует от контроллера запрос прерывания (interrupt request, IRQ). Контроллер транслирует IRQ в номер прерывания, используемый как индекс в структуре, называемой ЭКСПЕРИМЕНТ: просмотр IDT Просмотреть содержимое IDT, включая сведения об обработчиках ловушек, которые Windows назначила прерываниям, можно с помощью команды Ниже показано, что выводит команда B системе, задействованной в этом эксперименте, номер прерывания 0x3C — с ISR драйвера клавиатуры (I8042prt.sys), а прерывание 0x3B совместно используется несколькими устройствами, в том числе видеоадаптером, шиной PCMCIA, портами USB и IEEE 1394, а также сетевым адаптером. Windows увязывает аппаратные IRQ с номерами прерываний в IDT. Эта таблица используется системой и при конфигурировании обработчиков ловушек для исключений. Так, номер исключения для ошибки страницы на x86 и x64 (это исключение возникает, когда поток пытается получить доступ к отсутствующей или не определенной в виртуальной памяти странице) равен 0xe. Следовательно, запись 0xe в IDT указывает на системный обработчик ошибок страниц. Хотя архитектуры, поддерживаемые Windows, допус-каютдо 256 элементов в IDT, число IRQ на конкретной машине определяется архитектурой используемого в ней контроллера прерываний. У каждого процессора имеется своя IDT, так что разные процессоры могут при необходимости выполнять разные ISR. Например, в многопроцессорной системе каждый процессор получает прерывания системного таймера, но обновление значения системного таймера в результате обработки этого прерывания осуществляется только одним процессором. Однако все процессоры используют это прерывание для измерения кванта времени, выделенного потоку, и для инициации новой процедуры планирования по истечении этого кванта. Аналогичным образом в некоторых конфигурациях может понадобиться, чтобы определенные аппаратные прерывания обрабатывал конкретный процессор. B большинстве систем x86 применяется либо программируемый контроллер прерываний (Programmable Interrupt Controller, PIC) i8259A, либо его разновидность, усовершенствованный программируемый контроллер прерываний (Advanced Programmable Interrupt Controller, APIC) i82489. Новые компьютеры, как правило, оснащаются APIC Стандарт PIC был разработан для оригинальных IBM PC PIC работает только в однопроцессорных системах и имеет 15 линий прерываний. APIC и SAPIC (о нем чуть позже) способен работать в многопроцессорных системах и предлагает 256 линий прерываний. Intel совместно с другими компаниями создали спецификацию Multiprocessor (MP) Specification, стандарт для многопроцессорных систем x86, основанный на использовании APIC Для совместимости с однопроцессорными операционными системами и загрузочным кодом, запускающим многопроцессорную систему в однопроцессорном режиме, APIC поддерживает PIC-совместимый режим с 15 линиями прерываний и передачей прерываний лишь главному процессору. Архитектура APIC показана на рис. 3–2. Ha самом деле APIC состоит из нескольких компонентов: APIC ввода-вывода, принимающего прерывания от устройств, локальных APIC, принимающих прерывания от APIC ввода-вывода по выделенной шине и прерывающих pa-ботутого процессора, с которым они связаны, а также 18259А-совместимого контроллера прерываний, транслирующего входные сигналы APIC в соответствующие PIC-эквиваленты. APIC ввода-вывода отвечает за реализацию алгоритмов перенаправления прерываний, и операционная система выбирает нужный ей алгоритм (в Windows выбор возлагается на HAL). Эти алгоритмы равномерно распределяют между процессорами нагрузку, связанную с обработкой прерываний от устройств, и в максимальной мере используют все преимущества локальности, направляя такие прерывания процессору, который только что обрабатывал прерывания аналогичного типа. Поскольку архитектура x64 совместима с операционными системами для x86, системы на базе x64 должны предоставлять те же контроллеры прерываний, что и на базе x86. Однако х64-версии Windows не будут работать на системах без APIC (т. е. они не поддерживают PIC). B архитектуре IA64 используется контроллер прерываний Streamlined Advanced Programmable Interrupt Controller (SAPIC) — результат эволюционного развития APIC Главное различие между архитектурами APIC и SAPIC в том, что APIC ввода-вывода в APIC-системе направляет прерывания локальным APIC по выделенной шине APIC, тогда как в системе SAPIC прерывания передаются по шине ввода-вывода и системы (I/O and system bus) для большего быстродействия. Еще одно различие — перенаправление прерываний и балансировка нагрузки в APIC-системе обрабатывается самой шиной APIC, а в SAPIC-системе, где нет выделенной шины APIC, требуется, чтобы соответствующая поддержка была запрограммирована в микрокоде (прошивке). Ho, даже если эта поддержка имеется в микрокоде, Windows не использует ее — вместо этого она статически назначает прерывания процессорам по принципу карусели. Конфигурацию PIC в однопроцессорной системе и APIC в многопроцессорной системе можно просмотреть с помощью команд Ha следующем листинге приводится выходная информация команды Теперь взгляните на образец вывода команды Хотя контроллеры прерываний различают уровни приоритетов прерываний, Windows использует свою схему приоритетов прерываний, известную под названием Рис. 3–4. Уровни запросов прерываний (IRQL) в системах x64 и IA64 Прерывания обслуживаются в порядке их приоритета, и прерывания с более высоким приоритетом вытесняют обработку прерываний с меньшим приоритетом. При возникновении прерывания с высоким приоритетом процессор сохраняет информацию о состоянии прерванного потока и активизирует сопоставленный с данным прерыванием диспетчер ловушки. Последний повышает IRQL и вызывает процедуру обслуживания прерывания (ISR). После выполнения ISR диспетчер прерывания понижает IRQL процессора до исходного уровня и загружает сохраненные ранее данные о состоянии машины. Прерванный поток возобновляется с той точки, где он был прерван. Когда ядро понижает IRQL, могут «материализоваться» ранее замаскированные прерывания с более низким приоритетом. Тогда вышеописанный процесс повторяется ядром для обработки и этих прерываний. Уровни приоритетов IRQL имеют совершенно иной смысл, чем приоритеты в схеме планирования потоков (см. главу 6). Приоритет в этой схеме является атрибутом потока, тогда как IRQL — атрибутом источника прерывания, например клавиатуры или мыши. Кроме того, IRQL каждого процессора меняется во время выполнения команд операционной системы. Значение IRQL определяет, какие прерывания может получать данный процессор. IRQL также используется для синхронизации доступа к структурам данных режима ядра (о синхронизации мы поговорим позже). При выполнении поток режима ядра повышает или понижает IRQL процессора либо напрямую (вызовом соответственно Поскольку доступ к PIC — операция довольно медленная, в HAL, использующих PIC, реализован механизм оптимизации «отложенный IRQL» (lazy IRQL), который избегает обращений к PIC Когда IRQL повышается, HAL — вместо того чтобы изменять маску прерывания — просто отмечает новый IRQL. Если вслед за этим возникает прерывание с более низким приоритетом, HAL устанавливает маску прерывания в соответствии с первым и откладывает обработку прерывания с более низким приоритетом до понижения IRQL. Таким образом, если при повышенном IRQL не возникнет прерываний с более низким приоритетом, HAL не потребуется обращаться к PIC. Поток режима ядра повышает и понижает IRQL процессора, на котором он выполняется, в зависимости от того, что именно делает этот поток. Например, обработчик ловушки (или сам процессор) при прерывании повышает IRQL процессора до IRQL источника прерывания. B результате все прерывания с более низким или равным IRQL маскируются (только на этом процессоре), что не дает прерыванию с таким же или более низким IRQL помешать процессору обработать текущее прерывание. Замаскированные прерывания либо обрабатываются другим процессором, либо откладываются до понижения IRQL. Поэтому все системные компоненты, в том числе ядро и драйверы устройств, пытаются удерживать IRQL на уровне passive («пассивный»), иногда называемом низким уровнем. Если бы IRQL долго оставался неоправданно высоким, драйверы устройств не смогли бы оперативно реагировать на аппаратные прерывания. ЭКСПЕРИМЕНТ: определяем IRQL Если вы работаете с отладчиком ядра в Windows Server 2003, то можете определить IRQL процессора командой Заметьте, что в структуре данных, называемой PCR (processor control region), и ее расширении — PRCB (processor control block) имеется поле с именем Irql. Эти структуры содержат информацию о состоянии каждого процессора в системе, в том числе текущий IRQL, указатель на аппаратную IDT, сведения о текущем потоке и потоке, который будет выполняться следующим. Ядро и HAL используют эту информацию для выполнения операций, специфичных для данной машины и ее архитектуры. Отдельные части структур PCR и PRCB открыто определены в заголовочном файле Ntddk.h (в Windows DDK). Загляните в него, чтобы получить представление об этих структурах. Для просмотра содержимого PCR воспользуемся командой K сожалению, Windows не поддерживает поле Irql на платформах, не использующих отложенные IRQL, поэтому в большинстве систем это поле всегда содержит 0. Так как изменения IRQL процессора существенно влияют на функционирование системы, они возможны только в режиме ядра. Потоки пользовательского режима не могут изменять IRQL процессора. Это значит, что при выполнении потоков пользовательского режима значение IRQL процессора всегда равно passive. Только при выполнении кода режима ядра IRQL может быть выше этого уровня. Каждый уровень прерывания имеет определенное назначение. Так, ядро генерирует Уровни IRQL и запросы прерываний (IRQ) — вещи разные. Концепция IRQL в архитектурах, на которых работает Windows, не реализована аппаратно. Тогда возникает вопрос как Windows определяет, какой IRQL следует присвоить прерыванию? Ответ нужно искать в HAL. B Windows за определение устройств на конкретной шине (PCI, USB и т. д.) и назначение им прерываний отвечают драйверы устройств особого типа — драйверы шин. Этот алгоритм неодинаков в различных версиях HAL. B однопроцессорных х86-системах HAL выполняет прямую трансляцию: IRQL данного вектора прерывания вычисляется путем вычитания значения вектора из 27. Таким образом, если устройство использует 5-й вектор прерывания, его ISR выполняется при IRQL, равном 22. B многопроцессорной х86-системе преобразования более сложны. APIC поддерживает более 200 векторов прерываний, поэтому при трансляции «один в один» имеющихся IRQL окажется недостаточно. Многопроцессорная версия HAL присваивает IRQL векторам прерываний, циклически перебирая значения из диапазона IRQL устройств (device IRQL, DIRQL). B итоге на многопроцессорной х86-системе не так-то просто предсказать или выяснить IRQL, назначаемый IRQ. Наконец, в x64- и IА64-системах HAL вычисляет IRQL для IRQ путем деления вектора прерывания, назначенного данному IRQ, на 16. Давайте повнимательнее приглядимся к предопределенным IRQL, начиная с самого верхнего уровня схемы, представленной на рис. 3–5. • Уровень «high» (высокий) используется ядром, только если оно останавливает систему в функции • Уровень «power fail» (отказ электропитания) был заложен еще в самый первый проект Microsoft Windows NT Он определяет поведение системы при отказе электропитания, но никогда не применялся. • Уровень «interprocessor interrupt» (межпроцессорное прерывание) используется для того, чтобы запрашивать от другого процессора выполнение какой-либо операции, например, при постановке в очередь прерывания DISPATCH_EVEL для планирования конкретного потока к выполнению, при обновлении кэша TLB, завершении работы или крахе системы. • Уровень «clock» (часы) используется для системных часов, с помощью которых ядро отслеживает время суток, измеряет и распределяет процессорное время между потоками. • Уровень «profile» (профиль) используется системным таймером реального времени, если активизирован механизм профилирования ядра (kernel profiling), т. е. измерения его производительности. Когда он активен, обработчик ловушки профилирования регистрирует адрес команды, выполнявшейся на момент прерывания. Co временем создается таблица адресов, которую можно извлечь и проанализировать с помощью соответствующих утилит. Вы можете скачать утилиту Kernrate, позволяющую просматривать статистику, полученную при использовании механизма профилирования ядра. Подробнее об этой утилите см. описание эксперимента с Kernrate. • Уровень «device» (устройство) применяется для задания приоритетов прерываний от устройств (о принципах увязки аппаратных прерываний с IRQL см. предыдущий раздел). • Прерывания уровней «DPC/dispatch» и «APC» являются программными; они генерируются ядром и драйверами устройств (о DPC и APC будет рассказано позже). • Самый низкий уровень IRQL, «passive» (пассивный), на самом деле вообще не является уровнем прерывания. При этом значении IRQL потоки выполняются обычным образом и могут возникать любые прерывания. ЭКСПЕРИМЕНТ: применение утилиты Kernrate Утилита профилирования ядра (Kernrate) позволяет включать таймер профилирования системы, собирать образцы кода, выполняемого при срабатывании таймера, и выводить сводную информацию, отражающую распределение процессорного времени по образам файлов и функциям. Ee можно использовать для отслеживания процессорного времени, потребляемого индивидуальными процессами, и/или времени, проведенного в режиме ядра независимо от процессов (например, для выполнения процедур обслуживания прерываний). Профилирование ядра полезно, когда вы хотите выявить точки, в которых на выполнение кода тратится больше всего процессорного времени. B своей простейшей форме Kernrate сообщает, сколько процессорного времени было использовано каждым модулем ядра (Ntoskrnl, драйверами и т. д.). Попробуйте, к примеру, выполнить следующие операции. 1. Откройте окно командной строки. 2. Введите cd c: \program files\krview\kernrates. 3. Введите dir. (Вы увидите образы kernrate для каждой платформы.) 4. Запустите образ, который подходит для вашей платформы (без аргументов или ключей). Например, Kernrate_i386_XP.exe — это образ для Windows XP на платформе x86. 5. Пока Kernrate выполняется, поделайте что-нибудь в системе. Скажем, запустите Windows Media Player и проиграйте музыку, запустите игру, интенсивно работающую с графикой, или перечислите содержимое каталога на удаленном сетевом ресурсе. 6. Нажмите Ctrl+C, чтобы остановить Kernrate. Это заставит Kernrate вывести статистику за прошедший период. Ниже приведена часть вывода Kernrate, когда выполнялся Windows Media Player, воспроизводивший дорожку с компакт-диска. Сводные данные показывают, что система провела 11,7 % времени в режиме ядра, 30,2 % в пользовательском режиме, 58,1 % в простое, 6,4 % на уровне DPC и 1,3 % на уровне прерываний (interrupt level). Модуль, чаще всего требовавший к себе внимания, был GV3.SYS, драйвер процессора для Pentium M (семейства Geyserville). Он используется для сбора информации о производительности, поэтому и оказался на первом месте. Модуль, занявший второе место, — Smwdm.sys, драйвер звуковой платы на компьютере, где проводился тест. Это вполне объяснимо, учитывая, что основную нагрузку в системе создавал Windows Media Player, посылавший звуковой ввод-вывод этому драйверу. Если у вас есть файлы символов, вы можете исследовать индивидуальные модули и посмотреть, сколько времени было затрачено каждой из их функций. Например, профилирование системы в процессе быстрого перемещения окна по экрану давало такой вывод (здесь приведена лишь его часть): B данном случае самым «прожорливым» был модуль Win32k.sys, драйвер системы, отвечающей за работу с окнами. Второй в списке — видеодрайвер. И действительно, основная нагрузка в системе была связана с рисованием окна на экране. B детальном выводе для Win32k.sys видно, что наиболее активна была его функция EngPaint, основная GDI-функция для рисования на экране. Ha код, выполняемый на уровне «DPC/dispatch» и выше, накладывается важное ограничение: он не может ждать освобождения объекта, если такое ожидание заставило бы планировщик подключить к процессору другой поток (а это недопустимая операция, так как планировщик синхронизирует свои структуры данных на уровне «DPC/dispatch» и, следовательно, не может быть активизирован для выполнения перераспределения процессорного времени). Другое ограничение заключается в том, что при уровне IRQL «DPC/ dispatch» или выше доступна только неподкачиваемая память. Ha самом деле второе ограничение является следствием первого, так как обращение к отсутствующей в оперативной памяти странице вызывает ошибку страницы. Тогда диспетчер памяти должен был бы инициировать операцию дискового ввода-вывода, после чего ждать, когда драйвер файловой системы загрузит эту страницу с диска. Это в свою очередь вынудило бы планировщик переключить контекст (возможно, на поток простоя, если нет ни одного пользовательского потока, ждущего выполнения). B результате было бы нарушено правило, запрещающее вызов планировщика в таких ситуациях (поскольку при чтении с диска IRQL все еще остается на уровне «DPC/dispatch» или выше). При нарушении любого из этих двух ограничений происходит крах системы с кодом завершения IRQL_NOT_LESS_OR_EQUAL (подробнее о кодах завершения при крахе системы см. главу 4). Кстати, нарушение этих ограничений является довольно распространенной ошибкой в драйверах устройств. Локализовать причину ошибок такого типа помогает утилита Driver Verifier, о которой будет подробно рассказано в разделе «Утилита Driver Verifier» главы 7. Ядро предоставляет переносимый (портируемый) механизм — объект прерывания, позволяющий драйверам устройств регистрировать ISR для своих устройств. Этот объект содержит всю информацию, необходимую ядру для назначения конкретного уровня прерывания для ISR устройства, включая адрес ISR, IRQL устройства и запись в IDT ядра, с которой должна быть сопоставлена ISR. При инициализации в объект прерывания из шаблона обработки прерываний, KiIn-terruptTemplate, копируется несколько ассемблерных инструкций — код диспетчеризации. Этот код выполняется при возникновении прерывания. Код, хранящийся в объекте прерывания, вызывает реальный диспетчер прерываний, которым обычно является процедура ядра ЭКСПЕРИМЕНТ: изучение внутреннего устройства прерываний C помощью отладчика ядра вы можете просмотреть детальные сведения об объекте прерывания, в том числе его IRQL, адрес ISR и собственный код диспетчеризации прерывания (custom interrupt dispatching code). Во-первых, введите команду Для просмотра содержимого объекта, сопоставленного с прерыванием, введите dt nt!_kinterrupt, указав адрес, следующий за KINTERRUPT: B этом примере IRQL, назначенный Windows прерыванию, — 0x1a (или 26 в десятичной системе). Поскольку вывод получен на однопроцессорной х86-системе, IRQ равно 1 (IRQL в таких системах вычисляются путем вычитания IRQ из 27). Это можно проверить, открыв Device Manager (Диспетчер устройств) [на вкладке Hardware (Оборудование) в окне свойств системы)], найдя PS/2-клавиатуру и посмотрев назначенные ей ресурсы, как показано на следующей иллюстрации. B многопроцессорных х86-системах IRQ назначается в основном случайным образом, а в x64- или IА64-системе вы увидите, что IRQ — это номер вектора прерываний [0x31 (49 в десятичной системе)], деленный на 16. Адрес ISR для объекта прерывания хранится в поле ServiceRoutine (его и показывает K средам, предназначенным для работы в реальном времени, предъявляются либо жесткие, либо очень жесткие требования к максимальному времени реакции. Реакция системы реального времени, к которой предъявляются очень жесткие требования (например, системы управления атомной электростанцией), должна быть исключительно быстрой, иначе неизбежны катастрофы, опасные не только для оборудования, но и для жизни людей. Менее ответственные системы реального времени (например, система экономичного расхода топлива автомобиля) могут в какой-то мере отклоняться от этих требований, но их соблюдение все же желательно. B системах реального времени устройствами ввода служат датчики, а устройствами вывода — управляющие устройства. Проектировщик компьютерных систем реального времени должен знать величину максимально допустимого запаздывания между моментом генерации прерывания устройством ввода и ответом управляющего устройства, контролируемого драйвером. Анализ самых неблагоприятных вариантов должен учитывать как запаздывание операционной системы, так и запаздывание драйверов и приложений. Поскольку проконтролировать расстановку приоритетов IRQ устройств операционной системой Windows нельзя, а пользовательские приложения выполняются лишь при IRQL уровня «passive», Windows не всегда подходит для обработки данных в реальном времени. Максимальное запаздывание определяется в конечном счете устройствами и драйверами системы, а не самой Windows. Этот фактор становится проблемой при использовании готового оборудования, имеющегося в продаже. Проектировщик может столкнуться с трудностями при определении максимального времени выполнения ISR или DPC драйвера готового устройства. Даже после тестирования он не сможет гарантировать, что запаздывание ни при каких обстоятельствах не превысит заданного предела. Более того, суммарное запаздывание системных DPC и ISR, как правило, существенно превосходит значения, приемлемые для чувствительных к задержкам систем. Хотя ко многим типам встраиваемых систем (например, к принтерам и автомобильным компьютерам) предъявляются требования, как к системам реального времени, Windows XP Embedded не обладает соответствующими характеристиками. Это просто версия Windows XP, которая создана с использованием технологии, лицензированной Microsoft у компании VenturCom, и способна работать в системах с ограниченными ресурсами. Так, для устройства без сетевых функций исключается вся функциональность Windows XP, связанная с поддержкой работы в сетях, включая средства управления сетью, драйверы стека протокола и сетевого адаптера. Тем не менее третьи фирмы поставляют версии ядра реального времени для Windows. Их подход заключается в том, что они встраивают ядро реального времени в собственный HAL и выполняют Windows как задачу в операционной системе реального времени. Windows, выполняемая в таком виде, служит в качестве пользовательского интерфейса системы и имеет меньший приоритет по сравнению с задачами, ответственными за управление нужным устройством. Пример расширения ядра Windows реального времени можно увидеть на Web-сайте VenturCom Сопоставление ISR с конкретным уровнем прерывания называется Применение объекта прерывания для регистрации ISR позволяет драйверам устройств избегать прямого взаимодействия с контроллером прерываний (разным на разных процессорных архитектурах) и исключает необходимость детального знания IDT. Это дает возможность создавать переносимые драйверы устройств, поскольку благодаря такой функциональности ядра программировать драйверы устройств на ассемблере и учитывать в них особенности конкретных процессоров больше не нужно. Использование объекта прерывания дает и другие преимущества: ядро может синхронизировать выполнение ISR с другими частями драйвера устройства, которые могут разделять данные с ISR. (Подробнее о том, как драйверы устройств реагируют на прерывания, см. главу 9.) Более того, объекты прерывания позволяют ядру легко вызывать более одной ISR для любого уровня прерывания. Если несколько драйверов создают объекты прерывания и сопоставляют их с одной записью в IDT, то при прерывании на определенной линии диспетчер вызывает каждую из этих процедур (ISR). Такая функциональность позволяет ядру поддерживать конфигурации в виде цепочек, когда несколько устройств совместно используют одну линию прерывания. Когда одна из ISR объявляет диспетчеру о захвате прерывания, происходит разрыв цепочки. Если несколько устройств, разделяющих одну линию прерывания, одновременно запрашивают обслуживание, то устройства, не получившие подтверждения от своих ISR, будут вновь генерировать прерывания, как только диспетчер понизит IRQL. Связывание устройств в цепочку разрешается, только если все драйверы устройств, стремящиеся использовать одно и то же прерывание, сообщат ядру о своей способности разделять данное прерывание. Если они не в состоянии совместно использовать это прерывание, диспетчер Plug and Play переназначит IRQ с учетом запросов каждого устройства. Если разделяемым является вектор прерываний, объект прерывания вызывает Хотя большинство прерываний генерируется аппаратно, ядро Windows тоже может генерировать прерывания — только они являются программными. Этот вид прерываний служит для решения многих задач, в том числе: • инициации диспетчеризации потоков; • обработки прерываний, не критичных по времени; • обработки событий таймеров; • асинхронного выполнения какой-либо процедуры в контексте конкретного потока; • поддержки асинхронного ввода-вывода. Эти задачи подробно рассматриваются ниже. Когда дальнейшее выполнение потока невозможно, например из-за его завершения или перехода в ждущее состояние, ядро напрямую обращается к диспетчеру, чтобы вызвать немедленное переключение контекста. Однако иногда ядро обнаруживает, что перераспределение процессорного времени (rescheduling) должно произойти при выполнении глубоко вложенных уровней кода. B этой ситуации ядро запрашивает диспетчеризацию, но саму операцию откладывает до выполнения текущих действий. Такую задержку удобно организовать с помощью программного прерывания DPC (deferred procedure call). При необходимости синхронизации доступа к разделяемым структурам ядра последнее всегда повышает IRQL процессора до уровня «DPC/dispatch» или выше. При этом дополнительные программные прерывания и диспетчеризация потоков запрещаются. Обнаружив необходимость в диспетчеризации, ядро генерирует прерывание уровня «DPC/dispatch». Ho поскольку IRQL уже находится на этом уровне или выше, процессор откладывает обработку этого прерывания. Когда ядро завершает свои операции, оно определяет, что должно последовать снижение IRQL ниже уровня "DPC/dispatch", и проверяет, не ожидают ли выполнения отложенные прерывания диспетчеризации. Если да, IRQL понижается до уровня «DPC/dispatch», и эти отложенные прерывания обрабатываются. Активизация диспетчера потоков через программное прерывание — способ отложить диспетчеризацию до подходящего момента. Однако Windows использует программные прерывания для отложенного выполнения и других операций. При этом IRQL ядро обрабатывает не только диспетчеризацию потоков, но и DPC DPC — это функция, выполняющая системную задачу, менее критичную по времени в сравнении с текущей. Эти функции называются DPC позволяют операционной системе генерировать прерывания и выполнять системные функции в режиме ядра. Ядро использует DPC для обработки прерываний по таймеру (и освобождения потоков, ждущих на таймерах), а также для перераспределения процессорного времени по истечении кванта времени, отведенного текущему потоку. Драйверы устройств используют DPC для выполнения запросов ввода-вывода. Для своевременного обслуживания аппаратных прерываний Windows — во взаимодействии с драйверами устройств — пытается удерживать текущий IRQL ниже IRQL устройств. Один из способов достижения этой цели заключается в следующем. ISR должна выполнять минимум действий по обслуживанию своего устройства, сохранять переменные данные о состоянии прерывания и откладывать передачу данных или выполнение других не столь критичных по времени операций, как DPC, до снижения IRQL к уровню «DPC/dispatch» (подробнее о DPC и системе ввода-вывода см. главу 9). DPC представляется По умолчанию ядро помещает DPC-объекты в конец очереди DPC процессора, на котором был запрошен DPC (как правило, это процессор, на котором выполняется ISR). Однако драйвер устройства может изменить это, указав приоритет DPC (низкий, средний или высокий; по умолчанию — средний) или направив DPC конкретному процессору. DPC, направленный конкретному процессору, называется Когда IRQL процессора вот-вот понизится с уровня «DPC/dispatch» или более высокого до уровня «APC» или «passive», ядро переходит к обработке всех DPC Windows оставляет IRQL на уровне «DPC/dispatch» и извлекает все DPC-объекты из очереди данного процессора (т. е. ядро опустошает очередь), поочередно вызывая каждую DPC-функцию. Ядро разрешает уменьшить IRQL ниже уровня «DPC/dispatch» для продолжения выполнения обычных потоков только после опустошения очереди. Схема обработки DPC показана на рис. 3–7. Приоритеты DPC могут влиять на поведение системы и иным способом. Обычно ядро начинает опустошение очереди DPC с прерывания уровня «DPC/dispatch». Такое прерывание генерируется ядром, только если DPC направлен на процессор, на котором выполняется ISR, и DPC имеет средний или высокий приоритет. Если у DPC низкий приоритет, ядро генерирует прерывание, только если число незавершенных запросов DPC превышает пороговое значение или если число DPC, запрошенных на процессоре за установленный период, невелико. Если DPC направлен другому процессору (не тому, на котором выполняется ISR) и его приоритет высокий, ядро немедленно посылает ему диспетчерское IPI, сигнализируя целевому процессору о необходимости опустошения его очереди DPC Если приоритет DPC средний или низкий, для появления прерывания «DPC/dispatch» число DPC в очереди целевого процессора должно превышать пороговое значение. Системный поток простоя также опустошает очередь DPC процессора, на котором он выполняется. Хотя уровни приоритета и направление DPC являются довольно гибкими средствами, у драйверов устройств редко возникает необходимость в изменении заданного по умолчанию поведения своих DPC-объектов. B таблице 3–1 даются сведения о ситуациях, в которых начинается опустошение очереди DPC Поскольку потоки пользовательского режима выполняются при низком IRQL, вероятность того, что DPC прервет выполнение обычного пользовательского потока, довольно велика. DPC-процедуры выполняются независимо от того, какой поток работает в настоящий момент. Это означает, что выполняемая DPC-процедура не в состоянии предугадать текущий размер спроецированного адресного пространства процесса. DPC-процедуры могут вызывать функции ядра, но не могут обращаться к системным сервисам, генерировать ошибки страницы, создавать или ждать объекты диспетчера. Однако они способны получать доступ к неподкачиваемым областям системной памяти, поскольку системное адресное пространство всегда спроецировано независимо от того, что представляет собой текущий процесс. DPC используются не только драйверами, но и ядром. Ядро чаще всего применяет DPC для обработки ситуации, когда истекает выделенный квант времени. При каждом такте системного таймера генерируется прерывание с IRQL-уровнем «clock». Обработчик прерываний таймера (выполняемый при IRQL, равном «clock») обновляет системное время и уменьшает значение счетчика, отслеживающего время выполнения текущего потока. Когда значение счетчика обнуляется, квант времени, отведенный потоку, заканчивается, и ядру может понадобиться перераспределить процессорное время — эта задача имеет более низкий приоритет и должна выполняться при IRQL, равном «DPC/dispatch». Обработчик прерываний таймера ставит DPC в очередь, чтобы инициировать диспетчеризацию потоков, после чего завершает свою работу и понижает IRQL процессора. Поскольку приоритет прерываний DPC ниже, чем аппаратных, перед генерацией прерывания DPC сначала обрабатываются все аппаратные прерывания, возникающие до завершения обработки прерывания таймера. ЭКСПЕРИМЕНТ: мониторинг активности прерываний и DPC Process Explorer позволяет вести мониторинг активности прерываний и DPC, добавив столбец Context Switch Delta и наблюдая за процессами Interrupt и DPC Это не настоящие процессы, они показываются как процессы просто для удобства и не вызывают переключений контекста. Счетчик переключений контекста в Process Explorer для этих псевдопроцессов отражает число возникновений каждого из них в течение предыдущего интервала обновления (refresh interval). Вы можете имитировать активность прерываний и DPC, быстро перемещая курсор мыши по экрану. Вы также можете проследить выполнение конкретных процедур обслуживания прерываний (ISR) и отложенных вызовов процедур (DPC), используя встроенную поддержку трассировки событий (подробнее об этом будет рассказано позже) в Windows XP Service Pack 2 или Windows Server 2003 Service Pack 1 (и выше). 1. Инициируйте захват событий, введя команду: 2. Остановите захват событий, введя: 3. Создайте отчеты по захваченным событиям, набрав команду: Это приведет к генерации двух файлов: workload.txt и dumpfile.csv. 4. Откройте workload.txt и вы увидите сводные сведения о времени, проведенном драйверами каждого типа в ISR- и DPC-процедурах. 5. Откройте файл dumpfile.csv, созданный на этапе 3; найдите строки, где во втором значении встречается ‹‹DPC» или «ISR». Например, следующие три строки из dumpfile.csv показывают DPC таймера, DPC и ISR: Первый адрес относится к DPC, вызванному срабатыванием таймера, который был поставлен в очередь клиентским драйвером редиректора файловой системы (file system redirector client driver). Второй относится к DPQ вызванному срабатыванием универсального таймера (generic timer). Наконец, третий — это адрес ISR для порт-драйвера ATAPI. Более подробные сведения см. по ссылке h APC (asynchronous procedure call) позволяет выполнять пользовательские программы и системный код в контексте конкретного пользовательского потока (а значит, и в адресном пространстве конкретного процесса). Посколькудля выполнения в контексте конкретного потока APC ставятся в очередь и выполняются при IRQL ниже «DPC/dispatch», на их работу не налагаются ограничения, свойственные DPC АРС-процедура может обращаться к ресурсам (объектам), ждать освобождения описателей объектов, генерировать ошибки страниц и вызывать системные сервисы. APC описывается управляющим объектом ядра — APC бывают двух видов: режима ядра и пользовательского режима. APC режима ядра для выполнения в контексте целевого потока не нужно «разрешение» со стороны этого потока, тогда как для APC пользовательского режима это обязательно. APC режима ядра прерывает поток и выполняет процедуру без вмешательства или согласия потока. APC режима ядра тоже бывают двух типов: обычные (normal) и специальные (special). Поток может отключить все APC режима ядра, повысив IRQL до уровня APC_LEVEL или вызвав Исполнительная система использует APC режима ядра для тех задач операционной системы, которые нужно выполнить в адресном пространстве (контексте) конкретного потока. Так, через специальные APC режима ядра она может указать потоку прекратить выполнение системного сервиса, допускающего прерывание, или записать результаты операции асинхронного ввода-вывода в адресное пространство этого потока. Подсистемы окружения используют такие APC, чтобы приостановить поток или завершить себя, а также чтобы получить или установить контекст пользовательского потока. Подсистема POSIX эмулирует через APC режима ядра передачу POSIX-сигна-лов процессам POSIX. Драйверы устройств также применяют APC режима ядра. Например, если инициирована операция ввода-вывода и поток переходит в состояние ожидания, к процессору может быть подключен другой поток из другого процесса. По завершении передачи данных устройством система ввода-вывода должна как-то вернуться в контекст потока, инициировавшего эту операцию ввода-вывода, чтобы он мог скопировать ее результаты в буфер в адресном пространстве своего процесса. Система ввода-вывода использует для выполнения подобных действий специальные APC режима ядра (применение APC в системе ввода-вывода подробно рассматривается в главе 9). Некоторые Windows-функции вроде Появление APC может переупорядочить очереди ожидания — списки, определяющие, какие потоки ждут, в каком порядке и на каких объектах (см. раздел по синхронизации далее в этой главе). Если в момент появления APC поток находится в состоянии ожидания, то после обработки АРС-процедуры поток возвращается в состояние ожидания, но перемещается в конец списка потоков, ждущих те же объекты. B отличие от прерываний, которые могут возникать в любой момент, исключения являются прямым следствием действий выполняемой программы. Windows вводит понятие B системах типа x86 все исключения имеют предопределенные номера прерываний, прямо соответствующие записям в IDT, ссылающимся на обработчики ловушек конкретных исключений. B таблице 3–2 перечислены исключения, определенные для систем типа x86, с указанием номеров прерываний. Как уже говорилось, первая часть IDT используется для исключений, а аппаратные прерывания располагаются за ней. Все исключения, кроме достаточно простых, которые могут быть разрешены обработчиком ловушек, обслуживаются модулем ядра — Ядро перехватывает и обрабатывает некоторые из этих исключений прозрачно для пользовательских программ. Так, если при выполнении отлаживаемой программы встретилась точка прерывания, генерируется исключение, обрабатываемое ядром за счет вызова отладчика. Ряд исключений ядро обрабатывает, просто возвращая код неудачной операции. Определенные исключения могут передаваться в неизменном виде пользовательским процессам. Например, при ошибке доступа к памяти или при переполнении в ходе арифметической операции генерируется исключение, не обрабатываемое операционной системой. Для обработки этих исключений подсистема окружения может устанавливать Когда происходит исключение (аппаратное или программное), цепочка событий начинается в ядре. Процессор передает управление обработчику ловушки в ядре, который создает фрейм ловушки по аналогии с тем, как это происходит при прерывании. Фрейм ловушки позволяет системе после обработки исключения возобновить работу с той точки, где она была прервана. Обработчик ловушки также создает запись исключения, содержащую сведения о ее причине и другую сопутствующую информацию. Если исключение возникает в режиме ядра, то для его обработки диспетчер исключений просто вызывает процедуру поиска подходящего обработчика SEH-фрейма. Поскольку необработанные исключения режима ядра были бы фатальными ошибками операционной системы, диспетчер всегда находит какой-нибудь обработчик. Если исключение возникает в пользовательском режиме, диспетчер исключений предпринимает более сложные действия. Как поясняется в главе 6, подсистема Windows предусматривает порт отладчика (debugger port) и порт исключений (exception port) для приема уведомлений об исключениях пользовательского режима в Windows-процессах. Они применяются ядром при обработке исключений по умолчанию, как показано на рис. 3–8. Точки прерывания в отлаживаемой программе являются распространенной причиной исключений. Поэтому диспетчер исключений первым делом проверяет, подключен ли к процессу, вызвавшему исключение, отладчик. Если подключен и системой является Windows 2000, диспетчер исключений посылает отладчику через LPC первое предупреждение. (Это уведомление на самом деле сначала поступает диспетчеру сеансов, а тот пересылает его соответствующему отладчику.) B Windows XP и Windows Server 2003 диспетчер исключений посылает сообщение объекта отладчика (debugger object message) Если к процессу не подключен отладчик или если отладчик не в состоянии обработать данное исключение, диспетчер исключений переключается в пользовательский режим, копирует фрейм ловушки в пользовательский стек, имеющий формат структуры данных CONTEXT (документирована в Platform SDK), и вызывает процедуру поиска обработчика SEH-фрейма. Если поиск не дал результатов, диспетчер возвращается в режим ядра и снова вызывает отладчик, чтобы пользователь мог продолжить отладку. При этом посылается второе (и последнее) предупреждение. Если отладчик не запущен и обработчики SEH-фреймов не найдены, ядро посылает сообщение в порт исключений, сопоставленный с процессом потока. Этот порт (если таковой есть) регистрируется подсистемой окружения, контролирующей данный поток. Порт исключений дает возможность подсистеме окружения (прослушивающей этот порт) транслировать исходное исключение в уведомление или исключение, специфичное для ее окружения. CSRSS (Client/Server Run-Time Subsystem) просто выводит окно сообщения, уведомляющее пользователя о сбое, и завершает процесс. Когда подсистема POSIX получает от ядра сообщение о том, что один из потоков вызвал исключение, эта подсистема посылает вызвавшему исключение потоку сигнал в стиле POSIX. Ho, если ядро уже дошло до этого этапа в обработке исключения, а подсистема не способна обработать данное исключение, выполняется обработчик ядра по умолчанию, просто завершающий процесс, поток которого вызвал исключение. Ha вершине стека любого Windows-потока объявляется обработчик, имеющий дело с необработанными исключениями. За объявление отвечает внутренняя Windows-функция ЭКСПЕРИМЕНТ: определение истинного стартового адреса Windows-потоков Тот факт, что выполнение каждого Windows-потока начинается с системной (а не пользовательской) функции, объясняет, почему у каждого Windows-процесса стартовый адрес нулевого потока одинаков (как и стартовые адреса вторичных потоков). Стартовый адрес нулевого потока Windows-процессов соответствует Windows-функции Стартовый адрес нулевого потока, сообщаемый Pstat, соответствует внутренней Windows-функции Поскольку большинство потоков в Windows-процессах начинается в одной из системных функций-оболочек, Process Explorer, показывая стартовые адреса потоков в процессе, пропускает фрейм начального вызова, представляющий функцию-оболочку, и вместо этого отображает второй фрейм в стеке. Например, обратите внимание на стартовый адрес потока в процессе, выполняющем Notepad.exe. Process Explorer не выводит всю иерархию вызовов при отображении стека вызовов. Вот что вы получите, щелкнув кнопку Stack. B строке 12 на этой иллюстрации показан первый фрейм в стеке — начало процесса-оболочки. Второй фрейм (строка 11) является основной точкой входа в Notepad.exe. Базовый код внутренних стартовых функций выглядит так: Заметьте: если при выполнении потока возникает исключение, не обрабатываемое этим потоком, вызывается Windows-фильтр необработанных исключений. Эта функция реализует поведение системы, когда та обнаруживает необработанное исключение. Поведение зависит от содержимого раздела реестра HKLM\Software\Microsoft\Windows NT\CurrentVersion\AeDebug. B нем есть два важных параметра: Auto и Debugger. Auto сообщает фильтру необработанных исключений, надо ли автоматически запускать отладчик или спросить у пользователя, что делать. По умолчанию этому параметру присваивается 1, что подразумевает автоматический запуск отладчика. Однако после установки средств разработки вроде Visual Studio его значение меняется на 0. Параметр Debugger является строкой, которая указывает путь к исполняемому файлу отладчика, который следует запускать при появлении необработанного исключения. Отладчик по умолчанию — \Windows\System32\Drwtsn32.exe (Dr. Wat-son), который на самом деле является не отладчиком, а утилитой, сохраняющей сведения о рухнувшем приложении в фарше журнала (Drwtsn32.log) и обрабатывающей файл аварийного дампа (User.dmp). Оба этих файла по умолчанию помещаются в папку \Documents And Settings\All Users\ Documents\DrWatson. Для просмотра или изменения конфигурации утилиту Dr. Watson можно запустить в интерактивном режиме; при этом выводится окно с текущими параметрами (пример для Windows 2000 показан на рис. 3–9). Файл журнала содержит такую базовую информацию, как код исключения, имя рухнувшего образа, список загруженных DLL, а также содержимое стека и последовательность команд потока, вызвавшая исключение. Более подробные сведения о содержимом этого файла можно получить, запустив Dr. Watson и щелкнув кнопку HeIp (Справка) в его окне. B файл аварийного дампа записывается содержимое закрытых страниц процесса на момент возникновения исключения (но страницы кода из EXE-и DLL-модулей не включаются). Этот файл можно открыть с помощью Win-Dbg — Windows-отладчика, поставляемого с пакетом Debugging Tools или с Visual Studio 2003 и выше). Файл аварийного дампа перезаписывается при каждом крахе процесса. Поэтому, если его предварительно не скопировать или не переименовать, в нем будет содержаться информация лишь о последнем крахе процесса. B Windows 2000 Professional визуальное уведомление включено по умолчанию. Окно сообщения, представленное на рис. 3-10, выводится Dr. Watson после того, как он сгенерирует аварийный дамп и запишет информацию в свой файл журнала. Процесс Dr. Watson остается до тех пор, пока не будет закрыто это окно, и именно поэтому в Windows 2000 Server визуальное уведомление по умолчанию отключено. Дело вот в чем. Обычно сервер находится в отдельной комнате и возле него никто не сидит. Если на сервере рушится какое-то приложение, то подобное окно просто некому закрыть. По этой причине серверные приложения должны регистрировать ошибки в журнале событий Windows. B Windows 2000, если параметр Auto установлен в 0, отображается окно, приведенное на рис. 3-11. После щелчка кнопки OK процесс завершается. A если вы нажимаете кнопку Cancel, запускается системный отладчик (заданный параметром Debugger в реестре). ЭКСПЕРИМЕНТ: необработанные исключения Чтобы увидеть образец файла журнала Dr. Watson, запустите программу Accvio.exe. Эта программа вызовет нарушение доступа (ошибку защиты памяти) при попытке записи по нулевому адресу, всегда недей-cтвитeльнoмy дляWindows-пpoцeccoв (см. таблицу 7–6 в главе 7). 1. Запустите Registry Editor (Редактор реестра) и найдите раздел HKLM\ SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug. 2. Если значение параметра Debugger равно «drwtsn32 — p %ld — e %ld — g», ваша система настроена на использование Dr. Watson в качестве отладчика по умолчанию. Переходите в п. 4. 3. Если в параметре Debugger не указан Drwtsn32.exe, вы все равно можете протестировать Dr. Watson, временно установив его, а затем восстановив исходные параметры своего отладчика: • сохраните где-нибудь текущее значение параметра (например, в файле Notepad или в буфере обмена); • выберите из меню Start (Пуск) команду Run (Выполнить) и введите команду drwtsn32 — i (чтобы инициализировать параметр Debugger для запуска Dr. Watson). 4. Запустите тестовую программу Accvio.exe. 5. Вы должны увидеть одно из окон, описанных ранее (в зависимости от версии Windows, в которой вы работаете). 6. Если вы используете параметры Dr. Watson по умолчанию, то теперь сможете изучить файл журнала и файл дампа в каталоге файлов дампов. Для просмотра параметров Dr. Watson, запустите 7. B качестве альтернативы щелкните последнюю запись в списке Application Errors (Ошибки приложения) и нажмите кнопку View (Показать) — будет выведена часть файла журнала Dr. Watson, содержащая сведения о нарушении доступа, вызванном Accvio.exe. Если вас интересуют детали формата файла журнала, щелкните кнопку HeIp (Справка) и выберите раздел Dr. Watson Log File Overview (Обзор файла журнала доктора Ватсона). 8. Если Dr. Watson не был отладчиком по умолчанию, восстановите исходное значение, сохраненное в п. 1. Проведите еще один эксперимент: попробуйте перенастроить параметр Debugger на другую программу, например Notepad.exe (Блокнот) или Sol.exe (Solitaire). Снова запустите Accvio.exe. Обратите внимание, что запускается любая программа, указанная в параметре Debugger. To есть система не проверяет, действительно ли указанная в этом параметре программа является отладчиком. Обязательно восстановите исходные значения параметров реестра (введите drwtsn32 — i). B Windows XP и Windows Server 2003 появился новый, более изощренный механизм отчетов об ошибках, называемый Windows Error Reporting. Он автоматизирует передачу информации о крахе как в пользовательском режиме, так и в режиме ядра. (Как применить этот механизм для получения сведений о крахе системы, см. главу 14.) Windows Error Reporting можно настроить, последовательно выбрав My Computer (Мой компьютер), Properties (Свойства), Advanced (Дополнительно) и Error Reporting (Отчет об ошибках) (на экране появится диалоговое окно, показанное на рис. 3-12); то же самое можно сделать через параметры локальной или доменной политики группы, которые хранятся в разделе реестра HKLM\Software\Microsoft\PCHealth\ErrorReporting. Перехватив необработанное исключение (об этом шла речь в предыдущем разделе), фильтр необработанных исключений выполняет начальную проверку, чтобы решить, надо ли запустить механизм Windows Error Reporting. Если параметр реестра HKLM\SOFTWARE\Microsoft\Windows NT\Current Version\AeDebug\Auto установлен в 0 или строка Debugger содержит текст «Drwtsn32», фильтр необработанных исключений загружает в аварийный процесс библиотеку \Windows\System32\Faultrep.dll и вызывает ее функцию При щелчке кнопки Send Error Report (Послать отчет), отчет об ошибках (минидамп и текстовый файл с детальными сведениями о номерах версий DLL, загруженных в рухнувший процесс) передается на онлайновый сервер анализа аварийных ситуаций, Watson.Microsoft.com. (B отличие от краха системы в режиме ядра здесь нет возможности найти какое-либо решение на момент отправки отчета.) Затем фильтр необработанных исключений создает процесс для запуска отладчика (обычно Drwtsn32.exe), который по умолчанию создает свой файл дампа и запись в журнале. B отличие от Windows 2000 этот файл содержит не полный дамп, а минидамп. Поэтому в ситуации, где для отладки рухнувшего приложения нужен полный дамп памяти процесса, вы можете изменить конфигурацию Dr. Watson, запустив его без аргументов командной строки, как было описано в предыдущем разделе. B средах, где системы не подключены к Интернету или где администратор хочет контролировать, какие именно отчеты об ошибках посылаются в Microsoft, эти отчеты можно передавать на внутренний файл-сервер. Microsoft предоставляет опытным заказчикам утилиту Corporate Error Reporting, которая понимает структуру каталогов, создаваемую Windows Error Reporting, и позволяет администратору задавать условия, при которых отчеты формируются и передаются в Microsoft. (Подробности см. по ссылке Как показано на рис. 3–1, обработчики ловушек ядра обслуживают прерывания, исключения и вызовы системных сервисов. Из предыдущих разделов вы знаете, как проводится обработка прерываний и исключений. Здесь будет рассказано о вызовах системных сервисов. Диспетчеризация системных сервисов начинается с выполнения инструкции, закрепленной за такой диспетчеризацией. Эта инструкция зависит от процессора, на котором работает Windows. Ha процессорах x86 до Pentium II использовалась инструкция Ha х86-процессорах Pentium II и выше Windows использует инструкцию Ha 32-разрядных процессорах AMD Кб и выше Windows применяет специальную инструкцию При загрузке Windows распознает тип процессора, на котором она работает, и выбирает подходящий системный код. Этот код для Номер системного сервиса — 0xb7 (183 в десятичной форме), инструкция вызова выполняет код диспетчеризации системного сервиса, установленный ядром, который в данном примере находится по адресу 0x7ffe0300. Поскольку пример взят для Pentium M, используется B архитектуре x64 операционная система Windows использует инструкцию B архитектуре IA64 для тех же целей применяется инструкция Как показано на рис. 3-14, ядро использует номер системного сервиса для поиска информации о нем в Диспетчер системных сервисов, Как будет показано в главе 6, у каждого потока есть указатель на таблицу системных сервисов. Windows располагает двумя встроенными таблицами системных сервисов, но поддерживает до четырех. Диспетчер системных сервисов определяет, в какой таблице содержится запрошенный сервис, интерпретируя 2-битное поле 32-битного номера системного сервиса как указатель на таблицу. Младшие 12 битов номера системного сервиса служат индексом внутри указанной таблицы. Эти поля показаны на рис. 3-15. Главная таблица по умолчанию, Инструкции для диспетчеризации сервисов исполнительной системы Windows содержатся в системной библиотеке Ntdll.dll. DLL-модули подсистем окружения вызывают функции из Ntdll.dll для реализации своих документированных функций. Исключением являются функции USER и GDI — здесь инструкции для диспетчеризации системных сервисов реализованы непосредственно в User32.dll и Gdi32.dll, а не в Ntdll.dll. Эти два случая иллюстрирует рис. 3-l6. Как показано на рис. 3-l6, Windows-функция ЭКСПЕРИМЕНТ: наблюдение за частотой вызова системных сервисов Вы можете наблюдать за частотой вызова системных сервисов с помощью счетчика System Calls/Sec (Системных вызовов/сек) объекта System (Система). Откройте оснастку Performance (Производительность) и щелкните кнопку Add (Добавить), чтобы добавить на график счетчик. Выберите объект System и счетчик System Calls/Sec, затем щелкните кнопки Add и Close (Закрыть). Как говорилось в главе 2, реализованная в Windows модель объектов позволяет получать согласованный и безопасный доступ к различным внутренним сервисам исполнительной системы. B этом разделе описывается ЭКСПЕРИМЕНТ: исследование диспетчера объектов B этом разделе будут предлагаться эксперименты, которые покажут вам, как просмотреть базу данных диспетчера объектов. B них будут использоваться перечисленные ниже инструменты, которые вам нужно освоить (если вы их еще не освоили). • Winobj можно скачать с сайта • Process Explorer и Handle. Отображают открытые описатели для процесса. • Oh.exe (имеется в ресурсах Windows) выводит открытые описатели для процесса, но требует предварительной установки специального глобального флага. • Команда • Команда Средство просмотра объектов позволяет изучить пространство имен, поддерживаемое диспетчером объектов (имена есть не у всех объектов). Попробуйте запустить нашу версию утилиты WinObj и проанализировать полученный результат (см. иллюстрацию ниже). Как уже отмечалось, утилита OH и команда При разработке диспетчера объекта был выдвинут ряд требований, в соответствии с которыми он должен: • реализовать общий, унифицированный механизм использования системных ресурсов; • изолировать защиту объектов в одном участке операционной системы для соответствия требованиям безопасности класса C2; • предоставлять механизм учета использования объектов процессами, позволяющий устанавливать лимиты на выделение процессам системных ресурсов; • поддерживать такую схему именования объектов, которая позволяла бы легко включать как существующие объекты (устройства, файлы и каталоги файловой системы), так и независимые наборы объектов; • соответствовать требованиям различных подсистем окружения операционной системы — например, поддерживать наследование ресурсов родительских процессов дочерними (необходимо в Windows и POSIX) и имена файлов, чувствительные к регистру букв (требуется в POSIX); • устанавливать единообразные правила сохранения объектов в памяти (т. е. объект должен быть доступен, пока используется какими-либо процессами). B Windows существует два вида внутренних объектов: Структура объектов ядра и способы их применения для синхронизации других объектов будут рассмотрены в этой главе несколько позже. A сейчас мы сосредоточимся на принципах работы диспетчера объектов, а также на структуре объектов исполнительной системы, описателях и таблицах описателей. Вопросы, связанные с использованием этих объектов для управления доступом в Windows, здесь затрагиваются лишь вскользь — подробнее на эту тему см. главу 8. Каждая подсистема окружения проецирует на свои приложения разные образы операционной системы. Объекты исполнительной системы и сервисы объектов — именно те примитивы, из которых подсистемы окружения конструируют собственные версии объектов и других ресурсов. Как правило, объекты исполнительной системы создаются подсистемой окружения в интересах пользовательских приложений или компонентов операционной системы в процессе обычной работы. Так, для создания файла Windows-приложение вызывает Windows-функцию Набор объектов, предоставляемый приложениям подсистемой окружения, может быть больше или меньше того набора, который предоставляется исполнительной системой. Подсистема Windows использует объекты исполнительной системы для экспорта собственных объектов, многие из которых прямо соответствуют объектам исполнительной системы. Например, Windows-объекты «мьютекс и «семафор» основаны непосредственно на объектах исполнительной системы (которые в свою очередь базируются на соответствующих объектах ядра). Кроме того, подсистема Windows предоставляет именованные каналы и почтовые ящики — ресурсы, созданные на основе объектов «файл» исполнительной системы. Некоторые подсистемы вроде POSIX вообще не поддерживают объекты как таковые. Подсистема POSIX использует объекты и сервисы исполнительной системы просто как основу для POSIX-процессов, каналов и других ресурсов, которые она предоставляет своим приложениям. B таблице 3–3 кратко описываются основные объекты, предоставляемые исполнительной системой. Подробнее об объектах исполнительной системы см. главы, в которых рассматриваются соответствующие компоненты исполнительной системы (а также справочную документацию Windows API, если речь идет об объектах исполнительной системы, напрямую экспортируемых в Windows). Как показано на рис. 3-18, у каждого объекта есть заголовок и тело. Диспетчер объектов управляет заголовками объектов, а телами объектов управляют владеющие ими компоненты исполнительной системы. Кроме того, каждый заголовок объекта указывает на список процессов, которые открыли этот объект, и на специальный объект, называемый Диспетчер объектов использует данные, хранящиеся в заголовке, для управления объектами независимо от их типа. Стандартные атрибуты заголовка кратко описываются в таблице 3–4. Кроме заголовка у каждого объекта имеется тело, чей формат и содержимое уникальны для данного типа объектов; все объекты одного типа имеют одинаковый формат тела. Создавая тип объектов и предоставляя для него сервисы, компонент исполнительной системы может контролировать манипуляции с данными в телах всех объектов этого типа. Диспетчер объектов предоставляет небольшой набор базовых сервисов, которые работают с атрибутами заголовка объекта и применимы к объектам любого типа (хотя некоторые базовые сервисы не имеют смысла для отдельных объектов). Эти сервисы, часть которых доступна Windows-приложениям через подсистему Windows, перечислены в таблице 3–5. Базовые сервисы поддерживаются для всех типов объектов, но у каждого объекта есть свои сервисы для создания, открытия и запроса. Так, подсистема ввода-вывода реализует сервис создания файлов для объектов «файл», а диспетчер процессов — сервис создания процессов для объектов «процесс». Конечно, можно было бы реализовать единый сервис создания объектов, но подобная процедура оказалась бы весьма сложной, так как набор параметров, необходимых для инициализации объекта «файл», значительно отличается, скажем, от параметров инициализации объекта «процесс». A при вызове потоком сервиса объекта для определения его типа и обращении к соответствующей версии сервиса диспетчер объектов каждый раз сталкивался бы с необходимостью обработки дополнительных данных. B силу этих и иных причин сервисы, обеспечивающие создание, открытие и запросы, для каждого типа объектов реализованы отдельно. B заголовках объектов содержатся общие для всех объектов атрибуты, но их значения могут меняться у конкретных экземпляров данного объекта. Так, у каждого объекта есть уникальное имя и может быть уникальный дескриптор защиты. Однако некоторые атрибуты объектов остаются неизменными для всех объектов данного типа. Например, при открытии описателя объекта можно выбрать права доступа из набора, специфичного для объектов данного типа. Исполнительная система предоставляет в том числе атрибуты доступа «завершение» (terminate) и «приостановка» (suspend) для объектов «поток», а также «чтение» (read), «запись» (write), «дозапись» (append) и «удаление» (delete) для объектов «файл». Другой пример атрибута, специфичного для типа объектов, — синхронизация, о которой мы кратко расскажем ниже. Чтобы сэкономить память, диспетчер объектов сохраняет статические атрибуты, специфичные для конкретного типа объектов, только при создании нового типа объектов. Для записи этих данных он использует собственный объект типа. Как показано на рис. 3-19, если установлен отладочный флаг отслеживания объектов (описываемый в разделе «Глобальные флаги Windows» далее в этой главе), все объекты одного типа (в данном случае — «процесс») связываются между собой с помощью объекта типа, что позволяет диспетчеру объектов при необходимости находить их и перечислять. ЭКСПЕРИМЕНТ: просмотр заголовков объектов и объектов типа Вы можете увидеть список объектов типа, объявленных диспетчеру объектов, с помощью утилиты Winobj. Далее в Winobj откройте каталог \ObjectTypes, как показано на следующей иллюстрации. Чтобы просмотреть структуру данных типа объектов «процесс» в отладчике ядра, сначала идентифицируйте этот объект командой Этот вывод показывает, что структура типа включает имя типа объекта, счетчики активных объектов этого типа, а также счетчики пикового числа описателей и объектов данного типа. B поле TypeInfo хранится указатель на структуру данных, в которой содержатся атрибуты, общие для всех объектов этого типа, а также указатели на методы типа: Объектами типов нельзя управлять из пользовательского режима из-за отсутствия соответствующих сервисов диспетчера объектов. Ho некоторые из определяемых ими атрибутов видимы через отдельные системные сервисы и функции Windows API. Атрибуты, хранящиеся в объектах типа, описываются в таблице 3–6. Синхронизация, один из атрибутов, видимых Windows-приложениям, относится к способности потока синхронизировать свое выполнение, ожидая изменения состояния определенного объекта. Поток можно синхронизировать по таким объектам исполнительной системы, как задание, процесс, поток, файл, событие, семафор, мьютекс и таймер. Другие объекты исполнительной системы не поддерживают синхронизацию. Способность объекта к синхронизации зависит от того, содержит ли он встроенный объект диспетчера — объект ядра, который будет рассмотрен далее в этой главе. Атрибут «методы», последний из перечисленных в таблице 3–6, состоит из набора внутренних процедур, похожих на конструкторы и деструкторы C++, т. е. на процедуры, автоматически вызываемые при создании или уничтожении объекта. B диспетчере объектов эта идея получила дальнейшее развитие: он вызывает методы объекта и в других ситуациях, например при открытии или закрытии описателя объекта или при попытке изменения параметров защиты объекта. B некоторых типах объектов методы определяются в зависимости от того, как должен использоваться данный тип объектов. При создании нового типа объектов компонент исполнительной системы может зарегистрировать у диспетчера объектов один или несколько методов. После этого диспетчер объектов вызывает методы на определенных этапах жизненного цикла объектов данного типа — обычно при их создании, удалении или модификации. Поддерживаемые диспетчером объектов методы перечислены в таблице 3–7. Диспетчер объектов вызывает метод open всякий раз, когда создает описатель объекта (что происходит при создании или открытии объекта). Однако метод open определен только в одном типе объектов — WindowStation. Этот метод необходим таким объектам для того, чтобы Win32k.sys мог использовать часть памяти совместно с процессом, который служит в качестве пула памяти, связанного с объектом «рабочий стол». Пример использования метода close можно найти в подсистеме ввода-вывода. Диспетчер ввода-вывода регистрирует метод close для объектов типа «файл», а диспетчер объектов вызывает метод close при закрытии каждого описателя объекта этого типа. Метод close проверяет, не осталось ли у процесса, закрывающего описатель файла, каких-либо блокировок для файла, и, если таковые есть, снимает их. Диспетчер объектов не может и не должен самостоятельно проверять наличие блокировок для файла. Перед удалением временного объекта из памяти диспетчер объектов вызывает метод delete, если он зарегистрирован. Например, диспетчер памяти регистрирует для объектов типа «раздел» метод delete, освобождающий физические страницы, используемые данным разделом. Перед удалением объекта «раздел» этот метод также проверяет различные внутренние структуры данных, выделенные для раздела диспетчером памяти. Диспетчер объектов не мог бы сделать эту работу, поскольку он ничего не знает о внутреннем устройстве диспетчера памяти. Методы delete других объектов выполняют аналогичные функции. Если диспетчер объектов находит существующий вне его пространства имен объект, метод parse (по аналогии с методом query name) позволяет этому диспетчеру передавать управление вторичному диспетчеру объектов. Когда диспетчер объектов анализирует имя объекта, он приостанавливает анализ, встретив объект с сопоставленным методом parse, и вызывает метод parse, передавая ему оставшуюся часть строки с именем объекта. Кроме пространства имен диспетчера объектов, в Windows есть еще два пространства имен: реестра (реализуемое диспетчером конфигурации) и файловой системы (реализуемое диспетчером ввода-вывода через драйверы файловой системы). O диспетчере конфигурации см. главу 5; о диспетчере ввода-вывода и драйверах файловой системы см. главу 9. Например, когда процесс открывает описатель объекта с именем \Device\ Floppy0\docs\resume.doc, диспетчер объектов просматривает свое дерево имен и ищет объект с именем FloppyO. Обнаружив, что с этим объектом сопоставлен метод parse, он вызывает его, передавая ему остаток строки с именем объекта (в данном случае — строку \docs\resume.doc). Метод parse объектов «устройство» (device objects) является процедурой ввода-вывода, которая регистрируется диспетчером ввода-вывода при определении типа объекта «устройство». Процедура parse диспетчера ввода-вывода принимает строку с именем и передает ее соответствующей файловой системе, которая ищет файл на диске и открывает его. Подсистема ввода-вывода также использует метод security, аналогичный методу parse. Он вызывается каждый раз, когда поток пытается запросить или изменить параметры защиты файла. Эта информация для файлов отличается от таковой для других объектов, поскольку хранится в самом файле, а не в памяти. Поэтому для поиска, считывания или изменения параметров защиты нужно обращаться к подсистеме ввода-вывода. Когда процесс создает или открывает объект по имени, он получает Чтобы потоки процесса пользовательского режима могли оперировать объектом, им нужен описатель этого объекта. Идея применения описателей для управления ресурсами сама по себе не нова. Например, стандартные библиотеки языков С, Pascal (и других) при открытии файла возвращают его описатель. Описатели служат косвенными указателями на системные ресурсы, что позволяет прикладным программам избегать прямого взаимодействия с системными структурами данных. Описатели дают и другие преимущества. Во-первых, описатели файлов, событий или процессов совершенно одинаковы — просто ссылаются на разные объекты. Это дает возможность создать унифицированный интерфейс для ссылок на объекты любого типа. Во-вторых, только диспетчер объектов имеет право физически создавать описатели и искать их объекты, а значит, он может проанализировать любое действие с объектом в пользовательском режиме и решить, позволяет ли профиль защиты вызывающей программы выполнить над объектом запрошенную операцию. ЭКСПЕРИМЕНТ: просмотр открытых описателей Запустите Process Explorer и убедитесь, что в его окне нижняя секция включена и настроена на отображение открытых описателей. (Выберите View, Lower Pane View и Handles.) Затем откройте окно командной строки и просмотрите таблицу описателей для нового процесса Cmd.exe. Вы должны увидеть открытый описатель файла — текущего каталога. Например, если текущий каталог — C: \, Process Explorer выводит следующее. Если вы теперь смените текущий каталог командой CD, то Process Explorer покажет, что описатель предыдущего каталога закрыт и открыт описатель нового текущего каталога. Предыдущий описатель ненадолго выделяется красным цветом, а новый — зеленым. Длительность подсветки настраивается щелчком кнопки Options и регулировкой параметра Difference Highlight Duration. Такая функциональность Process Explorer упрощает наблюдение за изменениями в таблице описателей. Например, если в процессе происходит утечка описателей, просмотр таблицы описателей в Process Explorer позволяет быстро увидеть, какой описатель (или описатели) открывается, но не закрывается. Эта информация помогает программисту обнаружить утечку описателей. Таблицу открытых описателей также можно вывести, используя командную строку утилиты Handle. Например, обратите внимание на следующий фрагмент вывода Handle, где показана таблица описателей для процесса Cmd.exe до и после смены каталога: Описатель объекта представляет собой индекс в При создании процесса в Windows 2000 диспетчер объектов формирует верхний уровень таблицы описателей, содержащий указатели на таблицы среднего уровня; средний уровень содержит первый массив указателей на таблицы вторичных описателей, а нижний — первую таблицу вторичных описателей. Ha рис. 3-20 показана структура таблицы описателей в Windows 2000. B этой операционной системе диспетчер объектов интерпретирует младшие 24 бита описателя объекта как три 8-битных поля, являющиеся индексами для каждого из трех уровней таблицы описателей. B Windows XP и Windows Server 2003 при создании процесса создается лишь таблица описателей нижнего уровня — остальные уровни формируются по мере необходимости. B Windows 2000 таблица вторичных описателей состоит из 255 элементов. B Windows XP и Windows Server 2003 такая же таблица включает столько элементов, сколько помещается на страницу памяти минус один элемент, который используется для аудита описателей (handle auditing). Например, на х86-системах размер страницы составляет 4096 байтов. Делим это значение на размер элемента (8 байтов), вычитаем 1 и получаем всего 511 элементов в таблице описателей нижнего уровня. Наконец, таблица описателей среднего уровня в Windows XP и Windows Server 2003 содержит полную страницу указателей на таблицы вторичных описателей, поэтому количество таблиц вторичных описателей зависит от размеров страницы и указателя на конкретной аппаратной платформе. ЭКСПЕРИМЕНТ: создание максимального количества описателей Тестовая программа Testlimit 1. Скачайте файл Testlimit.zip по только что упомянутой ссылке и раз-архивируйте его в какой-нибудь каталог. 2. Запустите Process Explorer, щелкните View, затем System Information. Обратите внимание на текущий и максимальный размеры пула подкачиваемой памяти. (Для вывода максимальных размеров пулов Process Explorer нужно настроить на доступ к символам для образа ядра, Ntoskrnl.exe.) Пусть эта информация отображается, когда вы запустите программу Testlimit. 3. Откройте окно командной строки. 4. Запустите программу Testlimit с ключом 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-функцию Системным компонентам и драйверам устройств зачастую нужно открывать описатели объектов, доступа к которым у приложений пользовательского режима нет. Это достигается созданием описателей в ЭКСПЕРИМЕНТ: просмотр таблицы описателей с помощью отладчика ядра Команда Индекс описателя определяет элемент в таблице описателей (0 — вывод всех описателей). Индекс первого описателя равен 4, второго — 8 и т. д. Например, введя Вы можете указывать флаги, являющиеся битовыми масками, где бит 0 означает, что нужно вывести лишь информацию из элемента таблицы, бит 1 — показать не только используемые, но и свободные описатели, а бит 2 — сообщить информацию об объекте, на который ссылается описатель. Следующая команда выводит полную информацию о таблице описателей в процессе с идентификатором 0x408. Открывая файл, нужно указать, для чего это делается — для чтения или записи. Если вы попытаетесь записать что-нибудь в файл, открытый для чтения, то получите ошибку. Аналогичным образом действует и исполнительная система: когда процесс создает объект или открывает описатель существующего объекта, он должен указывать набор Когда процесс открывает описатель объекта, диспетчер объектов вызывает так называемый * Ha самом деле этот компонент представляет собой нечто вроде монитора запросов к подсистеме защиты. — После этого всякий раз, когда потоки процесса используют описатель, диспетчер объектов может быстро проверить, соответствует ли набор предоставленных прав доступа, хранящихся в описателе, действиям, которые намеревается выполнить вызванный потоками сервис объекта. Так, если вызывающая программа запросила доступ для чтения к объекту «раздел», а затем вызывает сервис для записи в этот объект, последний сервис не выполняется. Объекты бывают двух типов: временные (temporary) и постоянные (permanent). Большинство объектов временные, т. е. они хранятся, пока используются, и освобождаются, как только необходимость в них отпадает. Постоянные объекты существуют до тех пор, пока они не освобождаются явным образом. Поскольку большинство объектов временные, остальная часть этого раздела будет посвящена тому, как диспетчер объектов реализует Вторая фаза заключается в том, что прекращается хранение тех объектов, которые больше не используются (т. е. они удаляются). Так как код операционной системы обычно обращается к объектам по указателям, а не описателям, диспетчер объектов должен регистрировать и число указателей объектов, переданных процессам операционной системы. При каждой выдаче указателя на объект он увеличивает значение Ha рис. 3-22 показаны два задействованных объекта-события. Процесс A открыл первый объект, а процесс B — оба объекта. Кроме того, на первый объект ссылается какая-то структура режима ядра, и его счетчик ссылок paвен 3. Так что, даже если оба процесса закроют свои описатели первого объекта, он по-прежнему будет существовать, поскольку его счетчик ссылок еще не обнулится. Ho, когда процесс B закроет свой описатель второго объекта, этот объект будет удален. Таким образом, даже если счетчик открытых описателей объекта обну-лится, счетчик ссылок может превышать нулевое значение, указывая, что операционная система еще использует объект. B конце концов счетчик ссылок тоже обнулится, и тогда диспетчер удалит соответствующий объект из памяти. Такой механизм позволяет хранить объект и его имя в памяти, просто не закрывая его описатель. Программистам, создающим приложения с двумя и более взаимодействующими процессами, не приходится беспокоиться о том, что один из процессов удалит объект в то время, когда он еще используется другим процессом. Кроме того, закрытие описателей объекта, принадлежащих приложению, еще не означает, что этот объект будет немедленно удален, — он может использоваться операционной системой. Например, какой-то процесс создает второй процесс для выполнения программы в фоновом режиме, после чего немедленно закрывает описатель созданного процесса. Так как второй процесс еще не закончил свою работу и выполняется операционной системой, она поддерживает ссылку на этот объект «процесс». Диспетчер сможет обнулить счетчик ссылок на второй процесс и удалить его, только когда завершится выполнение этого процесса. Учет ресурсов, как и хранение объектов, тесно связан с использованием описателей объектов. Положительное значение счетчика открытых описателей указывает на то, что данный ресурс задействован какими-то процессами. Когда счетчик описателей и счетчик ссылок на некий объект обнуляются, процессы, использовавшие этот объект, больше не занимают память, отведенную под него. Bo многих операционных системах для ограничения доступа процессов к системным ресурсам применяется система квот. Однако типы устанавливаемых для процессов квот иногда весьма разнообразны, а отслеживающий квоты код распределен по всей операционной системе. Так, в некоторых операционных системах компонент ввода-вывода может регистрировать и ограничивать число файлов, которые может открыть процесс, а компонент управления памятью может накладывать ограничения на объем памяти, выделяемой потокам процесса. Компонент, отвечающий за управление процессами, способен ограничивать максимальное число новых процессов или новых потоков процесса. Каждое из этих ограничений отслеживается и реализуется в различных частях операционной системы. Диспетчер объектов Windows, напротив, представляет собой компонент централизованного учета ресурсов. B заголовке каждого объекта содержится атрибут квоты, определяющий, насколько диспетчер объектов уменьшает квоту подкачиваемой или неподкачиваемой памяти процесса при открытии его потоком описателя этого объекта. У каждого процесса в Windows имеется структура квот, регистрирующая лимиты и текущее количество используемой памяти из подкачиваемого и неподкачиваемого пулов, а также из страничного файла. (Введите dt nt!_ EPROCESS_QUOTA_ENTRY в отладчике ядра, чтобы увидеть формат этой структуры.) Значения данных квот по умолчанию равны 0 (ограничений нет), но их можно указать, модифицировав параметры в реестре (см. параметры NonPagedPoolQuota, PagedPoolQuota и PagingFileQuota в разделе HKLM\System\CurrentControlSet\Session Manager\Memory Management). Заметьте, что все процессы в интерактивном сеансе используют один и тот же блок квот (документированного способа создания процессов с собственными блоками квот нет). Важное условие для создания множества объектов — эффективная система учета. Для учета диспетчеру объектов нужна следующая информация: • способ, которым можно было бы отличать один объект от другого; • метод поиска и получения конкретного объекта. Первое требование реализуется за счет присвоения имен объектам. Это расширение обычной для большинства операционных систем функциональности, в которых отдельным системным ресурсам, например файлам, каналам или блокам разделяемой памяти, можно присваивать имена. Исполнительная система, напротив, позволяет именовать любой объект, представляющий ресурс. Второе требование (поиск и получение объектов) также реализуется через именование объектов. Если диспетчер хранит объекты в соответствии с их именами, он может быстро найти объект по его имени. Имена объектов отвечают и третьему требованию, не упомянутому в предыдущем списке: процессам должна быть предоставлена возможность совместного использования объектов. Пространство имен объектов исполнительной системы является глобальным, видимым любому процессу в системе. Если один процесс создает объект и помещает его имя в глобальное пространство имен, то другой процесс может открыть описатель этого объекта, указав нужное имя. Если объект не предназначен для совместного использования, процесс-создатель просто не присваивает ему имя. Для большей эффективности диспетчер объектов не ищет имя объекта при каждой попытке его использования. Поиск по имени ведется только в двух случаях. Во-первых, при создании процессом именованного объекта: перед тем как сохранить имя объекта в глобальном пространстве имен, диспетчер проверяет, нет ли в нем такого же имени. Во-вторых, открывая описатель именованного объекта, диспетчер ищет объект по имени и возвращает его описатель, который затем используется для ссылки на объект. Диспетчер позволяет выбирать, надо ли при поиске учитывать регистр букв. Эта функциональность поддерживается POSIX и другими подсистемами окружения, в которых имена файлов чувствительны к регистру букв. Где именно хранятся имена объектов, зависит от типа объектов. B таблице 3–8 перечислены стандартные каталоги объектов, имеющиеся на всех системах под управлением Windows. Пользовательским программам видны только каталоги \BaseNamedObjects и \GLOBAL?? (\?? в Windows 2000). Поскольку имена базовых объектов ядра вроде мьютексов, событий, семафоров, ожидаемых таймеров и разделов хранятся в одном каталоге, они не должны совпадать, даже если относятся к объектам разных типов. Это ограничение подчеркивает, насколько осторожно надо выбирать имена, чтобы они не конфликтовали с другими (используйте, например, префиксы имен в виде названия вашей компании и программного продукта). Имена объектов глобальны в пределах компьютера (или всех процессоров на многопроцессорной системе) и невидимы через сеть. Однако метод parse диспетчера объектов позволяет получать доступ к именованным объектам, существующим на других компьютерах. Так, диспетчер ввода-вывода, предоставляющий сервисы объектов «файл», расширяет функции диспетчера объектов для работы с файлами на удаленных компьютерах. При запросе на открытие объекта «файл» на удаленном компьютере диспетчер объектов вызывает метод parse, что позволяет диспетчеру ввода-вывода перехватить запрос и направить его сетевому редиректору — драйверу, обращающемуся к файлам через сеть. Серверный код на удаленной Windows-системе вызывает диспетчер объектов и диспетчер ввода-вывода на этой системе для поиска нужного объекта «файл» и возврата данных через сеть. ЭКСПЕРИМЕНТ: просмотр именованных базовых объектов Список именованных базовых объектов можно просмотреть с помощью утилиты Winobj. Запустите Winobj.exe и щелкните каталог \BaseNamedObjects, как показано ниже. Именованные объекты отображаются справа. Тип объектов обозначается следующими значками: • «stop» — мьютексы; • в виде микросхем памяти — разделы (объекты «проекция файла»); • в виде восклицательного знака — события; • похожие на светофоры — семафоры; • в виде изогнутой стрелки — символьные ссылки. C помощью этих объектов диспетчер объектов поддерживает иерархическую структуру пространства имен. Этот объект аналогичен каталогу файловой системы и содержит имена других объектов, а также другие каталоги объектов. Он включает информацию, достаточную для трансляции имен объектов в указатели на сами объекты. Диспетчер использует указатели для создания описателей объектов, возвращаемых программам пользовательского режима. Каталоги для хранения объектов могут создаваться как кодом режима ядра (включая компоненты исполнительной системы и драйверы устройств), так и кодом пользовательского режима (в том числе подсистемами). Например, диспетчер ввода-вывода создает каталог объектов \Device с именами объектов, представляющих устройства ввода-вывода. B некоторых файловых системах (например, NTFS и отдельных UNIX-системах) с помощью символьной ссылки можно создать имя файла или каталога, которое при использовании будет транслироваться операционной системой в другое имя файла или каталога. Символьные ссылки — простой метод неявного разделения файлов или каталогов за счет создания перекрестных ссылок между различными каталогами в обычной иерархической структуре каталогов. Диспетчер объектов реализует объект «символьная ссылка», который выполняет аналогичную функцию в отношении имен объектов в пространстве имен. Символьная ссылка может находиться в любом месте строки с именем объекта. Когда вызывающая программа ссылается на имя объекта «символьная ссылка», диспетчер просматривает пространство имен в поисках такого объекта. Далее он анализирует содержимое символьной ссылки и находит строку, которую надо подставить вместо ссылки. После этого начинается поиск другого объекта, соответствующего полученному имени. Исполнительная система использует такие объекты при трансляции имен устройств в стиле MS-DOS во внутренние имена устройств Windows. Пользователь обращается к гибким и жестким дискам по именам A:, B:, С: и т. д. или к последовательным портам по именам COMl, COM2 и т. п. Подсистема Windows делает эти объекты «символьная ссылка» в защищенные глобальные данные, помещая их в каталог объектов \?? (в Windows 2000) или \GLOBAL?? (в Windows XP и Windows Server 2003). Windows NT изначально создавалась в расчете на регистрацию в системе одного интерактивного пользователя и выполнение лишь одного экземпляра любого из интерактивных приложений. Добавление Windows Terminal Services в Windows 2000 Server и поддержки быстрого переключения пользователей в Windows XP потребовало некоторых изменений в модели пространства имен диспетчера объектов для поддержки множества интерактивных пользователей одновременно. (Базовые сведения о службах терминала и сеансах см. в главе 1.) Пользователь, зарегистрированный в консольном сеансе, получает доступ к Win32k.sys создает в каталоге \Windows интерактивный oбъeктWindow-Station, \WinSta0. Среда Terminal Services может поддерживать несколько интерактивных пользователей, но для сохранения иллюзии доступа к предопределенному интерактивному объекту WindowStation в Windows каждому пользователю нужна собственная версия WinSta0. Наконец, в каталоге \BaseNamedObjects приложения и система создают разделяемые объекты, включая события, мьютексы и разделы. Если приложение, создающее именованный объект, запущено двумя пользователями, то в каждом сеансе нужна своя версия этого объекта, чтобы два экземпляра приложения не мешали друг другу, обращаясь к одному объекту. Диспетчер объектов реализует локальное пространство имен, создавая закрытые версии трех каталогов, которые находятся в каталоге, сопоставленном с сеансом пользователя (\Sessions\X, где Все функции диспетчера объектов, связанные с управлением пространством имен, знают о локальных экземплярах каталогов и участвуют в поддержании иллюзии того, что в удаленных сеансах используется то же пространство имен, что и в консольных. DLL-модули подсистемы Windows добавляют к именам, передаваемым Windows-приложениями, которые ссылаются на объекты в \DosDevices, префиксы \?? (например, C: \Windows превращается в \??\C: \Windows). Когда диспетчер объектов обнаруживает специальный префикс \?? предпринимаемые им действия зависят от версии Windows, но при этом он всегда полагается на поле • Если системой является Windows 2000 и Terminal Services не установлены, поле • Если системой является Windows 2000 и Terminal Services установлены, то, когда активным становится новый сеанс, система копирует все объекты из глобального каталога \?? в локальный для сеанса каталог \DosDevices, и поле • B Windows XP и Windows Server 2003 система не копирует глобальные объекты в локальные каталоги DosDevices. Диспетчер объектов, встретив ссылку на \?? находит локальный для процесса каталог \DosDevices, используя поле B определенных обстоятельствах приложениям, поддерживающим Terminal Services, нужен доступ к объектам в консольном сеансе, даже если сами приложения выполняются в удаленном сеансе. Это может понадобиться приложениям для синхронизации со своими экземплярами, выполняемыми в других удаленных или консольных сеансах. B таких случаях для доступа к глобальному пространству имен приложения могут использовать специальный префикс \Global, поддерживаемый диспетчером объектов. Так, объект \Global\ApplicationInitialized, открываемый приложением в сеансе номер 2, направляется вместо каталога \Sessions\2\BaseNamedObjects\Application-Initialized в каталог \BasedNamedObjects\ApplicationInitialized. B Windows XP и Windows Server 2003 приложение, которому нужно обратиться к объекту в глобальном каталоге \DosDevices, не требуется использовать префикс \Global, если только этого объекта нет в локальном каталоге \DosDevices. Это вызвано тем, что диспетчер объектов автоматически ищет объект в глобальном каталоге, не найдя его в локальном. Однако приложение, работающее в Windows 2000 с Terminal Services, должно всегда указывать префикс \Global для доступа к объектам в глобальном каталоге \Dos-Devices. ЭКСПЕРИМЕНТ: просмотр экземпляров пространства имен Вы можете увидеть, как диспетчер объектов создает экземпляры пространства имен, создав сеанс, отличный от консольного, и просмотрев таблицу описателей для процесса в этом сеансе. B Windows XP Home Edition или Windows XP Professional в системе, которая не входит в домен, отключите консольный сеанс [откройте меню Start (Пуск), щелкните Log Off (Выход из системы) и выберите Disconnect and Switch User (Смена пользователя) или нажмите комбинацию клавиш Win-dows+L]. Теперь войдите в систему под новой учетной записью. Если вы работаете с Windows 2000 Server, Advanced Server или Datacenter Server, запустите клиент Terminal Services, подключитесь к серверу и войдите в систему. Войдя в систему в новом сеансе, запустите Winobj, щелкните каталог \Sessions и вы увидите подкаталог с числовым именем для каждого активного удаленного сеанса. Открыв один из таких каталогов, вы обнаружите подкаталоги \DosDevices, \Windows и \Base-NamedObjects, которые относятся к локальному пространству имен сеанса. Одно из таких локальных пространств имен показано на иллюстрации ниже. Далее запустите Process Explorer и выберите какой-нибудь процесс в новом сеансе (вроде Explorer.exe). Просмотрите таблицу описателей, щелкнув View, Lower Pane View и Handles. Вы должны увидеть описатель \Windows\Windowstations\WinStaO под \Sessions\n, где Концепция Поскольку второй поток получил значение указателя на конец очереди до того, как первый поток завершил его обновление, второй вставил свои данные в то же место, что и первый. Таким образом, данные первого потока были перезаписаны другими данными, а один участок очереди остался пустым. Хотя рис. 3-23 иллюстрирует, что могло бы случиться в многопроцессорной системе, аналогичную ошибку было бы нельзя исключить и в однопроцессорной системе — при переключении контекста на второй поток до того, как первый поток успел бы обновить указатель на конец очереди. Разделы кода, обращающиеся к неразделяемым ресурсам, называются Взаимоисключение, важное для всех операционных систем, особенно значимо (и запутанно) в случае операционной системы с Так как планировщик синхронизирует доступ к своим структурам данных при IRQL уровня «DPC/dispatch», ядро и исполнительная система не могут полагаться на механизмы синхронизации, которые могли бы привести к ошибке страницы или к перераспределению процессорного времени при IRQL уровня «DPC/dispatch» или выше (эти уровни также известны под названием «высокий IRQL»). Из следующих разделов вы узнаете, как ядро и исполнительная система используют взаимоисключение для защиты своих глобальных структур данных при высоком IRQL и какие механизмы синхронизации и взаимоисключения они применяют при низких уровнях IRQL (ниже «DPC/dispatch»). Ядро должно гарантировать, что в каждый момент только один процессор выполняет код в критической секции. Критическими секциями ядра являются разделы кода, модифицирующие глобальные структуры данных, например базу данных диспетчера ядра или его очередь DPC Операционная система не смогла бы корректно работать, если бы ядро не гарантировало взаимоисключающий доступ потоков к этим структурам данных. B этом плане больше всего проблем с прерываниями. Так, в момент обновления ядром глобальной структуры данных может возникнуть прерывание, процедура обработки которого изменяет ту же структуру. B простых однопроцессорных системах развитие событий по такому сценарию исключается путем отключения всех прерываний на время доступа к глобальным данным, однако в ядре Windows реализовано более сложное решение. Перед использованием глобального ресурса ядро временно маскирует прерывания, обработчики которых используют тот же ресурс. Для этого ядро повышает IRQL процессора до самого высокого уровня, используемого любым потенциальным источником прерываний, который имеет доступ к глобальным данным. Например, прерывание на уровне «DPC/dispatch» приводит к запуску диспетчера ядра, использующего диспетчерскую базу данных. Следовательно, любая другая часть ядра, имеющая дело с этой базой данных, повышает IRQL до уровня «DPC/dispatch», маскируя прерывания того же уровня перед обращением к диспетчерской базе данных. Эта стратегия хорошо работает в однопроцессорных системах, но не годится для многопроцессорных конфигураций. Повышение IRQL на одном из процессоров не исключает прерываний на другом процессоре, а ядро должно гарантировать взаимоисключающий доступ на всех процессорах. Простейшая форма механизмов синхронизации опирается на аппаратную поддержку безопасных операций над целыми значениями и выполнения сравнений в многопроцессорной среде. Сюда относятся такие функции, как Механизм, применяемый ядром для взаимоисключения в многопроцессорных системах, называется Перед входом в любую из критических секций, показанных на рис. 3-24, ядро должно установить спин-блокировку, связанную с защищенной очередью DPC Если спин-блокировка пока занята, ядро продолжает попытки установить спин-блокировку до тех пор, пока не достигнет успеха. Термин получил такое название из-за поведения ядра (и соответственно процессора), которое «крутится» (spin) в цикле, повторяя попытки, пока не захватит блокировку. Спин-блокировки, как и защищаемые ими структуры данных, находятся в глобальной памяти. Код для их установки и снятия написан на языке ассемблера для максимального быстродействия. Bo многих архитектурах спин-блокировка реализуется аппаратно поддерживаемой командой test-and-set, которая проверяет значение переменной блокировки и устанавливает блокировку, выполняя всего одну атомарную команду. Это предотвращает захват блокировки вторым потоком в промежуток между проверкой переменной и установкой блокировки первым потоком. Всем спин-блокировкам режима ядра в Windows назначен IRQL, всегда соответствующий уровню «DPC/dispatch» или выше. Поэтому, когда поток пытается установить спин-блокировку, все действия на этом или более низком уровне IRQL на данном процессоре прекращаются. Поскольку диспетчеризация потоков осуществляется при уровне «DPC/dispatch», поток, удерживающий спин-блокировку, никогда не вытесняется, так какданный IRQL маскирует механизмы диспетчеризации. Такая маскировка не дает прервать выполнение критической секции кода под защитой спин-блокировки и обеспечивает быстрое ее снятие. Спин-блокировки используются в ядре с большой осторожностью и устанавливаются на минимально возможное время. Ядро предоставляет доступ к спин-блокировкам другим компонентам исполнительной системы через набор функций ядра, включающий Спин-блокировки ядра накладывают ограничения на использующий их код. Как уже отмечалось, их IRQL всегда равен «DPC/dispatch», поэтому установивший спин-блокировку код может привести к краху системы, если попытается заставить планировщик выполнить операцию диспетчеризации или вызовет ошибку страницы. B некоторых ситуациях вместо стандартной спин-блокировки применяется особый тип спин-блокировки — с очередью (queued spinlock). Спин-блокировка с очередью лучше масштабируется в многопроцессорных системах, чем стандартная. Как правило, Windows использует лишь стандартные спин-блокировки, когда конкуренция за спин-блокировку ожидается низкой. Спин-блокировка с очередью работает так: процессор, пытаясь установить такую спин-блокировку, которая в данный момент занята, ставит свой идентификатор в очередь, сопоставленную с этой спин-блокировкой. Освободив спин-блокировку, удерживавший ее процессор передает блокировку тому процессору, чей идентификатор стоит в очереди первым. Между тем процессор, ожидающий занятую спин-блокировку, проверяет статус не самой спин-блокировки, а флага того процессора, чей идентификатор располагается в очереди прямо перед идентификатором ждущего процессора. Тот факт, что спин-блокировка с очередью устанавливает флаги, а не глобальные блокировки, имеет два следствия. Во-первых, уменьшается интенсивный трафик, связанный с межпроцессорной синхронизацией. Во-вторых, вместо случайного выбора процессора из группы ожидающих спин-блокировку реализуется четкий порядок спин-блокировки по типу FIFO («первым вошел, первым вышел»). Такой порядок позволяет достичь более согласованной работы процессоров, использующих одну и ту же блокировку. Windows определяет ряд глобальных спин-блокировок с очередями, сохраняя указатели на них в массиве, который содержится в блоке PCR (processor control region) каждого процессора. Глобальную спин-блокировку можно получить вызовом ЭКСПЕРИМЕНТ: просмотр глобальных спин-блокировок с очередями Вы можете наблюдать за состоянием глобальных спин-блокировок с очередями, используя команду Помимо статических спин-блокировок с очередями, определяемых глобально, ядра Windows XP и Windows Server 2003 поддерживают динамически создаваемые спин-блокировки с очередями. Для их создания предназначены функции Ядро предоставляет ряд функций синхронизации, использующих спин-блокировки для более сложных операций, например для добавления и удаления элементов из одно- и двунаправленных связанных списков. K таким функциям, в частности, относятся ExfoterlockedPopEntryList и ExInterlockedPushEntryList (для однонаправленных связанных списков), ExInterlockedInsertHeadList и ExInter-lockedRemoveHeadList (ддя двунаправленных связанных списков). Все эти функции требуют передачи стандартной спин-блокировки в качестве параметра и интенсивно используются в ядре и драйверах устройств. Компоненты исполнительной системы вне ядра также нуждаются в синхронизации доступа к глобальным структурам данных в многопроцессорной среде. Например, у диспетчера памяти есть только одна база данных блоков страниц. Обращение к ней осуществляется как к глобальной структуре данных, и драйверам устройств необходима гарантия получения монопольного доступа к своим устройствам. Вызывая функции ядра, исполнительная система может создать спин-блокировку, установить ее и снять. Однако спин-блокировка лишь частично удовлетворяет потребности исполнительной системы в синхронизации. Поскольку спин-блокировка означает фактическую остановку процессора, она применяется только при двух условиях: • требуется непродолжительное обращение к защищенным ресурсам без сложного взаимодействия с другим кодом; • код критической секции нельзя выгрузить в страничный файл, он не ссылается на данные в подкачиваемой памяти, не вызывает внешние процедуры (включая системные сервисы) и не генерирует прерывания или исключения. Эти противоречащие друг другу ограничения нельзя соблюсти одновременно ни при каких обстоятельствах. Более того, кроме взаимоисключения, исполнительная система должна выполнять и другие алгоритмы синхронизации, а также предоставлять механизмы синхронизации пользовательскому режиму. Существует несколько дополнительных механизмов синхронизации, применяемых, когда спин-блокировки не годятся: • объекты диспетчера ядра (kernel dispatcher objects); • быстрые мьютексы (fast mutexes) и защищенные мьютексы (guarded mu-texes); • блокировки с заталкиванием указателя (push locks); • ресурсы исполнительной системы (executive resources). B таблице 3–9 кратко сравниваются возможности этих механизмов и их взаимосвязь с доставкой APC режима ядра. Ядро предоставляет исполнительной системе дополнительные механизмы синхронизации в форме объектов, в совокупности известных как объекты диспетчера ядра. Синхронизирующие объекты, видимые из пользовательского режима, берут свое начало именно от этих объектов диспетчера ядра. Каждый синхронизирующий объект, видимый из пользовательского режима, инкапсулирует минимум один объект диспетчера ядра. Семантика синхронизации исполнительной системы доступна программистам через Windows-функции Еще один тип синхронизирующих объектов исполнительной системы назван (без особой на то причины) B остальных подразделах мы детально обсудим, как реализуется ожидание на объектах диспетчера ядра. Поток синхронизируется с объектом диспетчера ядра, ожидая освобождения его описателя. При этом ядро приостанавливает поток и соответственно меняет состояние диспетчера, как показано на рис. 3-25. Ядро удаляет поток из очереди готовых к выполнению потоков и перестает учитывать его в планировании. B любой момент синхронизирующий объект находится в одном из двух состояний: Взаимосвязь синхронизации с диспетчеризацией потоков иллюстрирует следующий пример с использованием объекта «событие». • Поток пользовательского режима ждет на описателе объекта «событие» (т. е. ждет перехода этого объекта в свободное состояние). • Ядро изменяет состояние потока с «готов» на «ожидает» и добавляет его в список потоков, ждущих объект «событие». • Другой поток устанавливает объект «событие». • Ядро просматривает список потоков, ожидающих этот объект. Если условия ожидания какого-либо потока выполнены (см. примечание ниже), ядро переводит его из состояния «ожидает» в состояние «готов». Если это поток с динамическим приоритетом, ядро может повысить его приоритет для выполнения. • Поскольку новый поток теперь готов к выполнению, происходит перераспределение процессорного времени. Если при этом диспетчер обнаружит, что приоритет выполняемого потока ниже, чем приоритет потока, только что перешедшего в состояние «готов», он вытеснит поток с более низким приоритетом и выдаст программное прерывание для инициации переключения контекста на поток с более высоким приоритетом. • Если в данный момент вытеснение невозможно ни на одном из процессоров, диспетчер включает поток в свою очередь потоков, готовых к выполнению. Эти условия различны для разных объектов. Например, объект «поток» находится в занятом состоянии в течение всего срока своей жизни и переводится ядром в свободное состояние лишь при завершении. Аналогичным образом, ядро переводит объект «процесс» в свободное состояние в момент завершения последнего потока процесса. Ho такой объект, как таймер, переводится в свободное состояние по истечении заданного времени. Выбирая механизм синхронизации, вы должны учитывать в своей программе поведение синхронизирующих объектов. B таблице 3-10 показано, когда переходят в свободное состояние синхронизирующие объекты различных типов. Когда объект переводится в свободное состояние, ожидающие его потоки обычно немедленно выходят из ждущего состояния. Однако, как показано на рис. 3-26, некоторые объекты диспетчера ядра и системные события ведут себя иначе. Например, объект «событие уведомления» — в Windows API он называется событием со сбросом вручную (manual reset event) — используется для уведомления о каком-либо событии. Когда этот объект переводится в свободное состояние, все потоки, ожидающие его, освобождаются. Исключением является тот поток, который ждет сразу несколько объектов: он может продолжать ожидание, пока не освободятся дополнительные объекты. B отличие от события мьютекс предусматривает возможность владения. Этот объект используется для взаимоисключающего доступа к ресурсу, поэтому единовременно только один поток может владеть мьютексом. При освобождении мьютекса ядро переводит его в свободное состояние и выбирает для выполнения один из ожидающих потоков. Выбранный ядром поток захватывает мьютекс, а остальные потоки остаются в ожидании. Синхронизирующий объект, впервые появившийся в Windows XP и названный Windows-процессы часто используют функции критических секций — Если создать объект «событие» для критической секции не удалось из-за нехватки системной памяти, Мы не ставили себе задачу исчерпывающе описать все объекты исполнительной системы, а лишь хотели дать представление об их базовой функциональности и механизмах синхронизации. Об использовании этих объектов в Windows-программах см. справочную документацию Windows или четвертое издание книги Джеффри Рихтера «Windows для профессионалов». Учет ожидающих потоков и их объектов ожидания базируется на двух ключевых структурах данных: заголовках диспетчера (dispatcher headers) и блоках ожидания (wait blocks). Обе эти структуры определены в Ntddk.h, заголовочном файле DDK. Для удобства мы воспроизводим здесь эти определения. Заголовок диспетчера содержит тип объекта, информацию о состоянии (занят/свободен) и список потоков, ожидающих этот объект. У каждого ждущего потока есть список блоков ожидания, где перечислены ожидаемые потоком объекты, а у каждого объекта диспетчера ядра — список блоков ожидания, где перечислены ожидающие его потоки. Этот список ведется так, что при освобождении объекта диспетчера ядро может быстро определить, кто ожидает данный объект. B блоке ожидания имеются указатели на объект ожидания, ожидающий поток и на следующий блок ожидания (если поток ждет более одного объекта). Он также регистрирует тип ожидания («любой» или «все») и позицию соответствующего элемента в таблице описателей, переданную потоком в функцию Ha рис. 3-27 показана связь объектов диспетчера ядра с блоками ожидания потоков. B данном примере поток 1 ждет объект В, а поток 2 — объекты A и В. Если объект A освободится, поток 2 не сможет возобновить свое выполнение, так как ядро обнаружит, что он ждет и другой объект. C другой стороны, при освобождении объекта B ядро сразу же подготовит поток 1 к выполнению, поскольку он не ждет никакие другие объекты. ЭКСПЕРИМЕНТ: просмотр очередей ожидания Хотя многие утилиты просмотра процессов умеют определять, находится ли поток в состоянии ожидания (отмечая в этом случае и тип ожидания), список объектов, ожидаемых потоком, можно увидеть только с помощью команды Эти данные позволяют нам убедиться в отсутствии других потоков, ожидающих данный объект, поскольку указатели начала и конца списка ожидания указывают на одно и то же место (на один блок ожидания). Копия блока ожидания (по адресу 0x8a12a398) дает следующее: Если в списке ожидания более одного элемента, вы можете выполнить ту же команду со вторым указателем в поле Однако быстрые мьютексы годятся, только если можно отключить доставку обычных APC режима ядра. B исполнительной системе определены две функции для захвата быстрых мьютексов: Потоки, которым нужно захватить какой-либо ресурс для совместного доступа, ждут на семафоре, сопоставленном с этим ресурсом, а потоки, которым требуется захватить ресурс для монопольного доступа, — на событии. Семафор с неограниченным счетчиком применяется потому, что в первом случае можно пробудить все ждущие потоки и предоставить им доступ к ресурсу, как только этот семафор перейдет в свободное состояние (ресурс будет освобожден потоком, захватившим его в монопольное владение). Когда потоку нужен монопольный доступ к занятому на данный момент ресурсу, он ждет на синхронизирующем объекте «событие», так как при освобождении события пробуждается только один из ожидающих потоков. Для захвата ресурсов предназначен целый ряд функций: ЭКСПЕРИМЕНТ: перечисление захваченных ресурсов исполнительной системы Команда Заметьте, что счетчик конкурирующих потоков (contention count), извлекаемый из структуры ресурса, фиксирует, сколько раз потоки пытались захватить данный ресурс и были вынуждены переходить в состояние ожидания из-за того, что он уже занят. Для изучения деталей конкретного объекта ресурса (в частности, кто владеет ресурсом и кто ждет его освобождения) укажите ключ Существует два типа блокировок с заталкиванием указателя: обычный и с поддержкой кэша (cache aware). Первый тип занимает в памяти тот же объем, что и указатель (4 байта в 32-разрядных системах и 8 байтов в 64-разрядных). Когда поток захватывает обычную блокировку с заталкиванием указателя, код этой блокировки помечает ее как занятую, если она на данный момент свободна. Если блокировка захвачена для монопольного доступа или если потоку нужно захватить ее монопольно, а она уже захвачена для разделяемого доступа, ее код создает в стеке потока блок ожидания, инициализирует объект «событие» в этом блоке и добавляет последний в список ожидания, сопоставленный с блокировкой. Как только блокировка освобождается, ее код пробуждает ждущий поток (если таковой имеется), освобождая событие в блоке ожидания потока. Второй тип создает обычную блокировку с заталкиванием указателя для каждого процессора в системе и сопоставляет ее с блокировкой с заталкиванием указателя, поддерживающей кэш. Когда потоку нужно захватить такую блокировку, он просто захватывает обычную блокировку, созданную для текущего процессора в соответствующем режиме доступа. Подобные блокировки используются, в том числе, диспетчером объектов, когда возникает необходимость в защите глобальных структур данных и дескрипторов защиты объектов, а также диспетчером памяти для защиты структур данных AWE. Взаимоблокировка (deadlock) — это проблема синхронизации, возникающая, когда два потока или процессора удерживают ресурсы, нужные другому, и ни один из них не отдает их. Такая ситуация может приводить к зависанию системы или процесса. Утилита Driver Verifier, описываемая в главах 7 и 9, позволяет проверять возможность взаимоблокировки, в том числе на спин-блокировках, быстрых и обычных мьютексах. O том, как пользоваться Driver Verifier для анализа зависания системы, см. главу 14. При инициализации Windows создает несколько потоков в процессе System, которые называются Некоторые драйверы устройств и компоненты исполнительной системы создают собственные потоки для обработки данных на уровне «passive», но большинство вместо этого использует системные рабочие потоки, что помогает избежать слишком частого переключения потоков и чрезмерной нагрузки на память из-за диспетчеризации дополнительных потоков. Драйвер устройства или компонент исполнительной системы запрашивает сервисы системных рабочих потоков через функцию исполнительной системы Рабочий элемент включает указатель на процедуру и параметр, передаваемый потоком этой процедуре при обработке рабочего элемента. Процедура реализуется драйвером устройства или компонентом исполнительной системы, выполняемым на уровне «passive». Например, DPC-процедура, которая должна ждать на объекте диспетчера, может инициализировать рабочий элемент, который указывает на процедуру в драйвере, ждущем на объекте диспетчера, и, возможно, на указатель на объект. Ha каком-то этапе системный рабочий поток извлекает из своей очереди рабочий элемент и выполняет процедуру драйвера. После ее выполнения системный рабочий поток проверяет, нет ли еще рабочих элементов, подлежащих обработке. Если нет, системный рабочий поток блокируется, пока в очередь не будет помещен новый рабочий элемент. Выполнение DPC-процедуры может и не закончиться в ходе обработки ее рабочего элемента системным рабочим потоком. (B однопроцессорной системе выполнение этой процедуры всегда завершается до обработки ее рабочего элемента, так как на уровне IRQL «DPC/dispatch» потоки не планируются.) Существует три типа системных рабочих потоков: • • • Число отложенных и критичных системных рабочих потоков, создаваемых функцией исполнительной системы Исполнительная система старается балансировать число критичных системных рабочих потоков в соответствии с текущей рабочей нагрузкой. Каждую секунду функция исполнительной системы • Очередь критичных рабочих элементов не должна быть пустой. • Число неактивных критичных потоков (блокированных в ожидании рабочих элементов или на объектах диспетчера при выполнении рабочей процедуры) должно быть меньше количества процессоров в системе. • B системе должно быть менее 16 динамических рабочих потоков. Динамические потоки завершаются через 10 минут пребывания в неактивном состоянии. B зависимости от рабочей нагрузки исполнительная система может создавать до 16 таких потоков. Windows поддерживает набор флагов, который хранится в общесистемной глобальной переменной K счастью, в Platform SDK и средствах отладки есть утилита Gflags.exe, позволяющая просматривать и изменять системные глобальные флаги (либо в реестре, либо в работающей системе) и глобальные флаги образов исполняемых файлов. Gflags поддерживает как GUI-интерфейс, так и командную строку. Параметры командной строки можно узнать, введя Вы можете переключаться между реестром (System Registry) и текущим значением переменной в системной памяти (Kernel Mode). Для внесения изменений нужно щелкнуть кнопку Аррlу (кнопка OK просто закрывает программу). Хотя вы можете изменять флаги в работающей системе, большинство из них требует перезагрузки для того, чтобы изменения вступили в силу. Поскольку документации на этот счет нет, лучше перезагрузиться после любых изменений. Выбрав Image File Options, вы должны ввести имя исполняемого в системе файла. Этот переключатель позволяет изменять набор глобальных флагов отдельного образа (а не всей системы). Заметьте, что флаги на рис. 3-29 отличаются от флагов на рис. 3-28. Рис. 3-29. Настройка в Gflags глобальных флагов образа исполняемого файла ЭКСПЕРИМЕНТ: включение трассировки загрузчика образов и просмотр NtGlobalFlag Чтобы увидеть пример детальной трассировочной информации, которую можно получить при установке глобальных флагов, попробуйте запустить Gflags в системе с загруженным отладчиком ядра, которая подключена к компьютеру с запущенной утилитой Kd или Windbg. Далее попробуйте установить, например, глобальный флаг Show Loader Snaps. Для этого выберите Kernel Mode, установите флажок Show Loader Snaps и щелкните кнопку Apply. Теперь запустите на этой машине какую-нибудь программу, и отладчик ядра будет выдавать информацию, аналогичную показанной ниже. Для просмотра состояния переменной LPC LPC (local procedure call) — это механизм межпроцессной связи для высокоскоростной передачи сообщений. Он недоступен через Windows API напрямую и является внутренним механизмом, которым пользуются только компоненты операционной системы Windows. Вот несколько примеров того, где применяется LPC • Windows-приложения, использующие RPC (документированный API), неявно используют и LPC, когда указывают локальный RPC — разновидность RPC, применяемую для взаимодействия между процессами в рамках одной системы. • Некоторые функции Windows API обращаются к LPC, посылая сообщения процессу подсистемы Windows. • Winlogon взаимодействует с процессом LSASS через LPC • Монитор состояния защиты (компонент исполнительной системы, рассматриваемый в главе 8) также взаимодействует с процессом LSASS через LPC ЭКСПЕРИМЕНТ: просмотр объектов «порт LPC» Вы можете увидеть именованные объекты «порт LPC» (LPC port objects) с помощью утилиты Winobj. Запустите Winobj.exe и выберите корневой каталог. Интересующие нас объекты обозначаются значком в виде разъема, как показано ниже. Для просмотра объектов «порт LPC», используемых RPC, выберите каталог \RPC Control, как на следующей иллюстрации. Вы также можете наблюдать объекты «порт LPC» с помощью команды Как правило, LPC используются для взаимодействия между серверным процессом и одним или несколькими клиентскими процессами. LPC-соеди-нение может быть установлено между двумя процессами пользовательского режима или между компонентом режима ядра и процессом пользовательского режима. Например, как говорилось в главе 2, Windows-процессы иногда посылают сообщения подсистеме Windows через LPC Некоторые системные процессы вроде Winlogon и LSASS тоже используют LPC Примерами компонентов режима ядра, взаимодействующих с пользовательскими процессами через LPC, могут служить монитор состояния защиты и LSASS. LPC предусматривает три способа обмена сообщениями. • Сообщение длиной менее 256 байтов можно передать вызовом LPC с буфером, содержащим сообщение. Затем это сообщение копируется из адресного пространства процесса-отправителя в системное адресное пространство, а оттуда — в адресное пространство процесса-получателя. • Если клиент и сервер хотят обменяться данными, размер которых превышает 256 байтов, они могут использовать общий раздел, на который они оба спроецированы. Отправитель помещает данные в общий раздел и посылает получателю уведомление с указателем на область раздела, где находятся данные. • Если серверу нужно считать или записать данные, объем которых превышает размер общего раздела, то их можно напрямую считать из клиентского адресного пространства или записать туда. Для этого LPC предоставляет серверу две функции. Сообщение, посланное первой функцией, обеспечивает синхронизацию передачи последующих сообщений. LPC экспортирует единственный объект исполнительной системы объект «порт» (port object). Однако порты бывают нескольких видов. • Порт серверного соединения (server connection port) Именованный порт, служащий точкой запроса связи с сервером. Через него клиенты могут соединяться с сервером. • Коммуникационный порт сервера (server communication port) Безымянный порт, используемый сервером для связи с конкретным клиентом. У сервера имеется по одному такому порту на каждый активный клиент. • Коммуникационный порт клиента (client communication port) Безымянный порт, используемый конкретным клиентским потоком для связи с конкретным сервером. • Безымянный коммуникационный порт (unnamed communication port) Порт, создаваемый для связи между двумя потоками одного процесса. LPC обычно используется так. Сервер создает именованный порт соединения. Клиент посылает в него запрос на установление связи. Если запрос удовлетворен, создается два безымянных порта — коммуникационный порт клиента и коммуникационный порт сервера. Клиент получает описатель коммуникационного порта клиента, а сервер — описатель коммуникационного порта сервера. После этого клиент и сервер используют новые порты для обмена данными. Схема соединения между клиентом и сервером показана на рис. 3-30. Различные компоненты ядра Windows и несколько базовых драйверов устройств оснащены средствами мониторинга для записи трассировочных данных об их работе, используемых при анализе проблем в системе. Эти компоненты опираются на общую инфраструктуру в ядре, которая предоставляет трассировочные данные механизму пользовательского режима — Event Tracing for Windows (ETW). Приложение, использующее ETW, попадает в одну или более следующих категорий. • Контроллер (controller) Начинает и прекращает сеансы протоколирования (logging sessions), а также управляет буферными пулами. • Провайдер (provider) Определяет GUID (globally unique identifiers) для классов событий, для которых он может создавать трассировочные данные, и регистрирует их в ETW. Провайдер принимает команды от контроллера на запуск и остановку трассировки классов событий, за которые он отвечает. • Потребитель (consumer) Выбирает один или более сеансов трассировки, для которых ему нужно считывать трассировочные данные. Принимает информацию о событиях в буферы в режиме реального времени или в файлы журнала. B системы Windows Server встроено несколько провайдеров пользовательского режима, в том числе для Active Directory, Kerberos и Netlogon. ETW определяет сеанс протоколирования с именем NT Kernel Logger [также известный как регистратор ядра (kernel logger)] для использования ядром и базовыми драйверами. Провайдер для NT Kernel Logger реализуется драйвером устройства Windows Management Instrumentation (WMI) (драйвер называется Wmixwdm), который является частью Ntoskrnl.exe. (Подробнее о WMI см. соответствующий раздел в главе 5.) Этот драйвер не только служит основой регистратора ядра, но и управляет регистрацией классов событий ETW пользовательского режима. Драйвер WMI экспортирует интерфейсы управления вводом-выводом для применения в ETW-процедурах пользовательского режима и драйверах устройств, предоставляющих трассировочные данные для регистратора ядра. (O командах управления вводом-выводом см. главу 9.) Он также реализует функции для использования компонентами в Ntoskrnl.exe режима ядра, которые формируют трассировочный вывод. Когда в пользовательском режиме включается контроллер, регистратор ядра (библиотека ETW, реализованная в \Windows\System32\Ntdll.dll) посылает запрос управления вводом-выводом (I/O control request) дpaйвepy WMI, сообщая ему, для каких классов событий контроллер хочет начать трассировку. Если настроено протоколирование в файлы журналов (в противоположность протоколированию в буфер памяти), драйвер WMI создает специальный системный поток в системном процессе, а тот создает файл журнала. Принимая события трассировки от активизированных источников трассировочных данных, драйвер WMI записывает их в буфер. Поток записи в журнал пробуждается раз в секунду, чтобы сбросить содержимое буферов в файл журнала. Записи трассировки, генерируемые для регистратора ядра, имеют стандартный ETW-заголовок события трассировки, в котором содержатся временная метка, идентификаторы процесса и потока, а также сведения о том, какому классу события соответствует данная запись. Классы событий могут предоставлять дополнительные данные, специфичные для их событий. Например, класс дисковых событий (disk event class) указывает тип операции (чтение или запись), номер диска, на котором выполняется операция, а также смещение начального сектора и количество секторов, затрагиваемых данной операцией. Классы трассировки, которые можно включить для регистратора ядра, и компонент, генерирующий каждый класс, перечислены ниже. • Дисковый ввод-вывод Драйвер класса дисков. • Файловый ввод-вывод Драйверы файловой системы. • Конфигурирование оборудования Диспетчер Plug and Play (см. главу 9). • Загрузка/выгрузка образов Системный загрузчик образов в ядре. • Ошибки страниц Диспетчер памяти (см. главу 7). • Создание/удаление процессов Диспетчер процессов (см. главу 6). • Создание/удаление потоков Диспетчер процессов. • Операции с реестром Диспетчер конфигурации (см. раздел «Реестр» в главе 4). • АктивностьТСР/UDP ДрайверТСР/IР. Более подробные сведения о ETW и регистраторе ядра, в том числе примеры кода для контроллеров и потребителей, см. в Platform SDK. ЭКСПЕРИМЕНТ: трассировка активности TCP/IP с помощью регистратора ядра Чтобы включить регистратор ядра и получить от него файл журнала активности TCP/IP, действуйте следующим образом. 1. Запустите оснастку Performance (Производительность) и выберите узел Performance Logs And AIerts (Журналы и оповещения производительности). 2. Укажите Trace Logs (Журналы трассировки) и выберите из меню Action (Действие) команду New Log Settings (Новые параметры журнала). 3. B появившемся окне присвойте имя новым параметрам (например, experiment). 4. B следующем диалоговом окне выберите Events Logged By System Provider (События, протоколируемые системным поставщиком) и сбросьте все, кроме Network TCP/IP (События сети TCP/IP). 5. B поле ввода Run As (От имени) введите имя учетной записи администратора и ее пароль. 6. Закройте это диалоговое окно и создайте активность в сети, открыв браузер и зайдя на какой-нибудь Web-сайт. 7. Укажите журнал трассировки, созданный в узле таких журналов, и выберите Stop (Остановка) из меню Action (Действие). 8. Откройте окно командной строки и перейдите в каталог C: \Perflogs (или тот каталог, который вы указали как место хранения файла журнала). 9. Если вы используете Windows XP или Windows Server 2003, запустите Tracerpt (эта утилита находится в каталоге \Windows\Sys-tem32) и передайте ей имя файла журнала трассировки. Если вы работаете в Windows 2000, скачайте и запустите Tracedmp из ресурсов Windows 2000. Обе утилиты генерируют два файла: dumpfile.csv и summary.txt. 10. Откройте dumpfile.csv в Microsoft Excel или в любом текстовом редакторе. Вы должны увидеть записи трассировки TCP и/или UDP: Wow64 (эмуляция Win32 в 64-разрядной Windows) относится к программному обеспечению, которое дает возможность выполнять 32-разрядные х8б-приложения в 64-разрядной Windows. Этот компонент реализован как набор DLL пользовательского режима. • Wow64.dll — управляет созданием процессов и потоков, подключается к диспетчеризации исключений и перехватывает вызовы базовых системных функций, экспортируемых Ntoskrnl.exe. Также реализует перенаправление файловой системы (file system redirection) и перенаправление реестра и отражение (reflection). • Wow64Cpu.dll — управляет 32-разрядным контекстом процессора каждого потока, выполняемого внутри Wow64, и предоставляет специфичную для процессорной архитектуры поддержку переключения режима процессора из 32-разрядного в 64-разрядный и наоборот. • Wow64Win.dll — перехватывает вызовы системных GUI-функций, экспортируемых Win32k.sys. Взаимосвязь этих DLL показана на рис. 3-31. Wow64 ставит ловушки на всех путях выполнения, где 32-разрядный код должен взаимодействовать с родным 64-разрядным или где 64-разрядной системе нужно обращаться к 32-разрядному коду пользовательского режима. При создании процесса диспетчер процессов проецирует на его адресное пространство 64-разрядную библиотеку Ntdll.dll. Загрузчик 64-разрядной системы проверяет заголовок образа и, если этот процесс 32-разрядный для платформы x86, загружает Wow64.dll. После этого Wow64 проецирует 32-разрядную Ntdll.dll (она хранится в каталоге \Windows\Syswow64). Далее Wow64 настраивает стартовый контекст внутри Ntdll, переключает процессор в 32-разрядный режим и начинает выполнять 32-разрядный загрузчик. C этого момента все идет так же, как в обычной 32-разрядной системе. Специальные 32-разрядные версии Ntdll.dll, User32.dll и Gdi32.dll находятся в каталоге \Windows\Syswow64. Они вызывают Wow64, не выдавая инструкции вызова, которые используются в истинно 32-разрядной системе. Wow64 переключается в «родной» 64-разрядный режим, захватывает параметры, связанные с системным вызовом, преобразует 32-разрядныеуказате-ли в 64-разрядные и выдает соответствующий для 64-разрядной системы системный вызов. Когда последняя возвращает управление, Wow64 при необходимости преобразует любые выходные параметры из 64-битных в 32-битные форматы и вновь переключается в 32-разрядный режим. Wow64 перехватывает диспетчеризацию исключений через Wow64 перехватывает все обратные вызовы из режима ядра в пользовательский режим. Wow64 интерпретирует их как системные вызовы; однако трансляция данных происходит в обратном порядке: входные параметры преобразуются из 64-битных форматов в 32-битные, а выходные (после возврата из обратного вызова) — из 32-битных в 64-битные. Чтобы обеспечить совместимость приложений и упростить перенос Win32-программ на платформу 64-разрядной Windows, имена системных каталогов сохранены прежними. Поэтому в \Windows\System32 содержатся «родные» 64-разрядные исполняемые файлы. Так как Wow64 ставит ловушки на все системные вызовы, этот компонент транслирует все API-вызовы, относящиеся к путям, и заменяет в них каталог \Windows\System32 на \Win-dows\Syswow64. Wow64 также перенаправляет \Windows\System32 \Ime в \Windows\System32\IME (x86), чтобы обеспечить совместимость 32-разрядных приложений в 64-разрядных системах с установленной поддержкой дальневосточных языков. Кроме того, 32-разрядные программы устанавливаются в каталог \Program Files (x86), тогда как 64-разрядные — в обычный каталог \Program Files. B каталоге \Windows\System32 есть несколько подкаталогов, которые по соображениям совместимости исключаются из перенаправления. Так что, если 32-разрядным приложениям понадобится доступ к этим каталогам, они смогут обращаться к ним напрямую. B число таких каталогов входят: • %windir%\system32\drivers\etc; • %windir%\system32\spool; • %windir%\system32\catroot2; • %windir%\system32\logfiles. Наконец, Wow64 предоставляет механизм, позволяющий отключать перенаправление файловой системы, встроенное в Wow64, для каждого потока индивидуально. Данный механизм доступен через функцию Приложения и компоненты хранят свои конфигурационные данные в реестре. Эту информацию компоненты обычно записывают в реестр при регистрации в ходе установки. Если один и тот же компонент поочередно устанавливается и регистрируется как 32- и 64-разрядный, тогда компонент, зарегистрированный последним, переопределяет регистрацию предыдущего, поскольку оба они пишут по одному адресу в реестре. Чтобы решить эту проблему, не модифицируя 32-разрядные компоненты, реестр делится на две части: Native и Wow64. По умолчанию 32-разрядные компоненты получают доступ к 32-разрядному представлению реестра, а 64-разрядные — к 64-разрядному представлению. Это создает безопасную среду исполнения для 32- и 64-разрядных компонентов и отделяет состояние 32-разрядных приложений от состояния 64-разрядных (если таковые есть). Реализуя это решение, Wow64 перехватывает все системные вызовы, открывающие разделы реестра, и модифицирует пути к разделам так, чтобы они указывали на контролируемое Wow64 представление реестра. Wow64 разбивает реестр в следующих точках: • HKLM\Software; • HKEY_CLASSES_ROOT; • HKEY_CURRENT_USER\Software\Classes. B каждом из этих разделов Wow64 создает раздел с именем Wow6432-Node. B нем сохраняется конфигурационная информация 32-разрядного программного обеспечения. Остальные части реестра 32- и 64-разрядные приложения используют совместно (например, HKLM\System). При вызове функций • KEY_WOW64_64KEY — для явного открытия 64-разрядного раздела из 32-или 64-разрядного приложения; • KEY_WOW64_32KEY — для явного открытия 32-разрядного раздела из 32-или 64-разрядного приложения. Для обеспечения взаимодействия через 32- и 64-разрядные СОМ-компо-ненты Wow64 отражает изменения в некоторых частях одного представления реестра на другое. Для этого Wow64 перехватывает операции обновления любого из отслеживаемых разделов в одном из представлений и отражает соответствующие изменения на другое представление. Вот список отслеживаемых разделов: • HKLM\Software\Classes; • HKLM\Software\Ole; • HKLM\Software\Rpc; • HKLM\Software\COM3; • HKLM\Software\EventSystem. Wow64 использует интеллектуальный подход к отражению HKLM\Soft-ware\Classes\CLSID: транслируются только CLSID-идентификаторы Local-Server32, так как они могут быть СОМ-активированы 32- или 64-разрядными приложениями, а CLSID-идентификаторы InProcServer32 не отражаются, поскольку 32-разрядные COM DLL нельзя загрузить в 64-разрядный процесс, равно как и 64-разрядные COM DLL в 32-разрядный процесс. При отражении раздела или параметра механизм отражения реестра (registry reflector) помечает раздел так, чтобы было понятно, что он создан именно этим механизмом. Это позволяет ему выбирать дальнейший алгоритм действий при удалении отражаемого раздела. Приложения могут не только выполнять обычные операции чтения и записи, но и взаимодействовать с некоторыми драйверами устройств через интерфейс управления вводом-выводом на устройствах, используя API-функцию Wow64 не поддерживает выполнение 16-разрядных приложений. Ho поскольку многие программы установки являются 16-разрядными, в Wow64 предусмотрен специальный код, все же позволяющий выполнять 16-разрядные программы установки общеизвестных приложений. K таким средствам установки, в частности, относятся: • Microsoft ACME Setup версий 2.6, 3.0, 3.01 и 3.1; • InstallShield версий 5x. Всякий раз, когда с помощью API-функции Использовать 32-разрядные драйверы принтера в 64-разрядной Windows нельзя. Они должны быть 64-разрядными версиями, «родными» для данной системы. Однако, поскольку драйверы принтера работают в пользовательском адресном пространстве запрашивающего процесса, а 64-разрядная Windows поддерживает лишь истинно 64-разрядные драйверы принтера, нужен специальный механизм для поддержки печати из 32-разрядных процессов. Для этого все вызовы функций печати перенаправляются в Splwow64.exe — RPC-сервер печати Wow64. Так как Splwow64 является 64-разрядным процессом, он может загрузить 64-разрядные драйверы принтера. Wow64 (в отличие от 32-разрядных версий Windows) не поддерживает выполнение 16-разрядных приложений или загрузку 32-разрядныхдрайверов устройств режима ядра (их нужно перевести в истинно 64-разрядные). Wow64-npoцессы могут загружать лишь 32-разрядные DLL (загрузка истинно 64-разрядных DLL невозможна). Аналогичным образом 64-разрядные процессы не могут загружать 32-разрядные DLL. B дополнение к сказанному Wow64 в системах IA64 из-за различий в размерах страниц памяти не поддерживает функции B этой главе мы изучили важнейшие базовые механизмы, на которых построена исполнительная система Windows. B следующей главе будут рассмотрены три важных механизма, образующих инфраструктуру управления в Windows: реестр, сервисы и WMI (Windows Management Instrumentation). |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|