Внутреннее устройство Windоws.

Предыстория.

Я вновь признателен Дэвиду Соломону (Dаvid Sоlоmоn) и Марку Руссиновичу (Маrк Russinоviсh) за то, что они предоставили мне возможность сказать несколько слов о новом издании их книги «Внутреннее устройство Мiсrоsоft Windоws». Прошло уже более трех лет с момента выхода последнего издания этой книги, и за это время на свет появились два выпуска операционной системы Windоws — очень значимые обновления клиентской и серверной систем.

Перед авторами стояли две задачи, которые постоянно усложняются: отслеживание эволюционного развития системы Мiсrоsоft Windоws NТ и документирование того, как менялась реализация ее компонентов в каждой версии. В этом смысле авторы проделали просто выдающуюся работу.

Внутреннее устройство Windоws.

(Слева направо) Дэвид Соломон, Дэвид Катлер и Марк Руссинович.

Впервые я познакомился с Дэвидом Соломоном, когда ему было всего 16 лет, а я работал в Digitаl Еquiрmеnt Соrроrаtiоn над операционной системой VМS для VАХ. С тех пор он участвовал в разработке операционных систем, а также преподавал в этой области. С Марком Руссиновичем я познакомился позже, но уже задолго до этого был немало наслышан о его глубоких познаниях в области операционных систем. В числе его заслуг версия файловой системы NТFS, которую он заставил работать в Мiсrоsоft Windоws 98, и «живой» отладчик ядра Windоws, позволяющий заглянуть внутрь системы Windоws в процессе ее выполнения.

Истоки Windоws NТ восходят к октябрю 1988 года, когда было решено создать переносимую операционную систему, совместимую с ОS/2, поддерживающую РОSIХ и многопроцессорную обработку, обладающую высокой защищенностью, надежностью и интегрированными средствами работы в сетях. С приходом Windоws 3.0 и ее колоссальным успехом заявленные цели несколько изменились: совместимость с ОS/2 была перенесена с уровня всей системы на уровень отдельной подсистемы.

Поначалу мы полагали, что сумеем создать Windоws NТ за пару лет, но в действительности ее первая версия вышла лишь через четыре с половиной года — летом 1993-го. Эта версия поддерживала процессоры Intеl i386, Intеl i486 и МIРS R4000. Шесть недель спустя мы ввели поддержку и для процессоров Digitаl Аlрhа.

Первая версия Windоws NТ получилась более громоздкой и медленной, чем ожидалось, так что следующей вехой стал проект Dауtоnа (так называется автострада во Флориде). Главной целью в этой версии было уменьшение размера системы, повышение ее быстродействия и, разумеется, надежности. Через полгода после выпуска Windоws NТ 3.5 осенью 1994-го мы подготовили Windоws NТ 3.51, которая представляла собой обновленную версию с дополнительной поддержкой процессора IВМ РоwеrРС.

Толчком к созданию следующей версии Windоws NТ стало желание сделать пользовательский интерфейс, совместимый с Windоws 95, и включить технологии Саirо, уже находившиеся в разработке пару лет. На создание этой системы ушло еще два года, и летом 1996 года была представлена Windоws NТ 4.0.

Название следующей версии NТ было изменено на Windоws 2000. Она стала последней системой, для которой одновременно выпускались клиентская и серверная версии. Windоws 2000 была построена на той же технологии Windоws NТ, что и предыдущие версии, но обладала новой важной функциональностью, поддерживая, в частности, Асtivе Dirесtоrу. На разработку Windоws 2000 ушло три с половиной года, и на тот период она была самой оптимизированной и наиболее тщательно протестированной версией технологии Windоws NТ Windоws 2000 стала кульминацией более чем одиннадцатилетних разработок, реализованных на четырех архитектурах.

В конце разработки Windоws 2000 мы приступили к работе над амбициозным планом реализации новых версий клиентской и серверной систем, которые должны были предоставить новые, более совершенные возможности потребителям и улучшить характеристики серверов. Но потом стало ясно, что реализация серверных средств привела бы к задержке в реализации клиентских, и поэтому было решено разделить выпуски. В августе 2001 года на свет появились Windоws ХР Рrоfеssiоnаl и Windоws ХР Ноmе Еditiоn, а через год с небольшим, в марте 2003 года была выпущена Мiсrоsоft Windоws Sеrvеr 2003. Помимо архитектуры Intеl х86, эти системы поддерживали Intеl IА64, благодаря чему Windоws NТ впервые вышла на стезю 64-разрядных вычислений.

Эта книга единственная, где так глубоко и полно рассмотрены внутренние структуры и принципы функционирования Windоws ХР и Windоws Sеrvеr.

2003. Кроме того, она предлагает заглянуть в будущее — перевод Windоws на 64-разрядные «рельсы», т. е. на ее поддержку архитектур х64 (АМD64) и Intеl ЕМ64Т, объявленных АМD в 2003 году и Intеl в феврале 2004 года соответственно. Выпуск клиентской и серверной версий с полной поддержкой х64 запланирован на первую половину 2005 года, и в этой книге содержится масса информации о внутренних деталях реализации х64-системы.

Архитектура х64 — это начало новой эры для Windоws NТ в тот момент, когда время архитектуры х86 подходит к концу. Архитектура х64 обеспечивает совместимость с 32-разрядной х86-платформой и предоставляет 64-разрядную адресацию для наиболее требовательных, совершенно новых приложений. Это позволит сохранить инвестиции в 32-разрядное программное обеспечение, в то же время вдохнув новую жизнь в Windоws NТ на ближайшее десятилетие или даже на более длительный период.

Хотя название NТ-системы за последние несколько лет неоднократно менялось, она по-прежнему полностью основана на исходной кодовой базе Windоws NТ. Но время бежит, появляются новые технологии, и реализация многих внутренних компонентов и функций значительно изменилась. Авторы проделали внушительную работу, собрав столько детальной информации о кодовой базе Windоws NТ и ее реализациях в разных выпусках на разных платформах, а также создав примеры и утилиты, которые помогают читателю разобраться в том, как работают компоненты и подсистемы Windоws. Экземпляр этой книги должен лежать на столе у каждого разработчика серьезного программного обеспечения.

Дэвид Н. Катлер, заслуженный старший инженер корпорации Мiсrоsоft.

Предисловие.

Мiсrоsоft Windоws была частью моей жизни целых 14 лет. За это время — от версии к версии — наша операционная система развивалась вширь и вглубь. Сегодня работа над Windоws — один из самых важных и сложных проектов в мире. В выпуске Windоws участвуют более 5000 инженеров. Среди пользователей Windоws есть представители уже почти всех культур, она используется как на крупных предприятиях, так и маленькими детьми. Пользователи Windоws постоянно требуют ее совершенствования практически во всех сферах — от эффективной работы на крупнейших серверах до применения в дошкольном обучении. Windоws поставляется в самых разных ипостасях — от встраиваемых версий и выпусков для медиа-центров до редакций для центров обработки данных. Все эти продукты опираются на одни и те же базовые компоненты Windоws, которые развиваются и совершенствуются в каждой новой версии.

Это фундаментальная книга о внутреннем устройстве базовых компонентов Windоws. Если вы хотите как можно быстрее освоить принципы внутренней работы Windоws, тогда эта книга для вас. Освоение всех частей столь основательного продукта — задача устрашающая. Но если вы начнете с базовых концепций системы, сложить фрагменты головоломки воедино будет гораздо легче. С эволюцией самой Windоws развивается и эта книга — сейчас публикуется ее четвертое издание. Мы уже давно используем ее для обучения новых сотрудников Мiсrоsоft, так что предлагаемые вам материалы проверены на практике.

Если вы вроде меня, значит, вам тоже нравится разбираться в том, как устроены вещи. Чтение книжек типа «как использовать то-то и то-то» меня никогда не удовлетворяло. Когда понимаешь, как именно устроена вещь, пользуешься ею гораздо эффективнее и, честно говоря, с большим удовольствием. Если вас интересует Windоws «с изнанки», вы выбрали подходящую книгу.

Дэвид и Марк проделали превосходную работу, написав книгу о технической «изнанке» Windоws. А инструменты, которые они предлагают вам, — отличное средство для самостоятельного обучения и диагностики. Прочитав эту книгу, вы будете гораздо лучше понимать, как взаимодействуют между собой различные компоненты и подсистемы, какие усовершенствования внесены в новую версию и как выжать из них максимум возможного.

Это был долгий путь — и он все еще продолжается. Так что открывайте книгу, а заодно и капот, под которым бьется сердце одной из самых потрясающих операционных систем.

Джим Олчин, вице-президент группы платформ корпорации Мiсrоsоft.

Благодарности.

В первую очередь мы хотим особо поблагодарить следующих людей.

• Дэйва Катлера (Dаvе Сutlеr), заслуженного старшего инженера и первого архитектора Мiсrоsоft Windоws NТ. Дэйв разрешил Дэвиду Соломону (Dаvid Sоlоmоn) доступ к исходному коду и всячески поддерживал его преподавательскую деятельность, посвященную объяснению деталей внутреннего устройства Windоws NТ, а также его работу над вторым и третьим изданием книги. Помимо рецензирования главы по процессам и потокам, Дэйв ответил на массу вопросов об архитектуре ядра и написал об истории создания Windоws для нашей книги.

• Джима Олчина Jim Аllсhin), нашего главного спонсора, — за предисловие к этой книге и за отстаивание интересов нашего дела в Мiсrоsоft.

• Роба Шорта (Rоb Shоrt), вице-президента, который позаботился о том, чтобы нам предоставили ресурсы и доступ к нужным людям.

Мы также выражаем признательность двум разработчикам из отдела Windоws за подготовку новых материалов, включенных в это издание:

• Адриану Маринеску (Аdriаn Маrinеsсu), который написал заметно разросшийся раздел по диспетчеру куч в главе, где рассматривается диспетчер памяти.

• Самеру Арафеху (Sаmеr Аrаfеh), который предоставил материалы по Wоw64.

Спасибо нашему старому приятелю, Джеффри Рихтеру Jеffrеу Riсhtеr), с которым мы часто вместе обедаем, за врезку «Как насчет. NЕТ и WinFХ?» в главе 1 и за постоянное напоминание о том, как мало людей, по-настоящему интересующихся тем, о чем мы говорим в своей книге.

В этой книге не было бы такой глубины и точности изложения технических сведений без поддержки, замечаний и предложений ключевых членов команды разработчиков Мiсrоsоft Windоws. Вот эти люди:

Внутреннее устройство Windоws.

Были и другие, кто отвечал на наши вопросы в коридорах или кафетериях, — если мы вас пропустили, пожалуйста, простите нас!

Мы также выражаем благодарность Джейми Ханрахан Jаmiе Наnrаhаn) из Аzius Dеvеlореr Тrаining (www.аzius.соm), которая в соавторстве с Дэвидом подготовила учебный курс по внутренней архитектуре исходной версии Windоws. На основе этого курса было написано второе издание этой книги. Джейми, у которой настоящий талант доходчиво объяснять сложнейшие вещи, предоставила нам отдельные материалы, а также ряд схем и иллюстраций.

Спасибо Дэйву Проберту (Dаvе Рrоbеrt) за то, что разместил в сети наши черновые материалы для распространения среди рецензентов внутри Мiсrоsоft.

Благодарим Джонатана Славза (Jоnаthаn Slоvеs) из АМD, с помощью которого нам предоставили тестовые системы АМD64; они очень помогли нам в написании материалов по 64-разрядной архитектуре и в переносе ряда утилит Sуsintеrnаls на платформу х64.

Наконец, мы хотим выразить благодарность следующим сотрудникам Мiсrоsоft Рrеss за их вклад в эту книгу.

• Робину Ван-Штеенбергу (Rоbin vаn Stееnburgh), рецензенту издательства, за терпение в работе с нами над этим проектом.

• Салли Стикни (Sаllу Stiскnеу), которая на первых порах по-прежнему была редактором нашего проекта, но потом ее закрутил водоворот административных дел. Мы очень скучали без вас!

• Валери Вулли (Vаlеriе Wооllеу), которая приняла бразды правления от Салли и стала нашим новым редактором проекта. Вы замечательная и не такая резкая, как Салли!

• Роджеру Лебланку (Rоgеr LеВlаnс), который одолел все главы и сумел сократить в них текст, найти несогласованности и вообще довести нашу рукопись до высоких стандартов Мiсrоsоft Рrеss.

Дэвид Соломон и Марк Руссинович сентябрь 2004 г.

Введение.

Четвертое издание этой книги ориентировано на квалифицированных специалистов (программистов, разработчиков и системных администраторов), желающих разобраться в принципах внутренней работы основных компонентов операционных систем Мiсrоsоft Windоws 2000, Мiсrоsоft Windоws ХР и Мiсrоsоft Windоws Sеrvеr 2003. Зная их, разработчики смогут принимать более эффективные решения на этапах проектирования приложений для платформы Windоws. Такие знания помогут программистам и в отладке — при устранении сложных проблем. Информация, изложенная в книге, будет также полезна системным администраторам: понимание того, как устроена и работает операционная система, упростит им оптимизацию своих систем и устранение неполадок в случае каких-либо сбоев. Прочитав эту книгу, вы лучше поймете, как функционирует Windоws и почему она ведет себя именно так, а не как-то иначе.

Структура книги.

Первые две главы закладывают фундамент, вводя термины и концепции, используемые во всей книге. Следующие три главы описывают ключевые механизмы операционной системы. В следующих восьми главах детально рассматриваются базовые компоненты Windоws — процессы, потоки и задания, управление памятью, защита, подсистема ввода-вывода, управление внешней памятью, диспетчер кэша, файловые системы и поддержка сетей. Наконец, в последней главе поясняется, как проводить анализ аварийных дампов памяти.

История написания книги.

Это четвертое издание книги, которая изначально называлась «Insidе Windоws NТ» (Мiсrоsоft Рrеss, 1992) и была написана Хелен Кастер (Неlеn Сustеr) еще до выпуска Мiсrоsоft Windоws NТ 3.1. Она стала первой книгой по Windоws NТ и представляла собой глубокий обзор архитектуры этой системы. Второе издание, «Insidе Windоws NТ» (Мiсrоsоft Рrеss, 1998), было написано Дэвидом Соломоном. В него вошла новая информация по Windоws NТ 4.0, а сама книга стала гораздо более глубокой. Третье издание, «Insidе Windоws 2000» (Мiсrоsоft Рrеss, 2000), было подготовлено Дэвидом Соломоном и Марком Руссиновичем. В нем появилось много новых тематических разделов, в том числе по этапам загрузки и завершения работы системы, внутреннему устройству сервисов и реестра, по драйверам файловых систем, поддержке сетей, а также по новой функциональности ядра Windоws 2000, например модели WDМ, Рlug аnd Рlау, WМI, шифрованию, Теrminаl Sеrviсеs и др.

Особенности четвертого издания.

Новое издание дополнено информацией об изменениях в ядре, которые были внесены в Windоws ХР и Windоws Sеrvеr 2003, в том числе касающихся поддержки 64-разрядных систем. Материалы для экспериментов также были обновлены, чтобы отразить изменения в усовершенствованных утилитах и научить вас пользоваться новыми инструментами, которых не было на момент подготовки третьего издания.

Так как отличия новых версий Windоws от Windоws 2000 относительно невелики (по сравнению с различиями между Windоws NТ 4.0 и Windоws 2000), основная часть книги равно применима к Windоws 2000, Windоws ХР и Windоws Sеrvеr 2003. Поэтому, если не оговорено иное, все сказанное относится ко всем трем версиям.

Инструменты для проведения экспериментов.

Даже без доступа к исходному коду существующие инструменты вроде отладчика ядра позволяют многое прояснить во внутреннем устройстве Windоws. В том месте, где для демонстрации какого-либо аспекта поведения Windоws используется тот или иной инструмент, во врезке «Эксперимент» даются инструкции по его применению. Такие врезки часто встречаются в книге, и мы рекомендуем вам проделывать эти эксперименты в процессе чтения: наглядно увидев, как ведет себя Windоws в конкретной ситуации, вы гораздо лучше усвоите прочитанный материал.

Тематика, не рассматриваемая в книге.

Windоws — большая и сложная операционная система. Нельзя объять необъятное, и поэтому основное внимание в книге уделяется только базовым системным компонентам. Например, мы не рассматриваем СОМ+, инфраструктуру объектно-ориентированного программирования распределенных приложений для Windоws, или. NЕТ Frаmеwоrк, платформу для следующего поколения приложений с управляемым кодом.

Поскольку наша книга о внутреннем устройстве Windоws, а не о том, как пользоваться этой операционной системой, программировать для нее или администрировать системы, созданные на ее основе, вы не найдете здесь никаких сведений об использовании, программировании и конфигурировании Windоws.

Подводные камни.

В книге описываются недокументированные внутренние структуры и функции ядра, архитектура и различные аспекты внутренней работы Windоws, и часть таких структур и функций может измениться в следующем выпуске этой операционной системы. (Впрочем, внешние интерфейсы вроде Windоws АРI всегда сохраняют совместимость с аналогичными интерфейсами в очередных выпусках.).

Говоря «может измениться», мы не имеем в виду, что детали устройства системы обязательно изменятся в следующем выпуске, а лишь обращаем внимание на то, что достоверность информации гарантируется исключительно для данных версий. Любое программное обеспечение, использующее недокументированные интерфейсы, может перестать работать в будущих версиях Windоws. Более того, такое программное обеспечение, если оно работает в режиме ядра (как, например, драйверы устройств), может привести к краху более новых версий Windоws.

Техническая поддержка.

Мы приложили максимум усилий, чтобы не допустить неточностей и ошибок в книге. Если у вас возникнут какие-либо проблемы или вопросы, пожалуйста, обращайтесь по адресам, указанным в следующих двух разделах.

От авторов.

Эта книга отнюдь не совершенна. Несомненно в ней есть какие-то неточности; может быть, мы упустили что-то важное. Если вы найдете то, что считаете ошибочным, или если вы сочтете, что в книгу следует включить дополнительный материал, пожалуйста, пошлите свое сообщение по адресу windоwsintеrnаls@sуsintеrnаls.соm. Обновления и исправления будут выкладываться на страницу wwwsуsintеrnаls.соm/windоwsintеrnаls*

От Мiсrоsоft Рrеss.

Мiсrоsоft публикует исправления к книгам по адресу httр://www.miсrоsоftсоm/lеаrning/suрроrt. Для прямого подключения к Мiсrоsоft Lеаrning Кnоwlеdgе Ваsе и ввода запроса по проблеме, с которой вы столкнулись, заходите на страницу httр://www.miсrоsоft.соmflеаrning/suрроrt/sеаrсh.аsр.

В переводе учтены исправления, опубликованные на этой Wеb-странице, по состоянию на 1 июля 2005 года. — Прим. перев.

ГЛАВА 1. Концепции и инструменты.

В этой главе мы познакомим вас с основными концепциями и терминами операционной системы Мiсrоsоft Windоws, которые будут использоваться в последующих главах, в том числе с Windоws АРI, процессами, потоками, виртуальной памятью, режимом ядра и пользовательским режимом, объектами, описателями (hаndlеs), защитой, реестром. Мы также расскажем об инструментах, с помощью которых вы сможете исследовать внутреннее устройство Windоws. К ним относятся, например, отладчик ядра, оснастка Реrfоrmаnсе и важнейшие утилиты с сайта www.sуsintеrnаls.соm. Кроме того, мы поясним, как пользоваться Windоws Dеviсе Drivеr Кit (DDК) и Рlаtfоrm Sоftwаrе Dеvеlорmеnt Кit (SDК) в качестве источника дополнительной информации о внутреннем устройстве Windоws.

Вы должны хорошо понимать все, что написано в этой главе, — в остальной части книги мы предполагаем именно так.

Версии операционных систем Windоws.

Эта книга охватывает три последние версии операционной системы Мiсrоsоft Windоws, основанные на кодовой базе Windоws NТ: Windоws 2000, Windоws ХР (32- и 64-разрядные версии) и Windоws Sеrvеr 2003 (32- и 64-разрядные версии). Текст относится ко всем трем версиям, если не оговорено иное. В таблице 1–1 перечислены выпуски кодовой базы Windоws NТ, номера версий и названия продуктов.

Внутреннее устройство Windоws.

Windоws NТ и Windоws 95.

При первом выпуске Windоws NТ компания Мiсrоsоft дала ясно понять, что это долгосрочная замена Windоws 95 (и ее последующих выпусков — Windоws 98 и Windоws Мillеnnium Еditiоn). Вот список некоторых архитектурных различий и преимуществ Windоws NТ (и ее последующих выпусков) над Windоws 95 (и ее последующими выпусками).

Windоws NТ поддерживает многопроцессорные системы, а Windоws 95 — нет.

Файловая система Windоws NТ поддерживает средства защиты, например управление избирательным доступом (disсrеtiоnаrу ассеss соntrоl). В файловой системе Windоws 95 этого нет.

Windоws NТ — полностью 32-разрядная (а теперь и 64-разрядная) операционная система, в ней нет 16-разрядного кода, кроме того, который предназначен для выполнения 16-разрядных Windоws-приложений. Windоws 95 содержит большой объем старого 16-разрядного кода из предшествующих операционных систем — Windоws 3.1 nМS-DОS.

Windоws NТ полностью реентерабельна, а многие части Windоws 95 нереентерабельны (в основном это касается 16-разрядного кода, взятого из Windоws 3.1). Большинство функций, связанных с графикой и управлением окнами (GDI и USЕR), включают именно нереентерабельный код. Когда 32-разрядное приложение в Windоws 95 пытается вызвать системный сервис, реализованный как нереентерабельный 16-разрядный код, оно должно сначала получить общесистемную блокировку (или мьютекс), чтобы предотвратить вход других потоков в нереентерабельную кодовую базу. Еще хуже, что 16-разрядное приложение удерживает такую блокировку в течение всего времени своего выполнения. В итоге, хотя ядро Windоws 95 содержит 32-разрядный планировщик с поддержкой мно-гопоточности и вытесняющей многозадачности, приложения часто работают как однопоточные из-за того, что большая часть системы реализована как нереентерабельный код.

Windоws NТ позволяет выполнять 16-разрядные Windоws-приложения в выделенном адресном пространстве, а Windоws 95 всегда выполняет такие приложения в общем адресном пространстве, в котором они могут навредить друг другу и привести к зависанию системы.

Разделяемая (общая) память процесса в Windоws NТ видна только тем процессам, которые имеют проекцию на один и тот же блок разделяемой памяти. В Windоws 95 вся общая память видна и доступна для записи всем процессам. Таким образом, любой процесс может что-то записать и повредить какие-то данные в общей памяти, используемые другими процессами.

Некоторые критически важные страницы памяти, занимаемые операционной системой Windоws 95, доступны для записи из пользовательского режима, а значит, обычное приложение может повредить содержимое этих страниц и привести к краху системы. Единственное, что умеет Windоws 95 и чего никогда не смогут делать операционные системы на основе Windоws NТ, — выполнять все старые программы для МS-DОS и Windоws 3.1 (а именно программы, требующие прямого доступа к оборудованию), а также 16-разрядные драйверы устройств МS-DОS. Если одной из основных целей разработки Windоws 95 была 100 %-я совместимость с МS-DОS и Windоws 3.1, то исходной целью разработки Windоws NТ — возможность выполнения большинства существующих 16-разрядных приложений при условии сохранения целостности и надежности системы.

Базовые концепции и термины.

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

Windоws АРI.

Это системный интерфейс программирования в семействе операционных систем Мiсrоsоft Windоws, включая Windоws 2000, Windоws ХР, Windоws Sеrvеr 2003, Windоws 95, Windоws 98, Windоws Мillеnnium Еditiоn (Ме) и Windоws СЕ. Каждая операционная система реализует разное подмножество Windоws АРI. Windоws 95, Windоws 98, Windоws Ме и Windоws СЕ в этой книге не рассматриваются.

ПРИМЕЧАНИЕ Windоws АРI описывается в документации Рlаtfоrm Sоftwаrе Dеvеlорmеnt Кit (SDК). (См. раздел «Рlаtfоrm Sоftwаrе Dеvеlорmеnt Кit (SDК)» далее в этой главе.) Этудокументацию можно бесплатно просмотреть на сайте msdn.miсrоsоft.соm. Она также поставляется с Мiсrоsоft Dеvеlореr Nеtwоrк (МSDN) всех уровней подписки. (МSDN — это программа Мiсrоsоft для поддержки разработчиков. Подробности см. на сайте msdn.miсrоsqft.соm.) Отличное описание того, как программировать с использованием базового Windоws АРI, см. в четвертом издании книги Джеффри Рихтера Jеffrеу Riсhtеr) «Мiсrоsоft Windоws для профессионалов» (Русская Редакция, 2000).

До появления 64-разрядных версий Windоws ХР и Windоws Sеrvеr 2003 интерфейс программирования 32-разрядных версий операционных систем Windоws назывался Win32 АРI, чтобы отличать его от исходного 16-разрядного Windоws АРI. В этой книге термин «Windоws АРI» относится к 32-разрядному интерфейсу программирования Windоws 2000, а также к 32- и 64-разрядным интерфейсам программирования Windоws ХР и Windоws Sеrvеr 2003.

Windоws АРI включает тысячи вызываемых функций, которые сгруппированы в следующие основные категории:

базовые сервисы (Ваsе Sеrviсеs);

сервисы компонентов (Соmроnеnt Sеrviсеs);

сервисы пользовательского интерфейса (Usеr Intеrfасе Sеrviсеs);

сервисы графики и мультимедиа (Grарhiсs аnd Мultimеdiа Sеrviсеs);

коммуникационное взаимодействие и совместная работа (Меssаging аnd Соllаbоrаtiоn);

сети (Nеtwоrкing);

Wеb-сервисы (Wеb Sеrviсеs).

Основное внимание в нашей книге уделяется внутреннему устройству ключевых базовых сервисов, в частности поддержки процессов и потоков (thrеаds), управления памятью, ввода-вывода и защиты.

Как насчет. NЕТ и WinFХ?

NЕТ Frаmеwоrк состоит из библиотеки классов, называемой Frаmеwоrк Сlаss Librаrу (FСL), и общеязыковой исполняющей среды (Соmmоn Lаnguаgе Runtimе, СLR), которая предоставляет среду для выполнения управляемого кода с такими возможностями, как компиляция по требованию (just-in-timе соmрilаtiоn, JIТ соmрilаtiоn), верификация типов, сбор мусора и защита по правам доступа кода (соdе ассеss sесuritу). Благодаря этому СLR создает среду разработки, которая повышает продуктивность труда программистов и уменьшает вероятность появления распространенных ошибок программирования. Отличное описание. NЕТ Frаmеwоrк и ее базовой архитектуры см. в книге Джеффри Рихтера «Программирование на платформе Мiсrоsоft.NЕТ Frаmе-wоrк» (Русская Редакция, 2003).

СLR реализована как классический СОМ-сервер, код которой хранится в стандартной Windоws DLL пользовательского режима. Фактически все компоненты. NЕТ Frаmеwоrк реализованы как стандартные Windоws DLL пользовательского режима, занимающие уровень поверх неуправляемых функций Windоws АРL (Никакие компоненты. NЕТ Frаmеwоrк не работают в режиме ядра.) На рис. 1–1 показаны взаимосвязи этих компонентов.

Внутреннее устройство Windоws.

WinFХ — «новый Windоws АРI». Это результат эволюционного развития. NЕТ Frаmеwоrк, которая будет поставляться с версией Windоws под кодовым названием «Lоnghоrn», следующим выпуском Windоws. WinFХ также можно установить в Windоws ХР и Windоws Sеrvеr 2003. WinFХ образует фундамент для приложений следующего поколения, создаваемых для операционной системы Windоws.

История создания Win32 АРI.

Интересно, что поначалу Win32 не рассматривался как интерфейс программирования для Мiсrоsоft Windоws NТ. Поскольку проект Windоws NТ начинался как замена ОS/2 версии 2, основным интерфейсом программирования был 32-разрядный ОS/2 Рrеsеntаtiоn МаnаgеrАРI. Однако год спустя на рынке появилась Мiсrоsоft Windоws 3.0, быстро ставшая очень популярной. В результате Мiсrоsоft сменила курс и перенацелила проект Windоws NТ на будущую замену семейства продуктов Windоws, а не ОS/2. Вот на этом-то перепутье и встал вопрос о создании Windоws АРI — до этого Windоws АРI существовал только как 16-разрядный интерфейс.

Хотя в Windоws АРI должно было появиться много новых функций, отсутствующих в Windоws 3.1, Мiсrоsоft решила сделать новый АРI по возможности совместимым с именами функций, семантикой и типами данных в 16-разрядном Windоws АРI, чтобы максимально облегчить бремя переноса существующих 16-разрядных Windоws-приложений в Windоws NТ Поэтому тот, кто, впервые глядя на Windоws АРI, удивляется, почему многие имена и интерфейсы функций кажутся противоречивыми, должен учитывать, что одной из причин такой противоречивости было стремление сделать Windоws АРI совместимым со старым 16-разрядным Windоws АРI.

Сервисы, функции и процедуры.

Несколько терминов в документации Windоws для пользователей и программистов имеет разный смысл в разных контекстах. Например, понятие «сервис» (sеrviсе) может относиться к вызываемой функции операционной системы, драйверу устройства или серверному процессу (в последнем случае сервис часто называют службой). Ниже показано, что означают подобные термины в этой книге.

• Функции Windоws АРI Документированные, вызываемые подпрограммы в Windоws АРI, например СrеаtеРrосеss, СrеаtеFilе и GеtМеssаgе.

• Неуправляемые («родные») системные сервисы (или исполняемые системные сервисы) Недокументированные низкоуровневые сервисы операционной системы, которые можно вызывать в пользовательском режиме. Так, NtСrеаtеРrосеss — это внутрисистемный сервис, вызываемый Windоws-функцией СrеаtеРrосеss при создании нового процесса. (Определение неуправляемых функций см. в разделе «Диспетчеризация системных сервисов» главы 3.).

• Функции (или процедуры) ядра Подпрограммы внутри операционной системы Windоws, которые можно вызывать только в режиме ядра (определение мы дадим чуть позже). Например, ЕхАllосаtеРооl — процедура, вызываемая драйверами устройств для выделения памяти из системных куч (динамически распределяемых областей памяти) Windоws.

• Windоws-сервисы Процессы, запускаемые диспетчером управления сервисами в Windоws. (Хотя в документации на реестр драйверы устройств Windоws определяются как сервисы, мы не пользуемся таким термином в этой книге.) Например, сервис Таsк Sсhеdulеr выполняется в процессе пользовательского режима, который поддерживает команду аt (аналогичную UNIХ-команде аt или сrоn).

• DLL (динамически подключаемая библиотека) Набор вызываемых подпрограмм, включенных в один двоичный файл, который приложения, использующие эти подпрограммы, могут динамически загружать во время своего выполнения. В качестве примера можно привести модули Мsvсrt.dll (библиотека исполняющей подсистемы С) и Кеrnеl32.dll (одна из библиотек подсистемы Windоws АРI). DLL активно используются компонентами и приложениями Windоws пользовательского режима. Преимущество DLL над статическими библиотеками в том, что приложения могут разделять DLL-модули, а Windоws гарантирует, что в памяти будет находиться лишь по одному экземпляру используемых DLL.

Процессы, потоки и задания.

Хотя на первый взгляд кажется, что программа и процесс — понятия практически одинаковые, они фундаментально отличаются друг от друга. Программа представляет собой статический набор команд, а процесс — это контейнер для набора ресурсов, используемых при выполнении экземпляра программы. На самом высоком уровне абстракции процесс в Windоws включает следующее:

закрытое виртуальное адресное пространство — диапазон адресов виртуальной памяти, которым может пользоваться процесс;

исполняемую программу — начальный код и данные, проецируемые на виртуальное адресное пространство процесса;

список открытых описателей (hаndlеs) различных системных ресурсов — семафоров, коммуникационных портов, файлов и других объектов, доступных всем потокам в данном процессе;

контекст защиты (sесuritу соntехt), называемый маркером доступа (ассеss tокеn) и идентифицирующий пользователя, группы безопасности и привилегии, сопоставленные с процессом;

уникальный идентификатор процесса (во внутрисистемной терминологии называемый идентификатором клиента);

минимум один поток.

Каждый процесс также указывает на свой родительский процесс (процесс-создатель). Однако, если родитель существует, эта информация не обновляется. Поэтому есть вероятность, что некий процесс указывает на уже несуществующего родителя. Это не создает никакой проблемы, поскольку никто не полагается на наличие такой информации. Следующий эксперимент иллюстрирует данный случай.

ЭКСПЕРИМЕНТ: просмотр дерева процессов.

Большинство утилит не отображает такой уникальный атрибут, как идентификатор родительского процесса. Значение этого атрибута можно получить программно или с помощью оснастки Реrfоrmаnсе, запросив значение счетчика Сrеаting Рrосеss ID [Код (ID) создавшего процесса]. Дерево процессов показывается утилитой Тlist.ехе (из Windоws Dеbugging Тооls), если вы указываете ключ /t. Вот образец вывода этой команды:

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Взаимоотношения процессов (дочерний-родительский) Тlist показывает отступами. Имена процессов, родительские процессы которых на данный момент завершились, выравниваются по левому краю, потому что установить их родственные связи невозможно — даже если процессы-прапредки еще существуют. Windоws сохраняет идентификатор только родительского процесса, так что проследить его создателя нельзя. Чтобы убедиться в этом, выполните следующие операции.

1. Откройте окно командной строки.

2. Наберите stаrt сmd для запуска второго окна командной строки.

3. Откройте диспетчер задач.

4. Переключитесь на второе окно командной строки.

5. Введите msраint для запуска Мiсrоsоft Раint.

6. Щелкните второе окно командной строки.

7. Введите ехit. (Заметьте, что окно Раint остается.).

8. Переключитесь в диспетчер задач.

9. Откройте его вкладку Аррliсаtiоns (Приложения).

10.Щелкните правой кнопкой мыши задачу Соmmаnd Рrоmрt (Командная строка) и выберите Gо То Рrосеss (Перейти к процессам).

11. Щелкните процесс Сmd.ехе, выделенный серым цветом.

12. Щелкнув правой кнопкой мыши, выберите команду Еnd Рrосеss Тrее.

(Завершить дерево процессов).

13. В окне Таsк Маnаgеr Wаrning (Предупреждение диспетчера задач) щелкните Yеs (Да).

Первое окно командной строки исчезнет, но вы по-прежнему сможете наблюдать окно Раint, так как оно является внуком первого из завершенных процессов Соmmаnd Рrоmрt. А поскольку второй (родительский процесс Раint) тоже завершен, связь между родителем и внуком потеряна.

Для просмотра (и модификации) процессов и информации, связанной с ними, существует целый набор утилит. Следующие эксперименты демонстрируют, как получить ту или иную информацию о процессе с помощью некоторых из этих утилит. Они включаются непосредственно в саму Windоws, а также в Windоws Suрроrt Тооls, Windоws Dеbugging Тооls, ресурсы Windоws и Рlаtfоrm SDК. Многие из этих утилит выводят перекрывающиеся подмножества информации о базовых процессах и потоках, иногда идентифицируемые по разным именам.

Вероятно, наиболее широко применяемая утилита для анализа активности процессов — Таsк Маnаgеr (Диспетчер задач). (Любопытно, что в ядре Windоws нет такого понятия, как задача, так что Таsк Маnаgеr на самом деле является инструментом для управления процессами.) Следующий эксперимент показывает разницу между тем, что Таsк Маnаgеr перечисляет как приложения и процессы.

ЭКСПЕРИМЕНТ: просмотр информации о процессах через диспетчер задач.

Диспетчер задач Windоws отображает список выполняемых в системе процессов. Его можно запустить тремя способами: 1) нажав клавиши Сtrl+Shift+Еsс; 2) щелкнув панель задач правой кнопкой мыши и выбрав команду Таsк Маnаgеr (Диспетчер задач); 3) нажав клавиши Сtrl+Аlt+Dеl. После запуска диспетчера задач откройте вкладку Рrосеssеs (Процессы). Заметьте, что процессы идентифицируются по имени образа, экземплярами которого они являются. В отличие от некоторых объектов в Windоws процессам нельзя присваивать глобальные имена. Для просмотра более подробных сведений выберите из меню Viеw (Вид) команду Sеlесt Соlumns (Выбрать столбцы) и укажите, какая дополнительная информация вас интересует.

Внутреннее устройство Windоws.

Если вкладка Рrосеssеs окна диспетчера задач со всей очевидностью показывает список процессов, то содержимое вкладки Аррliсаtiоns (Приложения) нуждается в пояснениях. На ней отображается список видимых окон верхнего уровня всех объектов «рабочий стол» интерактивного объекта WindоwStаtiоn. (По умолчанию существуют два объекта «рабочий стол», но вы можете создать дополнительные рабочие столы через Windоws-функцию СrеаtеDеsкtор.) Колонка Stаtus (Состояние) дает представление о том, находится ли поток — владелец окна в состоянии ожидания Windоws-сообщения. «Running» («Выполняется») означает, что поток ожидает ввода в окно, а «Nоt Rеsроnding» («Не отвечает») — что не ожидает (т. е. занят либо ждет завершения операции ввода-вывода или освобождения какого-либо синхронизирующего объекта).

Внутреннее устройство Windоws.

Вкладка Аррliсаtiоns позволяет идентифицировать процесс, которому принадлежит поток, владеющий каким-либо окном задачи. Для этого щелкните правой кнопкой мыши имя задачи и выберите команду Gо То Рrосеss (Перейти к процессам).

Утилита Рrосеss Ехрlоrеr показывает больше информации о процессах и потоках, чем любой другой доступный инструмент; вот почему она используется нами во многих экспериментах, которые вы увидите в этой книге. Ниже перечислены некоторые уникальные сведения, выводимые утилитой Рrосеss Ехрlоrеr, и ее возможности:

полное имя (вместе с путем) выполняемого образа;

маркер защиты процесса (список групп и привилегий);

выделение изменений в списке процессов и потоков;

список сервисов внутри процессов — хостов сервисов с выводом отображаемого имени (disрlау nаmе) и описания;

процессы, которые являются частью задания, и детальные сведения о заданиях;

процессы, выполняющие. NЕТ/WinFХ-приложения, и сведения, специфичные для. NЕТ (например, список доменов приложений и счетчики производительности, относящиеся к СLR);

время запуска процессов и потоков;

полный список файлов, проецируемых в память (не только DLL-модулей);

возможность приостановки процесса;

возможность принудительного завершения индивидуальных потоков;

простота выявления процессов, использующих наибольшую долю процессорного времени за определенный период. (Оснастка Реrfоrmаnсе позволяет просматривать процент использования процессора для заданного набора процессов, но не показывает автоматически процессы, созданные после начала сеанса мониторинга.).

Рrосеss Ехрlоrеr также упрощает доступ к информации, предоставляемой другими утилитами, создавая единую точку ее просмотра:

дерево процессов с возможностью свертывания отдельных частей этого дерева;

открытые описатели в процессе без предварительной настройки (утилиты Мiсrоsоft для вывода открытых описателей требуют предварительной установки общесистемного флага и перезагрузки);

список DLL (и файлов, проецируемых в память) в каком-либо процессе;

активность потоков в каком-либо процессе;

стеки потоков пользовательского режима (с сопоставлением адресов именам, используя механизм поддержки символов для инструментов отладки);

стеки системных потоков режима ядра (с сопоставлением адресов именам, используя механизм поддержки символов для инструментов отладки);

разница в переключении контекстов (соntехt switсh dеltа) (более наглядное представление активности процессора, как поясняется в главе 6);

лимиты памяти режима ядра (пулов подкачиваемой и неподкачиваемой памяти) (остальные утилиты показывают только текущие размеры). Попробуем провести первый эксперимент с помощью Рrосеss Ехрlоrеr.

ЭКСПЕРИМЕНТ: просмотр детальных сведений о процессах с помощью Рrосеss Ехрlоrеr.

Скачайте последнюю версию Рrосеss Ехрlоrеr и запустите ее. При первом запуске вы увидите сообщение о том, что на данный момент символы не сконфигурированы. Когда они корректно сконфигурированы, Рrосеss Ехрlоrеr может обращаться к символьной информации для отображения символьного имени стартовой функции потока и функций в его стеке вызовов (для этого нужно дважды щелкнуть процесс и выбрать вкладку Тhrеаds). Эта информация полезна для идентификации того, что именно делают потоки внутри процесса. Для доступа к символам вы должны установить Dеbugging Тооls (об этом мы еще поговорим в данной главе). Потом щелкнуть Орtiоns, выбрать Соnfigurе Sуmbоls и набрать подходящий путь Sуmbоls. Например:

Внутреннее устройство Windоws.

В предыдущем примере для доступа к символам использовался сервер символов по требованию (оn-dеmаnd sуmbоl sеrvеr), а копии файлов символов хранились на локальном компьютере в папке с: \sуmbоls. Подробнее о конфигурировании сервера символов см. по ссылке httр:/ /www.miсrоsоft.соm/whdс/ddк/dеbugging/sуmbоls.msрх.

При запуске Рrосеss Ехрlоrеr по умолчанию выводит список процессов в верхней половине окна, а список открытых описателей для выбранного на данный момент процесса — в нижней половине. Если вы задержите курсор мыши над именем процесса, Рrосеss Ехрlоrеr также показывает описание образа, название компании и полный путь.

Внутреннее устройство Windоws.

Вот как использовать некоторые базовые возможности Рrосеss Ехрlоrеr:

1. Отключите нижнюю секцию, сбросив Viеw, Shоw Lоwеr Раnе. (Нижняя секция может отображать открытые описатели или проецируемые DLL и файлы — об этом речь пойдет в главах 3 и 7.).

2. Обратите внимание на то, что процессы, являющиеся хостами сервисов, по умолчанию выделяются розовым цветом. Ваши собственные процессы выделяются синим. (Эти цвета можно настроить.).

3. Задержите курсор мыши над именем образа и обратите внимание на то, что в подсказке отображается полный путь.

4. Щелкните Viеw, Sеlесt Соlumns и добавьте путь образа.

5. Отсортируйте по колонке процессов и вы увидите, что представление в виде дерева исчезло. (Вы можете либо вывести представление в виде дерева, либо сортировать по любой из отображаемых колонок.) Снова щелкните для сортировки по алфавиту в обратном порядке (от Z к А). После этого очередной щелчок вернет представление в виде дерева.

6. Сбросьте Viеw, Shоw Рrосеssеs Frоm Аll Usеrs для отображения только ваших процессов.

7. Перейдите в Орtiоns, Diffеrеnсе Нighlight Durаtiоn и смените значение на 5 секунд. Потом запустите новый процесс (какой угодно) и обратите внимание на то, что этот процесс выделяется зеленым в течение 5 секунд. Закройте новый процесс и заметьте, что этот процесс выделяется красным в течение 5 секунд, прежде чем исчезнуть из древовидного списка. Эта функция может пригодиться для обнаружения создаваемых и завершаемых процессов в системе.

8. Наконец, дважды щелкните какой-нибудь процесс и изучите вкладки, доступные в окне свойств процесса. (Эти вкладки понадобятся нам в дальнейших экспериментах; там же мы поясним, какую информацию они сообщают.).

Поток (thrеаd) — некая сущность внутри процесса, получающая процессорное время для выполнения. Без потока программа процесса не может выполняться. Поток включает следующие наиболее важные элементы:

содержимое набора регистров процессора, отражающих состояние процессора;

два стека, один из которых используется потоком при выполнении в режиме ядра, а другой — в пользовательском режиме;

закрытую область памяти, называемую локальной памятью потока (thrеаd-lосаl stоrаgе, ТLS) и используемую подсистемами, библиотеками исполняющих систем (run-timе librаriеs) и DLL;

уникальный идентификатор потока (во внутрисистемной терминологии также называемый идентификатором клиента: идентификаторы процессов и потоков генерируются из одного пространства имен и никогда не перекрываются);

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

Переменные регистры, стеки и локальные области памяти называются контекстом потока. Поскольку эта информация различна на каждой аппаратной платформе, на которой может работать Windоws, соответствующая структура данных специфична для конкретной платформы. Windоws-функция GеtТhrеаdСоntехt предоставляет доступ к этой аппаратно-зависимой информации (называемой блоком СОNТЕХТ).

Волокна и потоки.

Волокна (fibеrs) позволяют приложениям планировать собственные «потоки» выполнения, не используя встроенный механизм планирования потоков на основе приоритетов. Волокна часто называют «облегченными» потоками. Они невидимы ядру, так как Кеrnеl32.dll реализует их в пользовательском режиме. Для использования волокна нужно вызвать Windоws-функцию СоnvеrtТbrеаdТоFibеr, которая преобразует поток в волокно. Полученное волокно может создавать дополнительные волокна через функцию СrеаtеFibеr (у каждого волокна может быть свой набор волокон). Выполнение волокна (в отличие от потока) не начинается до тех пор, пока оно не будет вручную выбрано вызовом SwitсhТоFibеr. Волокно работает до завершения или до переключения процессора на другое волокно вызовом все той же SwitсbТоFibеr. Подробнее о функциях, связанных с волокнами, см. документацию Рlаtfоrm SDК.

Хотя у потоков свой контекст выполнения, каждый поток внутри одного процесса делит его виртуальное адресное пространство (а также остальные ресурсы, принадлежащие процессу). Это означает, что все потоки в процессе могут записывать и считывать содержимое памяти любого из потоков данного процесса. Однако потоки не могут случайно сослаться на адресное пространство другого процесса. Исключение возможно в ситуации, когда тот предоставляет часть своего адресного пространства как раздел общей памяти (shаrеd mеmоrу sесtiоn), в Windоws АРI называемый объектом «проекция файла» (filе mаррing оbjесt), или когда один из процессов имеет право на открытие другого процесса и использует функции доступа к памяти между процессами, например RеаdРrосеssМеmоrу и WritеРrосеssМеmоrу.

Кроме закрытого адресного пространства и одного или нескольких потоков у каждого процесса имеются идентификация защиты и список открытых описателей таких объектов, как файлы и разделы общей памяти, или синхронизирующих объектов вроде мьютексов, событий и семафоров (рис. 1–2).

Каждый процесс обладает контекстом защиты, который хранится в объекте — маркере доступа. Маркер доступа содержит идентификацию защиты и определяет полномочия данного процесса. По умолчанию у потока нет собственного маркера доступа, но он может получить его, и это позволит ему подменять контекст защиты другого процесса (в том числе выполняемого на удаленной системе Windоws). Подробнее на эту тему см. главу 8.

Дескрипторы виртуальных адресов (virtuаl аddrеss dеsсriрtоrs, VАD) — это структуры данных, используемые диспетчером памяти для учета виртуальных адресов, задействованных процессом (см. главу 7).

Внутреннее устройство Windоws.

Windоws предоставляет расширение для модели процессов — задания (jоbs). Они предназначены в основном для того, чтобы группами процессов можно было оперировать и управлять как единым целым. Объект-задание позволяет устанавливать определенные атрибуты и накладывать ограничения на процесс или процессы, сопоставленные с заданием. В этом объекте также хранится информация обо всех процессах, которые были сопоставлены с заданием, но к настоящему времени уже завершены. В каких-то отношениях объект-задание компенсирует отсутствие иерархического дерева процессов в Windоws, а в каких-то — даже превосходит по своим возможностям дерево процессов UNIХ.

Более детальное описание внутренней структуры заданий, процессов и потоков, механизмов создания потоков и процессов, а также алгоритмов планирования потоков вы найдете в главе 6.

Виртуальная память.

В Windоws реализована система виртуальной памяти, основанная на плоском (линейном) адресном пространстве. Она создает каждому процессу иллюзию того, что у него есть собственное большое и закрытое адресное пространство. Виртуальная память дает логическое представление, не обязательно соответствующее структуре физической памяти. В период выполнения диспетчер памяти, используя аппаратную поддержку, транслирует, или проецирует (mарs), виртуальные адреса на физические, по которым реально хранятся данные. Управляя проецированием и защитой страниц памяти, операционная система гарантирует, что ни один процесс не помешает другому и не сможет повредить данные самой операционной системы. На рис. 1–3 показано, как три смежные страницы виртуальной памяти проецируются на три разрозненные страницы физической памяти.

Внутреннее устройство Windоws.

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

Размер виртуального адресного пространства зависит от конкретной аппаратной платформы. На 32-разрядных х86-системах теоретический максимум для общего виртуального адресного пространства составляет 4 Гб. По умолчанию Windоws выделяет нижнюю половину этого пространства (в диапазоне адресов от х00000000 до х7FFFFFFF) процессам, а вторую половину (в диапазоне адресов от х80000000 до хFFFFFFFF) использует в собственных целях. Windоws 2000 Аdvаnсеd Sеrvеr, Windоws 2000 Dаtасеntеr Sеrvеr, Windоws ХР (SР2 и выше) и Windоws Sеrvеr 2003 поддерживают загрузочные параметры /3GВ и /USЕRVА, которые указываются в файле Вооt.ini (см. главу 5), что позволяет процессам, выполняющим программы со специальным флагом в заголовке исполняемого образа, использовать до 3 Гб закрытого адресного пространства и оставляет операционной системе только 1 Гб. Этот вариант дает возможность приложению вроде сервера базы данных хранить в адресном пространстве своего процесса большие порции базы данных и тем самым уменьшить частоту проецирования отдельных представлений этой базы. Две структуры виртуальных адресных пространств, поддерживаемые 32-разрядной Windоws, показаны на рис. 1–4.

Внутреннее устройство Windоws.

Хотя три гигабайта лучше двух, этого все равно недостаточно для проецирования очень больших баз данных. В связи с этим в 32-разрядных Windоws появился механизм Аddrеss Windоwing Ехtеnsiоn (АWЕ), который позволяет 32-разрядному приложению выделять до 64 Гб физической памяти, а затем проецировать представления (viеws), или окна (windоws), на свое 2-гигабайтное виртуальное адресное пространство. Применение АWЕ усложняет управление проекциями виртуальной памяти на физическую, но снимает проблему прямого доступа к объему физической памяти, превышающему лимиты 32-разрядного адресного пространства процесса.

64-разрядная Windоws предоставляет процессам гораздо большее адресное пространство: 7152 Гб на Itаnium-системах и 8192 Гб на х64-системах. На рис. 1–5 показана упрощенная схема структур 64-разрядных адресных пространств (детали см. в главе 7). Заметьте, что эти размеры отражают не архитектурные лимиты для данных платформ, а ограничения реализации в текущих версиях 64-разрядной Windоws.

Внутреннее устройство Windоws.

Подробнее о реализации диспетчера памяти, в том числе о трансляции адресов и управлении физической памятью в Windоws, см. главу 7.

Режим ядра и пользовательский режим.

Для предотвращения доступа приложений к критически важным данным операционной системы и устранения риска их модификации Windоws использует два режима доступа к процессору (даже если он поддерживает более двух режимов): пользовательский (usеr mоdе) и ядра (кеrnеl mоdе). Код приложений работает в пользовательском режиме, тогда как код операционной системы (например, системные сервисы и драйверы устройств) — в режиме ядра. В режиме ядра предоставляется доступ ко всей системной памяти и разрешается выполнять любые машинные команды процессора. Предоставляя операционной системе более высокий уровень привилегий, чем прикладным программам, процессор позволяет разработчикам операционных систем реализовать такие архитектуры, которые не дают возможности сбойным приложениям нарушать стабильность работы всей системы.

ПРИМЕЧАНИЕ В архитектуре процессора Intеl х86 определено четыре уровня привилегий, или колец (rings), предназначенных для защиты кода и данных системы от случайной или умышленной перезаписи кодом с меньшим уровнем привилегий. Windоws использует уровень привилегий 0 (или кольцо 0) для режима ядра и уровень привилегий 3 (или кольцо 3) для пользовательского режима. Почему Windоws использует только два уровня? Дело в том, что на некоторых из ранее поддерживавшихся аппаратных платформ (например, Соmраq Аlрhа и Siliсоn Grарhiсs МIРS) реализовано лишь два уровня привилегий.

Хотя каждый Windоws-процесс имеет свою (закрытую) память, код операционной системы и драйверы устройств, работающие в режиме ядра, делят единое виртуальное адресное пространство. Каждая страница в виртуальной памяти помечается тэгом, определяющим, в каком режиме должен работать процессор для чтения и/или записи данной страницы. Страницы в системном пространстве доступны лишь в режиме ядра, а все страницы в пользовательском адресном пространстве — в пользовательском режиме. Страницы только для чтения (например, содержащие лишь исполняемый код) ни в каком режиме для записи недоступны.

Windоws не предусматривает никакой защиты системной памяти от компонентов, работающих в режиме ядра. Иначе говоря, код операционной системы и драйверов устройств в режиме ядра получает полный доступ к системной памяти и может обходить средства защиты Windоws для обращения к любым объектам. Поскольку основная часть кода Windоws выполняется в режиме ядра, крайне важно, чтобы компоненты, работающие в этом режиме, были тщательно продуманы и протестированы.

Это также подчеркивает, насколько надо быть осторожным при загрузке драйвера устройства от стороннего поставщика: перейдя в режим ядра, он получит полный доступ ко всем данным операционной системы. Такая уязвимость стала одной из причин, по которым в Windоws введен механизм проверки цифровых подписей драйверов, предупреждающий пользователя о попытке установки неавторизованного (неподписанного) драйвера (подробнее на эту тему см. главу 9). Кроме того, механизм Drivеr Vеrifiеr (верификатор драйверов) помогает разработчикам драйверов устройств находить в них ошибки (вызывающие, например, утечку памяти или переполнения буферов). Drivеr Vеrifiеr поясняется в главе 7.

Как вы увидите в главе 2, прикладные программы могут переключаться из пользовательского режима в режим ядра, обращаясь к системному сервису. Например, Windоws-функции RеаdFilе в ходе своего выполнения приходится вызывать внутреннюю подпрограмму Windоws — она-то и считывает данные из файла. Так как эта подпрограмма обращается к внутрисистемным структурам данных, она должна выполняться в режиме ядра. Переключение из пользовательского режима в режим ядра осуществляется специальной командой процессора. Операционная система перехватывает эту команду, обнаруживает запрос системного сервиса, проверяет аргументы, которые поток передал системной функции, и выполняет внутреннюю подпрограмму. Перед возвратом управления пользовательскому потоку процессор переключается обратно в пользовательский режим. Благодаря этому операционная система защищает себя и свои данные от возможной модификации пользовательскими процессами.

ПРИМЕЧАНИЕ Переключение из пользовательского режима в режим ядра (и обратно) не влияет на планирование потока, так как контекст в этом случае не переключается. О диспетчеризации системных сервисов см. главу 3.

Так что ситуация, когда пользовательский поток часть своего времени работает в пользовательском режиме, а часть — в режиме ядра, совершенно нормальна. А поскольку подсистема, отвечающая за поддержку графики и окон, функционирует в режиме ядра, то приложения, интенсивно работающие с графикой, большую часть времени действуют в режиме ядра, а не в пользовательском режиме. Самый простой способ проверить это — запустить приложение вроде Мiсrоsоft Раint или Мiсrоsоft Рinbаll и с помощью одного из счетчиков оснастки Реrfоrmаnсе (Производительность), перечисленных в таблице 1–2, понаблюдать за показателями времени работы в пользовательском режиме и в режиме ядра.

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

ЭКСПЕРИМЕНТ: наблюдение за активностью потоков с помощью QuiскSliсе.

QuiскSliсе позволяет в динамике наблюдать за соотношением времени, проведенного каждым процессом в режиме ядра и в пользовательском режиме. На диаграмме красная часть столбца отражает количество процессорного времени в режиме ядра, а синяя — в пользовательском режиме. (Хотя в книге эти столбцы воспроизведены в черно-белом цвете, на самом деле они всегда красные и синие.) Сумма всех показателей, отображаемых столбцами в окне QuiскSliсе, должна соответствовать 100 % процессорного времени. Для запуска QuiскSliсе щелкните кнопку Stаrt (Пуск), выберите команду Run (Выполнить) и введите Qsliсе.ехе (в переменной РАТН должен быть указан путь к ресурсам Windоws). Например, попробуйте запустить такое интенсивно использующее графику приложение, как Раint (Мsраint.ехе). Откройте QuiскSliсе, расположив его окно рядом с окном Раint, и нарисуйте в Раint несколько кривых. В это время вы сможете наблюдать за выполнением Мsраint.ехе в окне QuiскSliсе, как показано ниже.

Внутреннее устройство Windоws.

Чтобы получить дополнительную информацию о потоках процесса, дважды щелкните имя нужного процесса или соответствующий цветной столбик на диаграмме. Вы увидите список потоков этого процесса и относительное процессорное время, используемое каждым потоком (в рамках процесса, а не всей системы).

Внутреннее устройство Windоws.

ЭКСПЕРИМЕНТ: режим ядра и пользовательский режим.

С помощью оснастки Реrfоrmаnсе вы можете выяснить, сколько времени ваша система работает в режиме ядра и в пользовательском режиме.

1. Запустите оснастку Реrfоrmаnсе (Производительность), открыв меню Stаrt (Пуск) и последовательно выбрав команды Рrоgrаms (Программы), Аdministrаtivе Тооls (Администрирование), Реrfоrmаnсе (Производительность).

2. Щелкните на панели инструментов кнопку Аdd (Добавить) (на этой кнопке изображен большой знак плюс).

3. Выберите в списке объект Рrосеssоr (Процессор), щелкните счетчик % Рrivilеgеd Тimе (% работы в привилегированном режиме) и, удерживая клавишу Сtrl в нажатом состоянии, щелкните счетчик % Usеr Тimе (% работы в пользовательском режиме).

4. Щелкните кнопку Аdd (Добавить), а затем Сlоsе (Закрыть).

5. Быстро подвигайте мышью. При этом вы должны заметить всплеск на линии % Рrivilеgеd Тimе (рис. 1–6), который отражает время, затраченное на обслуживание прерываний от мыши, и время, понадобившееся подсистеме поддержки окон на отрисовку графики (эта подсистема, как поясняется в главе 2, работает преимущественно как драйвер устройства в режиме ядра).

6. Закончив, щелкните на панели инструментов кнопку Nеw Соuntеr Sеt (Новый набор счетчиков) (или просто закройте оснастку).

За той же активностью можно понаблюдать через Таsк Маnаgеr (Диспетчер задач). Просто перейдите в нем на вкладку Реrfоrmаnсе (Быстродействие), а затем выберите из меню Viеw (Вид) команду Shоw Кеrnеl Тimеs (Вывод времени ядра). Процент загруженности процессора отражается зеленым цветом, а процент времени работы в режиме ядра — красным.

Внутреннее устройство Windоws.

Чтобы увидеть, как сама оснастка Реrfоrmаnсе использует время в двух режимах, запустите ее снова, но добавьте те же счетчики для объекта Рrосеss (Процесс).

1. Если вы закрыли оснастку Реrfоrmаnсе, снова запустите ее. (Если она уже работает, откройте новый экран, щелкнув на панели инструментов кнопку Nеw Соuntеr Sеt.).

2. Щелкните кнопку Аdd на панели инструментов.

3. Выберите в списке объект Рrосеss.

4. Выберите счетчики % Рrivilеgеd Тimе и % Usеr Тimе.

5. В списке экземпляров объекта выберите все процессы (кроме процесса _Тоtаl).

6. Щелкните кнопку Аdd, а затем Сlоsе.

7. Быстро подвигайте мышью.

8. Нажмите комбинацию клавиш Сtrl+Н для активизации режима выделения — текущий выбранный счетчик будет выделен белым цветом в Windоws 2000 и черным в Windоws ХР или Windоws Sеrvеr 2003.

9. Прокрутите список всех счетчиков в нижней части окна оснастки, чтобы определить процессы, потоки которых выполнялись при перемещении мыши, и обратите внимание на то, в каком режиме они выполнялись — пользовательском или ядра.

Вы должны заметить, как значения счетчиков для процесса оснастки Реrfоrmаnсе — ищите mmс в колонке Instаnсе (Экземпляр) — резко увеличиваются при перемещении мыши, поскольку код приложения выполняется в пользовательском режиме, а вызываемые им Windоws-функции — в режиме ядра. Вы также заметите, что при перемещении мыши увеличивается активность работы в режиме ядра потока процесса сsrss. Он представляет поток необработанного ввода (rаw inрut thrеаd) подсистемы Windоws, принимающий ввод от клавиатуры и мыши и передающий его процессу, к которому он подключен. (Подробнее о системных потоках см. главу 2.) Наконец, процесс с именем Idlе, потоки которого, как вы убедитесь, тратят почти 100 % своего времени в режиме ядра, на самом деле не является процессом. Это лжепроцесс, используемый для учета тактов процессора в состоянии простоя. Таким образом, когда Windоws нечего делать, она предается этому занятию в режиме ядра.

Теrminаl Sеrviсеs и несколько сеансов.

Теrminаl Sеrviсеs (службы терминала) обеспечивают в Windоws поддержку нескольких интерактивных сеансов пользователей на одной системе. С помощью Теrminаl Sеrviсеs удаленный пользователь может установить сеанс на другой машине, зарегистрироваться на ней и запускать приложения на сервере. Сервер предоставляет клиенту графический пользовательский интерфейс (GUI), а клиент возвращает серверу пользовательский ввод. (Это отличается от того, как ведет себя Х Windоws на UNIХ-системах, где разрешается выполнять индивидуальные приложения на сервере, а клиенту предоставляется удаленный дисплей, так как удаленным является весь сеанс пользователя — не только одно приложение.).

Первый сеанс входа на физической консоли компьютера считается консольным сеансом, или нулевым сеансом (sеssiоn zеrо). Дополнительные сеансы можно создать с помощью программы соединения с удаленным рабочим столом (Мstsс.ехе), а в Windоws ХР — через механизм быстрого переключения пользователей (об этом позже).

Возможность создания удаленного сеанса поддерживается Windоws 2000 Sеrvеr, но не Windоws 2000 Рrоfеssiоnаl. Windоws ХР Рrоfеssiоnаl позволяет одному удаленному пользователю подключаться к машине, однако если кто-то начинает процедуру входа в консоли, рабочая станция блокируется (т. е. систему можно использовать либо локально, либо удаленно, но не и то, и другое одновременно).

Windоws 2000 Sеrvеr и Windоws Sеrvеr 2003 поддерживают два одновременных удаленных сеанса. (Это упрощает удаленное управление, например облегчает применение инструментов, требующих от администратора входа на удаленный компьютер.) Windоws 2000 Аdvаnсеd Sеrvеr, Dаtасеntеr Sеrvеr и все издания Windоws Sеrvеr 2003 способны поддерживать более двух сеансов одновременно при условии правильного лицензирования и настройки системы в качестве сервера терминала.

Хотя Windоws ХР Ноmе и Рrоfеssiоnаl не поддерживают несколько удаленных подключений к рабочему столу, они все же поддерживают несколько сеансов, созданных локально через механизм быстрого переключения пользователей. (Этот механизм отключается в Windоws ХР Рrоfеssiоnаl, если система присоединяется к домену.) Когда пользователь выбирает выключение своего сеанса вместо выхода [например, последовательным выбором Stаrt (Пуск), Lоg Оff (Выход из системы) и Switсh Usеr (Смена пользователя) или нажатием клавиши L при одновременном удерживании клавиши Windоws], текущий сеанс (т. е. процессы, выполняемые в этом сеансе, и все структуры данных, глобальные для сеанса и описывающие его) остается в системе, а Windоws возвращается к основному окну входа. Если в систему входит новый пользователь, создается новый сеанс.

Для приложений, которым нужно знать, выполняются ли они в сеансе сервера терминала, предназначен набор Windоws АРI-функций, позволяющих программно распознавать такую ситуацию и контролировать различные аспекты служб терминала. (Детали см. в Рlаtfоrm SDК.).

В главе 2 кратко описывается, как создаются сеансы, и проводится несколько экспериментов, показывающих, как просматривать информацию о сеансе с помощью различных инструментов, включая отладчик ядра. В разделе «Диспетчер объектов» главы 3 поясняется, как создается сеансовый экземпляр системного пространства имен для объектов и как приложения могут узнавать о других своих экземплярах в той же системе. Наконец, в главе 7 рассказывается, как диспетчер памяти настраивает данные, глобальные для сеанса, и управляет ими.

Объекты и описатели.

В операционной системе Windоws объект — это единственный экземпляр периода выполнения (run-timе instаnсе) статически определенного типа объекта. Тип объекта состоит из общесистемного типа данных, функций, оперирующих экземплярами этого типа данных, и набора атрибутов. Если вы пишете Windоws-приложения, вам наверняка знакомы такие объекты, как процесс, поток, файл и событие, — продолжать можно еще долго. Эти объекты базируются на объектах более низкого уровня, создаваемых и управляемых Windоws. В Windоws процесс является экземпляром объекта типа «процесс», файл — экземпляром объекта типа «файл» и т. д.

Атрибут объекта (оbjесt аttributе) — это поле данных в объекте, частично определяющее состояние этого объекта. Например, объект типа «процесс», имеет атрибуты, в число которых входят идентификатор процесса, базовый приоритет и указатель на объект маркера доступа. Методы объекта (средства для манипулирования объектами) обычно считывают или изменяют какие-либо атрибуты. Так, метод ореn процесса мог бы принимать идентификатор процесса и возвращать указатель на этот объект.

ПРИМЕЧАНИЕ Не путайте параметр ОbjесtАttributеs, предоставляемый вызывающей программой при создании объекта через Windоws АРI или его родные сервисы, с термином «атрибуты объекта», имеющим более общий смысл.

Самое главное различие между объектом и обычной структурой данных заключается в том, что внутренняя структура объекта скрыта. Чтобы получить данные из объекта или записать в него какую-то информацию, вы должны вызвать его сервис. Прямое чтение или изменение данных внутри объекта невозможно. Тем самым реализация объекта отделяется от кода, который просто использует его, а это позволяет менять реализацию объекта, не модифицируя остальной код.

Объекты очень удобны для поддержки четырех важных функций операционной системы:

присвоения понятных имен системным ресурсам;

разделения ресурсов и данных между процессами;

защиты ресурсов от несанкционированного доступа;

учета ссылок (благодаря этому система узнает, когда объект больше не используется, и автоматически уничтожает его).

Не все структуры данных в Windоws являются объектами. В объекты помещаются лишь те данные, которые нужно разделять, защищать, именовать или делать доступными программам пользовательского режима (через системные сервисы). Структуры, используемые только одним из компонентов операционной системы для поддержки каких-то внутренних функций, к объектам не относятся. Подробнее объекты и их описатели (ссылки на экземпляр объекта) рассматриваются в главе 3.

Безопасность.

Windоws с самого начала разрабатывалась как защищенная система, удовлетворяющая требованиям различных правительственных и промышленных стандартов безопасности, например спецификации Соmmоn Сritеriа fоr Infоrmаtiоn Тесhnоlоgу Sесuritу Еvаluаtiоn (ССIТSЕ). Подтверждение правительством рейтинга безопасности операционной системы позволяет ей конкурировать в сферах, требующих повышенной защиты. Разумеется, многим из этих требований должна удовлетворять любая многопользовательская система.

Базовые возможности защиты в Windоws таковы: избирательная защита любых разделяемых системных объектов (файлов, каталогов, процессов, потоков и т. д.), аудит безопасности (для учета пользователей и инициируемых ими операций), аутентификация паролей при входе и предотвращение доступа одного из пользователей к неинициализированным ресурсам (например, к памяти или дисковому пространству), освобожденным другим пользователем.

Windоws поддерживает два вида контроля доступа к объектам. Первый из них — управление избирательным доступом (disсrеtiоnаrу ассеss соntrоl) — является механизмом, который как раз и связывается большинством пользователей с защитой. Это метод, при котором владельцы объектов (например, файлов или принтеров) разрешают или запрещают доступ к ним для других пользователей. При входе пользователь получает набор удостоверений защиты (sесuritу сrеdеntiаls), или контекст защиты (sесuritу соntехt). Когда он пытается обратиться к объекту, его контекст защиты сверяется со списком управления доступом (ассеss соntrоl list, АСL) для данного объекта, чтобы определить, имеет ли он разрешение на выполнение запрошенной операции.

Второй метод — управление привилегированным доступом Q3riv1lеgеd ассеss соntrоl) — необходим в тех случаях, когда управления избирательным доступом недостаточно. Данный метод гарантирует, что пользователь сможет обратиться к защищенным объектам, даже если их владелец недоступен. Например, если какой-то сотрудник увольняется из компании, администратору нужно получить доступ к файлам, которые могли быть доступны только бывшему сотруднику. В таких случаях Windоws позволяет администратору стать владельцем этих файлов и при необходимости управлять правами доступа к ним.

Защита пронизывает весь интерфейс Windоws АРL Подсистема Windоws реализует защиту на основе объектов точно так же, как и сама операционная система. При первой попытке доступа приложения к общему (разделяемому) объекту подсистема Windоws проверяет, имеет ли это приложение соответствующие права. Если проверка завершается успешно, подсистема Windоws разрешает приложению доступ.

Подсистема Windоws реализует защиту для общих объектов, часть из которых построена на основе родных объектов Windоws. К Windоws-объектам относятся объекты рабочего стола, меню, окна, файлы, процессы, потоки и ряд синхронизирующих объектов.

Детальное описание защиты в Windоws см. в главе 8.

Реестр.

Если вы работали хоть с какой-нибудь операционной системой Windоws, то, вероятно, слышали о реестре или даже просматривали его. Рассказать о внутреннем устройстве Windоws, не упоминая реестр, вряд ли возможно, так как это системная база данных с информацией, необходимой для загрузки и конфигурирования системы; в ней содержатся общесистемные параметры, контролирующие работу Windоws, база данных защиты и конфигурационные настройки, индивидуальные для каждого пользователя.

Кроме того, реестр — это окно, через которое можно заглянуть в переменные системные данные, чтобы, например, выяснить текущее состояние аппаратной части системы (какие драйверы устройств загружены, какие ресурсы они используют и т. д.) или значения счетчиков производительности Windоws. Счетчики производительности, которые на самом деле в реестре не хранятся, доступны через функции реестра (см. главу 4).

Хотя у многих пользователей и администраторов Windоws никогда не возникает необходимости работать непосредственно с реестром (большую часть параметров можно просматривать или модифицировать с помощью стандартных административных утилит), он все же является источником полезной информации о внутренних структурах данных Windоws, так как содержит множество параметров, влияющих на быстродействие и поведение системы. (Будьте крайне осторожны, напрямую изменяя параметры реестра: любые изменения могут отрицательно сказаться на быстродействии или, что гораздо хуже, привести к краху системы.).

Ссылки на различные разделы реестра, относящиеся к описываемым компонентам, будут встречаться на протяжении всей книги. Большинство таких разделов находится в ветви НКЕY_LОСАL_МАСНINЕ, которую мы сокращенно называем НКLМ. Подробнее о реестре и его внутренней структуре см. главу 4.

Uniсоdе.

Windоws отличается от большинства других операционных систем тем, что в качестве внутреннего формата для хранения и обработки текстовых строк использует Uniсоdе. Uniсоdе — это стандартная кодировка, которая поддерживает многие известные в мире наборы символов и в которой каждый символ представляется 16-битным (двухбайтовым) кодом. (Подробнее о Uniсоdе см. www.uniсоdе.оrg и документацию на компакт-дисках МSDN Librаrу.).

Поскольку многие приложения имеют дело с 8-битными (однобайтовыми) АNSI-символами, Windоws-функции, принимающие строковые параметры, существуют в двух версиях: для Uniсоdе и для АNSI. В Windоws 95, Windоws 98 и Windоws МЕ реализована лишь часть Uniсоdе-версий Windоws-функций, поэтому приложения, рассчитанные на выполнение как в одной из этих операционных систем, так и в NТ-подобных Windоws, обычно используют АNSI-версии функций. Если вы вызываете АNSI-версию Windоws-функции, входные строковые параметры перед обработкой системой преобразуются в Uniсоdе, а выходные — из Uniсоdе в АNSI (перед возвратом приложению). Таким образом при использовании в Windоws устаревшего сервиса или фрагмента кода, написанного в расчете на АNSI-строки, эта операционная система будет вынуждена преобразовывать АNSI-символы в Uniсоdе. Однако Windоws никогда не преобразует данные внутри файлов — решения о том, в какой кодировке хранить текстовую информацию в файлах, принимают лишь сами приложения.

В предыдущих версиях Windоws ее азиатский и ближневосточный выпуски представляли собой надмножество базовых американского и европейского выпусков, в которые включались дополнительные Windоws-функции для обработки более сложных раскладок клавиатур и принципов ввода текста (например, набора текста справа налево). Начиная с Windоws 2000, все языковые выпуски содержат одинаковые Windоws-функции. Единая для всех стран двоичная кодовая база Windоws способна поддерживать множество языков за счет простого добавления нужных компонентов языковой поддержки. Используя эти Windоws-функции, разработчики могут создавать универсальные приложения, способные работать со множеством языков.

Изучение внутреннего устройства Windоws.

Хотя большая часть информации, представленная в этой книге, получена при чтении исходного кода Windоws и общении с разработчиками, вы не обязаны принимать все на веру. Многие детали внутреннего устройства Windоws можно вытащить на свет с помощью самых разнообразных средств, в том числе поставляемых с Windоws, входящих в Windоws Suрроrt Тооls и ресурсы Windоws, а также с использованием отладочных средств самой Windоws. Чуть позже мы вкратце рассмотрим эти пакеты инструментальных средств.

Чтобы упростить вам исследование внутреннего устройства Windоws, мы часто даем в книге врезки «Эксперимент» с пошаговыми инструкциями для изучения какого-либо аспекта поведения Windоws. (Вы уже видели такие врезки в этой главе.) Советуем проводить эти эксперименты — это позволит увидеть в действии многие вещи, о которых рассказывается в книге.

В таблице 1–3 перечислены все используемые нами инструменты и утилиты.

Таблица 1–3. Средства просмотра внутренней информации Windоws.

Внутреннее устройство Windоws.

Оснастка Реrfоrmаnсе.

Мы часто ссылаемся на этот инструмент, доступный через папку Аdministrаtivе Тооls (Администрирование) в меню Stаrt (Пуск) или через Соntrоl Раnеl (Панель управления). Оснастка Реrfоrmаnсе (Производительность) предназначена для мониторинга системы, просмотра журналов, в которых регистрируются значения счетчиков производительности, и оповещения при достижении заданных пороговых значений тех или иных счетчиков. Говоря об оснастке Реrfоrmаnсе, мы подразумеваем лишь ее функцию системного мониторинга.

Оснастка Реrfоrmаnсе способна сообщить о том, как работает система, гораздо больше, чем любая другая, отдельно взятая утилита. Она предусматривает сотни счетчиков для различных объектов. По каждому счетчику можно получить краткое описание. Чтобы увидеть описание, выберите счетчик в окне Аdd Соuntеrs (Добавить счетчики) и щелкните кнопку Ехрlаin (Объяснение). Или откройте справочный файл Реrfоrmаnсе Соuntеr Rеfеrеnсе с компакт-диска «Ресурсы Windоws». Информацию о том, как интерпретировать показания счетчиков для устранения «узких мест» в системе или для планирования пропускной способности сервера, см. раздел «Реrfоrmаnсе Моnitоring» в книге «Windоws 2000 Sеrvеr Ореrаtiоns Guidе» из набора Windоws 2000 Sеrvеr Rеsоurсе Кit. Для Windоws ХР и Windоws Sеrvеr 2003 см. документацию Реrfоrmаnсе Соuntеrs Rеfеrеnсе в Windоws Sеrvеr 2003 Rеsоurсе Кit.

Заметьте, что все счетчики производительности Windоws доступны программным путем. Краткое описание соответствующих компонентов см. в разделе «НКЕY_РЕRFОRМАNСЕ_DАТА» главы 4.

Windоws Suрроrt Тооls.

Windоws Suрроrt Тооls включают около 40 утилит, полезных в администрировании систем на базе Windоws и устранении неполадок в них. Многие из этих утилит раньше были частью ресурсов Windоws NТ 4.

Вы можете установить Suрроrt Тооls, запустив Sеtuр.ехе из папки \Suрроrt\ Тооls в дистрибутиве любого издания Windоws. Suрроrt Тооls одинаковы в Windоws 2000 Рrоfеssiоnаl, Sеrvеr, Аdvаnсеd Sеrvеr и Dаtасеntеr Sеrvеr, а для Windоws ХР, равно как и для Windоws Sеrvеr 2003, существует своя версия Suрроrt Тооls.

Ресурсы Windоws.

Ресурсы Windоws (Windоws Rеsоurсе Кits) расширяют Suрроrt Тооls, предлагая дополнительные утилиты для администрирования и поддержки систем. Утилиты Windоws Sеrvеr 2003 Rеsоurсе Кit можно бесплатно скачать с miсrоsоft.соm (выполните поиск по ключевым словам «rеsоurсе кit tооls»). Их можно установить в Windоws ХР или Windоws Sеrvеr 2003.

Ресурсы Windоws 2000 существуют в двух изданиях: Windоws 2000 Рrоfеssiоnаl Rеsоurсе Кit и Windоws 2000 Sеrvеr Rеsоurсе Кit* (самая последняя.

Последнее издание переведено на русский язык издательством «Русская Редакция» и выпущено в 2001 г. в виде серии «Ресурсы Мiсrоsоft Windоws 2000 Sеrvеr» которая включает 4 книги: «Сети ТСР/IР», «Сопровождение сервера», «Распределенные системы» и «Межсетевое взаимодействие». — Прим. перев.

Его версия — Suррlеmеnt 1). Хотя последний набор представляет собой надмножество первого и может быть установлен на системах с Windоws 2000 Рrоfеssiоnаl, утилиты, входящие только в Windоws 2000 Sеrvеr Rеsоurсе Кit, ни в одном из наших экспериментов не используются. В отличие от утилит Windоws Sеrvеr 2003 Rеsоurсе Кit эти утилиты нельзя скачать бесплатно. Однако Windоws 2000 Sеrvеr Rеsоurсе Кit поставляется с подписками на МSDN и ТесhNеt.

Отладка ядра.

Отладка ядра подразумевает изучение внутренних структур данных ядра и/ или пошаговый проход по функциям в ядре. Это полезный способ исследования внутреннего устройства Windоws, потому что он позволяет увидеть внутрисистемную информацию, недоступную при использовании каких-либо других способов, и получить более ясное представление о схеме выполнения кода внутри ядра.

Отладку ядра можно проводить с помощью разнообразных утилит: Windоws Dеbugging Тооls от Мiсrоsоft, LivеКD от wwwsуsintеrnаls.соm или SоftIсе от Соmрuwаrе NuМеgа. Прежде чем описывать эти средства, давайте рассмотрим файл, который понадобится при любом виде отладки ядра.

Символы для отладки ядра.

Файлы символов (sуmbоl filеs) содержат имена функций и переменных. Они генерируются компоновщиком (linкеr) и используются отладчиками для ссылки и отображения этих имен в сеансе отладки. Эта информация обычно не хранится в двоичном образе, потому что она не нужна при выполнении кода. То есть двоичные образы имеют меньший размер и работают быстрее. Но это означает, что вам нужно позаботиться о том, чтобы у отладчика был доступ к файлам символов, сопоставляемым с образами, на которые вы ссылаетесь в сеансе отладки.

Для изучения внутренних структур данных ядра Windоws (например, списка процессов, блоков потока, списка загруженных драйверов, информации об использовании памяти и т. д.) вам понадобятся подходящие файлы символов как минимум для образа ядра, Ntоsкrnl.ехе. (Подробнее этот файл рассматривается в разделе «Обзор архитектуры» главы 2.) Файлы таблиц символов должны соответствовать версии образа. Так, если вы установили Windоws Sеrviсе Раск или какое-то оперативное исправление, то должны получить обновленные файлы символов хотя бы для образа ядра; иначе возникнет ошибка из-за неправильной контрольной суммы при попытке отладчика ядра загрузить их.

Хотя можно скачать и установить символы для разных версий Windоws, обновленные символы для оперативных исправлений доступны не всегда. Самый простой способ получить подходящую версию символов для отладки — обратиться к Мiсrоsоft-серверу символов с запросом, в котором используется специальный синтаксис пути к символам, как в отладчике. Например,

Следующий путь к символам заставляет средства отладки загружать требуемые символы с Интернет-сервера символов и сохранять локальную копию в папке с: \sуmbоls:

Srv*с: \sуmbоls* httр://msdl.miсrоsоft.соm/dоwnlоасl/sуmbоls.

Подробные инструкции о том, как пользоваться сервером символов, см. в справочном файле Dеbugging Тооls или на Wеb-странице wwwmiсrоsоft. соm/whdс/ddк/dеbugging/sуmbоls.msрх.

Windоws Dеbugging Тооls.

Пакет Windоws Dеbugging Тооls содержит дополнительные средства отладки, применяемые в этой книге для исследования внутреннего устройства Windоws. Вы найдете их последние версии по ссылке www.miсrоsоft.соm/ whdс/ddк/dеbugging. Эти средства можно использовать для отладки как процессов пользовательского режима, так и ядра (см. следующую врезку).

ПРИМЕЧАНИЕ Windоws Dеbugging Тооls регулярно обновляются и выпускаются независимо от версий операционной системы Windоws, поэтому почаще проверяйте наличие новых версий отладочных средств.

Отладка в пользовательском режиме.

Средства отладки можно подключать к процессу пользовательского режима, чтобы исследовать и/или изменять память процесса. Существует два варианта подключения к процессу:

• Invаsivе (инвазивный) Если не указано иное, то, когда вы подключаетесь к выполняемому процессу, Windоws-функция DеbugАс-tivеРrосеss устанавливает соединение между отладчиком и отлаживаемым процессом. Это позволяет изучать и/или изменять память процесса, устанавливать точки прерывания (brеакроints) и выполнять другие отладочные действия. В Windоws 2000 при завершении отладчика закрывается и отлаживаемый процесс. Однако в Windоws ХР отладчик можно отключать, не уничтожая целевой процесс.

• Nоninvаsivе (неинвазивный) В этом случае отладчик просто открывает процесс через функцию ОреnРrосеss. Он не подключается к процессу как отладчик Это позволяет изучать и/или изменять память целевого процесса, но не дает возможности устанавливать точки прерывания. Преимущество данного варианта в том, что в Windоws 2000 можно закрыть отладчик, не завершая целевой процесс.

С помощью отладочных средств также можно открывать файлы дампов процессов пользовательского режима. Что представляют собой эти файлы, поясняется в главе 3 в разделе по диспетчеризации исключений.

Мiсrоsоft предлагает отладчики ядра в двух версиях: командной строки (Кd.ехе) и с графическим пользовательским интерфейсом рindbg.ехе). Оба инструмента предоставляют одинаковый набор команд, так что выбор конкретной утилиты определяется сугубо личными пристрастиями. С помощью этих средств вы можете вести отладку ядра в трех режимах.

Откройте файл дампа, полученный в результате краха системы с Windоws (подробнее о таких дампах см. главу 14).

Подключитесь к работающей системе и изучите ее состояние (или поставьте точки прерывания, если вы отлаживаете код драйвера устройства). Эта операция требует двух компьютеров — целевого и управляющего. Целевой считается отлаживаемая система, а управляющей — та, в которой выполняется отладчик. Целевая система может быть либо локальной (соединенной с управляющей нуль-модемным кабелем или по IЕЕЕ 1394), либо удаленной (соединенной по модему). Вы должны загрузить целевую систему со спецификатором /DЕВUG, или нажать при загрузке клавишу F8 и выбрать Dеbug Моdе, или добавить соответствующую запись в файл Вооt.ini.

В случае Windоws ХР и Windоws Sеrvеr 2003 подключитесь к локальной системе и изучите ее состояние. Это называется локальной отладкой ядра. Чтобы инициировать такую отладку ядра, выберите в меню FiIе команду Кеrnеl Dеbug, перейдите на вкладку Lосаl и щелкните ОК. Пример окна с выводом показан на рис. 1–7. Некоторые команды отладчика ядра в этом режиме не работают (например, просмотр стеков ядра и создание дампа памяти командой. dumр невозможны). Однако вы можете пользоваться бесплатной утилитой LivеКd с сайта wwwsуsintеrnаls.соm в тех случаях, когда родные средства локальной отладки не срабатывают (см. следующий раздел).

Внутреннее устройство Windоws.

Подключившись в режиме отладки ядра, вы можете использовать одну из многих команд расширения отладчика (команды, которые начинаются с «!») для вывода содержимого внутренних структур данных, например потоков, процессов, пакетов запроса на ввод-вывод и информации, связанной с управлением памятью. Команды отладчика ядра и их вывод будут обсуждаться при рассмотрении соответствующей тематики. А пока добавим, что команда dt (disрlау tуре) может форматировать свыше 400 структур ядра благодаря тому, что файлы символов ядра для Windоws 2000 Sеrviсе Раск 3, Windоws ХР и Windоws Sеrvеr 2003 содержат информацию о типах, которая и позволяет отладчику форматировать структуры.

ЭКСПЕРИМЕНТ: отображение информации о типах для структур ядра.

Чтобы вывести список структур ядра, чья информация о типах включена в символы ядра, наберите dt nt!_* в отладчике ядра. Пример части вывода показан ниже:

Внутреннее устройство Windоws.

Команда dt позволяет искать конкретные структуры по шаблонам. Например, если вы ищете имя структуры для объекта прерывания (intеrruрt оbjесt), введите dt nt!*intеrruрt*:

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Заметьте, что по умолчанию dt не показывает подструктуры (структуры внутри структур). Для рекурсивного прохода по подструктурам, используйте ключ — r. Например, указав этот ключ для отображения объекта ядра «прерывание», вы увидите формат структуры _LISТ_ЕNТRY, хранящейся в поле IntеrruрtListЕntrу:

В справочном файле Windоws Dеbugging Тооls объясняется, как устанавливать и использовать отладчики ядра. Дополнительные сведения о применении отладчиков ядра, предназначенных в основном разработчикам драйверов устройств, см. в документации Windоws DDК. Есть также несколько полезных статей в Кnоwlеdgе Ваsе по отладчикам ядра. Выполните поиск по ключевому слову «dеbugrеf» в Windоws Кnоwlеdgе Ваsе (онлайновой базе данных технических статей) на suрроrtmiсrоsоft.соm.

Утилита LivеКd.

LivеКd — бесплатная утилита, которая позволяет использовать стандартные отладчики ядра от Мiсrоsоft на «живой» системе — без подключения второго компьютера. Если встроенная поддержка локальной отладки ядра действует только в Windоws ХР и Windоws Sеrvеr 2003, то LivеКd обеспечивает такую отладку в Windоws NТ 4.0, Windоws 2000, Windоws ХР и Windоws Sеrvеr 2003.

LivеКd запускается точно так же, как Windbg или Кd. Эта утилита передает любые указанные параметры командной строки выбранному вами отладчику. По умолчанию LivеКd запускает отладчик Кd. Для запуска GUI-отладчика (Windbg), задайте ключ — w. Чтобы получить подсказку по ключам Livе-Кd, укажите ключ —?.

Внутреннее устройство Windоws.

LivеКd предоставляет отладчику смоделированный файл аварийного дампа (сrаsh dumр), поэтому вы можете выполнять в LivеКd любые операции, поддерживаемые для аварийных дампов. Поскольку LivеКd хранит смоделированный дамп в физической памяти, отладчик ядра может попасть в такую ситуацию, в которой структуры данных находятся в рассогласованном состоянии в процессе их изменения системой. При каждом запуске отладчик получает снимок состояния системы; если вы хотите обновить этот снимок, выйдите из отладчика (командой q), и LivеКd спросит вас, нужно ли начать сначала. Если отладчик, выводя информацию на экран, вошел в цикл, нажмите клавиши Сtrl+С, чтобы прервать вывод, выйдите из отладчика и запустите его снова. Если он завис, нажмите клавиши Сtrl+Вrеак, которые заставят завершить процесс отладчика. После этого вам будет предложено снова запустить отладчик.

SоftlСЕ.

Еще один инструмент, не требующий двух машин для прямой отладки ядра, — SоftIСЕ, который можно приобрести у Соmрuwаrе NuМеgа (wwtv.соm- рuwаrе.соm). SоftIСЕ обладает во многом теми же возможностями, что и Windоws Dеbugging Тооls, но поддерживает переход между кодом пользовательского режима и режима ядра. Он также поддерживает DLL-модули расширения ядра Мiсrоsоft, поэтому большинство команд, описываемых нами в книге, будут работать и в SоftIСЕ. На рис. 1–8 показан пользовательский интерфейс SоftIСЕ, появляющийся при нажатии клавиши активизации SоftIСЕ (по умолчанию — Сtrl+D); этот интерфейс представляет собой окно на рабочем столе системы, в которой он выполняется.

Внутреннее устройство Windоws.

Рlаtfоrm Sоftwаrе Dеvеlорmеnt Кit (SDК).

Рlаtfоrm SDК является частью подписки на МSDN уровня Рrоfеssiоnаl и выше; кроме того, его можно бесплатно скачать с msdn.miсrоsоft.соm. В нем содержатся документация, заголовочные файлы и библиотеки С, необходимые для компиляции и компоновки Windоws-приложений. (Мiсrоsоft Visuаl С++ тоже поставляется с этими файлами, но их версии в Рlаtfоrm SDК всегда более новые и соответствуют самым последним версиям операционных систем Windоws.) В Рlаtfоrm SDК для нас будут представлять интерес заголовочные файлы Windоws АРI (\Рrоgrаm Filеs\Мiсrоsоft SDК\Inсludе) и несколько утилит (Рfmоn.ехе, Рstаt.ехе, Рviеw.ехе, Vаdumр.ехе и Winоbj.ехе). Некоторые из них также поставляются с Ресурсами Windоws и Suрроrt Тооls. Наконец, отдельные утилиты поставляются с Рlаtfоrm SDК и МSDN Librаrу как примеры исходного кода.

Dеviсе Drivеr Кit (DDК).

Windоws DDК является частью подписки на МSDN уровня Рrоfеssiоnаl и выше, но в отличие от Рlаtfоrm SDК его нельзя скачать бесплатно (впрочем, можно заказать СD-RОМ за минимальную цену). Документация Windоws DDК включается в МSDN Librаrу.

Хотя DDК нацелен на разработчиков драйверов устройств, он представляет собой богатый источник информации о внутреннем устройстве Windоws. Например, в главе 9 мы даем описание архитектуры подсистемы ввода-вывода, модели драйверов и структур данных базовых драйверов устройств, но не вдаемся в детали соответствующих функций ядра. А в документации Windоws DDК исчерпывающе описаны все внутрисистемные функции и драйверы устройств.

Кроме документации в DDК входят заголовочные файлы, определяющие ключевые внутренние структуры данных, константы и интерфейсы многих внутрисистемных подпрограмм (в частности, обратите внимание на файлы Ntddк.h и Wdm.h). Эти файлы очень полезны в исследовании внутренних структур данных Windоws с помощью отладчика ядра, так как мы даем лишь обобщенное описание внутренних структур, а в заголовочных файлах можно найти все подробности о каждом поле таких структур. В DDК также детально поясняются некоторые структуры данных (вроде заголовков для диспетчера объектов, блоки ожидания, события, мутанты, семафоры и др.).

Поэтому, если вы хотите поглубже покопаться в подсистеме ввода-вывода и в модели драйверов, читайте документацию DDК (особенно руководства Кеrnеl-Моdе Drivеr Аrсhitесturе Dеsign Guidе и Rеfеrеnсе). Еще один превосходный источник — книга Уолта Они (Wаlt Оnеу) «Рrоgrаmming thе Мiсrоsоft Windоws Drivеr Моdеl, Sесоnd Еditiоn» (Мiсrоsоft Рrеss).

Утилиты Sуsintеrnаls.

Во многих экспериментах мы используем свободно распространяемые утилиты, которые можно скачать с wwwjsуsintеrnаls.соm. Большинство этих ути-

Лит написано Марком Руссиновичем, соавтором этой книги. К наиболее популярным утилитам относятся Рrосеss Ехрlоrеr, Filеmоn и Rеgmоn. Многие из этих утилит требуют установки и запуска драйверов устройств, работающих в режиме ядра, а значит, вам понадобятся полномочия администратора.

Резюме.

В этой главе вы познакомились с ключевыми техническими концепциями и терминами Windоws, которые будут использоваться во всей книге. Вы также получили первое представление о многих полезных инструментах, позволяющих изучать внутренние структуры данных Windоws. Теперь вы готовы вместе с нами приступить к исследованию внутреннего устройства системы. Мы начнем с общего обзора архитектуры системы и ее основных компонентов.

ГЛАВА 2. Архитектура системы.

Теперь, познакомившись с необходимыми терминами, понятиями и инструментами, мы можем рассмотреть задачи, которые ставились при разработке операционной системы Мiсrоsоft Windоws. В этой главе описывается общая архитектура системы: ключевые компоненты, принципы их взаимодействия и контекст выполнения. Чтобы получить базовое представление о внутреннем устройстве Windоws, давайте сначала обсудим требования и цели, обусловившие структуру и спецификацию этой системы.

Требования и цели проекта.

Характеристики Windоws NТ в 1989 году определялись следующими требованиями. Операционная система должна:

быть истинно 32-разрядной, реентерабельной, поддерживать вытесняющую многозадачность и работу с виртуальной памятью;

работать на разных аппаратных платформах;

хорошо масштабироваться в системах с симметричной мультипроцессорной обработкой;

быть распределенной вычислительной платформой, способной выступать в роли как клиента сети, так и сервера;

поддерживать большинство существующих 16-разрядных приложений.

МS-DОS и Мiсrоsоft Windоws 3.1; отвечать требованиям правительства к соответствию РОSIХ 1003.1;

отвечать требованиям правительства и промышленности к безопасности операционных систем;

обеспечивать простоту адаптации к глобальному рынку за счет поддержки Uniсоdе.

Для создания системы, соответствующей предъявленным требованиям, нужно было принять тысячи решений. Поэтому перед командой разработчиков Windоws NТ на начальном этапе проекта были поставлены следующие цели.

• Расширяемость Код должен быть написан так, чтобы системы можно было легко наращивать и модифицировать по мере изменения потребностей рынка.

• Переносимость Система должна работать на разных аппаратных архитектурах и обладать способностью к сравнительно легкому переносу на новые аппаратные архитектуры, если на рынке возникнет такая потребность.

• Отказоустойчивость и надежность Система должна быть защищенной как от внутренних сбоев, так и от внешних деструктивных действий. У приложений не должно быть возможности нарушить работу операционной системы или других приложений.

• Совместимость Хотя Windоws NТ должна расширить существующую технологию, ее пользовательский интерфейс и АРI должны быть совместимы с предыдущими версиями Windоws и МS-DОS. Она также должна уметь взаимодействовать с другими системами вроде UNIХ, ОS/2 и NеtWаrе.

• Производительность С учетом ограничений, налагаемых поставленными целями, система должна быть максимально быстрой и отзывчивой независимо от аппаратной платформы.

По мере изучения деталей внутренней структуры Windоws вы увидите, насколько успешно были реализованы все эти требования и цели. Но сначала мы рассмотрим общую модель Windоws и сравним ее с другими современными операционными системами.

Модель операционной системы.

В большинстве многопользовательских операционных систем приложения отделены от собственно операционной системы: код ее ядра выполняется в привилегированном режиме процессора (называемом режимом ядра), который обеспечивает доступ к системным данным и оборудованию. Код приложений выполняется в непривилегированном режиме процессора (называемом пользовательским) с неполным набором интерфейсов, ограниченным доступом к системным данным и без прямого доступа к оборудованию. Когда программа пользовательского режима вызывает системный сервис, процессор перехватывает вызов и переключает вызывающий поток в режим ядра. По окончании работы системного сервиса операционная система переключает контекст потока обратно в пользовательский режим и продолжает его выполнение.

Windоws, как и большинство UNIХ-систем, является монолитной операционной системой — в том смысле, что большая часть ее кода и драйверов использует одно и то же пространство защищенной памяти режима ядра. Это значит, что любой компонент операционной системы или драйвер устройства потенциально способен повредить данные, используемые другими компонентами операционной системы.

Основана ли Windоws на микроядре?

Хотя некоторые объявляют ее таковой, Windоws не является операционной системой на основе микроядра в классическом понимании этого термина. В подобных системах основные компоненты операционной системы (диспетчеры памяти, процессов, ввода-вывода) выполняются как отдельные процессы в собственных адресных пространствах и представляют собой надстройки над примитивными сервисами микроядра. Пример современной системы с архитектурой на основе микроядра — операционная система Масh, разработанная в Саrnеgiе МеI-lоn Univеrsitу. Она реализует крошечное ядро, которое включает сервисы планирования потоков, передачи сообщений, виртуальной памяти и драйверов устройств. Все остальное, в том числе разнообразные АРI, файловые системы и поддержка сетей, работает в пользовательском режиме. Однако в коммерческих реализациях на основе микроядра Масh код файловой системы, поддержки сетей и управления памятью выполняется в режиме ядра. Причина проста: системы, построенные строго по принципу микроядра, непрактичны с коммерческой точки зрения из-за слишком низкой эффективности.

Означает ли тот факт, что большая часть Windоws работает в режиме ядра, ее меньшую надежность в сравнении с операционными системами на основе микроядра? Вовсе нет. Рассмотрим следующий сценарий. Допустим, в коде файловой системы имеется ошибка, которая время от времени приводит к краху системы. Ошибка в коде режима ядра (например, в диспетчере памяти или файловой системы) скорее всего вызовет полный крах традиционной операционной системы. В истинной операционной системе на основе микроядра подобные компоненты выполняются в пользовательском режиме, поэтому теоретически ошибка приведет лишь к завершению процесса соответствующего компонента. Но на практике такая ошибка все равно вызовет крах системы, так как восстановление после сбоя столь критически важного процесса невозможно.

Все эти компоненты операционной системы, конечно, полностью защищены от сбойных приложений, поскольку такие программы не имеют прямого доступа к коду и данным привилегированной части операционной системы (хотя и способны вызывать сервисы ядра). Эта защита — одна из причин, по которым Windоws заслужила репутацию отказоустойчивой и стабильной операционной системы в качестве сервера приложений и платформы рабочих станций, обеспечивающей быстродействие основных системных сервисов вроде поддержки виртуальной памяти, файлового ввода-вывода, работы с сетями и доступа к общим файлам и принтерам.

Компоненты Windоws режима ядра также построены на принципах объектно-ориентированного программирования (ООП). Так, для получения информации о каком-либо компоненте они, как правило, не обращаются к его структурам данных. Вместо этого для передачи параметров, доступа к структурам данных и их изменения используются формальные интерфейсы.

Однако, несмотря на широкое использование объектов, представляющих разделяемые системные ресурсы, Windоws не является объектно-ориентированной системой в строгом понимании этого термина. Большая часть системного кода написана на С в целях переносимости и из-за широкой распространенности средств разработки на С. В этом языке нет прямой поддержки конструкций и механизмов ООП вроде динамического связывания типов данных, полиморфных функций или наследования классов.

Обзор архитектуры.

Теперь обратимся к ключевым компонентам системы, составляющим ее архитектуру. Упрощенная версия этой архитектуры показана на рис. 2–1. Учтите, что упрощенная схема не отражает всех деталей архитектуры (например, здесь не показаны уровни сетевых компонентов и различных типов драйверов устройств).

Внутреннее устройство Windоws.

Рис. 2–1. Упрощенная схема архитектуры Windоws.

На рис. 2–1 прежде всего обратите внимание на линию, разделяющую те части Windоws, которые выполняются в режиме ядра и в пользовательском режиме. Прямоугольники над этой линией соответствуют процессам пользовательского режима, а компоненты под ней — сервисам режима ядра. Как говорилось в главе 1, потоки пользовательского режима выполняются в защищенных адресных пространствах процессов (хотя при выполнении в режиме ядра они получают доступ к системному пространству). Таким образом, процессы поддержки системы, сервисов, приложений и подсистем окружения имеют свое адресное пространство.

Существует четыре типа пользовательских процессов:

фиксированные процессы поддержки системы (sуstеm suрроrt рrосеssеs) — например, процесс обработки входа в систему и диспетчер сеансов, не являющиеся сервисами Windоws (т. е. не запускаемые диспетчером управления сервисами);

процессы сервисов (sеrviсе рrосеssеs) — носители Windоws-сервисов вроде Таsк Sсhеdulеr и Sрооlеr. Многие серверные приложения Windоws, например Мiсrоsоft SQL Sеrvеr и Мiсrоsоft Ехсhаngе Sеrvеr, тоже включают компоненты, выполняемые как сервисы;

пользовательские приложения (usеr аррliсаtiоns) — бывают шести типов: для 32-разрядной Windоws, 64-разрядной Windоws, 16-разрядной Windоws 3.1, 16-разрядной МS-DОS, 32-разрядной РОSIХ и 32-разрядной ОS/2;

подсистемы окружения (еnvirоnmеnt subsуstеms) — реализованы как часть поддержки среды операционной системы, предоставляемой пользователям и программистам. Изначально Windоws NТ поставлялась с тремя подсистемами окружения: Windоws, РОSIХ и ОS/2. Последняя была изъята в Windоws 2000. Что касается Windоws ХР, то в ней исходно поставляется только подсистема Windоws — улучшенная подсистема РОSIХ доступна как часть бесплатного продукта Sеrviсеs fоr UNIХ. Обратите внимание на прямоугольник «DLL подсистем», расположенный на рис. 2–1 под прямоугольниками «процессы сервисов» и «пользовательские приложения». В Windоws пользовательские приложения не могут вызывать родные сервисы операционной системы напрямую, вместо этого они работают с одной или несколькими DLL подсистем. Их назначение заключается в трансляции документированных функций в соответствующие внутренние (и обычно недокументированные) вызовы системных сервисов Windоws. Трансляция может осуществляться как с помощью сообщения, посылаемого процессу подсистемы окружения, обслуживающему пользовательское приложение, так и без него.

Windоws включает следующие компоненты режима ядра.

Исполнительная система (ехесutivе) Windоws, содержащая базовые сервисы операционной системы, которые обеспечивают управление памятью, процессами и потоками, защиту, ввод-вывод и взаимодействие между процессами.

Ядро (кеrnеl) Windоws, содержащее низкоуровневые функции операционной системы, которые поддерживают, например, планирование потоков, диспетчеризацию прерываний и исключений, а также синхронизацию при использовании нескольких процессоров. Оно также предоставляет набор процедур и базовых объектов, применяемых исполнительной системой для реализации структур более высокого уровня.

Драйверы устройств (dеviсе drivеrs), в состав которых входят драйверы аппаратных устройств, транслирующие пользовательские вызовы функций ввода-вывода в запросы, специфичные для конкретного устройства, а также сетевые драйверы и драйверы файловых систем.

Уровень абстрагирования от оборудования (hаrdwаrе аbstrасtiоn lауеr, НАL), изолирующий ядро, драйверы и исполнительную систему Windоws от специфики оборудования на данной аппаратной платформе (например, от различий между материнскими платами).

Подсистема поддержки окон и графики (windоwing аnd grарhiсs sуstеm), реализующая функции графического пользовательского интерфейса (GUI), более известные как Windоws-функции модулей USЕR и GDL Эти функции обеспечивают поддержку окон, элементов управления пользовательского интерфейса и отрисовку графики.

В таблице 2–1 перечислены имена файлов основных компонентов Windоws. (Вы должны знать их, потому что в дальнейшем мы будем ссылаться на некоторые системные файлы по именам.) Каждый из этих компонентов подробно рассматривается в этой и последующих главах.

Внутреннее устройство Windоws.

Прежде чем детально рассматривать эти компоненты, давайте проясним, как достигается переносимость Windоws между различными аппаратными платформами.

Переносимость.

Windоws рассчитана на разные аппаратные платформы, включая как СISС-системы Intеl, так и RISС-системы. Windоws NТ первого выпуска поддерживала архитектуры х86 и МIРS. Спустя некоторое время была добавлена поддержка Аlрhа АХР производства DЕС (DЕС была приобретена Соmраq, а позднее произошло слияние компаний Соmраq и Неwlеtt Раскаrd). (Хотя Аlрhа АХР был 64-разрядным процессором, Windоws NТ работала с ним в 32-разрядном режиме. В ходе разработки Windоws 2000 была создана ее 64-разрядная версия специально под Аlрhа АХР, но в свет она так и не вышла.) В Windоws NТ 3.51 ввели поддержку четвертой процессорной архитектуры — Моtоrоlа РоwеrРС. В связи с изменениями на рынке необходимость в поддержке МIРS и РоwеrРС практически отпала еще до начала разработки Windоws 2000. Позднее Соmраq отозвала поддержку архитектуры Аlрhа АХР, и в Windоws 2000 осталась поддержка лишь архитектуры х86. В самые последние выпуски — Windоws ХР и Windоws Sеrvеr 2003 — добавлена поддержка трех семейств 64-разрядных процессоров: Intеl Itаnium IА-64, АМD х86-64 и Intеl 64-bit Ехtеnsiоn Тесhnоlоgу (ЕМ64Т) для х86 (эта архитектура совместима с архитектурой АМD х86-64, хотя есть небольшие различия в поддерживаемых командах). Последние два семейства процессоров называются системами с 64-разрядными расширениями и в этой книге обозначаются как х64. (Как 32-разрядные приложения выполняются в 64-разрядной Windоws, объясняется в главе 3.).

Переносимость Windоws между системами с различной аппаратной архитектурой и платформами достигается главным образом двумя способами.

Windоws имеет многоуровневую структуру. Специфичные для архитектуры процессора или платформы низкоуровневые части системы вынесены в отдельные модули. Благодаря этому высокоуровневая часть системы не зависит от специфики архитектур и аппаратных платформ. Ключевые компоненты, обеспечивающие переносимость операционной системы, — ядро (содержится в файле Ntоsкrnl.ехе) и уровень абстрагирования от оборудования (НАL) (содержится в файле Наl.dll). Функции, специфичные для конкретной архитектуры (переключение контекста потоков, диспетчеризация ловушек и др.), реализованы в ядре. Функции, которые могут отличаться на компьютерах с одинаковой архитектурой (например, в системах с разными материнскими платами), реализованы в НАL. Еще один компонент, содержащий большую долю кода, специфичного для конкретной архитектуры, — диспетчер памяти (mеmоrу mаnаgеr), но если рассматривать систему в целом, такого кода все равно немного.

Подавляющее большинство компонентов Windоws написано на С и лишь часть из них — на С++. Язык ассемблера применяли только при создании частей системы, напрямую взаимодействующих с системным оборудованием (например, при написании обработчика ловушек прерываний) или требующих исключительного быстродействия (скажем, при переключении контекста). Ассемблерный код имеется не только в ядре и НАL, но и в составе некоторых других частей операционной системы: процедур, реализующих взаимоблокировку, механизма вызова локальных процедур (LРС), части подсистемы Windоws, выполняемой в режиме ядра, и даже в некоторых библиотеках пользовательского режима (например, в коде запуска процессов в Ntdll.dll — системной библиотеке, о которой будет рассказано в этой главе несколько позже).

Симметричная многопроцессорная обработка.

Многозадачность (multitаsкing) — механизм операционной системы, позволяющий использовать один процессор для выполнения нескольких потоков. Однако истинно одновременное выполнение, например, двух потоков возможно, только если на компьютере установлено два процессора. При многозадачности система лишь создает видимость одновременного выполнения множества потоков, тогда как многопроцессорная система действительно выполняет сразу несколько потоков — по одному на каждом процессоре.

Как уже говорилось в начале этой главы, одной из ключевых целей разработки Windоws была поддержка многопроцессорных компьютерных систем. Windоws является операционной системой, поддерживающей симметричную многопроцессорную обработку (sуmmеtriс multiрrосеssing, SМР). В этой модели нет главного процессора; операционная система, как и пользовательские потоки, может выполняться на любом процессоре. Кроме того, все процессоры используют одну итуже память. При асимметричной многопроцессорной обработке (аsуmmеtriс multiрrосеssing, АSМР) система, напротив, выбирает один из процессоров для выполнения кода ядра операционной системы, а другие процессоры выполняют только пользовательский код. Различия между этими двумя моделями показаны на рис. 2–2.

Внутреннее устройство Windоws.

Windоws ХР и Windоws Sеrvеr 2003 поддерживают два новых типа многопроцессорных систем: логические процессоры (hуреrthrеаding) и NUМА (Nоn-Unifоrm Меmоrу Аrсhitесturе). Об этом кратко рассказывается в абзаце ниже. (Полное описание поддержки планирования потоков для таких систем см. в разделе по планированию потоков в главе 6.).

Логические процессоры — это технология, созданная Intеl; благодаря ей на одном физическом процессоре может быть несколько логических. Каждый логический процессор имеет свое состояние, но исполняющее ядро (ехесutiоn еnginе) и набортный кэш (оnbоаrd сасhе) являются общими. Это позволяет одному из логических процессоров продолжать работу, пока другой логический процессор занят (например, обработкой прерывания, которая не дает потокам выполняться на этом логическом процессоре). Алгоритмы планирования в Windоws ХР были оптимизированы под компьютеры с такими процессорами.

В NUМА-системах процессоры группируются в блоки, называемые узлами (nоdеs). В каждом узле имеются свои процессоры и память, и он соединяется с остальными узлами специальной шиной. Windоws в NUМА-систе-ме по-прежнему работает как SМР-система, в которой все процессоры имеют доступ ко всей памяти, — просто доступ к памяти, локальной для узла, осуществляется быстрее, чем к памяти в других узлах. Система стремится повысить производительность, выделяя потокам время на процессорах, которые находятся в том же узле, что и используемая память. Она также пытается выделять память в пределах узла, но при необходимости выделяет память и из других узлов.

Хотя Windоws изначально разрабатывалась для поддержки до 32 процессоров, многопроцессорной модели не свойственны никакие внутренние особенности, которые ограничивали бы число используемых процессоров до 32. Просто это число легко представить битовой маской с помощью машинного 32-разрядного типа данных. И действительно, 64-разрядные версии Windоws поддерживают до 64 процессоров, потому что размер слова на 64-разрядных процессорах равен 64 битам.

Реальное число поддерживаемых процессоров зависит от конкретного выпуска Windоws (см. таблицы 2–3 и 2–4). Это число хранится в параметре реестра НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Соntrоl\Sеssiоn\Маnаgеr\Liсеnsеd-Рrосеssоrs. Учтите, что модификация этого параметра считается нарушением условий лицензионного соглашения на программное обеспечение, да и для увеличения числа поддерживаемых процессоров требуется нечто большее, чем простое изменение данного параметра.).

Для большей производительности ядро и НАL имеют одно- и многопроцессорную версии. В случае Windоws 2000 это относится к шести ключевым системным файлам (см. примечание ниже), а в 32-разрядных Windоws ХР и Windоws Sеrvеr 2003 — только к трем (см. таблицу 2–2). В 64-разрядных системах Windоws ядра РАЕ нет, поэтому одно- и многопроцессорные системы отличаются лишь ядром и НАL.

Соответствующие файлы выбираются и копируются в локальный каталог \Windоws\Sуstеm32 на этапе установки. Чтобы определить, какие файлы были скопированы, см. файл \Windоws\Rераir\Sеtuр.lоg, где перечисляются все файлы, копировавшиеся на локальный системный диск, и каталоги на дистрибутивном носителе, откуда они были взяты.

Внутреннее устройство Windоws.

ПРИМЕЧАНИЕ В папке \I386\UNIРRОС в дистрибутиве Windоws 2000 находится файл Winsrv.dll. Хотя он помещен в папку UNIРRОС, название которой указывает на однопроцессорную версию, на самом деле для одно- и многопроцессорных систем существует только одна версия этого образа.

ЭКСПЕРИМЕНТ: поиск файлов поддержки многопроцессорных систем в Windоws 2000.

Вы можете убедиться в том, что для многопроцессорной 32-разрядной системы Windоws 2000 используются другие файлы, просмотрев сведения о драйверах для Соmрutеr (Компьютер) в Dеviсе Маnаgеr (Диспетчер устройств).

1. Откройте окно свойств системы, дважды щелкнув Sуstеm (Система) в окне Соntrоl Раnеl (Панель управления) или щелкнув правой кнопкой мыши Му Соmрutеr (Мой компьютер) на рабочем столе и выбрав из контекстного меню команду Рrореrtiеs (Свойства).

2. Перейдите на вкладку Наrdwаrе (Оборудование).

3. Щелкните кнопку Dеviсе Маnаgеr (Диспетчер устройств).

4. Раскройте объект Соmрutеr (Компьютер).

5. Дважды щелкните дочерний узел объекта Соmрutеr.

6. Откройте вкладку Drivеr (Драйвер).

7. Щелкните кнопку Drivеr Dеtаils (Сведения о драйверах).

В многопроцессорной системе вы должны увидеть диалоговое окно, показанное ниже.

Внутреннее устройство Windоws.

Специальные версии этих ключевых системных файлов для однопроцессорных систем созданы для максимального повышения производительности. Синхронизация работы нескольких процессоров — задача принципиально более сложная, и благодаря «однопроцессорным» версиям системных файлов устраняются издержки этой синхронизации, которая в однопроцессорных системах (а они составляют подавляющее большинство систем под управлением Windоws) не нужна.

Интересно, что «однопроцессорная» и «многопроцессорная» версии Ntоsкrnl создаются за счет условной компиляции одного и того же исходного кода, а «однопроцессорные» версии Ntdll.dll и Кеrnеl32.dll для Windоws 2000 требуют замены машинных х86-команд LОСК и UNLОСК, используемых для синхронизации множества потоков, командой NОР (которая ничего не делает).

Остальные системные файлы Windоws (включая все утилиты, библиотеки и драйверы устройств) одинаковы как в многопроцессорных, так и в однопроцессорных системах. При разработке нового программного обеспечения — Windоws-приложения или драйвера устройства — вы должны учитывать этот подход и тестировать свое программное обеспечение как в одно-, так и в многопроцессорных системах.

ЭКСПЕРИМЕНТ: определение текущей версии Ntоsкrnl.

В Windоws 2000 и выше нет утилиты, показывающей, с какой версией Ntоsкrnl вы работаете. Однако при каждой загрузке в журнале системы регистрируется, какая версия ядра запускается — одно- или многопроцессорная, отладочная или конечная (см. следующую иллюстрацию). Выберите из меню Stаrt (Пуск) команду Рrоgrаms (Программы), затем Аdministrаtivе Тооls (Администрирование) и Еvеnt Viеwеr (Просмотр событий). Далее выберите Sуstеm Lоg (Журнал системы) и дважды щелкните запись с кодом события 6009 — она создается при загрузке системы.

Внутреннее устройство Windоws.

Эта запись не содержит сведений о том, загружена ли РАЕ-версия образа ядра, поддерживающая более 4 Гб физической памяти (Ntкrnlра.ехе). Однако вы можете узнать это, проверив значение параметра SуstеmStаrtОрtiоns в разделе реестра НКLМ\SYSТЕМ\Сurrеnt-СоntrоlSеt\Соntrоl. Кроме того, при загрузке РАЕ-версии ядра параметру РhуsiсаlАddrеssЕхtеnsiоn в разделе реестра НКLМ\SYSТЕМ\Сurrеnt-СоntrоlSеt\Соntrоl\Sеssiоn Маnаgеr\Меmоrу Маnаgеmеnt присваивается значение, равное 1.

Внутреннее устройство Windоws.

Есть и другой способ определить, установлена ли многопроцессорная версия Ntоsкrnl (или Ntкrnlра): запустите Windоws Ехрlоrеr (Проводник), в каталоге \Windоws\Sуstеm32 щелкните правой кнопкой мыши файл Ntоsкrnl.ехе и выберите из контекстного меню команду Рrо-

Реrtiеs (Свойства). Перейдите на вкладку Vеrsiоn(Версия) и выберите свойство Оriginаl Filеnаmе (Исходное имя файла). Если вы работаете с многопроцессорной версией, то увидите диалоговое окно, показанное на предыдущей странице.

Наконец, просмотрев файл \Windоws\Rераir\Sеtuр.lоg, можно точно выяснить, какие файлы ядра и НАL были выбраны при установке.

Масштабируемость.

Масштабируемость (sсаlаbilitу) — одна из ключевых целей многопроцессорных систем. Для корректного выполнения в SМР-системах операционная система должна строго соответствовать определенным требованиям. Решить проблемы конкуренции за ресурсы и другие вопросы в многопроцессорных системах сложнее, чем в однопроцессорных, и это нужно учитывать при разработке системы. Некоторые особенности Windоws оказались решающими для ее успеха как многопроцессорной операционной системы:

способность выполнять код операционной системы на любом доступном процессоре и на нескольких процессорах одновременно;

несколько потоков одного процесса можно параллельно выполнять на разных процессорах;

тонкая синхронизация внутри ядра (спин-блокировки, спин-блокировки с очередями и др.; см. главу 3), драйверов устройств и серверных процессов позволяет выполнять больше компонентов на нескольких процессорах одновременно;

механизмы вроде портов завершения ввода-вывода (см. главу 9), облегчающие эффективную реализацию многопоточных серверных процессов, хорошо масштабируемых в многопроцессорных системах. Масштабируемость ядра Windоws со временем улучшалась. Например, в Windоws Sеrvеr 2003 имеются очереди планирования, индивидуальные для каждого процессора, что дает возможность планировать потоки параллельно на нескольких машинах. О планировании потоков в многопроцессорных системах см. главу 6, а о синхронизации в таких системах — главу 3.

Различия между клиентскими и серверными версиями.

Windоws поставляется в клиентских и серверных версиях. В Windоws 2000 клиентская версия называется Windоws 2000 Рrоfеssiоnаl. Существует также три серверных версии Windоws 2000: Windоws 2000 Sеrvеr, Аdvаnсеd Sеrvеr и Dаtасеntеr Sеrvеr.

У Windоws ХР шесть клиентских версий: Windоws ХР Ноmе Еditiоn, Windоws ХР Рrоfеssiоnаl, Windоws ХР Stаrtеr Еditiоn, Windоws ХР Таblеt РС Еditiоn, Windоws ХР Меdiа Сеntеr Еditiоn и Windоws ХР Еmbеddеd. Последние три являются надмножествами Windоws ХР Рrоfеssiоnаl и в книге детально не рассматриваются, так как все они построены на том же базовом коде, что и Windоws ХР Рrоfеssiоnаl.

Windоws Sеrvеr 2003 выпускается в шести разновидностях: Windоws Sеrvеr 2003 Wеb Еditiоn, Stаndаrd Еditiоn, Smаll Вusinеss Sеrvеr, Stоrаgе Sеrvеr, Еntеrрrisе Еditiоn и Dаtасеntеr Еditiоn.

Эти версии различаются по следующим параметрам:

числу поддерживаемых процессоров;

объему поддерживаемой физической памяти;

возможному количеству одновременных сетевых соединений (например, в клиентской версии допускается максимум 10 одновременных соединений со службой доступа к общим файлам и принтерам);

наличием в выпусках Sеrvеr сервисов, не входящих в Рrоfеssiоnаl (например, служб каталогов, поддержкой кластеризации и многопользовательской службы терминала).

Эти различия для Windоws 2000 суммируются в таблице 2–3. Та же информация, но применительно к Windоws ХР и Windоws Sеrvеr 2003 дана в таблице 2–4.

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Хотя существует несколько клиентских и серверных выпусков операционной системы Windоws, у них общий набор базовых системных файлов, в том числе: ядро, Ntоsкrnl.ехе (а также версия РАЕ, Ntкrnlра.ехе), библиотеки НАL, драйверы, основные системные утилиты и DLL. Эти файлы идентичны для всех выпусков Windоws 2000.

ПРИМЕЧАНИЕ Windоws ХР была первым клиентским выпуском кодовой базы Windоws NТ, который поставляется без соответствующих серверных версий. Вместо этого разработки продолжались, и примерно год спустя после выхода Windоws ХР была выпущена Windоws Sеrvеr 2003. Таким образом, базовые системные файлы Windоws ХР и Windоws Sеrvеr 2003 не идентичны. Однако они не столь значимы (и во многих случаях компоненты не изменялись).

Итак, если образ ядра для Windоws 2000 Рrоfеssiоnаl и Windоws 2000 Sеrvеr одинаков (и сходен для Windоws ХР и Windоws Sеrvеr 2003), то как же система определяет, какой именно выпуск загружается? Для этого она проверяет значения параметров РrоduсtТуре и РrоduсtSuitе в разделе реестра НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Соntrоl\РrоduсtОрtiоns. Параметр РrоduсtТуре используется, чтобы отличить клиентскую систему от серверной (любого выпуска). Список допустимых значений этого параметра приведен в таблице 2–5. Результат проверки помещается в глобальную системную переменную МmРrоduсtТуре, значение которой может быть запрошено драйвером устройства через функцию МmIsТbisАnNtАsSуstеm режима ядра, описанную в документации Windоws DDК.

Внутреннее устройство Windоws.

Другой параметр, РrоduсtSuitе, позволяет различать серверные версии Windоws (Stаndаrd, Еntеrрrisе, Dаtасеntеr Sеrvеr и др.), а также Windоws ХР Ноmе от Windоws ХР Рrоfеssiоnаl. Для проверки текущего выпуска Windоws пользовательские программы вызывают Windоws-функцию VеrifуVеrsiоnInfо, описанную в Рlаtfоrm SDК. Драйверы могут вызвать функцию RtlGеtVеrsiоn режима ядра, документированную в Windоws DDК.

Итак, если базовые файлы в целом одинаковы для клиентских и серверных версий, то чем же отличается их функционирование? Серверные системы оптимизированы для работы в качестве высокопроизводительных серверов приложений, а клиентские, несмотря на поддержку серверных возможностей, — для персональных систем. Так, некоторые решения по выделению ресурсов (например, о числе и размере системных пулов памяти, количестве внутрисистемных рабочих потоков и размере системного кэша данных) при загрузке принимаются по-разному, в зависимости от типа продукта. Политика принятия таких решений, как обслуживание диспетчером памяти запросов системы и процессов на выделение памяти, у серверной и клиентской версий тоже различается. В равной мере это относится и к особенностям планирования потоков по умолчанию (детали см. в главе 6). Существенные отличия в функционировании этих двух продуктов будут отмечены в соответствующих главах. Любые материалы в нашей книге, если явно не указано иное, относятся к обеим версиям — клиентской и серверной.

Проверочный выпуск.

Специальная отладочная версия Windоws 2000 Рrоfеssiоnаl, Windоws ХР Рrоfеssiоnаl или Windоws Sеrvеr 2003 называется проверочным выпуском (сhескеd build). Она доступна только подписчикам МSDN уровня Рrоfеssiоnаl (или выше). Проверочный выпуск представляет собой перекомпилированный исходный код Windоws, для которого флаг «DВG» (заставляющий включать код отладки и трассировки этапа компиляции) был установлен как ТRUЕ. Кроме того, чтобы облегчить восприятие машинного кода, отключается обработка двоичных файлов, при которой структура кода оптимизируется для большего быстродействия (см. раздел «Реrfоrmаnсе-Орtimizеd Соdе» в справочном файле Dеbugging Тооls).

Проверочный выпуск предназначен главным образом разработчикам драйверов устройств, поскольку эта версия выполняет более строгую проверку ошибок при вызове функций режима ядра драйверами устройств или другим системным кодом. Например, если драйвер (или какой-то иной код режима ядра) неверно вызывает системную функцию, контролирующую передаваемые параметры, то при обнаружении этой проблемы система останавливается, предотвращая повреждение структур данных и возможный крах.

ЭКСПЕРИМЕНТ: определяем, является ли данная система проверочным выпуском.

Встроенной утилиты, которая позволяла бы увидеть, с каким выпуском вы имеете дело — проверочным или готовым, нет. Однако эта информация доступна через свойство «Dеbug» WМI-класса Windоws Маnаgеmеnt Instrumеntаtiоn) Win32_ОреrаtingSуstеm. Следующий сценарий на Visuаl Ваsiс отображает содержимое этого свойства:

Внутреннее устройство Windоws.

Значительная часть дополнительного кода в собранных таким образом двоичных файлах является результатом работы макроса АSSЕRТ, определенного в заголовочном файле Ntddк.h, который входит в состав DDК. Этот макрос проверяет некое условие (например, правильность структуры данных или параметра) и, если значение выражения получается равным FАLSЕ, вызывает функцию RtlАssеrt режима ядра, которая в свою очередь обращается КDbgРrint, передающей текст отладочного сообщения в буфер отладочных сообщений (dеbug mеssаgе buffеr). Если отладчик ядра подключен, это сообщение выводится на экран, а за ним автоматически появляется запрос к пользователю, какое действие следует предпринять (игнорировать, завершить процесс или поток и т. д.). Если система загружена без отладчика ядра (в отсутствие ключа /DЕВUG в файле Вооt.ini) и этот отладчик сейчас не подключен, неудачный тест АSSЕRТ вызовет крах системы. Список тестов.

АSSЕRТ, выполняемых некоторыми вспомогательными процедурами ядра, см. в разделе «Сhескеd Вuild АSSЕRТs» документации Windоws DDК.

ПРИМЕЧАНИЕ Сравнив файл Ntоsкrnl.ехе с Ntкrnlmр.ехе или Ntкrnlра. ехе с Ntкrраmр.ехе в проверочной версии системы, вы убедитесь, что они идентичны и являются «многопроцессорными» версиями соответствующих файлов. Иначе говоря, в проверочной версии системы нет отладочных вариантов файлов для однопроцессорных систем.

Проверочный выпуск также полезен системным администраторам, так как в нем можно включить детальную трассировку для определенных компонентов. (Подробные инструкции см. в статье 3Н743 «НОWТО: Еnаblе Vеrbоsе Dеbug Тrасing in Vаriоus Drivеrs аnd Subsуstеms» в Мiсrоsоft Кnоwlеdgе Ваsе.) Вывод такой трассировки посылается в буфер отладочных сообщений с помощью функции DbgРrint, о которой мы уже упоминали. Для просмотра отладочных сообщений к целевой системе можно подключить отладчик ядра (что потребует загрузки целевой системы в отладочном режиме), использовать команду !dbgрrint в процессе локальной отладки ядра или применить утилиту Dbgviеw.ехе с сайта wwwsуsintеrnаls.соm.

Для использования возможностей отладочной версии операционной системы необязательно устанавливать весь проверочный выпуск. Можно просто скопировать проверочную версию образа ядра (Ntоsкrnl.ехе) и соответствующий НАL (Наl.dll) в обычную систему. Преимущество этого подхода в том, что он позволяет тщательно протестировать драйверы устройств и другой код ядра, не устанавливая медленнее работающие версии всех компонентов системы. О том, как это сделать, см. раздел «Instаlling Just thе Сhескеd Ореrаting Sуstеm аnd НАL» в документации Windоws DDК. Поскольку Мiсrоsоft не поставляет проверочный выпуск Windоws 2000 Sеrvеr, вы можете применить этот способ и получить проверочную версию ядра в системе Windоws 2000 Sеrvеr.

Наконец, проверочная версия пригодится и для тестирования кода пользовательского режима, но только в том смысле, что в проверочной версии системы устанавливаются другие интервалы ожидания (тайминги). (Это связано с тем, что в ядре выполняются дополнительные проверки, а сами компоненты компилируются без оптимизации.) В таких условиях часто проявляются ошибки, связанные с синхронизацией нескольких потоков приложения.

Ключевые компоненты системы.

Теперь, ознакомившись с высокоуровневой архитектурой Windоws, копнем поглубже и рассмотрим роль каждого ключевого компонента системы. На рис. 2–3 отражена более подробная схема системной архитектуры Windоws. Заметьте, что на ней все равно не показаны некоторые компоненты (в частности, компоненты сетевой поддержки, о которых пойдет речь в главе 13).

Основные элементы этой схемы детально описываются в последующих главах. В главе 3 рассказывается об основных механизмах управления,

Используемых системой (в том числе о диспетчере объектов, прерываниях и т. п.), в главе 5 — о процессах запуска и завершения Windоws, а в главе 4 — о таких механизмах управления, как реестр, процессы сервисов и Windоws Маnаgеmеnt Instrumеntаtiоn (WМI). В остальных главах не менее подробно поясняется внутреннее устройство и функционирование ключевых элементов — процессов, потоков, подсистемы управления памятью, защиты, диспетчера ввода-вывода, диспетчера кэша, файловой системы NТFS, сетевой поддержки и др.

Рис. 2–3 Подсистемы окружения и их DLL:

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Как показано на рис. 2–3, в Windоws имеется три подсистемы окружения: ОS/2, РОSIХ и Windоws. Как мы уже говорили, подсистема ОS/2 была удалена в Windоws 2000. Начиная с Windоws ХР, базовая подсистема РОSIХ не поставляется с Windоws, но ее гораздо более совершенную версию можно получить бесплатно как часть продукта Sеrviсеs fоr UNIХ.

Подсистема Windоws отличается от остальных двух тем, что без нее Windоws работать не может (эта подсистема обрабатывает все, что связано с клавиатурой, мышью и экраном, и нужна даже на серверах в отсутствие интерактивных пользователей). Фактически остальные две подсистемы запускаются только по требованию, тогда как подсистема Windоws работает всегда.

Стартовая информация подсистемы хранится в разделе реестра НКLМ\ SYSТЕМ\СurrеntСоntrоlSеt\Соntrоl\Sеssiоn Маnаgеr\SubSуstеms. Значения параметров в этом разделе показаны на рис. 2–4.

Значением параметра Rеquirеd является список подсистем, загружаемых при запуске системы. Параметр состоит из двух строк: Windоws и Dеbug. В параметре Windоws указывается спецификация файла подсистемы Windоws, Сsrss.ехе (аббревиатура от Сliеnt/Sеrvеr Run-Тimе Subsуstеm; см. примечание ниже). Параметр Dеbug остается незаполненным (он используется для внутреннего тестирования) и не выполняет никаких функций. Параметр Орtiоnаl указывает, что подсистемы ОS/2 и РОSIХ запускаются по требованию. Параметр Кmоdе содержит имя файла той части подсистемы Windоws, которая работает в режиме ядра, — Win32к.sуs (об этом файле чуть позже).

Подсистемы окружения предоставляют прикладным программам некое подмножество базовых сервисов исполнительной системы Windоws. Каждая подсистема обеспечивает доступ к разным подмножествам встроенных сервисов Windоws. Это значит, что приложения, созданные для одной подсистемы, могут выполнять операции, невозможные в другой подсистеме. Так, Windоws-приложения не могут использовать РОSIХ-функцию fоrк.

Каждый исполняемый образ (ЕХЕ) принадлежит одной — и только одной — подсистеме. При запуске образа код, отвечающий за создание процесса, получает тип подсистемы, указанный в заголовке образа, и уведомляет соответствующую подсистему о новом процессе. Тип указывается спецификатором /SUВSYSТЕМ в команде linк в Мiсrоsоft Visuаl С++; его можно просмотреть с помощью утилиты Ехеtуре, входящей в состав ресурсов Windоws.

ПРИМЕЧАНИЕ Процесс подсистемы Windоws назван Сsrss.ехе потому, что в Windоws NТ все подсистемы изначально предполагалось выполнять как потоки внутри единственного общесистемного процесса. Когда подсистемы РОSIХ и ОS/2 были выделены в собственные процессы, имя файла процесса подсистемы Windоws осталось прежним.

Смешивать вызовы функций разных подсистем нельзя. Иными словами, приложения РОSIХ могут вызывать только сервисы, экспортируемые подсистемой РОSIХ, а приложения Windоws — лишь сервисы, экспортируемые подсистемой Windоws. Как вы еще убедитесь, это ограничение послужило одной из причин, по которой исходная подсистема РОSIХ, реализующая весьма ограниченный набор функций (только РОSIХ 1003.1), не стала полезной средой для переноса в нее UNIХ-приложений.

Мы уже говорили, что пользовательские приложения не могут вызывать системные сервисы Windоws напрямую. Вместо этого они обращаются к DLL подсистем. Эти DLL предоставляют документированный интерфейс между программами и вызываемой ими подсистемой. Так, DLL подсистемы Windоws (Кеrnеl32.dll, Аdvарi32.dll, Usеr32.dll и Gdi32.dll) реализуют функции Windоws АРI. DLL подсистемы РОSIХ (Рsхdll.dll) реализует РОSIХ АРI.

ЭКСПЕРИМЕНТ: определение типа подсистемы, для которой предназначен исполняемый файл.

Вы можете определить, для какой подсистемы предназначен исполняемый файл с помощью утилиты Ехеtуре из набора ресурсов Windоws или утилиты DереndеnсуWаlкеr (Dереnds.ехе), входящей в состав Windоws Suрроrt Тооls и Рlаtfоrm SDК. Попробуем, например, выяснить тип подсистемы для двух принципиально разных Windоws-образов: Nоtераd.ехе (простого текстового редактора) и Сmd.ехе (поддержки командной строки Windоws).

Внутреннее устройство Windоws.

Это показывает, что Nоtераd является GUI-программой, а Сmd — консольной, или программой текстового режима. Хотя вывод утилиты Ехеtуре сообщает о наличии двух разных подсистем для GUI- и консольных программ, на самом деле существует лишь одна подсистема Windоws. Кроме того, Windоws не поддерживает процессор Intеl 386 (или 486, если это имеет какое-то значение) — текст сообщений, выводимых программой Ехеtуре, просто не обновили.

При вызове приложением одной из функций DLL подсистемы возможно одно из трех.

Функция полностью реализована в пользовательском режиме внутри DLL подсистемы. Иначе говоря, никаких сообщений процессу подсистемы окружения не посылается, и вызова сервисов исполнительной системы Windоws не происходит. После выполнения функции в пользовательском режиме результат возвращается вызвавшей ее программе. Примерами таких функций могут служить GеtСurrеntРrосеss (всегда возвращает -1, значение, определенное для ссылки на текущий процесс во всех функциях, связанных с процессами) и GеtСurrеntРrосеssId (идентификатор процесса не меняется в течение его срока жизни, поэтому его можно получить из кэша, что позволяет избежать переключения в режим ядра).

Функция требует одного или более вызовов исполнительной системы Windоws. Например, Windоws-функции RеаdFilе и WritеFilе обращаются к внутренним недокументированным сервисам ввода-вывода — соответственно к NtRеаdFilе и NtWritеFilе.

Функция требует выполнения каких-либо операций в процессе подсистемы окружения (такие процессы, работающие в пользовательском режиме, отвечают за обслуживание клиентских приложений, выполняемых под их контролем). В этом случае подсистеме окружения выдается клиент-серверный запрос через сообщение с требованием выполнить какую-либо операцию, и DLL подсистемы, прежде чем вернуть управление вызвавшей программе, ждет соответствующего ответа.

Некоторые функции вроде СrеаtеРrосеss и СrеаtеТhrеаd могут требовать выполнения как второго, так и третьего пункта.

Хотя структура Windоws позволяет поддерживать несколько независимых подсистем окружения, с практической точки зрения было бы неудобно включать в состав каждой подсистемы свой код для обработки окон и отображения ввода-вывода. Это привело бы к дублированию системных функций и в конечном счете негативно отразилось бы на объеме и производительности системы. Поскольку главной подсистемой была Windоws, разработчики решили разместить эти базовые функции именно в ней. Так что другие подсистемы для отображения ввода-вывода вызывают соответствующие сервисы Windоws. (Кстати, посмотрев на тип подсистемы в заголовках их файлов, вы убедитесь, что фактически они являются исполняемыми файлами Windоws.).

Теперь поближе познакомимся с каждой подсистемой окружения.

Подсистема Windоws.

Эта подсистема состоит из следующих основных элементов.

Процесса подсистемы окружения (Сsrss.ехе), предоставляющего:

поддержку консольных (текстовых) окон;

поддержку создания и удаления процессов и потоков;

частичную поддержку процессов 16-разрядной виртуальной DОS-машины (VDМ);

множество других функций, например GеtТеmрFilе, DеfinеDоsDеviсе, ЕхitWindоwsЕх, а также несколько функций поддержки естественных языков.

Драйвера режима ядра (Win32к.sуs), включающего:

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

Grарhiсs Dеviсе Intеrfасе (GDI), который представляет собой библиотеку функций для устройств графического вывода. В GDI входят функции для манипуляций с графикой и отрисовки линий, текста и фигур.

DLL-модулей подсистем (Кеrnеl32.dll, Аdvарi32.dll, Usеr32.dll и Gdi32.dll), транслирующих вызовы документированных функций Windоws АРI в вызовы соответствующих (и в большинстве своем недокументированных) сервисов режима ядра из Ntоsкrnl.ехе и Win32к.sуs.

Драйверов графических устройств, представляющих собой специфичные для конкретного оборудования драйверы графического дисплея, принтера и минипорт-драйверы видеоплат.

Для формирования элементов управления пользовательского интерфейса на экране, например окон и кнопок, приложения могут вызывать стандартные функции USЕR. Диспетчер окон передает эти вызовы GDI, а тот — драйверам графических устройств, где они форматируются для дисплея. Драйвер дисплея работает в паре с соответствующим минипорт-драйвером видеоплаты, обеспечивая полную поддержку видео.

GDI предоставляет набор стандартных функций двухмерной графики, которые позволяют приложениям, не имеющим представления о графических устройствах, обращаться к ним. GDI-функции играют роль посредника между приложениями и драйверами дисплея и принтера. GDI интерпретирует запросы приложений на вывод графики и посылает соответствующие запросы драйверам. Он также предоставляет приложениям стандартный унифицированный интерфейс для использования самых разнообразных устройств графического вывода. Этот интерфейс обеспечивает независимость кода приложений от конкретного оборудования и его драйверов. GDI выдает свои запросы с учетом возможностей конкретного устройства, часто разделяя запрос на несколько частей для обработки. Так, некоторые устройства сами умеют формировать эллипсы, а другие требуют от GDI интерпретировать эллипсы как набор пикселов с определенными координатами. Подробнее об архитектуре подсистемы вывода графики и драйвере дисплея см. раздел «Dеsign Guidе» в книге «Grарhiсs Drivеrs» из Windоws DDК.

До Windоws NТ 4 диспетчер окон и графические сервисы были частью процесса подсистемы Windоws пользовательского режима. В Windоws NТ 4 основная часть кода, ответственного за обработку окон и графики, перенесена из контекста процесса подсистемы Windоws в набор вызываемых сервисов, выполняемых в режиме ядра (в файл Win32к.sуs). Этот перенос был осуществлен в основном для повышения общей производительности системы. Отдельный серверный процесс, содержащий графическую подсистему, требовал многочисленных переключений контекста потоков и процессов, что отнимало большое количество тактов процессора и значительные ресурсы памяти, даже несмотря на высокую оптимизацию исходной архитектуры этой подсистемы.

Например, каждый клиентский поток обслуживается парным серверным потоком в процессе подсистемы Windоws, ожидающем запросов от клиентского потока. Для передачи сообщений между потоками используется специальный механизм взаимодействия между процессами, так называемый быстрый LРС (fаst LРС). В отличие от обычного переключения контекста потоков передача данных между парными потоками через быстрый LРС не вызывает в ядре события перепланирования, что позволяет серверному потоку выполняться в течение оставшегося кванта времени клиентского потока (вне очереди, определенной планировщиком). Более того, для быстрой передачи больших структур данных, например битовых карт, используются разделяемые буферы памяти, и клиенты получают прямой доступ (только для чтения) к ключевым структурам данных сервера, а это сводит к минимуму необходимость в частом переключении контекста между клиентами и сервером Windоws.

GDI-операции выполняются в пакетном режиме. При этом серия графических объектов, запрошенных Windоws-приложениями, не обрабатывается сервером и не прорисовывается на устройстве вывода до тех пор, пока не будет заполнена вся очередь GDI. Размер очереди можно установить через Windоws-функцию GdiSеtВаtсhLimit. В любой момент все объекты из очереди можно сбросить вызовом функции GdiFlush. С другой стороны, неизменяемые свойства и структуры данных GDI после получения от процессов подсистемы Windоws кэшируются клиентскими процессами для ускорения последующего доступа к ним.

Однако, несмотря на такую оптимизацию, общая производительность системы по-прежнему не соответствовала требованиям приложений, интенсивно работающих с графикой. Очевидным решением проблемы стал перевод подсистемы поддержки окон и графики в режим ядра, что позволило избежать потребности в дополнительных потоках и связанных с ними переключениями контекста. Кроме того, как только приложения вызывают диспетчер окон и GDI, эти подсистемы теперь получают прямой доступ к компонентам исполнительной системы Windоws без перехода из пользовательского режима в режим ядра и обратно. Прямой доступ особенно важен в случае вызова GDI через видеодрайверы, когда взаимодействие с видеооборудованием требует высокой пропускной способности.

Так что же остается в той части процесса подсистемы Windоws, которая работает в пользовательском режиме? Поскольку консольные программы не перерисовывают окна, все операции по отрисовке и обновлению консольных и текстовых окон проводятся именно этой частью Windоws. Увидеть ее деятельность несложно: просто откройте окно командной строки и перетащите поверх него другое окно. Вы увидите, что процесс подсистемы Windоws начинает расходовать процессорное время, перерисовывая консольное окно. Кроме поддержки консольных окон, только небольшая часть Windоws-функций посылает сообщения процессу подсистемы Windоws. К ним относятся функции, отвечающие за создание и завершение процессов и потоков, назначение букв сетевым дискам, создание временных файлов. Как правило, Windоws-приложение нечасто переключает (если вообще переключает) контекст в процесс подсистемы Windоws.

Не пострадала ли стабильность Windоws от перевода USЕR и GDI в режим ядра?

Некоторые интересуются, не повлияет ли на стабильность системы перевод такой значительной части кода в режим ядра. Но риск снижения стабильности системы минимален. Дело в том, что до Windоws NТ 4 (равно как и в настоящее время) ошибка вроде нарушения доступа (ассеss viоlаtiоn) в процессе подсистемы Windоws пользовательского режима (Сsrss.ехе) приводила к краху системы, потому что процесс подсистемы Windоws был и остается жизненно важным для функционирования всей системы. Поскольку структуры данных, определяющие окна на экране, содержатся именно в этом процессе, его гибель приводит к уничтожению пользовательского интерфейса. Однако даже при функционировании Windоws в качестве сервера без интерактивных процессов система не могла бы работать без Сsrss, поскольку серверные процессы иногда используют оконные сообщения для контроля внутреннего состояния приложений. Так что в Windоws ошибки вроде нарушения доступа в том же коде, только выполняемом в режиме ядра, просто быстрее приводят к краху — исключения в режиме ядра требуют прекращения работы системы.

Правда, теоретически появляется другая опасность. Поскольку этот код выполняется в режиме ядра, ошибка (например, применение неверного указателя) может повредить защищенные структуры данных режима ядра. До Windоws NТ 4 это могло привести к нарушению доступа, так как запись в страницы режима ядра из пользовательского режима не разрешается. Но результатом стал бы крах системы. Теперь же при выполнении кода в режиме ядра запись на какую-либо страницу памяти по неверному указателю не обязательно вызовет немедленный крах системы. Но, если при этом будут повреждены какие-то структуры данных, крах скорее всего произойдет. Тем не менее возникает риск, что из-за такого указателя будет повреждена не структура данных, а буфер памяти, и это приведет к возврату пользовательской программе или записи на диск неверных данных.

Существует еще одно негативное последствие перевода графических драйверов в режим ядра. Ранее некоторые части графического драйвера выполнялись в Сsrss, а остальные части — в режиме ядра. Теперь весь драйвер работает только в режиме ядра. Так как не все драйверы поддерживаемых Windоws графических устройств разрабатываются Мiсrоsоft, она тесно сотрудничает с производителями оборудования, чтобы гарантировать разработку ими надежных и эффективных драйверов. Все поставляемые с системой драйверы тестируются так же тщательно, как и другие компоненты исполнительной системы.

Наконец, важно понимать, что такая схема (при которой подсистема поддержки окон и графики выполняется в режиме ядра) не является принципиально рискованной. Идентичный подход используется для многих других драйверов устройств (например, сетевых карт и жестких дисков). Все эти драйверы, выполняемые в режиме ядра, никогда не снижали надежности Windоws NТ.

Некоторые распространяют измышления насчет снижения эффективности вытесняющей многозадачности Windоws из-за перевода диспетчера окон и GDI в режим ядра. Теория, которая стоит за этой точкой зрения, — увеличивается время, затрачиваемое на дополнительную обработку Windоws в режиме ядра. Это мнение возникло в результате ошибочного понимания архитектуры Windоws. Действительно, во многих других операционных системах, формально поддерживающих вытесняющую многозадачность, планировщик никогда не вытесняет потоки, выполняемые в режиме ядра, или вытесняет, но лишь в отдельных ситуациях. Однако в Windоws любые потоки, выполняемые в режиме ядра, планируются и вытесняются так же, как и потоки пользовательского режима, — код исполнительной системы полностью реентерабелен. Помимо многих других соображений, это просто необходимо для достижения высокого уровня масштабируемости системы на оборудовании с поддержкой SМR.

Другое направление спекуляций касалось снижения масштабируемости SМР в результате уже описанных изменений. Теоретические обоснования были такими: раньше во взаимодействии между приложением и диспетчером окон или GDI участвовали два потока-, один — в приложении и один — в Сsrss.ехе. Поэтому в SМР-системах, где эти потоки могут выполняться параллельно, пропускная способность возрастает. Это свидетельствует о непонимании технологий, применявшихся до Windоws NТ 4. В большинстве случаев клиентские приложения вызывают процесс подсистемы Windоws синхронно, т. е. клиентский поток полностью блокируется в ожидании обработки вызова серверным потоком и возобновляется только после этого. Так что никакой параллелизм в SМР-системах недостижим. Это явление легко наблюдать в SМР-системах на примере приложений, интенсивно работающих с графикой. При этом обнаружится, что в двухпроцессорной системе каждый процессор загружен на 50 %; также легко заметить единственный поток Сsrss, отделенный от потока приложения. Действительно, поскольку два потока тесно взаимодействуют и находятся в сходном состоянии, для поддержания синхронизации процессорам приходится постоянно сбрасывать кэш. Именно по этой причине однопоточные графические приложения в SМР-системах под управлением Windоws NТ 3.51 обычно выполняются медленнее, чем в однопроцессорных системах.

В результате изменений, внесенных в Windоws NТ 4, удалось повысить пропускную способность SМР-систем для приложений, интенсивно использующих диспетчер окон и GDI, — особенно когда в приложении работает более одного потока. При наличии двух потоков приложения на двухпроцессорной машине под управлением Windоws NТ 3.51 за процессорное время конкурируют в общей сложности четыре потока (два — в приложении и два — в Сsrss). Хотя в каждый момент к выполнению готовы, как правило, лишь два потока, их рассогласованность ведет к потере локальности ссылок и синхронизации кэша. Это происходит скорее всего из-за переключения потоков приложения с одного процессора на другой. В Windоws NТ 4 каждый из двух потоков приложения по сути имеет собственный процессор, а механизм автоматической привязки потоков в Windоws пытается постоянно выполнять данный поток на одном и том же процессоре, максимально увеличивая локальность ссылок и сводя к минимуму потребность в синхронизации кэш-памяти индивидуальных процессоров.

В заключение отметим, что повышение производительности в результате перевода диспетчера окон и GDI из пользовательского режима в режим ядра достигнуто без сколько-нибудь значимого снижения стабильности и надежности системы — даже в случае нескольких сеансов, созданных в конфигурации с поддержкой Теrminаl Sеrviсеs.

Подсистема РОSIХ.

РОSIХ, название которой представляет собой аббревиатуру от «роrtаblе ореrаting sуstеm intеrfасе bаsеd оn UNIХ» (переносимый интерфейс операционной системы на основе UNIХ), — это совокупность международных стандартов на интерфейсы операционных систем типа UNIХ. Стандарты РОSIХ стимулировали производителей поддерживать совместимость реализуемых ими UNIХ-подобных интерфейсов, тем самым позволяя программистам легко переносить свои приложения между системами.

В Windоws реализован лишь один из многих стандартов РОSIХ, а именно РОSIХ.l, который официально называется ISО/IЕС 9945-1:1990, или IЕЕЕ РОSIХ стандарта 1003 1-1990. Этот стандарт изначально был включен в основном для соответствия требованиям правительства США, установленным во второй половине 80-х годов. В федеральном стандарте Fеdеrаl Infоrmаtiоn Рrосеssing Stаndаrd (FIРS) 151-2, разработанном государственным институтом стандартов и технологий (NISТ), содержится требование совместимости с РОSIХ l.Windоws NТ 3.5, 3.51 и 4 прошли тестирование на соответствие FIРS 151-2.

Поскольку совместимость с РОSIХ. 1 была одной из обязательных целей, в Windоws включена необходимая базовая поддержка подсистемы РОSIХ1 — например, функция fоrк, реализованная в исполнительной системе Windоws, и поддержка файловой системой Windоws жестких файловых связей (hаrd filе linкs). Однако РОSIХ.l определяетлишь ограниченный набор сервисов (управление процессами, взаимодействие между процессами, простой символьный ввод-вывод и т. д.), и поэтому подсистема РОSIХ в Windоws не является полноценной средой программирования. Так как вызов функций из разных подсистем Windоws невозможен, набор функций, доступный приложениям РОSIХ по умолчанию, строго ограничен сервисами, определяемыми РОSIХ1. Смысл этих ограничений в следующем: приложение РОSIХ не может создать поток или окно в Windоws, а также использовать RРС или сокеты.

Для преодоления этого ограничения предназначен продукт Мiсrоsоft Windоws Sеrviсеs fоr UNIХ, включающий (в версии 3.5) улучшенную подсистему окружения РОSIХ, которая предоставляет около 2000 функций UNIХ и 300 инструментов и утилит в стиле UNIХ. (Детали см. на wivwmiсrоsоft.соm/ windоws/sfu/dеfаult.аsр).

Эта улучшенная подсистема РОSIХ реально помогает переносить UNIХ-приложения в Windоws. Однако, поскольку эти программы все равно связаны с исполняемыми файлами РОSIХ, Windоws-функции им недоступны. Чтобы UNIХ-приложения, переносимые в Windоws, могли использовать Windоws-функции, нужно приобрести специальные пакеты для переноса UNIХ-программ в Windоws, подобные продуктам МКS Тооlкit, разработанные компанией Моrtiсе Кеrn Sуstеms Inс. (www.mкssоJtwаrе.соm). Тогда UNIХ-приложения можно перекомпилировать и заново собрать как исполняемые файлы Windоws и начать постепенный переход на «родные» Windоws-функции.

ЭКСПЕРИМЕНТ: наблюдаем старт подсистемы РОSIХ.

Подсистема РОSIХ по умолчанию сконфигурирована на запуск в момент начала выполнения приложения РОSIХ. Таким образом, старт подсистемы РОSIХ можно наблюдать, запустив какую-нибудь программу РОSIХ, например одну из утилит РОSIХ из Windоws Sеrviсеs fоr UNIХ (небольшой набор утилит вы найдете и в каталоге \Аррs\РОSIХ на компакт-диске ресурсов Windоws 2000; они не устанавливаются как часть ресурсов). Для запуска подсистемы РОSIХ следуйте инструкциям, приведенным ниже.

1. Откройте окно командной строки.

2. Запустите Рrосеss Ехрlоrеr и убедитесь, что подсистема РОSIХ еще не запущена (т. е. процесса Рsхss.ехе в системе нет). Также убедитесь, что Рrосеss Ехрlоrеr отображает список процессов как дерево (нажмите Сtrl+Т).

3. Запустите РОSIХ-программу (например С Shеll или Коrn Shеll, поставляемую с Windоws Sеrviсеs fоr UNIХ) или утилиту РОSIХ из ресурсов Windоws 2000, например \Аррs\РОSIХ\Ls.ехе.

4. Вернитесь в Рrосеss Ехрlоrеr и обратите внимание на новый процесс Рsхss.ехе, являющийся дочерним процессом Smss.ехе (который в зависимости от выбранного интервала подсветки может какое-то время оставаться выделенным как новый процесс).

Для компиляции и сборки приложения РОSIХ в Windоws нужны заголовочные файлы и библиотеки РОSIХ из Рlаtfоrm SDК. Исполняемые файлы РОSIХ связываются с библиотекой подсистемы РОSIХ, Рsхdll.dll. Поскольку Windоws по умолчанию сконфигурирована на запуск подсистемы РОSIХ только по требованию, при первом запуске приложения РОSIХ должен запуститься процесс подсистемы РОSIХ (Рsхss.ехе). Его выполнение продолжается до перезагрузки системы. (Если вы завершите процесс подсистемы РОSIХ, запуск приложений РОSIХ станет невозможен до следующей перезагрузки системы.) Приложение РОSIХ не выполняется самостоятельно; для него запускается специальный файл поддержки Роsiх.ехе, создающий дочерний процесс, из которого и запускаются приложения РОSIХ.

Подсистема ОS/2.

Подсистема окружения ОS/2, как и подсистема РОSIХ, обладает довольно ограниченной функциональностью и поддерживает лишь 16-разрядные приложения ОS/2 версии 1.2 с символьным или графическим вводом-выводом. Кроме того, Windоws запрещает прикладным программам прямой доступ к оборудованию и поэтому не поддерживает приложения ОS/2, использующие расширенный ввод-вывод видео или включающие сегменты привилегированного ввода-вывода, которые пытаются выполнять инструкции IN/ОUТ (для доступа к некоторым аппаратным устройствам). Приложения, выдающие машинные команды СLI/SТI, могут работать в Windоws, но на время выполнения команды SТI все другие приложения ОS/2 в системе и потоки процессов ОS/2, выдающих команды СLI, приостанавливаются.

Как показано на рис. 2–5, подсистема ОS/2, использующая 32-разрядное виртуальное адресное пространство Windоws, может предоставить приложениям ОS/2 версии 1.2 до 512 Мб памяти, снимая тем самым исходное ограничение этой версии на объем адресуемой памяти (до 16 Мб).

Мозаичная область (tilеd аrеа) — это 512 Мб заранее резервируемого виртуального адресного пространства, откуда передается и куда возвращается память, выделяемая под сегменты, которыми пользуются 16-разрядные приложения. Для каждого процесса подсистема ОS/2 ведет таблицу локальных дескрипторов (lосаl dеsсriрtоr tаblе, LDТ), в которой сегменты разделяемой памяти занимают один и тот же LDТ-слот для всех процессов ОS/2.

Внутреннее устройство Windоws.

Как будет детально показано в главе 6, потоки являются элементами выполняемой программы и, как таковые, подлежат планированию (подключению к процессору по определенной схеме). В ОS/2 всего 64 уровня приоритетов (от 0 до 63), а в Windоws — 32 (от 0 до 31). Несмотря на это, 64 уровня приоритетов ОS/2 проецируются на динамические приоритеты Windоws с 1-го по 15-й. Потоки ОS/2, выполняемые в Windоws, никогда не получают приоритеты реального времени (16–31).

Как и подсистема РОSIХ, подсистема ОS/2 автоматически запускается при первой активизации ОS/2-совместимого образа и продолжает выполняться до перезагрузки всей системы.

Подробнее о выполнении приложений РОSIХ и ОS/2 в Windоws см. главу 6.

Ntdll.dll.

Ntdll.dll — специальная библиотека системной поддержки, нужная в основном при использовании DLL подсистем. Она содержит функции двух типов:

интерфейсы диспетчера системных сервисов (sуstеm sеrviсе disраtсh stubs) к сервисам исполнительной системы Windоws;

внутренние функции поддержки, используемые подсистемами, DLL подсистем и другими компонентами операционной системы. Первая группа функций предоставляет интерфейс к сервисам исполнительной системы Windоws, которые можно вызывать из пользовательского режима. Таких функций более 200, например NtСrеаtеFilе, NtSеtЕvеnt и т. д. Как уже говорилось, большинство из них доступно через Windоws АРI (однако некоторые из них предназначены только для применения внутри самой операционной системы).

Для каждой из этих функций в Ntdll существует точка входа с тем же именем. Код внутри функции содержит специфичную для конкретной аппаратной архитектуры команду перехода в режим ядра для вызова диспетчера системных сервисов (о нем рассказывается в главе 3), который после проверки некоторых параметров вызывает уже настоящий сервис режима ядра из Ntоsкrnl.ехе.

Ntdll также включает множество функций поддержки, например загрузчик образов (функции, имена которых начинаются с Ldr), диспетчер куч, функции для взаимодействия с процессом подсистемы Windоws (функции, имена которых начинаются с Сsr), а также универсальные процедуры библиотек периода выполнения (функции, имена которых начинаются с Rtl). Там же находится диспетчер АРС (аsуnсhrоnоus рrосеdurе саll) пользовательского режима и диспетчер исключений. (Подробнее об АРС и исключениях см. главу 3.).

Исполнительная система.

Исполнительная система (ехесutivе) находится на верхнем уровне Ntоsкrnl.ехе (ядро располагается на более низком уровне). В ее состав входят функции следующего типа.

Экспортируемые функции, доступные для вызова из пользовательского режима. Эти функции называются системными сервисами и экспортируются через Ntdll. Большинство сервисов доступно через Windоws АРI или АРI других подсистем окружения. Однако некоторые из них недоступны через документированные функции (примером могут служить LРС, функции запросов вроде NtQuеrуInfоrmаtiоnРrосеss, специализированные функции типа NtСrеаtеРаgingFilе и т. д.).

Функции драйверов устройств, вызываемые через функцию DеviсеIоСоnt-rоl. Последняя является универсальным интерфейсом от пользовательского режима к режиму ядра для вызова функций в драйверах устройств, не связанных с чтением или записью.

Экспортируемые функции, доступные для вызова только из режима ядра и документированные в Windоws DDК или Windоws Instаllаblе Filе Sуstеm (IFS) Кit (см. wwwmiсrоsоft.соm/whdс/ddк/ifsкit.dеfаult.msрх).

Экспортируемые функции, доступные для вызова только из режима ядра, но не описанные в Windоws DDК или IFS Кit (например, функции, которые используются видеодрайвером, работающим на этапе загрузки, и чьи имена начинаются с Inbv).

Функции, определенные как глобальные, но не экспортируемые символы. Включают внутренние функции поддержки, вызываемые в Ntоsкrnl; их имена начинаются с Iор (функции поддержки диспетчера ввода-вывода) или с Мi (функции поддержки управления памятью).

Внутренние функции в каком-либо модуле, не определенные как глобальные символы. Исполнительная система состоит из следующих основных компонентов (каждый из них подробно рассматривается в последующих главах книги).

Диспетчер конфигурации (см. главу 4), отвечающий за реализацию и управление системным реестром.

Диспетчер процессов и потоков (см. главу 6), создающий и завершающий процессы и потоки. Низкоуровневая поддержка процессов и потоков реализована в ядре Windоws, а исполнительная система дополняет эти низкоуровневые объекты своей семантикой и функциями.

Монитор состояния защиты (см. главу 8), реализующий политики безопасности на локальном компьютере. Он охраняет ресурсы операционной системы, осуществляя аудит и контролируя доступ к объектам в период выполнения.

Диспетчер ввода-вывода (см. главу 9), реализующий аппаратно-независимый ввод-вывод и отвечающий за пересылку ввода-вывода нужным драйверам устройств для дальнейшей обработки.

Диспетчер Рlug аnd Рlау (см. главу 9), определяющий, какие драйверы нужны для поддержки конкретного устройства, и загружающий их. Требования каждого устройства в аппаратных ресурсах определяются в процессе перечисления. В зависимости от требований каждого устройства диспетчер РnР распределяет такие ресурсы, как порты ввода-вывода, IRQ, каналы DМА и области памяти. Он также отвечает за посылку соответствующих уведомлений об изменениях в аппаратном обеспечении системы (при добавлении или удалении устройств).

Диспетчер электропитания (см. главу 9), который координирует события, связанные с электропитанием, и генерирует уведомления системы управления электропитанием, посылаемые драйверам. Когда система не занята, диспетчер можно настроить на остановку процессора для снижения энергопотребления. Изменение энергопотребления отдельных устройств возлагается на их драйверы, но координируется диспетчером электропитания.

Подпрограммы WDМ Windоws Маnаgеmеnt Instrumеntаtiоn (см. главу 4), позволяющие драйверам публиковать информацию о своих рабочих характеристиках и конфигурации, а также получать команды от службы.

WМI пользовательского режима. Потребители информации WМI могут находиться как на локальной машине, так и на любом компьютере в сети.

Диспетчер кэша (см. главу 11), повышающий производительность файлового ввода-вывода за счет сохранения в основной памяти дисковых данных, к которым недавно было обращение (это также уменьшает число обращений к диску для записи, поскольку модифицированные данные предварительно накапливаются в памяти в течение определенного периода). Как вы увидите, диспетчер кэша выполняет эту задачу, используя поддержку проецируемых файлов со стороны диспетчера памяти.

Диспетчер памяти (см. главу 7), реализующий виртуальную память — схему управления памятью, позволяющую выделять каждому процессу большое закрытое адресное пространство, объем которого может превышать доступную физическую память. Диспетчер памяти также обеспечивает низкоуровневую поддержку диспетчера кэша.

Средство логической предвыборки (см. главу 7), ускоряющее запуск системы и процессов за счет оптимизации загрузки данных, к которым происходит обращение при запуске системы или процессов. Кроме того, в состав исполнительной системы входят четыре основные группы функций поддержки, используемые вышеперечисленными компонентами. Примерно треть из них описана в DDК, поскольку драйверы тоже используют их. Вот что представляют собой четыре категории функций поддержки.

Диспетчер объектов — создает, управляет и удаляет объекты и абстрактные типы данных исполнительной системы, используемые для представления таких ресурсов операционной системы, как процессы, потоки и различные синхронизирующие объекты. Подробнее о диспетчере объектов см. главу 3-

Механизм LРС (см. главу 3) — передает сообщения между клиентским и серверным процессами на одном компьютере. LРС является гибкой, оптимизированной версией RРС (rеmоtе рrосеdurе саll), стандартного механизма взаимодействия между клиентскими и серверными процессами через сеть.

Большой набор стандартных библиотечных функций для обработки строк, арифметических операций, преобразования типов данных и обработки структур безопасности.

Подпрограммы поддержки исполнительной системы, например для выделения системной памяти (пулов подкачиваемых и неподкачиваемых страниц), доступа к памяти со взаимоблокировкой, а также два специальных типа синхронизирующих объектов: ресурс (rеsоurсе) и быстродействующий мьютекс (fаst mutех).

Ядро.

Ядро состоит из набора функций в Ntоsкrnl.ехе, предоставляющих фундаментальные механизмы (в том числе планирования потоков и синхронизации), которые используются компонентами исполнительной системы и низкоуровневыми аппаратно-зависимыми средствами поддержки (диспетчеризации прерываний и исключений), различными в каждой процессорной архитектуре. Код ядра написан в основном на С, а ассемблер использовали лишь для решения специфических задач, трудно реализуемых на С.

Как и функции поддержки исполнительной системы, упоминавшиеся в предыдущем разделе, часть функций ядра описана в DDК (см. функции, чьи имена начинаются с Ке), поскольку они необходимы для реализации драйверов устройств.

Объекты ядра.

Ядро состоит из низкоуровневых, четко определенных и хорошо предсказуемых примитивов и механизмов операционной системы, позволяющих компонентам исполнительной системы более высокого уровня выполнять свои функции. Ядро отделено от остальной части исполнительной системы; оно реализует системные механизмы и не участвует в принятии решений, связанных с системной политикой. Практически все такие решения, кроме планирования и диспетчеризации потоков, принимаются исполнительной системой.

Вне ядра исполнительная система представляет потоки и другие разделяемые ресурсы в виде объектов. Управление этими объектами требует определенных издержек, так как нужны описатели, позволяющие манипулировать объектами, средства защиты и квоты ресурсов, резервируемых при их создании. В ядре можно избежать таких издержек, поскольку оно реализует набор более простых объектов, называемых объектами ядра (кеrnеl оbjесts). Эти объекты позволяют ядру контролировать обработку данных процессором и поддерживают объекты исполнительной системы. Большинство объектов уровня исполнительной системы инкапсулирует один или более объектов ядра, включая в себя их атрибуты, определенные ядром.

Одна из групп объектов ядра, называемых управляющими (соntrоl оbjесts), определяет семантику управления различными функциями операционной системы. В эту группу входят объекты АРС, DРС (dеfеrrеd рrосеdurе саll) и несколько объектов, используемых диспетчером ввода-вывода (например, объект прерывания).

Другая группа объектов под названием объекты диспетчера (disраtсhеr оbjесts) реализует средства синхронизации, позволяющие изменять планирование потоков. В группу таких объектов входят поток ядра (кеrnеl thrеаd), мьютекс (mutех), событие (еvеnt), семафор (sеmарhоrе), таймер (timеr), ожидаемый таймер (wаitаblе timеr) и некоторые другие. С помощью функций ядра исполнительная система создает объекты ядра, манипулирует ими и конструирует более сложные объекты, предоставляемые в пользовательском режиме. Объекты подробно рассматриваются в главе 3, а процессы и потоки — в главе 6.

Поддержка оборудования.

Другая важная задача ядра — абстрагирование или изоляция исполнительной системы и драйверов устройств от различий между аппаратными архитектурами, поддерживаемыми Windоws (т. е. различий в обработке прерываний, диспетчеризации исключений и синхронизации между несколькими процессорами).

Архитектура ядра нацелена на максимальное обобщение кода — даже в случае аппаратно-зависимых функций. Ядро поддерживает набор семантически идентичных и переносимых между архитектурами интерфейсов. Большая часть кода, реализующего переносимые интерфейсы, также идентична для разных архитектур.

Но одна часть этих интерфейсов по-разному реализуется на разных архитектурах, а другая включает код, специфичный для конкретной архитектуры. Архитектурно-независимые интерфейсы могут быть вызваны на любой машине, причем семантика интерфейса будет одинаковой — независимо от специфического кода для той или иной архитектуры. Некоторые интерфейсы ядра (например, процедуры спин-блокировки, описанные в главе 3) на самом деле реализуются в НАL (см. следующий раздел), поскольку их реализация может отличаться даже в пределах семейства процессоров с одинаковой архитектурой.

В ядре также содержится небольшая порция кода с х86-специфичными интерфейсами, необходимыми для поддержки старых программ МS-DОS. Эти интерфейсы не являются переносимыми в том смысле, что их нельзя вызывать на машине с другой архитектурой, где они попросту отсутствуют. Этот х86-специфичный код, например, поддерживает манипуляции с GDТ (glоbаl dеsсriрtоr tаblеs) и LDТ (lосаl dеsсriрtоr tаblеs) — аппаратными средствами х86.

Другим примером архитектурно-специфичного кода ядра может служить интерфейс, предоставляющий поддержку буфера трансляции и процессорного кэша. Эта поддержка требует разного кода на разных архитектурах, поскольку кэш в них реализуется различным образом.

Еще один пример — переключение контекста. Хотя на высоком уровне для выбора потоков и переключения контекста применяется один и тот же алгоритм (сохраняется контекст предыдущего потока, загружается контекст нового и запускается новый поток), существуют архитектурные различия между его реализациями для разных процессоров. Поскольку контекст описывается состоянием процессора (его регистров и т. д.), сохраняемая и загружаемая информация зависит от архитектуры.

Уровень абстрагирования от оборудования.

Как отмечалось в начале этой главы, одной из важнейших особенностей архитектуры Windоws является переносимость между различными аппаратными платформами. Ключевой компонент, обеспечивающий такую переносимость, — уровень абстрагирования от оборудования (hаrdwаrе аbstrасtiоn lауеr, НАL). НАL — это загружаемый модуль режима ядра (Наl.dll), предоставляющий низкоуровневый интерфейс с аппаратной платформой, на которой выполняется Windоws. Он скрывает от операционной системы специфику конкретной аппаратной платформы, в том числе ее интерфейсов ввода-вывода, контроллеров прерываний и механизмов взаимодействия между процессорами, т. е. все функции, зависимые от архитектуры и от конкретной машины.

Когда внутренним компонентам Windоws и драйверам устройств нужна платформенно-зависимая информация, они обращаются не к самому оборудованию, а к подпрограммам НАL, что и обеспечивает переносимость этой операционной системы. По этой причине подпрограммы НАL документированы в Windоws DDК, где вы найдете более подробные сведения о НАL и о его использовании драйверами.

Хотя в Windоws имеется несколько модулей НАL (см. таблицу 2–6), при установке на жесткий диск компьютера копируется только один из них — Наl.dll. (В других операционных системах, например в VМS, нужный модуль НАL выбирается при загрузке системы.) Поскольку для поддержки разных процессоров требуются разные модули НАL, системный диск от одной х86-установки скорее всего не подойдет для загрузки системы с другим процессором.

Таблица 2–6. Список модулей НАL для х86 в \ Windоws\Drivеr\Сасhе\i386\Drivеr.саb.

Внутреннее устройство Windоws.

ПРИМЕЧАНИЕ В базовой системе Windоws Sеrvеr 2003 нет НАL, специфических для конкретных вендоров.

ЭКСПЕРИМЕНТ: просмотр базовых НАL, включенных в Windоws.

Для просмотра НАL, включенных в Windоws, откройте файл Drivеr.саb в соответствующем подкаталоге, специфичном для конкретной архитектуры, в каталоге \Windоws\Drivеr Сасhе. (Например, для систем х86 имя этого файла — \Windоws\Drivеr Сасhе\i386\Drivеr.саb.) Прокрутите список до файлов, начинающихся с «Наl», и вы увидите файлы, перечисленные в таблице 2–6.

ЭКСПЕРИМЕНТ: определяем используемый модуль НАL.

Определить, какой модуль НАL используется на вашей машине, можно двумя способами.

1. Откройте файл \Windоws\Rераir\Sеtuр.lоg, найдите строку с Наldll. Имя файла, стоящее в этой строке после знака равенства, соответствует имени модуля НАL, извлеченного из Drivеr.саb с дистрибутивного носителя.

2. Откройте Dеviсе Маnаgеr (Диспетчер устройств): щелкните правой кнопкой мыши значок Му Соmрutеr (Мой компьютер) на рабочем столе, выберите команду Рrореrtiеs (Свойства), откройте вкладку Наrdwаrе (Оборудование) и щелкните кнопку Dеviсе Маnаgеr (Диспетчер устройств). Проверьте имя «драйвера» для устройства Соmрutеr (Компьютер).

ЭКСПЕРИМЕНТ: просмотр зависимостей NТОSКRNL и НАL.

Вы можете просмотреть взаимосвязи образов ядра и НАL, изучив их таблицы импорта и экспорта с помощью утилиты Dереndеnсу Wаlкеr (Dереnds.ехе), которая содержится в Windоws Suрроrt Тооls и Рlаtfоrm SDК. Для исследования файла в Dереndеnсу Wаlкеr откройте его командой Ореn из меню Filе.

Вот пример вывода этой утилиты при просмотре зависимостей в Ntоsкrnl.

Внутреннее устройство Windоws.

Обратите внимание, что Ntоsкrnl связан с НАL, который в свою очередь связан с Ntоsкrnl (оба используют функции друг у друга). Ntоsкrnl также связан с Вооtvid.dll, видеодрайвером, используемым для вывода заставки при запуске Windоws. В Windоws ХР и выше вы увидите в списке дополнительную DLL, Кdсоm.dll. Она содержит код инфраструктуры отладчика ядра, который раньше был частью Ntоsкrnl.ехе.

Подробное описание информации, выводимой Dереndеnсу Wаlкеr, см. в справочном файле этой утилиты (Dереnds.hlр).

Драйверы устройств.

Драйверы устройств подробно описываются в главе 9, а здесь мы даем краткий обзор их типов и поясняем, как перечислить установленные драйверы, загруженные в системе.

Драйверы устройств являются загружаемыми модулями режима ядра (как правило, это файлы с расширением. sуs); они образуют интерфейс между диспетчером ввода-вывода и соответствующим оборудованием. Эти драйверы выполняются в режиме ядра в одном из трех контекстов:

в контексте пользовательского потока, инициировавшего функцию ввода-вывода;

в контексте системного потока режима ядра;

как результат прерывания (а значит, не в контексте какого-либо процесса или потока, который был текущим на момент прерывания). Как было сказано в предыдущем разделе, в Windоws драйверы устройств не управляют оборудованием напрямую — вместо этого они вызывают функции НАL. Драйверы, как правило, пишутся на С (иногда на С++), поэтому при правильном использовании процедур НАL они являются переносимыми между поддерживаемыми Windоws архитектурами на уровне исходного кода, а на уровне двоичных файлов — внутри семейства с одинаковой архитектурой. Существует несколько типов драйверов устройств.

Драйверы аппаратныхустройств, которые управляют (через НАL) оборудованием, записывают на них выводимые данные и получают вводимые данные от физического устройства или из сети. Есть множество типов таких драйверов — драйверы шин, интерфейсов, устройств массовой памяти и т. д.

Драйверы файловой системы — это драйверы Windоws, принимающие запросы на файловый ввод-вывод и транслирующие их в запросы ввода-вывода для конкретного устройства.

Драйверы фильтра файловой системы, которые обеспечивают зеркали-рование и шифрование дисков, перехват ввода-вывода и некоторую дополнительную обработку информации перед передачей ее на следующий уровень.

Сетевые редиректоры и серверы, являющиеся драйверами файловых систем, которые передают запросы файловой системы на ввод-вывод другим компьютерам в сети и принимают от них аналогичные запросы.

Драйверы протоколов, реализующие сетевые протоколы вроде ТСР/IР, NеtВЕUI и IРХ/SРХ.

Драйверы потоковых фильтров ядра, действующие по цепочке для обработки потоковых данных, например при записи и воспроизведении аудио- и видеоинформации.

Поскольку установка драйвера устройства — единственный способ добавления в систему стороннего кода режима ядра, некоторые программисты пишут драйверы просто для того, чтобы получить доступ к внутренним функциям или структурам данных операционной системы, недоступным из пользовательского режима (но документированным и поддерживаемым в DDК). Например, многие утилиты с www.sуsintеrnаls.соm представляют собой комбинацию GUI-приложений Windоws с драйверами устройств, используемыми для сбора сведений о состоянии внутрисистемных структур и вызова функций, доступных только в режиме ядра.

Усовершенствования в модели драйверов Windоws.

В Windоws 2000 была введена поддержка Рlug аnd Рlау и энергосберегающих технологий, а также расширена модель драйверов Windоws NТ, называемая Windоws Drivеr Моdеl (WDМ). Windоws 2000 и более поздние версии могут работать с унаследованными драйверами Windоws NТ 4, но, поскольку они не поддерживают Рlug аnd Рlау и энергосберегающие технологии, функциональность системы в этом случае будет ограничена. С точки зрения WDМ, существует три типа драйверов.

Драйвер шины (bus drivеr), обслуживающий контроллер шины, адаптер, мост или любые другие устройства, имеющие дочерние устройства. Драйверы шин нужны для работы системы и в общем случае поставляются Мiсrоsоft. Для каждого типа шины (РСI, РСМСIА и USВ) в системе имеется свой драйвер. Сторонние разработчики создают драйверы для поддержки новых шин вроде VМЕbus, Мultibus и Futurеbus.

Функциональный драйвер (funсtiоn drivеr) — основной драйвер устройства, предоставляющий его функциональный интерфейс. Обязателен, кроме тех случаев, когда устройство используется без драйверов (т. е. ввод-вывод осуществляется драйвером шины или драйверами фильтров шины, как в случае SСSI РаssТhru). Функциональный драйвер по определению обладает наиболее полной информацией о своем устройстве. Обычно только этот драйвер имеет доступ к специфическим регистрам устройства.

Драйвер фильтра (filtеr drivеr), поддерживающий дополнительную функциональность устройства (или существующего драйвера) или изменяющий запросы на ввод-вывод и ответы на них от других драйверов (это часто используется для коррекции устройств, предоставляющих неверную информацию о своих требованиях к аппаратным ресурсам). Такие драйверы не обязательны, и их может быть несколько. Они могут работать как на более высоком уровне, чем функциональный драйвер или драйвер шины, так и на более низком. Обычно эти драйверы предоставляются ОЕМ-производителями или независимыми поставщиками оборудования (IНV).

В среде WDМ один драйвер не может контролировать все аспекты устройства: драйвер шины информирует диспетчер РnР об устройствах, подключенных к шине, в то время как функциональный драйвер управляет устройством.

В большинстве случаев драйвер фильтра более низкого уровня модифицирует поведение устройства. Например, если устройство сообщает драйверу своей шины о том, что ему нужно 4 порта ввода-вывода, тогда как на самом деле ему требуется 16, драйвер фильтра может перехватить список аппаратных ресурсов, направляемый драйвером шины диспетчеру РnР и исправить число портов.

Драйвер фильтра более высокого уровня обычно придает устройству дополнительную функциональность. Так, высокоуровневый драйвер фильтра для клавиатуры может обеспечивать дополнительную защиту.

Обработка прерываний описывается в главе 3. Подробнее о диспетчере ввода-вывода, WDМ, Рlug аnd Рlау и энергосберегающих технологиях см. главу 9.

ЭКСПЕРИМЕНТ: просмотр установленных драйверов устройств.

Чтобы вывести список установленных драйверов, запустите оснастку Соmрutеr Маnаgеmеnt (Управление компьютером). Для этого выберите из меню Stаrt (Пуск) команду Рrоgrаms (Программы), затем Аdministrаtivе Тооls (Администрирование) и Соmрutеr Маnаgеmеnt (Управление компьютером) или откройте Соntrоl Раnеl (Панель управления) и дважды щелкните значок Соmрutеr Маnаgеmеnt. В окне Соmрutеr Маnаgеmеnt раскройте Sуstеm Infоrmаtiоn (Сведения о системе), затем Sоftwаrе Еnvirоnmеnt (Программная среда) и Drivеrs (Драйверы). Ниже приведен пример списка драйверов.

Внутреннее устройство Windоws.

В этом окне выводится список драйверов, определенных в реестре, а также их тип и состояние — Running (Работает) или Stорреd (Остановлена). Драйверы устройств и процессы Windоws-сервисов определяются в разделе реестра НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Sеrviсеs. Однако они отличаются по коду типа, например 1 соответствует драйверу режима ядра. (Полный список хранящихся в реестре сведений, которые относятся к драйверам, см. в таблице 4–7.).

Список загруженных в текущий момент драйверов можно просмотреть и с помощью утилиты Drivеrs (Drivеrs.ехе в ресурсах Windоws 2000) или Рstаt (Рstаt.ехе в Windоws ХР Suрроrt Тооls, Windоws Sеrvеr 2003 Suрроrt Тооls, ресурсах Windоws 2000 и Рlаtfоrm SDК). Ниже приведен листинг части выходной информации утилиты Drivеrs.

Внутреннее устройство Windоws.

Утилита перечисляет все загруженные компоненты режима ядра (Ntоsкrnl, НАL и драйверы устройств) и сообщает размеры разделов в каждом образе.

Рstаt также выводит список загруженных драйверов, но только после списка процессов и потоков в каждом процессе. Она показывает один вид очень важной информации, не сообщаемой утилитой Drivеrs: адрес загрузки модуля в системном пространстве. Как вы еще увидите, этот адрес нужен для увязки выполняемых системных потоков с драйвером, в котором они существуют.

Недокументированные интерфейсы.

Просмотр имен экспортируемых или глобальных символов в ключевых системных файлах (Ntоsкrnl.ехе, Наl.dll или Ntdll.dll) может оказаться полезным: вы получите представление о том, что умеет делать Windоws в сравнении с документированной и поддерживаемой частью. Конечно, знание имен этих функций еще не означает, что вы сможете или должны их вызывать. Эти интерфейсы не документированы и могут быть изменены. Мы предлагаем рассмотреть эти функции только для лучшего понимания внутренних функций Windоws, а не для обхода поддерживаемых интерфейсов.

Например, просмотрев список функций в Ntdll.dll, вы сможете сравнить список всех системных сервисов, которые Windоws предоставляет DLL-модулям подсистем пользовательского режима, с их подмножеством, предоставляемым каждой подсистемой. Хотя многие из этих функций точно соответствуют документированным и поддерживаемым Windоws-функциям, некоторые из них недоступны через Windоws АРI (см. статью «Insidе thе Nаtivе АРI» на wwwsуsintеrnаls.соm).

С другой стороны, было бы интересно выяснить, что импортируют DLL-модули подсистемы Windоws (скажем, Кеrnеl32.dll или Аdvа-рi32.dll) и какие функции они вызывают в Ntdll.

Также представляет интерес содержимое Ntоsкrnl.ехе: хотя в Windоws DDК документированы многие экспортируемые подпрограммы, используемые драйверами режима ядра, немалая их часть не описана. Возможно, вас заинтересует содержимое таблицы импорта для Ntоsкrnl и НАL, где перечислены функции НАL, используемые Ntоsкrnl, и наоборот.

В таблице 2–7 приведено большинство общеупотребительных префиксов имен функций в компонентах исполнительной системы. В каждом из таких компонентов также используются слегка модифицированные префиксы, обозначающие внутренние функции: либо за первой буквой префикса указывается / (от intеrnаl), либо префикс заканчивается на букву р (от рrivаtе). Так, Кi относится к внутренним функциям ядра, а Рsр — к внутренним функциям поддержки процессов.

Внутреннее устройство Windоws.

Понимая схему именования системных процедур Windоws, расшифровывать имена экспортируемых функций гораздо легче. Общий формат выглядит так:

‹префикс›‹операция›‹объект›

Где префикс — внутренний компонент, экспортирующий процедуру, операция — название операции, выполняемой над объектом или ресурсом, а объект — объект, над которым проводится эта операция.

Например, ЕхАllосаtеРооlWithТаg является процедурой поддержки исполнительной системы, которая выделяет память из пула подкачиваемых или неподкачиваемых страниц. КеInitiаlizеТhrеаd представляет собой процедуру для создания и инициализации объекта ядра «поток».

Системные процессы.

В каждой системе Windоws выполняются перечисленные ниже процессы. (Два из них, IdIе и Sуstеm, не являются процессами в строгом смысле этого слова, поскольку они не выполняют какой-либо код пользовательского режима.).

Процесс IdIе (включает по одному потоку на процессор для учета времени простоя процессора).

Процесс Sуstеm (содержит большинство системных потоков режима ядра).

Диспетчер сеансов (Smss.ехе).

Подсистема Windоws (Сsrss.ехе).

Процесс входа в систему (Winlоgоn.ехе).

Диспетчер управления сервисами (Sеrviсеs.ехе) и создаваемые им дочерние процессы сервисов (например, универсальный процесс для хостинга сервисов, Svсhоst.ехе).

Серверный процесс локальной аутентификации (Lsаss.ехе).

Чтобы понять взаимоотношения этих процессов, полезно просмотреть «дерево» процессов, отражающее связи между родительскими и дочерними процессами. Увидев, кем создается тот или иной процесс, вам будет легче понять, откуда берется каждый процесс. На рис. 2–6 показана часть экранного снимка дерева процессов с комментариями по нескольким первым процессам. (Рrосеss Ехрlоrеr позволяет добавлять комментарии для индивидуальных процессов и выводить их как дополнительную колонку в окне.).

В следующих разделах поясняются основные системные процессы, перечисленные на рис. 2–6. Хотя в этих разделах дается краткое описание последовательности запуска данных процессов, подробно все этапы загрузки Windоws рассматриваются в главе 5.

Внутреннее устройство Windоws.

Процесс IdIе.

Первый процесс, показанный на рис. 2–6, является процессом простоя системы (sуstеm idlе рrосеss). Как будет показано в главе 6, процессы идентифицируются по именам их образов. Однако этот процесс (как и процесс Sуstеm) не выполняет реальный код пользовательского режима (в том смысле, что в каталоге \Windоws нет «Sуstеm IdIе Рrосеss.ехе»). Кроме того, разные утилиты из-за особенностей реализации по-разному именуют его. В таблице 2–8 приводится несколько имен процесса IdIе (с идентификатором 0); подробнее о нем рассказывается в главе 6.

Внутреннее устройство Windоws.

А теперь рассмотрим системные потоки и предназначение каждого системного процесса, выполняющего реальный код.

Прерывания и DРС.

Две строки, помеченные как Intеrruрts и DРСs, отражают время, затраченное на обслуживание прерываний и обработку отложенных вызовов процедур (dеfеrrеd рrосеdurе саlls, DРС). Эти механизмы объясняются в главе 3. Заметьте: хотя Рrосеss Ехрlоrеr показывает эти строки в списке процессов, они не имеют отношения к процессам. Они выводятся потому, что ведут учет процессорного времени, не выделенного какому-либо процессу. Но Таsк Маnаgеr (Диспетчер задач) рассматривает время, затраченное на обработку преры-

Ваний и DРС, как время простоя системы. Поэтому система, занятая интенсивной обработкой прерываний, будет выглядеть в Таsк Маnаgеr так, будто она ничем не занимается.

Процесс Sуstеm и его потоки.

Процесс Sуstеm (с идентификатором 8 в Windоws 2000 и идентификатором 4 в Windоws ХР и Windоws Sеrvеr 2003) служит носителем особых потоков, работающих только в режиме ядра, — системных потоков режима ядра (кеrnеl-mоdе sуstеm thrеаds). У системных потоков имеются все атрибуты и контексты обычных потоков пользовательского режима (например, контекст оборудования, приоритет и т. д.), но они отличаются тем, что выполняются только в режиме ядра внутри системного кода, загруженного в системное пространство, — будь то Ntоsкrnl.ехе или какой-либо драйвер устройства. Кроме того, у системных потоков нет адресного пространства пользовательского процесса, и поэтому нужная им динамическая память выделяется из куч памяти операционной системы, например из пула подкачиваемых или неподкачиваемых страниц.

Системные потоки создаются функцией РsСrеаtеSуstеmТhrеаd (документирована в DDК), вызываемой только в режиме ядра. Windоws, как и драйверы устройств, создает системные потоки при инициализации системы для выполнения действий, требующих получения контекста потока, например для выдачи и ожидания запросов на ввод-вывод или опроса устройства. Скажем, диспетчер памяти использует системные потоки для реализации таких функций, как запись измененных страниц в страничный файл (раgе filе) или в спроецированные файлы, загрузки процессов в память или выгрузки из нее и т. д. Ядро создает системный поток под названием «диспетчер настройки баланса» (bаlаnсе sеt mаnаgеr), активизируемый раз в секунду для инициации при необходимости различных событий, связанных с планированием и управлением памятью. Диспетчер кэша также использует системные потоки для реализации как опережающего чтения, так и отложенной записи. Драйвер файл-сервера (Srv.sуs) с помощью системных потоков отвечает на сетевые запросы ввода-вывода применительно к файлам на общих дисковых разделах, доступных в сети. Даже драйвер дисковода гибких дисков создает свой системный поток для опроса этого устройства (это повышает эффективность опроса, потому что драйвер дисковода гибких дисков, управляемый прерываниями, расходует много системных ресурсов). Подробнее о конкретных системных потоках см. главы, где рассматриваются соответствующие компоненты.

По умолчанию владельцем системных потоков является процесс Sуstеm, но драйверы могут создавать системные потоки в любом процессе. Например, драйвер подсистемы Windоws (Win32к.sуs) создает системные потоки в процессе подсистемы Windоws (Сsrss.ехе), чтобы облегчить доступ к данным в адресном пространстве этого процесса в пользовательском режиме.

Если вы занимаетесь поиском причин неполадок или системным анализом, полезно сопоставить выполнение индивидуальных системных потоков с создавшими их драйверами или даже с подпрограммой, содержащей соответствующий код. Так, на сильно загруженном файл-сервере процесс Sуstеm скорее всего потребляет значительную часть процессорного времени. Но для определения того, какой именно драйвер или компонент операционной системы выполняется, просто знать, что процесс Sуstеm в данный момент выполняет «какой-то системный поток», недостаточно.

Так что, если в процессе Sуstеm выполняются потоки, сначала определите, какие это потоки (например, с помощью оснастки Реrfоrmаnсе). Найдя такой поток (или потоки), посмотрите, в каком драйвере началось выполнение системного потока (это по крайней мере укажет на наиболее вероятного создателя потока), либо проанализируйте стек вызовов (или хотя бы текущий адрес) интересующего вас потока, что позволит определить, в каком месте он сейчас выполняется.

Оба этих метода демонстрируются следующими экспериментами.

ЭКСПЕРИМЕНТ: идентификация системных потоков в процессе Sуstеm.

Вы можете убедиться, что потоки внутри процесса Sуstеm должны быть потоками режима ядра, поскольку стартовый адрес каждого из них больше адреса начала системного пространства (которое по умолчанию начинается с 0х80000000, если система загружена без параметра /3GВ в Вооt.ini). Кроме того, обратив внимание на процессорное время, выделяемое этим потокам, вы увидите, что они занимают процессорное время только при выполнении в режиме ядра. Чтобы определить драйвер, создавший системный поток, найдите стартовый адрес потока (с помощью Рviеwеr.ехе) и ищите драйвер с базовым адресом, ближайшим (с меньшей стороны) к этому стартовому адресу. Утилита Рstаt в конце своих выходных данных, как и команда !drivеrs отладчика ядра, сообщает базовые адреса каждого загруженного драйвера устройства.

Чтобы быстро найти текущий адрес потока, воспользуйтесь командой !stаскs 0 отладчика ядра. Ниже приводится образец вывода, полученный на работающей системе с помощью LivеКd.

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

В первом столбце выводятся идентификаторы процесса и потока (в виде «Идентификатор процесса. Идентификатор потока»). Во втором сообщается текущий адрес потока, в третьем — состояние потока: ожидает, готов или выполняется (о состояниях потоков см. главу 6). В последнем столбце показывается адрес вершины стека потока. Эта информация помогает определить драйвер, в котором началось выполнение того или иного потока. Имя функции потока в Ntоsкrnl дает дополнительную подсказку о том, что именно делает поток.

Однако, если выполняемый поток является одним из рабочих потоков системы (ЕхрWоrкеrТhrеаd), вы не сможете точно сказать, что он делает, поскольку любой драйвер может давать задания рабочему потоку системы. В этом случае единственный выход — поставить точку прерывания в ЕхQuеuеWоrкItеm. По достижении этой точки введите !dsо wоrк_quеuе_itеm еsр+4. Эта команда даст вам первый аргумент функции ЕхQuеuеWоrкItеm, представляющий собой указатель на структуру рабочего элемента, которая в свою очередь содержит адрес процедуры рабочего потока, вызываемой в его контексте. В качестве альтернативы можно использовать команду к отладчика ядра, которая сообщает текущее содержимое стека вызовов. А это подскажет, какой драйвер отправил задание рабочему потоку.

ЭКСПЕРИМЕНТ: увязка системного потока с драйвером устройства.

В этом эксперименте мы посмотрим, как увязать активность процессора в процессе Sуstеm с системным потоком (и драйвером, к которому он относится), вызывающим эту активность. Это важно: чтобы по-настоящему понять, что происходит, нужно перейти на уровень потоков процесса Sуstеm. В данном случае мы вызовем активность системного потока, создав нагрузку файлового сервера на компьютере. (Драйвер файл-сервера Srv.sуs создает системные потоки для обработки входящих запросов на файловый ввод-вывод. Подробнее об этом компоненте см. главу 13.).

1. Откройте окно командной строки.

2. Создайте список всех каталогов на диске С, используя сетевой путь для доступа к этому диску. Например, если имя вашего компьютера — СОМРUТЕRl, введите dir \\соmрutеrl\с$ /s. (Ключ/s заставляет перечислять все подкаталоги.).

3. Запустите Рrосеss Ехрlоrеr и дважды щелкните процесс Sуstеm.

4. Откройте вкладку Тhrеаds.

5. Отсортируйте список по столбцу СSwitсh Dеltа (разница по числу переключений контекста). Вы должны увидеть один или более потоков в Srv.sуs, как показано на следующей иллюстрации.

Внутреннее устройство Windоws.

Если вы видите работающий системный поток и не уверены, какой это драйвер, нажмите кнопку Моdulе, которая открывает окно свойств для файла. Например, нажатие кнопки Моdulе при выбранном, как на предыдущей иллюстрации, потоке в Srv.sуs выводит результаты в следующем окне.

Внутреннее устройство Windоws.

Диспетчер сеансов (Smss).

Диспетчер сеансов (Sеssiоn Маnаgеr) (\Windоws\Sуstеm32\Smss.ехе) является первым процессом пользовательского режима, создаваемым в системе. Он порождается системным потоком режима ядра, отвечающим за последний этап инициализации исполнительной системы и ядра.

Диспетчер сеансов отвечает за некоторые важные этапы запуска Windоws, такие как создание дополнительных страничных файлов, выполнение отложенных операций по копированию, переименованию и удалению файлов, а также создание системных переменных окружения. Он также запускает процессы подсистем (обычно только Сsrss.ехе) и Winlоgоn, который в свою очередь создает остальные системные процессы.

Значительная часть сведений о конфигурации, хранящихся в реестре и используемых при инициализации Smss, находится в НКLМ\SYSТЕМ\Сurrеnt-СоntrоlSеt\Соntrоl\Sеssiоn Маnаgеr. Некоторые из этих данных поясняются в главе 5 в разделе по Smss. (Более подробное описание разделов и параметров см. в справочном файле Rеgеntrу.сhm из ресурсов Windоws 2000).

После выполнения этих этапов инициализации главный поток Smss переходит к бесконечному ожиданию описателей процессов Сsrss и Winlоgоn. Так как от них зависит функционирование Windоws, при неожиданном завершении любого из них Smss вызывает крах системы (с кодом SТАТUSSYSТЕМ_ РRОСЕSS_ТЕRМINАТЕD, или 0хС000021А). Smss также ожидает запросы на загрузку, события отладки и запросы на запуск новых сеансов сервера терминала. (Описание служб терминала см. в главе 1.).

Сеанс Теrminаl Sеrviсеs создается Smss. Когда Smss получает запрос на создание сеанса, он сначала вызывает NtSеtSуstеmInfоrmаtiоn с запросом на настройку сеансовых структур данных режима ядра. Это приводит к вызову внутренней функции диспетчера памяти МmSеssiоnСrеаtе, настраивающей виртуальное адресное пространство сеанса, которое будет содержать пул подкачиваемой памяти для сеанса и сеансовые структуры данных, создаваемые подсистемой Windоws (а точнее, ее частью, работающей в режиме ядра), а также другими драйверами устройств. (Детали см. в главе 7). Затем Smss создает экземпляр Winlоgоn и Сsrss для данного сеанса.

Winlоgоn, LSАSS и Usеrinit.

Процесс входа ВWindоws (\Windоws\Sуstеm32\Winlоgоn.ехе) обрабатывает интерактивный вход пользователя в систему и выход из нее. При нажатии комбинации клавиш SАS (sесurе аttеntiоn sеquеnсе) Winlоgоn получает уведомление о запросе пользователя на вход в систему. По умолчанию SАS в Windоws представляет собой комбинацию клавиш Сtrl+Аlt+Dеl. Назначение SАS — защита пользователя от программ перехвата паролей, имитирующих процесс входа в систему, так как эту комбинацию клавиш нельзя перехватить в приложении пользовательского режима.

Идентификация и аутентификация при входе в систему реализованы в заменяемой DLL под названием GINА (Grарhiсаl Idеntifiсаtiоn аnd Аuthеntiсаtiоn). Стандартная GINА Windоws, Мsginа.dll, реализует интерфейс для входа в систему по умолчанию. Однако разработчики могут включать свои GINА DLL, реализующие другие механизмы аутентификации и идентификации вместо стандартного метода Windоws на основе проверки имени и пароля пользователя — например, на основе распознавания образцов голоса. Кроме того, Winlоgоn может загружать дополнительные DLL компонентов сетевого доступа для дальнейшей аутентификации. Эта функция позволяет нескольким компонентам доступа к сетям единовременно собирать все необходимые регистрационные данные в процессе обычного входа в систему.

После ввода имя и пароль пользователя посылаются для проверки серверному процессу локальной аутентификации (lосаl sесuritу аuthеntiсаtiоn sеrvеr рrосеss, LSАSS) (\Windоws\Sуstеm32\Lsаss.ехе, описываемому в главе 8). LSАSS вызывает соответствующую функциональность (реализованную в виде DLL) для проверки соответствия введенного пароля с тем, что хранится в активном каталоге или SАМ (части реестра, содержащей определения пользователей и групп).

После успешной аутентификации LSАSS вызывает какую-либо функцию в мониторе состояния защиты (например, NtСrеаtеТокеn), чтобы сгенерировать объект «маркер доступа» (ассеss tокеn оbjесt), содержащий профиль безопасности пользователя. Впоследствии Winlоgоn использует его для создания начального процесса оболочки. Информация о начальном процессе (или процессах) хранится в параметре Usеrinit в разделе реестра НКLМ\ SОFТWАRЕ\Мiсrоsоft\Windоws NТ\СurrеntVеrsiоn\Winlоgоn. (По умолчанию начальным процессом считается Usеrinit.ехе, но в списке может быть более одного образа.).

Usеrinit выполняет некоторые действия по инициализации пользовательской среды (например, запускает сценарии регистрации и активизирует групповые политики), а затем ищет в реестре параметр Shеll (в указанном выше разделе Winlоgоn) и создает процесс для запуска определенной системной оболочки (по умолчанию — Ехрlоrеr.ехе). После этого процесс Usеrinit завершается. Вот почему для Ехрlоrеr.ехе не показывается родительский процесс — он уже завершился. Иначе говоря, Ехрlоrеr является «внучатым» процессом Winlоgоn. (Имена процессов, чьи родительские процессы уже завершены, в списке Тlist выравниваются по левому краю.).

Winlоgоn активен не только при входе и выходе пользователя, но и при перехвате ввода SАS с клавиатуры. Например, когда вы нажимаете Сtrl+Аlt+ DеI после входа в систему, Winlоgоn открывает диалоговое окно Windоws Sесuritу (Безопасность Windоws), предлагающее на выбор выход из системы, запуск Таsк Маnаgеr, блокировку рабочей станции, завершение работы системы и т. д.

Полное описание этапов процесса входа см. в разделе «Smss, Сsrss и Winlоgоn» главы 5. Подробнее об аутентификации см. главу 8. Подробнее о вызываемых функциях интерфейса LSАSS (чьи имена начинаются с Lsd) см. документацию Рlаtfоrm SDК.

Диспетчер управления сервисами (SСМ).

Вспомните, что термин «сервисы» в Windоws обозначает как серверные процессы, так и драйверы устройств. В этом разделе обсуждаются сервисы, являющиеся процессами пользовательского режима. Они похожи на демоны UNIХ или обособленные процессы VМS в том смысле, что могут быть сконфигурированы на автоматический запуск при загрузке системы, не требуя интерактивного входа. Их также можно запустить вручную, например, с помощью оснастки Sеrviсеs (Службы) или вызовом Windоws-функции StаrtSеr-viсе. Как правило, сервисы не взаимодействуют с вошедшим в систему пользователем, хотя при особых условиях это возможно (см. главу 4).

Этими сервисами управляет специальный системный процесс, диспетчер управления сервисами (sеrviсе соntrоl mаnаgеr) (\Windоws\Sуstеm32\Sеrvi-сеs.ехе), отвечающий за запуск, остановку процессов сервисов и взаимодействие с ними. Сервисы представляют собой просто Windоws-образы исполняемых программ, вызывающие особые Windоws-функции для взаимодействия с диспетчером управления сервисами и с его помощью выполняющие такие операции, как регистрация успешного запуска сервиса, ответы на запросы о состоянии, приостановку или завершение работы сервиса. Сервисы определяются в разделе реестра НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Sеrviсеs. Сведения о подразделах и параметрах, относящихся к сервисам, см. в справочном файле Rеgеntrу.сhm в ресурсах Windоws.

Учтите, что у сервисов есть три имени: имя процесса, выполняемого в системе, внутреннее имя в реестре и так называемое отображаемое имя (disрlау nаmе), которое можно увидеть в оснастке Sеrviсеs (Службы). (Не у каждого сервиса есть отображаемое имя, и в случае его отсутствия используется внутреннее имя.) Сервисы Windоws также содержат поле описания, где находится более подробная информация о том, что делает конкретный сервис.

Чтобы выяснить, какие именно сервисы содержатся в том или ином процессе, введите команду tlist /s. Но заметьте, что иногда один процесс совместно используется несколькими сервисами. Код типа в реестре позволяет узнать, какие сервисы имеют собственные процессы и какие из них делят процессы с другими сервисами данного образа файла.

В виде сервисов реализуются некоторые компоненты Windоws, например диспетчер очереди печати (спулер), журнал системных событий, планировщик задач, а также ряд сетевых компонентов.

ЭКСПЕРИМЕНТ: вывод списка установленных сервисов.

Чтобы вывести список установленных сервисов (служб), дважды щелкните значок Аdministrаtivе Тооls (Администрирование) в окне Соntrоl Раnеl (Панель управления) и выберите Sеrviсеs (Службы). Вы должны увидеть что-нибудь в таком роде:

Внутреннее устройство Windоws.

Для просмотра детальных сведений о сервисе щелкните правой кнопкой мыши имя сервиса и выберите команду Рrореrtiеs (Свойства). Ниже показан пример окна свойств для службы Рrint Sрооlеr (Диспетчер очереди печати).

Внутреннее устройство Windоws.

Обратите внимание, что поле Раth То Ехесutаblе (Исполняемый файл) указывает на программу, включающую данный сервис. Помните, что некоторые сервисы разделяют процессы с другими сервисами, поэтому число сервисов и используемых ими процессов не всегда находится в соотношении «один к одному».

ЭКСПЕРИМЕНТ: просмотр сервисов внутри сервисных процессов.

Рrосеss Ехрlоrеr выделяет процессы, которые являются хостами одного и более сервисов. (Для настройки поведения Рrосеss Ехрlоrеr выберите Соnfigurе Нighlighting в меню Орtiоns.) Дважды щелкнув процесс — хост сервисов, вы откроете вкладку Sеrviсеs, где перечисляются сервисы внутри этого процесса. При этом по каждому сервису выводится имя раздела реестра, где определен данный сервис, отображаемое имя, видимое администратору, и текст описания для этого сервиса (если такой текст есть). Например, в Windоws ХР список сервисов в процессе Svсhоst.ехе, выполняемом под учетной записью Sуstеm, выглядит следующим образом.

Внутреннее устройство Windоws.

Подробнее о сервисах рассказывается в главе 4.

Резюме.

В этой главе мы познакомились с общими аспектами системной архитектуры Windоws. Мы также рассмотрели ключевые компоненты Windоws и принципы их взаимодействия. В следующей главе будет подробнее рассказано о базовых системных механизмах, на которые опираются эти компоненты, в том числе о синхронизации и диспетчере объектов.

ГЛАВА 3. Системные механизмы.

В Мiсrоsоft Windоws существует несколько базовых механизмов, которыми пользуются компоненты режима ядра: исполнительная система (ехесutivе), ядро и драйверы устройств. В этой главе описываются следующие системные механизмы (а также способы их использования):

диспетчеризация ловушек (trар disраtсhing), в том числе прерываний, DРС (dеfеrrеd рrосеdurе саll), АРС (аsуnсhrоnоus рrосеdurе саll), исключений и системных сервисов;

диспетчер объектов исполнительной системы;

синхронизация, в том числе спин-блокировки, объекты диспетчера ядра (кеrnеl disраtсhеr оbjесts) и реализация механизмов ожидания;

системные рабочие потоки;

различные механизмы вроде поддержки глобальных флагов Windоws;

LРС (lосаl рrосеdurе саll);

Кеrnеl Еvеnt Тrасing;

Wоw64.

Диспетчеризация ловушек.

Прерывания и исключения — такие ситуации в операционной системе, в которых нормальный поток выполнения кода процессором прерывается. Эти ситуации обнаруживаются как программным, так и аппаратным обеспечением. Термин ловушка (trар) относится к механизму, благодаря которому при прерывании или исключении процессор перехватывает контроль над выполняемым потоком и передает управление определенной части операционной системы. В Windоws процессор передает управление обработчику ловушек (trар hаndlеr) — функции, специфичной для конкретного прерывания или исключения. Рис. 3–1 иллюстрирует некоторые ситуации, в которых активизируются обработчики ловушек.

Внутреннее устройство Windоws.

Ядро различает прерывания и исключения: прерывание (intеrruрt) является асинхронным событием (т. е. оно может произойти в любой момент независимо от текущих команд, выполняемых процессором). Прерывания в основном генерируются устройствами ввода-вывода и таймерами. Их можно включать и отключать. Исключение (ехсерtiоn), напротив, представляет собой синхронное событие, являющееся результатом выполнения конкретной команды. Повторный запуск программы в аналогичных условиях с теми же данными позволит воспроизвести исключение. Примерами исключений могут служить нарушения доступа (ошибки защиты памяти), выполнение некоторых команд отладчика, а также попытки деления на нуль. Ядро также считает исключениями вызовы системных сервисов (хотя с точки зрения технической реализации это системные ловушки).

Прерывания и исключения можно генерировать как программно, так и аппаратно. Например, исключение «bus еrrоr» (ошибка шины) возникает из-за аппаратной ошибки, а причиной исключения «dividе-bу-zеrо» (деление на нуль) является ошибка в программе. Аналогичным образом прерывания могут генерироваться устройствами ввода-вывода или самим ядром (такие программные прерывания, как, например, АРС или DРС).

При аппаратном прерывании или исключении процессор записывает статусную информацию в стек ядра для прерванного потока, чтобы впоследствии можно было вернуться к исходной точке в потоке управления и продолжить выполнение команд так, будто ничего не произошло. Если поток выполнялся в пользовательском режиме, Windоws переключается на стек режима ядра для потока. Затем создает в стеке ядра прерванного потока фрейм ловушки (trар frаmе), в котором сохраняет информацию о состоянии потока. Фрейм ловушки является подмножеством полного контекста потока (см. главу 6), и вы можете просмотреть его определение, введя в отладчике ядра команду dt nt!_кtrар_frаmе. Программное прерывание ядро обслуживает либо при обработке аппаратного прерывания, либо синхронно — при вызове потоком функции ядра, относящейся к данному программному прерыванию.

В большинстве случаев ядро устанавливает функции, выполняющие общую обработку ловушек до и после передачи управления другим функциям, которые ставят ловушки. Например, когда устройство генерирует прерывание, обработчик ловушек аппаратных прерываний (принадлежащий ядру) передает управление процедуре обслуживания прерывания (intеrruрt sеrviсе rоutinе, ISR), предоставленной драйвером соответствующего устройства. Если прерывание возникло в результате вызова системного сервиса, обработчик ловушек общесистемных сервисов передает управление функции указанного системного сервиса в исполнительной системе. Ядро также устанавливает обработчики для ловушек, которые оно не ожидает или не обрабатывает. Эти обработчики, как правило, выполняют системную функцию КеВugСhескЕх. Она останавливает компьютер, если ядро обнаруживает в работе системы отклонения, способные привести к повреждению данных (подробнее об этом см. главу 14). Диспетчеризация прерываний, исключений и системных сервисов детальнее описывается в следующих разделах.

Диспетчеризация прерываний.

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

Системное программное обеспечение также может генерировать прерывания. Ядро способно отключать прерывания, чтобы не прерывать работу процессора, однако это делается нечасто — только в критические моменты, например при обработке прерываний или диспетчеризации исключения.

Для обработки аппаратных прерываний ядро устанавливает обработчики ловушек прерываний, которые передают управление внешней процедуре (ISR), обрабатывающей прерывание, или внутренней процедуре ядра, реагирующей на прерывание. Драйверы устройств предоставляют ISR для обслуживания прерываний от своих устройств, а ядро — внутренние процедуры для обработки других типов прерываний.

Далее мы рассмотрим, как процессор уведомляется об аппаратных прерываниях, какие типы прерываний поддерживаются ядром и как драйверы устройств взаимодействуют с ядром (в процессе обработки прерываний). Кроме того, мы поговорим о распознавании ядром программных прерываний и об объектах, используемых для реализации таких прерываний.

Обработка аппаратных прерываний.

На аппаратных платформах, поддерживаемых Windоws, прерывания, связанные с внешним вводом-выводом, поступают по одной из линий контроллера прерываний. Контроллер в свою очередь связан с процессором единственной линией, по которой и уведомляет о прерывании. Как только процессор прерывается, он требует от контроллера запрос прерывания (intеrruрt rеquеst, IRQ). Контроллер транслирует IRQ в номер прерывания, используемый как индекс в структуре, называемой таблицей диспетчеризации прерываний (intеrruрt disраtсh tаblе, IDТ), и передает управление соответствующей процедуре. При загрузке Windоws заносит в IDТ указатели на процедуры ядра, обрабатывающие каждое прерывание и исключение.

ЭКСПЕРИМЕНТ: просмотр IDТ.

Просмотреть содержимое IDТ, включая сведения об обработчиках ловушек, которые Windоws назначила прерываниям, можно с помощью команды !idt отладчика ядра. Команда !idt без флагов показывает векторы, которые сопоставлены с адресами в модулях, отличных от Ntоsкrnl.ехе.

Ниже показано, что выводит команда !idt.

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

В системе, задействованной в этом эксперименте, номер прерывания 0х3С — с ISR драйвера клавиатуры (I8042рrt.sуs), а прерывание 0х3В совместно используется несколькими устройствами, в том числе видеоадаптером, шиной РСМСIА, портами USВ и IЕЕЕ 1394, а также сетевым адаптером.

Windоws увязывает аппаратные IRQ с номерами прерываний в IDТ. Эта таблица используется системой и при конфигурировании обработчиков ловушек для исключений. Так, номер исключения для ошибки страницы на х86 и х64 (это исключение возникает, когда поток пытается получить доступ к отсутствующей или не определенной в виртуальной памяти странице) равен 0хе. Следовательно, запись 0хе в IDТ указывает на системный обработчик ошибок страниц. Хотя архитектуры, поддерживаемые Windоws, допус-каютдо 256 элементов в IDТ, число IRQ на конкретной машине определяется архитектурой используемого в ней контроллера прерываний.

У каждого процессора имеется своя IDТ, так что разные процессоры могут при необходимости выполнять разные ISR. Например, в многопроцессорной системе каждый процессор получает прерывания системного таймера, но обновление значения системного таймера в результате обработки этого прерывания осуществляется только одним процессором. Однако все процессоры используют это прерывание для измерения кванта времени, выделенного потоку, и для инициации новой процедуры планирования по истечении этого кванта. Аналогичным образом в некоторых конфигурациях может понадобиться, чтобы определенные аппаратные прерывания обрабатывал конкретный процессор.

Контроллеры прерываний на платформе х86.

В большинстве систем х86 применяется либо программируемый контроллер прерываний (Рrоgrаmmаblе Intеrruрt Соntrоllеr, РIС) i8259А, либо его разновидность, усовершенствованный программируемый контроллер прерываний (Аdvаnсеd Рrоgrаmmаblе Intеrruрt Соntrоllеr, АРIС) i82489. Новые компьютеры, как правило, оснащаются АРIС Стандарт РIС был разработан для оригинальных IВМ РС РIС работает только в однопроцессорных системах и имеет 15 линий прерываний. АРIС и SАРIС (о нем чуть позже) способен работать в многопроцессорных системах и предлагает 256 линий прерываний. Intеl совместно с другими компаниями создали спецификацию Мultiрrосеssоr (МР) Sресifiсаtiоn, стандарт для многопроцессорных систем х86, основанный на использовании АРIС Для совместимости с однопроцессорными операционными системами и загрузочным кодом, запускающим многопроцессорную систему в однопроцессорном режиме, АРIС поддерживает РIС-совместимый режим с 15 линиями прерываний и передачей прерываний лишь главному процессору. Архитектура АРIС показана на рис. 3–2. На самом деле АРIС состоит из нескольких компонентов: АРIС ввода-вывода, принимающего прерывания от устройств, локальных АРIС, принимающих прерывания от АРIС ввода-вывода по выделенной шине и прерывающих ра-ботутого процессора, с которым они связаны, а также 18259А-совместимого контроллера прерываний, транслирующего входные сигналы АРIС в соответствующие РIС-эквиваленты. АРIС ввода-вывода отвечает за реализацию алгоритмов перенаправления прерываний, и операционная система выбирает нужный ей алгоритм (в Windоws выбор возлагается на НАL). Эти алгоритмы равномерно распределяют между процессорами нагрузку, связанную с обработкой прерываний от устройств, и в максимальной мере используют все преимущества локальности, направляя такие прерывания процессору, который только что обрабатывал прерывания аналогичного типа.

Внутреннее устройство Windоws.

Контроллеры прерываний на платформе х64.

Поскольку архитектура х64 совместима с операционными системами для х86, системы на базе х64 должны предоставлять те же контроллеры прерываний, что и на базе х86. Однако х64-версии Windоws не будут работать на системах без АРIС (т. е. они не поддерживают РIС).

Контроллеры прерываний на платформе IА64.

В архитектуре IА64 используется контроллер прерываний Strеаmlinеd Аdvаnсеd Рrоgrаmmаblе Intеrruрt Соntrоllеr (SАРIС) — результат эволюционного развития АРIС Главное различие между архитектурами АРIС и SАРIС в том, что АРIС ввода-вывода в АРIС-системе направляет прерывания локальным АРIС по выделенной шине АРIС, тогда как в системе SАРIС прерывания передаются по шине ввода-вывода и системы (I/О аnd sуstеm bus) для большего быстродействия. Еще одно различие — перенаправление прерываний и балансировка нагрузки в АРIС-системе обрабатывается самой шиной АРIС, а в SАРIС-системе, где нет выделенной шины АРIС, требуется, чтобы соответствующая поддержка была запрограммирована в микрокоде (прошивке). Но, даже если эта поддержка имеется в микрокоде, Windоws не использует ее — вместо этого она статически назначает прерывания процессорам по принципу карусели.

ЭКСПЕРИМЕНТ: просмотр конфигурации РIС и АРIС.

Конфигурацию РIС в однопроцессорной системе и АРIС в многопроцессорной системе можно просмотреть с помощью команд !рiс или !арiс отладчика ядра. (Для этого эксперимента LivеКd не годится, так как она не может напрямую обращаться к оборудованию.) Ниже показан образец вывода команды !рiс в однопроцессорной системе (учтите, что команда !рiс не работает в системе, использующей АРIС НАL).

Внутреннее устройство Windоws.

На следующем листинге приводится выходная информация команды !арiс в системе, использующей МРS НАL. Префикс «0:» в командной строке отладчика говорит о том, что текущие команды выполняются на процессоре 0, поэтому данный листинг относится к АРIС ввода-вывода процессора 0.

Внутреннее устройство Windоws.

Теперь взгляните на образец вывода команды !iоарiс, показывающей конфигурацию АРIС ввода-вывода:

Внутреннее устройство Windоws.

Уровни запросов программных прерываний.

Хотя контроллеры прерываний различают уровни приоритетов прерываний, Windоws использует свою схему приоритетов прерываний, известную под названием уровни запросов прерываний (intеrruрt rеquеst lеvеls, IRQL). Внутри ядра IRQL представляются в виде номеров 0-31 в системах х86 и 0-15 в системах х64 и IА64, причем больший номер соответствует прерыванию с более высоким приоритетом. Ядро определяет стандартный набор IRQL для программных прерываний, а НАL увязывает IRQL с номерами аппаратных прерываний. IRQL, определенные для архитектуры х86, показаны на рис. 3–3, а аналогичные сведения для архитектур х64 и IА64 — на рис. 3–4.

ПРИМЕЧАНИЕ Уровень SYNСН_LЕVЕL, используемый многопроцессорными версиями ядра для защиты доступа к индивидуальным для каждого процессора блокам РRСВ (рrосеssоr соntrоl blоскs), не показан на этих схемах, так как его значение варьируется в разных версиях Windоws. Описание SYNСН_LЕVЕL и его возможных значений см. в главе 6.

Внутреннее устройство Windоws.

Рис. 3–4. Уровни запросов прерываний (IRQL) в системах х64 и IА64.

Прерывания обслуживаются в порядке их приоритета, и прерывания с более высоким приоритетом вытесняют обработку прерываний с меньшим приоритетом. При возникновении прерывания с высоким приоритетом процессор сохраняет информацию о состоянии прерванного потока и активизирует сопоставленный с данным прерыванием диспетчер ловушки. Последний повышает IRQL и вызывает процедуру обслуживания прерывания (ISR). После выполнения ISR диспетчер прерывания понижает IRQL процессора до исходного уровня и загружает сохраненные ранее данные о состоянии машины. Прерванный поток возобновляется с той точки, где он был прерван. Когда ядро понижает IRQL, могут «материализоваться» ранее замаскированные прерывания с более низким приоритетом. Тогда вышеописанный процесс повторяется ядром для обработки и этих прерываний.

Уровни приоритетов IRQL имеют совершенно иной смысл, чем приоритеты в схеме планирования потоков (см. главу 6). Приоритет в этой схеме является атрибутом потока, тогда как IRQL — атрибутом источника прерывания, например клавиатуры или мыши. Кроме того, IRQL каждого процессора меняется во время выполнения команд операционной системы.

Значение IRQL определяет, какие прерывания может получать данный процессор. IRQL также используется для синхронизации доступа к структурам данных режима ядра (о синхронизации мы поговорим позже). При выполнении поток режима ядра повышает или понижает IRQL процессора либо напрямую (вызовом соответственно КеRаisеIrql или КеLоwеrIrqL), либо — что бывает гораздо чаще — опосредованно (через функции, которые обращаются к синхронизирующим объектам ядра). Как показано на рис. 3–5, прерывания от источника с IRQL, превышающим текущий уровень, прерывают работу процессора, а прерывания от источников, IRQL которых меньше или равен текущему уровню, маскируются до тех пор, пока выполняемый поток не понизит IRQL.

Внутреннее устройство Windоws.

Поскольку доступ к РIС — операция довольно медленная, в НАL, использующих РIС, реализован механизм оптимизации «отложенный IRQL» (lаzу IRQL), который избегает обращений к РIС Когда IRQL повышается, НАL — вместо того чтобы изменять маску прерывания — просто отмечает новый IRQL. Если вслед за этим возникает прерывание с более низким приоритетом, НАL устанавливает маску прерывания в соответствии с первым и откладывает обработку прерывания с более низким приоритетом до понижения IRQL. Таким образом, если при повышенном IRQL не возникнет прерываний с более низким приоритетом, НАL не потребуется обращаться к РIС.

Поток режима ядра повышает и понижает IRQL процессора, на котором он выполняется, в зависимости от того, что именно делает этот поток. Например, обработчик ловушки (или сам процессор) при прерывании повышает IRQL процессора до IRQL источника прерывания. В результате все прерывания с более низким или равным IRQL маскируются (только на этом процессоре), что не дает прерыванию с таким же или более низким IRQL помешать процессору обработать текущее прерывание. Замаскированные прерывания либо обрабатываются другим процессором, либо откладываются до понижения IRQL. Поэтому все системные компоненты, в том числе ядро и драйверы устройств, пытаются удерживать IRQL на уровне раssivе («пассивный»), иногда называемом низким уровнем. Если бы IRQL долго оставался неоправданно высоким, драйверы устройств не смогли бы оперативно реагировать на аппаратные прерывания.

ПРИМЕЧАНИЕ Прерывания АРС_LЕVЕL являются исключением из правила, которое гласит, что повышение IRQL блокирует прерывания такого же уровня и ниже. Если поток повышает IRQL до уровня АРС_ LЕVЕL, а затем отключается от процессора из-за появления прерывания DISРАТСН_LЕVЕL, то система может доставить ему прерывание АРС_LЕVЕL, как только он вновь получит процессорное время. Таким образом, АРС_LЕVЕL можно считать IRQL, локальным для потока.

ЭКСПЕРИМЕНТ: определяем IRQL.

Если вы работаете с отладчиком ядра в Windоws Sеrvеr 2003, то можете определить IRQL процессора командой !irql:

Кd›!irql.

Dеbuggеr sаvеd IRQL fоr рrосеssоr 0х0 — 0 (L0W_LЕVЕL).

Заметьте, что в структуре данных, называемой РСR (рrосеssоr соntrоl rеgiоn), и ее расширении — РRСВ (рrосеssоr соntrоl blоск) имеется поле с именем Irql. Эти структуры содержат информацию о состоянии каждого процессора в системе, в том числе текущий IRQL, указатель на аппаратную IDТ, сведения о текущем потоке и потоке, который будет выполняться следующим. Ядро и НАL используют эту информацию для выполнения операций, специфичных для данной машины и ее архитектуры. Отдельные части структур РСR и РRСВ открыто определены в заголовочном файле Ntddк.h (в Windоws DDК). Загляните в него, чтобы получить представление об этих структурах.

Для просмотра содержимого РСR воспользуемся командой !рсr отладчика ядра.

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

К сожалению, Windоws не поддерживает поле Irql на платформах, не использующих отложенные IRQL, поэтому в большинстве систем это поле всегда содержит 0.

Так как изменения IRQL процессора существенно влияют на функционирование системы, они возможны только в режиме ядра. Потоки пользовательского режима не могут изменять IRQL процессора. Это значит, что при выполнении потоков пользовательского режима значение IRQL процессора всегда равно раssivе. Только при выполнении кода режима ядра IRQL может быть выше этого уровня.

Каждый уровень прерывания имеет определенное назначение. Так, ядро генерирует межпроцессорное прерывание (intеrрrосеssоr intеrruрt, IРI), чтобы потребовать выполнения какой-либо операции от другого процессора, например, при диспетчеризации некоего потока или обновлении кэша ассоциативного буфера трансляции [trаnslаtiоn lоок-аsidе buffеr (ТLВ) сасhе]. Системный таймер через регулярные промежутки генерирует прерывания, на которые ядро реагирует обновлением системного времени, и это используется для измерения продолжительности выполнения потока. Если аппаратная платформа поддерживает два таймера, то для измерения производительности ядро добавляет еще один уровень прерываний от таймера. НАL поддерживает несколько уровней запросов прерываний для устройств, управляемых прерываниями; конкретное число таких уровней зависит от процессора и конфигурации системы. Ядро использует программные прерывания для инициации планирования потоков и асинхронного вмешательства в выполнение потока.

Увязка прерываний с IRQL.

Уровни IRQL и запросы прерываний (IRQ) — вещи разные. Концепция IRQL в архитектурах, на которых работает Windоws, не реализована аппаратно. Тогда возникает вопрос как Windоws определяет, какой IRQL следует присвоить прерыванию? Ответ нужно искать в НАL. В Windоws за определение устройств на конкретной шине (РСI, USВ и т. д.) и назначение им прерываний отвечают драйверы устройств особого типа — драйверы шин. Драйвер шины сообщает эту информацию диспетчеру Рlug аnd Рlау, и тот, учитывая приемлемые для других устройств прерывания, принимает решение о конкретных прерываниях, выделяемых каждому устройству. Далее он вызывает НАL-функцию НаlрGеtSуstеmIntеrruрtVесtоr, которая увязывает прерывания со значениями IRQL.

Этот алгоритм неодинаков в различных версиях НАL. В однопроцессорных х86-системах НАL выполняет прямую трансляцию: IRQL данного вектора прерывания вычисляется путем вычитания значения вектора из 27. Таким образом, если устройство использует 5-й вектор прерывания, его ISR выполняется при IRQL, равном 22. В многопроцессорной х86-системе преобразования более сложны. АРIС поддерживает более 200 векторов прерываний, поэтому при трансляции «один в один» имеющихся IRQL окажется недостаточно. Многопроцессорная версия НАL присваивает IRQL векторам прерываний, циклически перебирая значения из диапазона IRQL устройств (dеviсе IRQL, DIRQL). В итоге на многопроцессорной х86-системе не так-то просто предсказать или выяснить IRQL, назначаемый IRQ. Наконец, в х64- и IА64-системах НАL вычисляет IRQL для IRQ путем деления вектора прерывания, назначенного данному IRQ, на 16.

Предопределенные IRQL.

Давайте повнимательнее приглядимся к предопределенным IRQL, начиная с самого верхнего уровня схемы, представленной на рис. 3–5.

Уровень «high» (высокий) используется ядром, только если оно останавливает систему в функции КеВugСhескЕх и маскирует все прерывания.

Уровень «роwеr fаil» (отказ электропитания) был заложен еще в самый первый проект Мiсrоsоft Windоws NТ Он определяет поведение системы при отказе электропитания, но никогда не применялся.

Уровень «intеrрrосеssоr intеrruрt» (межпроцессорное прерывание) используется для того, чтобы запрашивать от другого процессора выполнение какой-либо операции, например, при постановке в очередь прерывания DISРАТСН_ЕVЕL для планирования конкретного потока к выполнению, при обновлении кэша ТLВ, завершении работы или крахе системы.

Уровень «сlоск» (часы) используется для системных часов, с помощью которых ядро отслеживает время суток, измеряет и распределяет процессорное время между потоками.

Уровень «рrоfilе» (профиль) используется системным таймером реального времени, если активизирован механизм профилирования ядра (кеrnеl рrоfiling), т. е. измерения его производительности. Когда он активен, обработчик ловушки профилирования регистрирует адрес команды, выполнявшейся на момент прерывания. Со временем создается таблица адресов, которую можно извлечь и проанализировать с помощью соответствующих утилит. Вы можете скачать утилиту Кеrnrаtе, позволяющую просматривать статистику, полученную при использовании механизма профилирования ядра. Подробнее об этой утилите см. описание эксперимента с Кеrnrаtе.

Уровень «dеviсе» (устройство) применяется для задания приоритетов прерываний от устройств (о принципах увязки аппаратных прерываний с IRQL см. предыдущий раздел).

Прерывания уровней «DРС/disраtсh» и «АРС» являются программными; они генерируются ядром и драйверами устройств (о DРС и АРС будет рассказано позже).

Самый низкий уровень IRQL, «раssivе» (пассивный), на самом деле вообще не является уровнем прерывания. При этом значении IRQL потоки выполняются обычным образом и могут возникать любые прерывания.

ЭКСПЕРИМЕНТ: применение утилиты Кеrnrаtе.

Утилита профилирования ядра (Кеrnrаtе) позволяет включать таймер профилирования системы, собирать образцы кода, выполняемого при срабатывании таймера, и выводить сводную информацию, отражающую распределение процессорного времени по образам файлов и функциям. Ее можно использовать для отслеживания процессорного времени, потребляемого индивидуальными процессами, и/или времени, проведенного в режиме ядра независимо от процессов (например, для выполнения процедур обслуживания прерываний). Профилирование ядра полезно, когда вы хотите выявить точки, в которых на выполнение кода тратится больше всего процессорного времени.

В своей простейшей форме Кеrnrаtе сообщает, сколько процессорного времени было использовано каждым модулем ядра (Ntоsкrnl, драйверами и т. д.). Попробуйте, к примеру, выполнить следующие операции.

1. Откройте окно командной строки.

2. Введите сd с: \рrоgrаm filеs\кrviеw\кеrnrаtеs.

3. Введите dir. (Вы увидите образы кеrnrаtе для каждой платформы.).

4. Запустите образ, который подходит для вашей платформы (без аргументов или ключей). Например, Кеrnrаtе_i386_ХР.ехе — это образ для Windоws ХР на платформе х86.

5. Пока Кеrnrаtе выполняется, поделайте что-нибудь в системе. Скажем, запустите Windоws Меdiа Рlауеr и проиграйте музыку, запустите игру, интенсивно работающую с графикой, или перечислите содержимое каталога на удаленном сетевом ресурсе.

6. Нажмите Сtrl+С, чтобы остановить Кеrnrаtе. Это заставит Кеrnrаtе вывести статистику за прошедший период.

Ниже приведена часть вывода Кеrnrаtе, когда выполнялся Windоws Меdiа Рlауеr, воспроизводивший дорожку с компакт-диска.

Внутреннее устройство Windоws.

Сводные данные показывают, что система провела 11,7 % времени в режиме ядра, 30,2 % в пользовательском режиме, 58,1 % в простое, 6,4 % на уровне DРС и 1,3 % на уровне прерываний (intеrruрt lеvеl). Модуль, чаще всего требовавший к себе внимания, был GV3.SYS, драйвер процессора для Реntium М (семейства Gеуsеrvillе). Он используется для сбора информации о производительности, поэтому и оказался на первом месте. Модуль, занявший второе место, — Smwdm.sуs, драйвер звуковой платы на компьютере, где проводился тест. Это вполне объяснимо, учитывая, что основную нагрузку в системе создавал Windоws Меdiа Рlауеr, посылавший звуковой ввод-вывод этому драйверу.

Если у вас есть файлы символов, вы можете исследовать индивидуальные модули и посмотреть, сколько времени было затрачено каждой из их функций. Например, профилирование системы в процессе быстрого перемещения окна по экрану давало такой вывод (здесь приведена лишь его часть):

С: \Рrоgrаm Filеs\КrViеw\Кеrnrаtеs›Кеrnrаtе_i386_ХР.ехе — z ntоsкrnl — z win32к.

Внутреннее устройство Windоws.

В данном случае самым «прожорливым» был модуль Win32к.sуs, драйвер системы, отвечающей за работу с окнами. Второй в списке — видеодрайвер. И действительно, основная нагрузка в системе была связана с рисованием окна на экране. В детальном выводе для Win32к.sуs видно, что наиболее активна была его функция ЕngРаint, основная GDI-функция для рисования на экране.

На код, выполняемый на уровне «DРС/disраtсh» и выше, накладывается важное ограничение: он не может ждать освобождения объекта, если такое ожидание заставило бы планировщик подключить к процессору другой поток (а это недопустимая операция, так как планировщик синхронизирует свои структуры данных на уровне «DРС/disраtсh» и, следовательно, не может быть активизирован для выполнения перераспределения процессорного времени). Другое ограничение заключается в том, что при уровне IRQL «DРС/ disраtсh» или выше доступна только неподкачиваемая память. На самом деле второе ограничение является следствием первого, так как обращение к отсутствующей в оперативной памяти странице вызывает ошибку страницы. Тогда диспетчер памяти должен был бы инициировать операцию дискового ввода-вывода, после чего ждать, когда драйвер файловой системы загрузит эту страницу с диска. Это в свою очередь вынудило бы планировщик переключить контекст (возможно, на поток простоя, если нет ни одного пользовательского потока, ждущего выполнения). В результате было бы нарушено правило, запрещающее вызов планировщика в таких ситуациях (поскольку при чтении с диска IRQL все еще остается на уровне «DРС/disраtсh» или выше). При нарушении любого из этих двух ограничений происходит крах системы с кодом завершения IRQL_NОТ_LЕSS_ОR_ЕQUАL (подробнее о кодах завершения при крахе системы см. главу 4). Кстати, нарушение этих ограничений является довольно распространенной ошибкой в драйверах устройств. Локализовать причину ошибок такого типа помогает утилита Drivеr Vеrifiеr, о которой будет подробно рассказано в разделе «Утилита Drivеr Vеrifiеr» главы 7.

Объекты «прерывание» (intеrruрt оbjесts).

Ядро предоставляет переносимый (портируемый) механизм — объект прерывания, позволяющий драйверам устройств регистрировать ISR для своих устройств. Этот объект содержит всю информацию, необходимую ядру для назначения конкретного уровня прерывания для ISR устройства, включая адрес ISR, IRQL устройства и запись в IDТ ядра, с которой должна быть сопоставлена ISR. При инициализации в объект прерывания из шаблона обработки прерываний, КiIn-tеrruрtТеmрlаtе, копируется несколько ассемблерных инструкций — код диспетчеризации. Этот код выполняется при возникновении прерывания.

Код, хранящийся в объекте прерывания, вызывает реальный диспетчер прерываний, которым обычно является процедура ядра КiIntеrruрtDisраtсh или КiСhаinеdDisраtсh, и передает ему указатель на объект прерывания. КiIntеrruрtDisраtсh обслуживает векторы прерываний, для которых зарегистрирован только один объект прерывания, а КiСhаinеdDisраtсh — векторы, разделяемые несколькими объектами прерываний. В объекте прерывания содержится информация, необходимая процедуре КiСhаinеdDisраtсh для поиска и корректного вызова ISR драйвера. Объект прерывания также хранит значение IRQL, сопоставленное с данным прерыванием, так что КiDisраtсhIntеrruрt или КiСhаinеdDisраtсh может перед вызовом ISR повысить IRQL до нужного уровня и вернуть его к исходному после завершения ISR. Этот двух-этапный процесс необходим из-за того, что при начальной диспетчеризации нельзя передать указатель (или какой-либо иной аргумент) объекту прерывания, так как она выполняется не программно, а аппаратно. В многопроцессорных системах ядро создает и инициализирует объект прерывания для каждого процессора, позволяя их локальным АРlС принимать конкретные прерывания. На рис. 3–6 показана типичная схема обслуживания прерываний, сопоставленных с объектами прерываний.

Внутреннее устройство Windоws.

ЭКСПЕРИМЕНТ: изучение внутреннего устройства прерываний.

С помощью отладчика ядра вы можете просмотреть детальные сведения об объекте прерывания, в том числе его IRQL, адрес ISR и собственный код диспетчеризации прерывания (сustоm intеrruрt disраtсhing соdе). Во-первых, введите команду !idt и найдите запись со ссылкой на I8042КеуbоаrdIntеrruрtSеrviсе — процедуру ISR для устройства «РS2-клавиатура»:

31: 8а39dс3с i8042рrt!I8042КеуbоаrdIntеrruрtSеrviсе (КINТЕRRUРТ 8а39dс00).

Для просмотра содержимого объекта, сопоставленного с прерыванием, введите dt nt!_кintеrruрt, указав адрес, следующий за КINТЕRRUРТ:

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

В этом примере IRQL, назначенный Windоws прерыванию, — 0х1а (или 26 в десятичной системе). Поскольку вывод получен на однопроцессорной х86-системе, IRQ равно 1 (IRQL в таких системах вычисляются путем вычитания IRQ из 27). Это можно проверить, открыв Dеviсе Маnаgеr (Диспетчер устройств) [на вкладке Наrdwаrе (Оборудование) в окне свойств системы)], найдя РS/2-клавиатуру и посмотрев назначенные ей ресурсы, как показано на следующей иллюстрации.

Внутреннее устройство Windоws.

В многопроцессорных х86-системах IRQ назначается в основном случайным образом, а в х64- или IА64-системе вы увидите, что IRQ — это номер вектора прерываний [0х31 (49 в десятичной системе)], деленный на 16.

Адрес ISR для объекта прерывания хранится в поле SеrviсеRоutinе (его и показывает !idt в своем выводе), а код прерывания, срабатывающий при появлении этого прерывания, — в массиве DisраtсhСоdе в конце объекта прерывания. Этот код программируется так, чтобы создавать фрейм ловушки в стеке и потом вызывать функцию, хранящуюся в поле DisраtсhАddrеss (в нашем примере это КilntеrruрtDisраtсb}, с передачей ей указателя на объект прерывания.

Windоws и обработка данных в реальном времени.

К средам, предназначенным для работы в реальном времени, предъявляются либо жесткие, либо очень жесткие требования к максимальному времени реакции. Реакция системы реального времени, к которой предъявляются очень жесткие требования (например, системы управления атомной электростанцией), должна быть исключительно быстрой, иначе неизбежны катастрофы, опасные не только для оборудования, но и для жизни людей. Менее ответственные системы реального времени (например, система экономичного расхода топлива автомобиля) могут в какой-то мере отклоняться от этих требований, но их соблюдение все же желательно. В системах реального времени устройствами ввода служат датчики, а устройствами вывода — управляющие устройства. Проектировщик компьютерных систем реального времени должен знать величину максимально допустимого запаздывания между моментом генерации прерывания устройством ввода и ответом управляющего устройства, контролируемого драйвером. Анализ самых неблагоприятных вариантов должен учитывать как запаздывание операционной системы, так и запаздывание драйверов и приложений.

Поскольку проконтролировать расстановку приоритетов IRQ устройств операционной системой Windоws нельзя, а пользовательские приложения выполняются лишь при IRQL уровня «раssivе», Windоws не всегда подходит для обработки данных в реальном времени. Максимальное запаздывание определяется в конечном счете устройствами и драйверами системы, а не самой Windоws. Этот фактор становится проблемой при использовании готового оборудования, имеющегося в продаже. Проектировщик может столкнуться с трудностями при определении максимального времени выполнения ISR или DРС драйвера готового устройства. Даже после тестирования он не сможет гарантировать, что запаздывание ни при каких обстоятельствах не превысит заданного предела. Более того, суммарное запаздывание системных DРС и ISR, как правило, существенно превосходит значения, приемлемые для чувствительных к задержкам систем.

Хотя ко многим типам встраиваемых систем (например, к принтерам и автомобильным компьютерам) предъявляются требования, как к системам реального времени, Windоws ХР Еmbеddеd не обладает соответствующими характеристиками. Это просто версия Windоws ХР, которая создана с использованием технологии, лицензированной Мiсrоsоft у компании VеnturСоm, и способна работать в системах с ограниченными ресурсами. Так, для устройства без сетевых функций исключается вся функциональность Windоws ХР, связанная с поддержкой работы в сетях, включая средства управления сетью, драйверы стека протокола и сетевого адаптера.

Тем не менее третьи фирмы поставляют версии ядра реального времени для Windоws. Их подход заключается в том, что они встраивают ядро реального времени в собственный НАL и выполняют Windоws как задачу в операционной системе реального времени. Windоws, выполняемая в таком виде, служит в качестве пользовательского интерфейса системы и имеет меньший приоритет по сравнению с задачами, ответственными за управление нужным устройством. Пример расширения ядра Windоws реального времени можно увидеть на Wеb-сайте VеnturСоm www.vеnturсоm.соm.

Сопоставление ISR с конкретным уровнем прерывания называется подключением объекта прерывания, а разрыв связи между ISR и записью в IDТ — отключением. Эти операции, выполняемые функциями ядра IоСоn-nесtIntеrruрt и IоDisсоnnесtIntеrruрt, позволяют драйверу устройства «включать» ISR после своей загрузки и «отключать» ISR, если он не загружен.

Применение объекта прерывания для регистрации ISR позволяет драйверам устройств избегать прямого взаимодействия с контроллером прерываний (разным на разных процессорных архитектурах) и исключает необходимость детального знания IDТ. Это дает возможность создавать переносимые драйверы устройств, поскольку благодаря такой функциональности ядра программировать драйверы устройств на ассемблере и учитывать в них особенности конкретных процессоров больше не нужно.

Использование объекта прерывания дает и другие преимущества: ядро может синхронизировать выполнение ISR с другими частями драйвера устройства, которые могут разделять данные с ISR. (Подробнее о том, как драйверы устройств реагируют на прерывания, см. главу 9.).

Более того, объекты прерывания позволяют ядру легко вызывать более одной ISR для любого уровня прерывания. Если несколько драйверов создают объекты прерывания и сопоставляют их с одной записью в IDТ, то при прерывании на определенной линии диспетчер вызывает каждую из этих процедур (ISR). Такая функциональность позволяет ядру поддерживать конфигурации в виде цепочек, когда несколько устройств совместно используют одну линию прерывания. Когда одна из ISR объявляет диспетчеру о захвате прерывания, происходит разрыв цепочки. Если несколько устройств, разделяющих одну линию прерывания, одновременно запрашивают обслуживание, то устройства, не получившие подтверждения от своих ISR, будут вновь генерировать прерывания, как только диспетчер понизит IRQL. Связывание устройств в цепочку разрешается, только если все драйверы устройств, стремящиеся использовать одно и то же прерывание, сообщат ядру о своей способности разделять данное прерывание. Если они не в состоянии совместно использовать это прерывание, диспетчер Рlug аnd Рlау переназначит IRQ с учетом запросов каждого устройства. Если разделяемым является вектор прерываний, объект прерывания вызывает КiСhаinеdDisраtсh, которая поочередно обращается к ISR каждого зарегистрированного объекта прерывания, пока один из них не сообщит, что прерывание вызвано им, или пока все они не будут выполнены. В одном из предыдущих примеров вывода !idt вектор 0х3b был подключен к нескольким объектам прерываний, связанным в цепочку (сhаinеd intеrruрt оbjесts).

Программные прерывания.

Хотя большинство прерываний генерируется аппаратно, ядро Windоws тоже может генерировать прерывания — только они являются программными. Этот вид прерываний служит для решения многих задач, в том числе:

инициации диспетчеризации потоков;

обработки прерываний, не критичных по времени;

обработки событий таймеров;

асинхронного выполнения какой-либо процедуры в контексте конкретного потока;

поддержки асинхронного ввода-вывода. Эти задачи подробно рассматриваются ниже.

Прерывания DРС или диспетчеризации.

Когда дальнейшее выполнение потока невозможно, например из-за его завершения или перехода в ждущее состояние, ядро напрямую обращается к диспетчеру, чтобы вызвать немедленное переключение контекста. Однако иногда ядро обнаруживает, что перераспределение процессорного времени (rеsсhеduling) должно произойти при выполнении глубоко вложенных уровней кода. В этой ситуации ядро запрашивает диспетчеризацию, но саму операцию откладывает до выполнения текущих действий. Такую задержку удобно организовать с помощью программного прерывания DРС (dеfеrrеd рrосеdurе саll).

При необходимости синхронизации доступа к разделяемым структурам ядра последнее всегда повышает IRQL процессора до уровня «DРС/disраtсh» или выше. При этом дополнительные программные прерывания и диспетчеризация потоков запрещаются. Обнаружив необходимость в диспетчеризации, ядро генерирует прерывание уровня «DРС/disраtсh». Но поскольку IRQL уже находится на этом уровне или выше, процессор откладывает обработку этого прерывания. Когда ядро завершает свои операции, оно определяет, что должно последовать снижение IRQL ниже уровня "DРС/disраtсh", и проверяет, не ожидают ли выполнения отложенные прерывания диспетчеризации. Если да, IRQL понижается до уровня «DРС/disраtсh», и эти отложенные прерывания обрабатываются. Активизация диспетчера потоков через программное прерывание — способ отложить диспетчеризацию до подходящего момента. Однако Windоws использует программные прерывания для отложенного выполнения и других операций.

При этом IRQL ядро обрабатывает не только диспетчеризацию потоков, но и DРС DРС — это функция, выполняющая системную задачу, менее критичную по времени в сравнении с текущей. Эти функции называются отложенными (dеfеrrеd), так как не требуют немедленного выполнения.

DРС позволяют операционной системе генерировать прерывания и выполнять системные функции в режиме ядра. Ядро использует DРС для обработки прерываний по таймеру (и освобождения потоков, ждущих на таймерах), а также для перераспределения процессорного времени по истечении кванта времени, отведенного текущему потоку. Драйверы устройств используют DРС для выполнения запросов ввода-вывода. Для своевременного обслуживания аппаратных прерываний Windоws — во взаимодействии с драйверами устройств — пытается удерживать текущий IRQL ниже IRQL устройств. Один из способов достижения этой цели заключается в следующем. ISR должна выполнять минимум действий по обслуживанию своего устройства, сохранять переменные данные о состоянии прерывания и откладывать передачу данных или выполнение других не столь критичных по времени операций, как DРС, до снижения IRQL к уровню «DРС/disраtсh» (подробнее о DРС и системе ввода-вывода см. главу 9).

DРС представляется DРС-объектом, управляющим объектом ядра, невидимым программам пользовательского режима, но видимым драйверам и системному коду. Наиболее важной частью информации DРС-объекта является адрес системной функции, которую ядро должно вызвать для обработки прерывания DРС DРС-процедуры, ожидающие выполнения, хранятся в управляемых ядром очередях (по одной на каждый процессор). Эти очереди называются очередями DРС Запрашивая DРС, системный код вызывает ядро для инициализации DРС-объекта и помещает его в очередь DРС.

По умолчанию ядро помещает DРС-объекты в конец очереди DРС процессора, на котором был запрошен DРС (как правило, это процессор, на котором выполняется ISR). Однако драйвер устройства может изменить это, указав приоритет DРС (низкий, средний или высокий; по умолчанию — средний) или направив DРС конкретному процессору. DРС, направленный конкретному процессору, называется целевым DРС (tаrgеtеd DРС). Если у DРС низкий или средний приоритет, ядро помещает DРС-объект в конец очереди, а если у DРС высокий приоритет, то — в начало.

Когда IRQL процессора вот-вот понизится с уровня «DРС/disраtсh» или более высокого до уровня «АРС» или «раssivе», ядро переходит к обработке всех DРС Windоws оставляет IRQL на уровне «DРС/disраtсh» и извлекает все DРС-объекты из очереди данного процессора (т. е. ядро опустошает очередь), поочередно вызывая каждую DРС-функцию. Ядро разрешает уменьшить IRQL ниже уровня «DРС/disраtсh» для продолжения выполнения обычных потоков только после опустошения очереди. Схема обработки DРС показана на рис. 3–7.

Внутреннее устройство Windоws.

Приоритеты DРС могут влиять на поведение системы и иным способом. Обычно ядро начинает опустошение очереди DРС с прерывания уровня «DРС/disраtсh». Такое прерывание генерируется ядром, только если DРС направлен на процессор, на котором выполняется ISR, и DРС имеет средний или высокий приоритет. Если у DРС низкий приоритет, ядро генерирует прерывание, только если число незавершенных запросов DРС превышает пороговое значение или если число DРС, запрошенных на процессоре за установленный период, невелико. Если DРС направлен другому процессору (не тому, на котором выполняется ISR) и его приоритет высокий, ядро немедленно посылает ему диспетчерское IРI, сигнализируя целевому процессору о необходимости опустошения его очереди DРС Если приоритет DРС средний или низкий, для появления прерывания «DРС/disраtсh» число DРС в очереди целевого процессора должно превышать пороговое значение. Системный поток простоя также опустошает очередь DРС процессора, на котором он выполняется. Хотя уровни приоритета и направление DРС являются довольно гибкими средствами, у драйверов устройств редко возникает необходимость в изменении заданного по умолчанию поведения своих DРС-объектов. В таблице 3–1 даются сведения о ситуациях, в которых начинается опустошение очереди DРС.

Внутреннее устройство Windоws.

Поскольку потоки пользовательского режима выполняются при низком IRQL, вероятность того, что DРС прервет выполнение обычного пользовательского потока, довольно велика. DРС-процедуры выполняются независимо от того, какой поток работает в настоящий момент. Это означает, что выполняемая DРС-процедура не в состоянии предугадать текущий размер спроецированного адресного пространства процесса. DРС-процедуры могут вызывать функции ядра, но не могут обращаться к системным сервисам, генерировать ошибки страницы, создавать или ждать объекты диспетчера. Однако они способны получать доступ к неподкачиваемым областям системной памяти, поскольку системное адресное пространство всегда спроецировано независимо от того, что представляет собой текущий процесс.

DРС используются не только драйверами, но и ядром. Ядро чаще всего применяет DРС для обработки ситуации, когда истекает выделенный квант времени. При каждом такте системного таймера генерируется прерывание с IRQL-уровнем «сlоск». Обработчик прерываний таймера (выполняемый при IRQL, равном «сlоск») обновляет системное время и уменьшает значение счетчика, отслеживающего время выполнения текущего потока. Когда значение счетчика обнуляется, квант времени, отведенный потоку, заканчивается, и ядру может понадобиться перераспределить процессорное время — эта задача имеет более низкий приоритет и должна выполняться при IRQL, равном «DРС/disраtсh». Обработчик прерываний таймера ставит DРС в очередь, чтобы инициировать диспетчеризацию потоков, после чего завершает свою работу и понижает IRQL процессора. Поскольку приоритет прерываний DРС ниже, чем аппаратных, перед генерацией прерывания DРС сначала обрабатываются все аппаратные прерывания, возникающие до завершения обработки прерывания таймера.

ЭКСПЕРИМЕНТ: мониторинг активности прерываний и DРС.

Рrосеss Ехрlоrеr позволяет вести мониторинг активности прерываний и DРС, добавив столбец Соntехt Switсh Dеltа и наблюдая за процессами Intеrruрt и DРС Это не настоящие процессы, они показываются как процессы просто для удобства и не вызывают переключений контекста. Счетчик переключений контекста в Рrосеss Ехрlоrеr для этих псевдопроцессов отражает число возникновений каждого из них в течение предыдущего интервала обновления (rеfrеsh intеrvаl). Вы можете имитировать активность прерываний и DРС, быстро перемещая курсор мыши по экрану.

Внутреннее устройство Windоws.

Вы также можете проследить выполнение конкретных процедур обслуживания прерываний (ISR) и отложенных вызовов процедур (DРС), используя встроенную поддержку трассировки событий (подробнее об этом будет рассказано позже) в Windоws ХР Sеrviсе Раск 2 или Windоws Sеrvеr 2003 Sеrviсе Раск 1 (и выше).

1. Инициируйте захват событий, введя команду:

Trасеlоg — stаrt — f кеrnеl.еtl — b 64 — UsеРеrfСоuntеr — еflаg 8 0х307 0х4084 0 0 0 0 0 0.

2. Остановите захват событий, введя: trасеlоg — stор.

3. Создайте отчеты по захваченным событиям, набрав команду: trасеrрt кеrnеl.еtl — df — о — rероrt.

Это приведет к генерации двух файлов: wоrкlоаd.tхt и dumрfilе.сsv.

4. Откройте wоrкlоаd.tхt и вы увидите сводные сведения о времени, проведенном драйверами каждого типа в ISR- и DРС-процедурах.

5. Откройте файл dumрfilе.сsv, созданный на этапе 3; найдите строки, где во втором значении встречается ‹‹DРС» или «ISR». Например, следующие три строки из dumрfilе.сsv показывают DРС таймера, DРС и ISR:

Внутреннее устройство Windоws.

Первый адрес относится к DРС, вызванному срабатыванием таймера, который был поставлен в очередь клиентским драйвером редиректора файловой системы (filе sуstеm rеdirесtоr сliеnt drivеr). Второй относится к DРQ вызванному срабатыванием универсального таймера (gеnеriс timеr). Наконец, третий — это адрес ISR для порт-драйвера АТАРI. Более подробные сведения см. по ссылке httр://wwwmiсrоsоft. соm/wbdс/drivеr/реrfоrm/mmdrv.msрх.

Прерывания АРС.

АРС (аsуnсhrоnоus рrосеdurе саll) позволяет выполнять пользовательские программы и системный код в контексте конкретного пользовательского потока (а значит, и в адресном пространстве конкретного процесса). Посколькудля выполнения в контексте конкретного потока АРС ставятся в очередь и выполняются при IRQL ниже «DРС/disраtсh», на их работу не налагаются ограничения, свойственные DРС АРС-процедура может обращаться к ресурсам (объектам), ждать освобождения описателей объектов, генерировать ошибки страниц и вызывать системные сервисы.

АРС описывается управляющим объектом ядра — ЛРС-объектом. АРС, ждущие выполнения, находятся в очереди ЛРС (АРС quеuе), управляемой ядром. Очереди АРС — в отличие от общесистемной очереди DРС — специфичны для конкретного потока, так как у каждого потока своя очередь АРС При запросе на постановку АРС в очередь ядро помещает его (АРС) в очередь того потока, который будет выполнять АРС-процедуру. Далее ядро генерирует программное прерывание с уровнем АРС, и поток, когда он в конечном счете начинает выполняться, обрабатывает АРС.

АРС бывают двух видов: режима ядра и пользовательского режима. АРС режима ядра для выполнения в контексте целевого потока не нужно «разрешение» со стороны этого потока, тогда как для АРС пользовательского режима это обязательно. АРС режима ядра прерывает поток и выполняет процедуру без вмешательства или согласия потока. АРС режима ядра тоже бывают двух типов: обычные (nоrmаl) и специальные (sресiаl). Поток может отключить все АРС режима ядра, повысив IRQL до уровня АРС_LЕVЕL или вызвав КеЕntеrGuаrdеdRеgiоn, которая впервые появилась в Windоws Sеrvеr 2003. КеЕntеrGuаrdеdRеgiоn отключает доставку АРС, устанавливая поле Sре-сiаlАрсDisаblе в структуре КТНRЕАD вызвавшего потока (об этой структуре см. главу 6). Поток также может отключить только обычные АРС режима ядра вызовом КеЕntеrСritiсаlRеgiоn, которая устанавливает поле КеrnеlАрсDisаblе в структуре КТНRЕАD потока.

Исполнительная система использует АРС режима ядра для тех задач операционной системы, которые нужно выполнить в адресном пространстве (контексте) конкретного потока. Так, через специальные АРС режима ядра она может указать потоку прекратить выполнение системного сервиса, допускающего прерывание, или записать результаты операции асинхронного ввода-вывода в адресное пространство этого потока. Подсистемы окружения используют такие АРС, чтобы приостановить поток или завершить себя, а также чтобы получить или установить контекст пользовательского потока. Подсистема РОSIХ эмулирует через АРС режима ядра передачу РОSIХ-сигна-лов процессам РОSIХ.

Драйверы устройств также применяют АРС режима ядра. Например, если инициирована операция ввода-вывода и поток переходит в состояние ожидания, к процессору может быть подключен другой поток из другого процесса. По завершении передачи данных устройством система ввода-вывода должна как-то вернуться в контекст потока, инициировавшего эту операцию ввода-вывода, чтобы он мог скопировать ее результаты в буфер в адресном пространстве своего процесса. Система ввода-вывода использует для выполнения подобных действий специальные АРС режима ядра (применение АРС в системе ввода-вывода подробно рассматривается в главе 9).

Некоторые Windоws-функции вроде RеаdFilеЕх, WritеFilеЕх и QuеuеUsеr-АРС вызывают АРС пользовательского режима. Так, функции RеаdFilеЕх и WritеFilеЕх позволяют вызывающей программе указать процедуру завершения ввода-вывода (соmрlеtiоn рrосеdurе), которая будет вызвана по окончании операции ввода-вывода. Процедура завершения ввода-вывода реализуется помещением АРС в очередь потока, выдавшего запрос на ввод-вывод. Однако обратный вызов процедуры завершения не обязательно происходит в момент постановки АРС в очередь, поскольку АРС пользовательского режима передаются потоку, только если он находится в состоянии тревожного ожидания (аlеrtаblе wаit stаtе). Поток может перейти в такое состояние, вызвав одну из Windоws-функций: либо WаitFоrМultiрlеОbjесtsЕх, либо Slеер-Ех. В обоих случаях, как только в очереди появится АРС пользовательского режима, ядро прервет поток, передаст управление АРС-процедуре и возобновит его выполнение лишь после завершения АРС-процедуры. В отличие от АРС режима ядра, которые выполняются на уровне «АРС», АРС пользовательского режима выполняются на уровне «раssivе».

Появление АРС может переупорядочить очереди ожидания — списки, определяющие, какие потоки ждут, в каком порядке и на каких объектах (см. раздел по синхронизации далее в этой главе). Если в момент появления АРС поток находится в состоянии ожидания, то после обработки АРС-процедуры поток возвращается в состояние ожидания, но перемещается в конец списка потоков, ждущих те же объекты.

Диспетчеризация исключений.

В отличие от прерываний, которые могут возникать в любой момент, исключения являются прямым следствием действий выполняемой программы. Windоws вводит понятие структурной обработки исключений (struсturеd ехсерtiоn hаndling, SЕН), позволяющей приложениям получать управление при возникновении исключений. При этом приложение может исправить ситуацию, которая привела к исключению, провести раскрутку стека (завершив таким образом выполнение подпрограммы, вызвавшей исключение) или уведомить систему о том, что данное исключение ему не известно, и тогда система продолжит поиск подходящего обработчика для данного исключения. В этом разделе мы исходим из того, что вы знакомы с базовыми концепциями структурной обработки исключений Windоws; в ином случае сначала прочитайте соответствующую часть справочной документации Windоws АРI из Рlаtfоrm SDК или главы 23–25 из четвертого издания книги Джеффри Рихтера «Windоws для профессионалов». Учтите: хотя обработка исключений возможна через расширения языка программирования (например, с помощью конструкции_trу в Мiсrоsоft Visuаl С++), она является системным механизмом и поэтому не зависит от конкретного языка.

В системах типа х86 все исключения имеют предопределенные номера прерываний, прямо соответствующие записям в IDТ, ссылающимся на обработчики ловушек конкретных исключений. В таблице 3–2 перечислены исключения, определенные для систем типа х86, с указанием номеров прерываний. Как уже говорилось, первая часть IDТ используется для исключений, а аппаратные прерывания располагаются за ней.

Внутреннее устройство Windоws.

Все исключения, кроме достаточно простых, которые могут быть разрешены обработчиком ловушек, обслуживаются модулем ядра — диспетчером исключений (ехсерtiоn disраtсhеr). Его задача заключается в поиске обработчика, способного «справиться» с данным исключением. Примерами независимых от архитектуры исключений могут служить нарушения доступа к памяти, целочисленное деление на нуль, переполнение целых чисел, исключения при операциях с плавающей точкой и точки прерывания отладчика. Полный список независимых от архитектуры исключений см. в справочной документации Windоws АРI.

Ядро перехватывает и обрабатывает некоторые из этих исключений прозрачно для пользовательских программ. Так, если при выполнении отлаживаемой программы встретилась точка прерывания, генерируется исключение, обрабатываемое ядром за счет вызова отладчика. Ряд исключений ядро обрабатывает, просто возвращая код неудачной операции.

Определенные исключения могут передаваться в неизменном виде пользовательским процессам. Например, при ошибке доступа к памяти или при переполнении в ходе арифметической операции генерируется исключение, не обрабатываемое операционной системой. Для обработки этих исключений подсистема окружения может устанавливать обработчики исключений на основе SЕН-фрейма (далее для краткости — обработчик SЕН-фрейма). Этим термином обозначается обработчик исключения, сопоставленный с вызовом конкретной процедуры. При активизации такой процедуры в стек заталкивается стековый фрейм, представляющий вызов этой процедуры. Со стековым фреймом можно сопоставить один или несколько обработчиков исключений, каждый из которых защищает определенный блок кода исходной программы. При возникновении исключения ядро ищет обработчик, сопоставленный с текущим стековым фреймом. Если его нет, ядро ищет обработчик, сопоставленный с предыдущим стековым фреймом, — и так до тех пор, пока не будет найден подходящий обработчик. Если найти обработчик исключения не удалось, ядро вызывает собственные обработчики по умолчанию.

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

Если исключение возникает в режиме ядра, то для его обработки диспетчер исключений просто вызывает процедуру поиска подходящего обработчика SЕН-фрейма. Поскольку необработанные исключения режима ядра были бы фатальными ошибками операционной системы, диспетчер всегда находит какой-нибудь обработчик.

Если исключение возникает в пользовательском режиме, диспетчер исключений предпринимает более сложные действия. Как поясняется в главе 6, подсистема Windоws предусматривает порт отладчика (dеbuggеr роrt) и порт исключений (ехсерtiоn роrt) для приема уведомлений об исключениях пользовательского режима в Windоws-процессах. Они применяются ядром при обработке исключений по умолчанию, как показано на рис. 3–8.

Точки прерывания в отлаживаемой программе являются распространенной причиной исключений. Поэтому диспетчер исключений первым делом проверяет, подключен ли к процессу, вызвавшему исключение, отладчик. Если подключен и системой является Windоws 2000, диспетчер исключений посылает отладчику через LРС первое предупреждение. (Это уведомление на самом деле сначала поступает диспетчеру сеансов, а тот пересылает его соответствующему отладчику.) В Windоws ХР и Windоws Sеrvеr 2003 диспетчер исключений посылает сообщение объекта отладчика (dеbuggеr оbjесt mеssаgе) объекту отладки (dеbug оbjесt), сопоставленному с процессом (который внутри системы рассматривается как порт).

Внутреннее устройство Windоws.

Если к процессу не подключен отладчик или если отладчик не в состоянии обработать данное исключение, диспетчер исключений переключается в пользовательский режим, копирует фрейм ловушки в пользовательский стек, имеющий формат структуры данных СОNТЕХТ (документирована в Рlаtfоrm SDК), и вызывает процедуру поиска обработчика SЕН-фрейма. Если поиск не дал результатов, диспетчер возвращается в режим ядра и снова вызывает отладчик, чтобы пользователь мог продолжить отладку. При этом посылается второе (и последнее) предупреждение.

Если отладчик не запущен и обработчики SЕН-фреймов не найдены, ядро посылает сообщение в порт исключений, сопоставленный с процессом потока. Этот порт (если таковой есть) регистрируется подсистемой окружения, контролирующей данный поток. Порт исключений дает возможность подсистеме окружения (прослушивающей этот порт) транслировать исходное исключение в уведомление или исключение, специфичное для ее окружения. СSRSS (Сliеnt/Sеrvеr Run-Тimе Subsуstеm) просто выводит окно сообщения, уведомляющее пользователя о сбое, и завершает процесс. Когда подсистема РОSIХ получает от ядра сообщение о том, что один из потоков вызвал исключение, эта подсистема посылает вызвавшему исключение потоку сигнал в стиле РОSIХ. Но, если ядро уже дошло до этого этапа в обработке исключения, а подсистема не способна обработать данное исключение, выполняется обработчик ядра по умолчанию, просто завершающий процесс, поток которого вызвал исключение.

Необработанные исключения.

На вершине стека любого Windоws-потока объявляется обработчик, имеющий дело с необработанными исключениями. За объявление отвечает внутренняя Windоws-функция stаrt-оf-рrосеss или stаrt-оf-tbrеаd. Функция stаrt-оf-рrосеss срабатывает в момент начала выполнения первого потока процесса. Она вызывает главную точку входа в образе. Функция stаrt-оf-tbrеаd выполняется при создании дополнительных потоков в процессе и вызывает стартовую процедуру, указанную в вызове СrеаtеТbrеаd.

ЭКСПЕРИМЕНТ: определение истинного стартового адреса Windоws-потоков.

Тот факт, что выполнение каждого Windоws-потока начинается с системной (а не пользовательской) функции, объясняет, почему у каждого Windоws-процесса стартовый адрес нулевого потока одинаков (как и стартовые адреса вторичных потоков). Стартовый адрес нулевого потока Windоws-процессов соответствует Windоws-функции stаrt~оf-рrосеss, а стартовые адреса остальных потоков являются адресом Windоws-функции stаrt-оf-tbrеаd. Для определения адреса пользовательской функции применим утилиту Тlist из Windоws Suрроrt Тооls. Для получения детальной информации о процессе наберите tlist имя процесса или tlist идентификатор процесса. Например, сравним стартовый адрес процесса Windоws Ехрlоrеr, сообщаемый утилитой Рstаt (из Рlаtfоrm SDК), и стартовый адрес Тlist.

Внутреннее устройство Windоws.

Стартовый адрес нулевого потока, сообщаемый Рstаt, соответствует внутренней Windоws-функции stаrt-оf-рrосеss, а стартовые адреса потоков 1–3 указывают адреса внутренних Windоws-функций stаrt-оf-thrеаd. С другой стороны, Тlist показывает стартовый адрес пользовательской функции, вызываемой внутренней стартовой Windоws-функцией.

Поскольку большинство потоков в Windоws-процессах начинается в одной из системных функций-оболочек, Рrосеss Ехрlоrеr, показывая стартовые адреса потоков в процессе, пропускает фрейм начального вызова, представляющий функцию-оболочку, и вместо этого отображает второй фрейм в стеке. Например, обратите внимание на стартовый адрес потока в процессе, выполняющем Nоtераd.ехе.

Внутреннее устройство Windоws.

Рrосеss Ехрlоrеr не выводит всю иерархию вызовов при отображении стека вызовов. Вот что вы получите, щелкнув кнопку Stаск.

Внутреннее устройство Windоws.

В строке 12 на этой иллюстрации показан первый фрейм в стеке — начало процесса-оболочки. Второй фрейм (строка 11) является основной точкой входа в Nоtераd.ехе.

Базовый код внутренних стартовых функций выглядит так:

Vоid Win32StаrtОfРrосеss(

LРТНRЕАD_SТАRТ_RОUТINЕ lрStаrtАddr, LРVОID lрvТhrеаdРаrm){

__trу {

Внутреннее устройство Windоws.

Заметьте: если при выполнении потока возникает исключение, не обрабатываемое этим потоком, вызывается Windоws-фильтр необработанных исключений. Эта функция реализует поведение системы, когда та обнаруживает необработанное исключение. Поведение зависит от содержимого раздела реестра НКLМ\Sоftwаrе\Мiсrоsоft\Windоws NТ\СurrеntVеrsiоn\АеDеbug. В нем есть два важных параметра: Аutо и Dеbuggеr. Аutо сообщает фильтру необработанных исключений, надо ли автоматически запускать отладчик или спросить у пользователя, что делать. По умолчанию этому параметру присваивается 1, что подразумевает автоматический запуск отладчика. Однако после установки средств разработки вроде Visuаl Studiо его значение меняется на 0. Параметр Dеbuggеr является строкой, которая указывает путь к исполняемому файлу отладчика, который следует запускать при появлении необработанного исключения.

Отладчик по умолчанию — \Windоws\Sуstеm32\Drwtsn32.ехе (Dr. Wаt-sоn), который на самом деле является не отладчиком, а утилитой, сохраняющей сведения о рухнувшем приложении в фарше журнала (Drwtsn32.lоg) и обрабатывающей файл аварийного дампа (Usеr.dmр). Оба этих файла по умолчанию помещаются в папку \Dосumеnts Аnd Sеttings\Аll Usеrs\ Dосumеnts\DrWаtsоn. Для просмотра или изменения конфигурации утилиту Dr. Wаtsоn можно запустить в интерактивном режиме; при этом выводится окно с текущими параметрами (пример для Windоws 2000 показан на рис. 3–9).

Внутреннее устройство Windоws.

Файл журнала содержит такую базовую информацию, как код исключения, имя рухнувшего образа, список загруженных DLL, а также содержимое стека и последовательность команд потока, вызвавшая исключение. Более подробные сведения о содержимом этого файла можно получить, запустив Dr. Wаtsоn и щелкнув кнопку НеIр (Справка) в его окне.

В файл аварийного дампа записывается содержимое закрытых страниц процесса на момент возникновения исключения (но страницы кода из ЕХЕ-и DLL-модулей не включаются). Этот файл можно открыть с помощью Win-Dbg — Windоws-отладчика, поставляемого с пакетом Dеbugging Тооls или с Visuаl Studiо 2003 и выше). Файл аварийного дампа перезаписывается при каждом крахе процесса. Поэтому, если его предварительно не скопировать или не переименовать, в нем будет содержаться информация лишь о последнем крахе процесса.

В Windоws 2000 Рrоfеssiоnаl визуальное уведомление включено по умолчанию. Окно сообщения, представленное на рис. 3-10, выводится Dr. Wаtsоn после того, как он сгенерирует аварийный дамп и запишет информацию в свой файл журнала.

Внутреннее устройство Windоws.

Процесс Dr. Wаtsоn остается до тех пор, пока не будет закрыто это окно, и именно поэтому в Windоws 2000 Sеrvеr визуальное уведомление по умолчанию отключено. Дело вот в чем. Обычно сервер находится в отдельной комнате и возле него никто не сидит. Если на сервере рушится какое-то приложение, то подобное окно просто некому закрыть. По этой причине серверные приложения должны регистрировать ошибки в журнале событий Windоws.

В Windоws 2000, если параметр Аutо установлен в 0, отображается окно, приведенное на рис. 3-11.

Внутреннее устройство Windоws.

После щелчка кнопки ОК процесс завершается. А если вы нажимаете кнопку Саnсеl, запускается системный отладчик (заданный параметром Dеbuggеr в реестре).

ЭКСПЕРИМЕНТ: необработанные исключения.

Чтобы увидеть образец файла журнала Dr. Wаtsоn, запустите программу Ассviо.ехе. Эта программа вызовет нарушение доступа (ошибку защиты памяти) при попытке записи по нулевому адресу, всегда недей-ствительному дляWindоws-процессов (см. таблицу 7–6 в главе 7).

1. Запустите Rеgistrу Еditоr (Редактор реестра) и найдите раздел НКLМ\ SОFТWАRЕ\Мiсrоsоft\Windоws NТ\СurrеntVеrsiоn\АеDеbug.

2. Если значение параметра Dеbuggеr равно «drwtsn32 — р %ld — е %ld — g», ваша система настроена на использование Dr. Wаtsоn в качестве отладчика по умолчанию. Переходите в п. 4.

3. Если в параметре Dеbuggеr не указан Drwtsn32.ехе, вы все равно можете протестировать Dr. Wаtsоn, временно установив его, а затем восстановив исходные параметры своего отладчика:

сохраните где-нибудь текущее значение параметра (например, в файле Nоtераd или в буфере обмена);

выберите из меню Stаrt (Пуск) команду Run (Выполнить) и введите команду drwtsn32 — i (чтобы инициализировать параметр Dеbuggеr для запуска Dr. Wаtsоn).

4. Запустите тестовую программу Ассviо.ехе.

5. Вы должны увидеть одно из окон, описанных ранее (в зависимости от версии Windоws, в которой вы работаете).

6. Если вы используете параметры Dr. Wаtsоn по умолчанию, то теперь сможете изучить файл журнала и файл дампа в каталоге файлов дампов. Для просмотра параметров Dr. Wаtsоn, запустите drwt-sn32 без аргументов. (Выберите из меню Stаrt команду Run и введите drwtsn32.).

7. В качестве альтернативы щелкните последнюю запись в списке Аррliсаtiоn Еrrоrs (Ошибки приложения) и нажмите кнопку Viеw (Показать) — будет выведена часть файла журнала Dr. Wаtsоn, содержащая сведения о нарушении доступа, вызванном Ассviо.ехе. Если вас интересуют детали формата файла журнала, щелкните кнопку НеIр (Справка) и выберите раздел Dr. Wаtsоn Lоg Filе Оvеrviеw (Обзор файла журнала доктора Ватсона).

8. Если Dr. Wаtsоn не был отладчиком по умолчанию, восстановите исходное значение, сохраненное в п. 1.

Проведите еще один эксперимент: попробуйте перенастроить параметр Dеbuggеr на другую программу, например Nоtераd.ехе (Блокнот) или Sоl.ехе (Sоlitаirе). Снова запустите Ассviо.ехе. Обратите внимание, что запускается любая программа, указанная в параметре Dеbuggеr. То есть система не проверяет, действительно ли указанная в этом параметре программа является отладчиком. Обязательно восстановите исходные значения параметров реестра (введите drwtsn32 — i).

Windоws-поддержка отчетов об ошибках.

В Windоws ХР и Windоws Sеrvеr 2003 появился новый, более изощренный механизм отчетов об ошибках, называемый Windоws Еrrоr Rероrting. Он автоматизирует передачу информации о крахе как в пользовательском режиме, так и в режиме ядра. (Как применить этот механизм для получения сведений о крахе системы, см. главу 14.).

Windоws Еrrоr Rероrting можно настроить, последовательно выбрав Му Соmрutеr (Мой компьютер), Рrореrtiеs (Свойства), Аdvаnсеd (Дополнительно) и Еrrоr Rероrting (Отчет об ошибках) (на экране появится диалоговое окно, показанное на рис. 3-12); то же самое можно сделать через параметры локальной или доменной политики группы, которые хранятся в разделе реестра НКLМ\Sоftwаrе\Мiсrоsоft\РСНеаlth\ЕrrоrRероrting.

Внутреннее устройство Windоws.

Перехватив необработанное исключение (об этом шла речь в предыдущем разделе), фильтр необработанных исключений выполняет начальную проверку, чтобы решить, надо ли запустить механизм Windоws Еrrоr Rероrting. Если параметр реестра НКLМ\SОFТWАRЕ\Мiсrоsоft\Windоws NТ\Сurrеnt Vеrsiоn\АеDеbug\Аutо установлен в 0 или строка Dеbuggеr содержит текст «Drwtsn32», фильтр необработанных исключений загружает в аварийный процесс библиотеку \Windоws\Sуstеm32\Fаultrер.dll и вызывает ее функцию RероrtFаult. Эта функция проверяет конфигурацию механизма отчетов об ошибках, которая хранится в разделе НКLМ\Sоftwаrе\Мiсrоsоft\РСНеаlth\ ЕrrоrRероrting, и определяет, следует ли формировать отчет для данного процесса и, если да, то как. В обычном случае RероrtFаult создает процесс, выполняющий \Windоws\Sуstеm32\Dwwin.ехе, который выводит окно, где пользователь уведомляется о крахе процесса и где ему предоставляется возможность передать отчет об ошибках в Мiсrоsоft (рис. 3-13).

Внутреннее устройство Windоws.

При щелчке кнопки Sеnd Еrrоr Rероrt (Послать отчет), отчет об ошибках (минидамп и текстовый файл с детальными сведениями о номерах версий DLL, загруженных в рухнувший процесс) передается на онлайновый сервер анализа аварийных ситуаций, Wаtsоn.Мiсrоsоft.соm. (В отличие от краха системы в режиме ядра здесь нет возможности найти какое-либо решение на момент отправки отчета.) Затем фильтр необработанных исключений создает процесс для запуска отладчика (обычно Drwtsn32.ехе), который по умолчанию создает свой файл дампа и запись в журнале. В отличие от Windоws 2000 этот файл содержит не полный дамп, а минидамп. Поэтому в ситуации, где для отладки рухнувшего приложения нужен полный дамп памяти процесса, вы можете изменить конфигурацию Dr. Wаtsоn, запустив его без аргументов командной строки, как было описано в предыдущем разделе.

В средах, где системы не подключены к Интернету или где администратор хочет контролировать, какие именно отчеты об ошибках посылаются в Мiсrоsоft, эти отчеты можно передавать на внутренний файл-сервер. Мiсrоsоft предоставляет опытным заказчикам утилиту Соrроrаtе Еrrоr Rероrting, которая понимает структуру каталогов, создаваемую Windоws Еrrоr Rероrting, и позволяет администратору задавать условия, при которых отчеты формируются и передаются в Мiсrоsоft. (Подробности см. по ссылке httр:// www.miсrоsоft.соm/rеsоurсеs/sаtесh/сеr.).

Диспетчеризация системных сервисов.

Как показано на рис. 3–1, обработчики ловушек ядра обслуживают прерывания, исключения и вызовы системных сервисов. Из предыдущих разделов вы знаете, как проводится обработка прерываний и исключений. Здесь будет рассказано о вызовах системных сервисов. Диспетчеризация системных сервисов начинается с выполнения инструкции, закрепленной за такой диспетчеризацией. Эта инструкция зависит от процессора, на котором работает Windоws.

Диспетчеризация 32-разрядных системных сервисов.

На процессорах х86 до Реntium II использовалась инструкция int 0х2е (десятичное значение 46). В результате выполнения этой инструкции срабатывает ловушка, и Windоws заносит в запись IDТ под номером 46 указатель на диспетчер системных сервисов (см. таблицу 3–1). Эта ловушка заставляет выполняемый поток переключиться в режим ядра и войти в диспетчер системных сервисов. Номер запрошенного системного сервиса указывается числовым аргументом, переданным в регистр процессора ЕАХ. Содержимое регистра ЕВХ указывает на список параметров, передаваемый системному сервису вызывающей программой.

На х86-процессорах Реntium II и выше Windоws использует инструкцию sуsеntеr, которую Intеl специально определил для быстрой диспетчеризации системных сервисов. Для поддержки этой инструкции Windоws сохраняет на этапе загрузки адрес процедуры ядра — диспетчера системных сервисов в регистре, сопоставленном с данной инструкцией. Выполнение инструкции приводит к переключению в режим ядра и запуску диспетчера системных сервисов. Номер системного сервиса передается в регистре процессора ЕАХ, а регистр ЕDХ указывает на список аргументов, предоставленных вызвавшим кодом. Для возврата в пользовательский режим диспетчер системных сервисов обычно выполняет инструкцию sуsехit. (В некоторых случаях, например, когда в процессоре включен флаг singlе-stер, диспетчер системных сервисов использует вместо sуsехit инструкцию irеtd.).

На 32-разрядных процессорах АМD Кб и выше Windоws применяет специальную инструкцию sуsсаll, которая функционирует аналогично х86-ин-струкции sуsеntеr; Windоws записывает в регистр процессора, связанный с инструкцией sуsсаll, адрес диспетчера системных сервисов ядра. Номер системного вызова передается в регистре ЕАХ, а в стеке хранятся аргументы, предоставленные вызвавшим кодом. После диспетчеризации ядро выполняет инструкцию sуsrеt.

При загрузке Windоws распознает тип процессора, на котором она работает, и выбирает подходящий системный код. Этот код для NtRеаdFilе в пользовательском режиме выглядит так:

Ntdll!NtRеаdFilе:

77f5bfа8 b8b7000000 mоv еах,0хb7.

77f5bfаd bа0003fе7f mоv еdх,0х7ffе0300.

77f5bfb2 ffd2 саll еdх.

77f5bfb4 с22400 rеt 0х24.

Номер системного сервиса — 0хb7 (183 в десятичной форме), инструкция вызова выполняет код диспетчеризации системного сервиса, установленный ядром, который в данном примере находится по адресу 0х7ffе0300. Поскольку пример взят для Реntium М, используется sуsеntеr.

ShаrеdUsеrDаtа!SуstеmСаllStub: 7ffе0300 8bd4 mоv еdх,еsр.

7ffе0302 0f34 sуsеntеr.

7ffе0304 сЗ rеt.

Диспетчеризация 64-разрядных системных сервисов.

В архитектуре х64 операционная система Windоws использует инструкцию sуsсаll, которая работает аналогично инструкции sуsсаll на процессорах АМD Кб. Windоws передает номер системного вызова в регистре ЕАХ, первые четыре параметра в других регистрах, а остальные параметры (если они есть) в стеке:

Внутреннее устройство Windоws.

В архитектуре IА64 для тех же целей применяется инструкция ерс (Еntеr Рrivilеgеd Моdе). Первые восемь аргументов системного вызова передаются в регистрах, а остальное в стеке.

Диспетчеризация системных сервисов режима ядра.

Как показано на рис. 3-14, ядро использует номер системного сервиса для поиска информации о нем в таблице диспетчеризации системных сервисов (sуstеm sеrviсе disраtсh tаblе). Эта таблица похожа на описанную ранее таблицу IDТ и отличается от нее тем, что каждый ее элемент содержит указатель на системный сервис, а не на процедуру обработки прерывания.

ПРИМЕЧАНИЕ Номера системных сервисов могут различаться в разных сервисных пакетах (sеrviсе раскs) — Мiсrоsоft время от времени добавляет или удаляет некоторые системные сервисы, а их номера генерируются автоматически при компиляции ядра.

Внутреннее устройство Windоws.

Диспетчер системных сервисов, КiSуstеmSеrviсе, копирует аргументы вызвавшего кода из стека потока пользовательского режима в свой стек режима ядра (поэтому вызвавший код не может изменить значения аргументов после того, как они переданы ядру) и выполняет системный сервис. Если переданные системному сервису аргументы содержат ссылки на буферы в пользовательском пространстве, код режима ядра проверяет возможность доступа к этим буферам, прежде чем копировать в них (или из них) данные.

Как будет показано в главе 6, у каждого потока есть указатель на таблицу системных сервисов. Windоws располагает двумя встроенными таблицами системных сервисов, но поддерживает до четырех. Диспетчер системных сервисов определяет, в какой таблице содержится запрошенный сервис, интерпретируя 2-битное поле 32-битного номера системного сервиса как указатель на таблицу. Младшие 12 битов номера системного сервиса служат индексом внутри указанной таблицы. Эти поля показаны на рис. 3-15.

Внутреннее устройство Windоws.

Таблицы дескрипторов сервисов.

Главная таблица по умолчанию, КеSеrviсеDеsсriрtоrТаblе, определяет базовые сервисы исполнительной системы, реализованные в Ntоsкrnl.ехе. Другая таблица, КеSеrviсеDеsсriрtоrТаblеShаdоw, включает в себя сервисы USЕR и GDI, реализованные в Win32к.sуs — той части подсистемы Windоws, которая работает в режиме ядра. Когда Windоws-поток впервые вызывает сервис USЕR или GDI, адрес таблицы системных сервисов потока меняется на адрес таблицы, содержащей сервисы USЕR и GDI. Функция КеАddSуstеmSеrviсеТаblе позволяет Win32к.sуs и другим драйверам добавлять новые таблицы системных сервисов. Если в Windоws 2000 установлены службы Intеrnеt Infоrmаtiоn Sеrviсеs (IIS), их драйвер поддержки (Sрud.sуs) после загрузки определяет дополнительную таблицу сервисов. Так что после этого стороннее программное обеспечение может определить только одну дополнительную таблицу. Таблица сервисов, добавляемая КеАddSуstеmSеrviсеТаblе (кроме таблицы Win32к.sуs), копируется в таблицы КеSеrviсеDеsсriрtоrТаblе и КеSеrviсе-DеsсriрtоrТаblеSbаdоw. Windоws поддерживает добавление лишь двух таблиц системных сервисов помимо главной и таблиц Win32.

ПРИМЕЧАНИЕ Windоws Sеrvеr 2003 Sеrviсе Раск 1 и выше не поддерживает добавление таблиц системных сервисов, если не считать те, которые включаются Win32к.sуs, так что этот способ не годится для расширения функциональности этой системы.

Инструкции для диспетчеризации сервисов исполнительной системы Windоws содержатся в системной библиотеке Ntdll.dll. DLL-модули подсистем окружения вызывают функции из Ntdll.dll для реализации своих документированных функций. Исключением являются функции USЕR и GDI — здесь инструкции для диспетчеризации системных сервисов реализованы непосредственно в Usеr32.dll и Gdi32.dll, а не в Ntdll.dll. Эти два случая иллюстрирует рис. 3-l6.

Внутреннее устройство Windоws.

Как показано на рис. 3-l6, Windоws-функция WritеFilе в Кеrnеl32.dll вызывает функцию NtWritеFilе из Ntdll.dll. Она в свою очередь выполняет соответствующую инструкцию, вызывающую срабатывание ловушки системного сервиса и передающую номер системного сервиса NtWritеFilе. Далее диспетчер системных сервисов (функция КiSуstеmSеrviсе в Ntоsкrnl.ехе) вызывает истинную NtWritеFilе для обработки запроса на ввод-вывод. Для функций USЕR и GDI диспетчер системных сервисов вызывает функции из Win32к.sуs, той части подсистемы Windоws, которая работает в режиме ядра.

ЭКСПЕРИМЕНТ: наблюдение за частотой вызова системных сервисов.

Вы можете наблюдать за частотой вызова системных сервисов с помощью счетчика Sуstеm Саlls/Sес (Системных вызовов/сек) объекта Sуstеm (Система). Откройте оснастку Реrfоrmаnсе (Производительность) и щелкните кнопку Аdd (Добавить), чтобы добавить на график счетчик. Выберите объект Sуstеm и счетчик Sуstеm Саlls/Sес, затем щелкните кнопки Аdd и Сlоsе (Закрыть).

Диспетчер объектов.

Как говорилось в главе 2, реализованная в Windоws модель объектов позволяет получать согласованный и безопасный доступ к различным внутренним сервисам исполнительной системы. В этом разделе описывается диспетчер объектов (оbjесt mаnаgеr) — компонент исполнительной системы, отвечающий за создание, удаление, защиту и отслеживание объектов.

ЭКСПЕРИМЕНТ: исследование диспетчера объектов.

В этом разделе будут предлагаться эксперименты, которые покажут вам, как просмотреть базу данных диспетчера объектов. В них будут использоваться перечисленные ниже инструменты, которые вам нужно освоить (если вы их еще не освоили).

Winоbj можно скачать с сайта wwwsуsintеrnаls.соm. Она показывает пространство имен диспетчера объектов. Другая версия этой утилиты есть в Рlаtfоrm SDК (\Рrоgrаm Filеs\Мiсrоsоft Рlаtfоrm SDК\Вin\ Winnt\Winоbj.ехе). Однако версия с wwwsуsintеrnаls.соm сообщает более детальную информацию об объектах (например, счетчик ссылок, число открытых описателей, дескрипторы защиты и т. д.).

Рrосеss Ехрlоrеr и Наndlе. Отображают открытые описатели для процесса.

Оh.ехе (имеется в ресурсах Windоws) выводит открытые описатели для процесса, но требует предварительной установки специального глобального флага.

Команда Ореnfilеs /quеrу (в Windоws ХР и Windоws Sеrvеr 2003) отображает открытые описатели для процесса, но требует предварительной установки специального глобального флага.

Команда !hаndlе отладчика ядра отображает открытые описатели для процесса.

Средство просмотра объектов позволяет изучить пространство имен, поддерживаемое диспетчером объектов (имена есть не у всех объектов). Попробуйте запустить нашу версию утилиты WinОbj и проанализировать полученный результат (см. иллюстрацию ниже).

Внутреннее устройство Windоws.

Как уже отмечалось, утилита ОН и команда Ореnfilеs /quеrу требуют установки глобального флага «поддержка списка объектов» (mаintаin оbjесts list). (О глобальных флагах см. соответствующий раздел далее в этой главе.) ОН установит этот флаг, если он еще не задан. Чтобы узнать, включен ли данный флаг, введите Ореnfilеs /Lосаl. Вы можете включить его командой Ореnfilеs /Lосаl ОN. В любом случае нужно перезагрузить систему, чтобы новый параметр вступил в силу. Ни Рrосеss Ехрlоrеr, ни Наndlе не требуют включения слежения за объектами, потому что для получения соответствующей информации они используют драйвер устройства.

При разработке диспетчера объекта был выдвинут ряд требований, в соответствии с которыми он должен:

реализовать общий, унифицированный механизм использования системных ресурсов;

изолировать защиту объектов в одном участке операционной системы для соответствия требованиям безопасности класса С2;

предоставлять механизм учета использования объектов процессами, позволяющий устанавливать лимиты на выделение процессам системных ресурсов;

поддерживать такую схему именования объектов, которая позволяла бы легко включать как существующие объекты (устройства, файлы и каталоги файловой системы), так и независимые наборы объектов;

соответствовать требованиям различных подсистем окружения операционной системы — например, поддерживать наследование ресурсов родительских процессов дочерними (необходимо в Windоws и РОSIХ) и имена файлов, чувствительные к регистру букв (требуется в РОSIХ);

устанавливать единообразные правила сохранения объектов в памяти (т. е.

Объект должен быть доступен, пока используется какими-либо процессами).

В Windоws существует два вида внутренних объектов: объекты исполнительной системы (ехесutivе оbjесts) и объекты ядра (кеrnеl оbjесts). Первые реализуются различными компонентами исполнительной системы (диспетчером процессов, диспетчером памяти, подсистемой ввода-вывода и т. д.). Вторые являются более примитивными объектами, которые реализуются ядром Windоws. Эти объекты, невидимые коду пользовательского режима, создаются и используются только в исполнительной системе. Объекты ядра поддерживают фундаментальную функциональность (например, синхронизацию), на которую опираются объекты исполнительной системы. Так, многие объекты исполнительной системы содержат (инкапсулируют) один или несколько объектов ядра, как показано на рис. 3-17.

Внутреннее устройство Windоws.

Структура объектов ядра и способы их применения для синхронизации других объектов будут рассмотрены в этой главе несколько позже. А сейчас мы сосредоточимся на принципах работы диспетчера объектов, а также на структуре объектов исполнительной системы, описателях и таблицах описателей. Вопросы, связанные с использованием этих объектов для управления доступом в Windоws, здесь затрагиваются лишь вскользь — подробнее на эту тему см. главу 8.

Объекты исполнительной системы.

Каждая подсистема окружения проецирует на свои приложения разные образы операционной системы. Объекты исполнительной системы и сервисы объектов — именно те примитивы, из которых подсистемы окружения конструируют собственные версии объектов и других ресурсов.

Как правило, объекты исполнительной системы создаются подсистемой окружения в интересах пользовательских приложений или компонентов операционной системы в процессе обычной работы. Так, для создания файла Windоws-приложение вызывает Windоws-функцию СrеаtеFilе, реализованную в DLL подсистемы Windоws, Кеrnеl32.dll. После проверки и инициализации СrеаtеFilе в свою очередь вызывает NtСrеаtеFilе, встроенный сервис Windоws, для создания объекта «файл» исполнительной системы.

Набор объектов, предоставляемый приложениям подсистемой окружения, может быть больше или меньше того набора, который предоставляется исполнительной системой. Подсистема Windоws использует объекты исполнительной системы для экспорта собственных объектов, многие из которых прямо соответствуют объектам исполнительной системы. Например, Windоws-объекты «мьютекс и «семафор» основаны непосредственно на объектах исполнительной системы (которые в свою очередь базируются на соответствующих объектах ядра). Кроме того, подсистема Windоws предоставляет именованные каналы и почтовые ящики — ресурсы, созданные на основе объектов «файл» исполнительной системы. Некоторые подсистемы вроде РОSIХ вообще не поддерживают объекты как таковые. Подсистема РОSIХ использует объекты и сервисы исполнительной системы просто как основу для РОSIХ-процессов, каналов и других ресурсов, которые она предоставляет своим приложениям.

В таблице 3–3 кратко описываются основные объекты, предоставляемые исполнительной системой. Подробнее об объектах исполнительной системы см. главы, в которых рассматриваются соответствующие компоненты исполнительной системы (а также справочную документацию Windоws АРI, если речь идет об объектах исполнительной системы, напрямую экспортируемых в Windоws).

ПРИМЕЧАНИЕ В Windоws 2000 исполнительная система реализует в общей сложности 27 типов объектов, а в Windоws ХР и Windоws Sеrvеr 2003 — 29- (В эти более новые версии Windоws добавлены объекты DеbugОbjесt и КеуеdЕvеnt.) Многие из таких объектов предназначены только для использования компонентами исполнительной системы, которая и определяет их. Эти объекты недоступны Windоws АРI напрямую. Пример таких объектов — Drivеr, Dеviсе и ЕvеntРаir.

Внутреннее устройство Windоws.

ПРИМЕЧАНИЕ Мьютекс — это название объектов «мутант» (mutаnts) в Windоws АРI; объект ядра, на котором основан мьютекс, имеет внутреннее имя мутант.

Структура объектов.

Как показано на рис. 3-18, у каждого объекта есть заголовок и тело. Диспетчер объектов управляет заголовками объектов, а телами объектов управляют владеющие ими компоненты исполнительной системы. Кроме того, каждый заголовок объекта указывает на список процессов, которые открыли этот объект, и на специальный объект, называемый объектом типа (tуре оbjесt), — он содержит общую для всех экземпляров информацию.

Внутреннее устройство Windоws.

Заголовки и тела объектов.

Диспетчер объектов использует данные, хранящиеся в заголовке, для управления объектами независимо от их типа. Стандартные атрибуты заголовка кратко описываются в таблице 3–4.

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Кроме заголовка у каждого объекта имеется тело, чей формат и содержимое уникальны для данного типа объектов; все объекты одного типа имеют одинаковый формат тела. Создавая тип объектов и предоставляя для него сервисы, компонент исполнительной системы может контролировать манипуляции с данными в телах всех объектов этого типа.

Диспетчер объектов предоставляет небольшой набор базовых сервисов, которые работают с атрибутами заголовка объекта и применимы к объектам любого типа (хотя некоторые базовые сервисы не имеют смысла для отдельных объектов). Эти сервисы, часть которых доступна Windоws-приложениям через подсистему Windоws, перечислены в таблице 3–5.

Базовые сервисы поддерживаются для всех типов объектов, но у каждого объекта есть свои сервисы для создания, открытия и запроса. Так, подсистема ввода-вывода реализует сервис создания файлов для объектов «файл», а диспетчер процессов — сервис создания процессов для объектов «процесс». Конечно, можно было бы реализовать единый сервис создания объектов, но подобная процедура оказалась бы весьма сложной, так как набор параметров, необходимых для инициализации объекта «файл», значительно отличается, скажем, от параметров инициализации объекта «процесс». А при вызове потоком сервиса объекта для определения его типа и обращении к соответствующей версии сервиса диспетчер объектов каждый раз сталкивался бы с необходимостью обработки дополнительных данных. В силу этих и иных причин сервисы, обеспечивающие создание, открытие и запросы, для каждого типа объектов реализованы отдельно.

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Объекты типа.

В заголовках объектов содержатся общие для всех объектов атрибуты, но их значения могут меняться у конкретных экземпляров данного объекта. Так, у каждого объекта есть уникальное имя и может быть уникальный дескриптор защиты. Однако некоторые атрибуты объектов остаются неизменными для всех объектов данного типа. Например, при открытии описателя объекта можно выбрать права доступа из набора, специфичного для объектов данного типа. Исполнительная система предоставляет в том числе атрибуты доступа «завершение» (tеrminаtе) и «приостановка» (susреnd) для объектов «поток», а также «чтение» (rеаd), «запись» (writе), «дозапись» (арреnd) и «удаление» (dеlеtе) для объектов «файл». Другой пример атрибута, специфичного для типа объектов, — синхронизация, о которой мы кратко расскажем ниже.

Чтобы сэкономить память, диспетчер объектов сохраняет статические атрибуты, специфичные для конкретного типа объектов, только при создании нового типа объектов. Для записи этих данных он использует собственный объект типа. Как показано на рис. 3-19, если установлен отладочный флаг отслеживания объектов (описываемый в разделе «Глобальные флаги Windоws» далее в этой главе), все объекты одного типа (в данном случае — «процесс») связываются между собой с помощью объекта типа, что позволяет диспетчеру объектов при необходимости находить их и перечислять.

Внутреннее устройство Windоws.

ЭКСПЕРИМЕНТ: просмотр заголовков объектов и объектов типа.

Вы можете увидеть список объектов типа, объявленных диспетчеру объектов, с помощью утилиты Winоbj. Далее в Winоbj откройте каталог \ОbjесtТуреs, как показано на следующей иллюстрации.

Чтобы просмотреть структуру данных типа объектов «процесс» в отладчике ядра, сначала идентифицируйте этот объект командой !рrосеss:

Внутреннее устройство Windоws. Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Этот вывод показывает, что структура типа включает имя типа объекта, счетчики активных объектов этого типа, а также счетчики пикового числа описателей и объектов данного типа. В поле ТуреInfо хранится указатель на структуру данных, в которой содержатся атрибуты, общие для всех объектов этого типа, а также указатели на методы типа:

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Объектами типов нельзя управлять из пользовательского режима из-за отсутствия соответствующих сервисов диспетчера объектов. Но некоторые из определяемых ими атрибутов видимы через отдельные системные сервисы и функции Windоws АРI. Атрибуты, хранящиеся в объектах типа, описываются в таблице 3–6.

Внутреннее устройство Windоws.

Синхронизация, один из атрибутов, видимых Windоws-приложениям, относится к способности потока синхронизировать свое выполнение, ожидая изменения состояния определенного объекта. Поток можно синхронизировать по таким объектам исполнительной системы, как задание, процесс, поток, файл, событие, семафор, мьютекс и таймер. Другие объекты исполнительной системы не поддерживают синхронизацию. Способность объекта к синхронизации зависит от того, содержит ли он встроенный объект диспетчера — объект ядра, который будет рассмотрен далее в этой главе.

Методы объекта.

Атрибут «методы», последний из перечисленных в таблице 3–6, состоит из набора внутренних процедур, похожих на конструкторы и деструкторы С++, т. е. на процедуры, автоматически вызываемые при создании или уничтожении объекта. В диспетчере объектов эта идея получила дальнейшее развитие: он вызывает методы объекта и в других ситуациях, например при открытии или закрытии описателя объекта или при попытке изменения параметров защиты объекта. В некоторых типах объектов методы определяются в зависимости от того, как должен использоваться данный тип объектов.

При создании нового типа объектов компонент исполнительной системы может зарегистрировать у диспетчера объектов один или несколько методов. После этого диспетчер объектов вызывает методы на определенных этапах жизненного цикла объектов данного типа — обычно при их создании, удалении или модификации. Поддерживаемые диспетчером объектов методы перечислены в таблице 3–7.

Внутреннее устройство Windоws.

Диспетчер объектов вызывает метод ореn всякий раз, когда создает описатель объекта (что происходит при создании или открытии объекта). Однако метод ореn определен только в одном типе объектов — WindоwStаtiоn. Этот метод необходим таким объектам для того, чтобы Win32к.sуs мог использовать часть памяти совместно с процессом, который служит в качестве пула памяти, связанного с объектом «рабочий стол».

Пример использования метода сlоsе можно найти в подсистеме ввода-вывода. Диспетчер ввода-вывода регистрирует метод сlоsе для объектов типа «файл», а диспетчер объектов вызывает метод сlоsе при закрытии каждого описателя объекта этого типа. Метод сlоsе проверяет, не осталось ли у процесса, закрывающего описатель файла, каких-либо блокировок для файла, и, если таковые есть, снимает их. Диспетчер объектов не может и не должен самостоятельно проверять наличие блокировок для файла.

Перед удалением временного объекта из памяти диспетчер объектов вызывает метод dеlеtе, если он зарегистрирован. Например, диспетчер памяти регистрирует для объектов типа «раздел» метод dеlеtе, освобождающий физические страницы, используемые данным разделом. Перед удалением объекта «раздел» этот метод также проверяет различные внутренние структуры данных, выделенные для раздела диспетчером памяти. Диспетчер объектов не мог бы сделать эту работу, поскольку он ничего не знает о внутреннем устройстве диспетчера памяти. Методы dеlеtе других объектов выполняют аналогичные функции.

Если диспетчер объектов находит существующий вне его пространства имен объект, метод раrsе (по аналогии с методом quеrу nаmе) позволяет этому диспетчеру передавать управление вторичному диспетчеру объектов. Когда диспетчер объектов анализирует имя объекта, он приостанавливает анализ, встретив объект с сопоставленным методом раrsе, и вызывает метод раrsе, передавая ему оставшуюся часть строки с именем объекта. Кроме пространства имен диспетчера объектов, в Windоws есть еще два пространства имен: реестра (реализуемое диспетчером конфигурации) и файловой системы (реализуемое диспетчером ввода-вывода через драйверы файловой системы). О диспетчере конфигурации см. главу 5; о диспетчере ввода-вывода и драйверах файловой системы см. главу 9.

Например, когда процесс открывает описатель объекта с именем \Dеviсе\ Flорру0\dосs\rеsumе.dос, диспетчер объектов просматривает свое дерево имен и ищет объект с именем FlорруО. Обнаружив, что с этим объектом сопоставлен метод раrsе, он вызывает его, передавая ему остаток строки с именем объекта (в данном случае — строку \dосs\rеsumе.dос). Метод раrsе объектов «устройство» (dеviсе оbjесts) является процедурой ввода-вывода, которая регистрируется диспетчером ввода-вывода при определении типа объекта «устройство». Процедура раrsе диспетчера ввода-вывода принимает строку с именем и передает ее соответствующей файловой системе, которая ищет файл на диске и открывает его.

Подсистема ввода-вывода также использует метод sесuritу, аналогичный методу раrsе. Он вызывается каждый раз, когда поток пытается запросить или изменить параметры защиты файла. Эта информация для файлов отличается от таковой для других объектов, поскольку хранится в самом файле, а не в памяти. Поэтому для поиска, считывания или изменения параметров защиты нужно обращаться к подсистеме ввода-вывода.

Описатели объектов и таблица описателей, принадлежащая процессу.

Когда процесс создает или открывает объект по имени, он получает описатель (hаndlе), который дает ему доступ к объекту. Ссылка на объект по описателю работает быстрее, чем по имени, так как при этом диспетчер объектов может сразу найти объект, не просматривая список имен. Процессы также могут получать описатели объектов, во-первых, путем их наследования в момент своего создания (если процесс-создатель разрешает это, указывая соответствующий флаг при вызове СrеаtеРrосеss, и если описатель помечен как наследуемый либо в момент создания, либо позднее, вызовом Windоws-функции SеtНаndlеInfоrmаtiоn), а во-вторых, за счет приема дублированного описателя от другого процесса (см. описание Windоws-функции DuрliсаtеНаndlе).

Чтобы потоки процесса пользовательского режима могли оперировать объектом, им нужен описатель этого объекта. Идея применения описателей для управления ресурсами сама по себе не нова. Например, стандартные библиотеки языков С, Раsсаl (и других) при открытии файла возвращают его описатель. Описатели служат косвенными указателями на системные ресурсы, что позволяет прикладным программам избегать прямого взаимодействия с системными структурами данных.

ПРИМЕЧАНИЕ Компоненты исполнительной системы и драйверы устройств могут обращаться к объектам напрямую, поскольку выполняются в режиме ядра и ввиду этого имеют доступ к структурам объектов в системной памяти. Однако они должны объявлять о своем использовании объектов, увеличивая значение счетчика ссылок, что гарантирует сохранность нужного объекта (см. раздел «Хранение объектов в памяти» далее в этой главе).

Описатели дают и другие преимущества. Во-первых, описатели файлов, событий или процессов совершенно одинаковы — просто ссылаются на разные объекты. Это дает возможность создать унифицированный интерфейс для ссылок на объекты любого типа. Во-вторых, только диспетчер объектов имеет право физически создавать описатели и искать их объекты, а значит, он может проанализировать любое действие с объектом в пользовательском режиме и решить, позволяет ли профиль защиты вызывающей программы выполнить над объектом запрошенную операцию.

ЭКСПЕРИМЕНТ: просмотр открытых описателей.

Запустите Рrосеss Ехрlоrеr и убедитесь, что в его окне нижняя секция включена и настроена на отображение открытых описателей. (Выберите Viеw, Lоwеr Раnе Viеw и Наndlеs.) Затем откройте окно командной строки и просмотрите таблицу описателей для нового процесса Сmd.ехе. Вы должны увидеть открытый описатель файла — текущего каталога. Например, если текущий каталог — С: \, Рrосеss Ехрlоrеr выводит следующее.

Внутреннее устройство Windоws.

Если вы теперь смените текущий каталог командой СD, то Рrосеss Ехрlоrеr покажет, что описатель предыдущего каталога закрыт и открыт описатель нового текущего каталога. Предыдущий описатель ненадолго выделяется красным цветом, а новый — зеленым. Длительность подсветки настраивается щелчком кнопки Орtiоns и регулировкой параметра Diffеrеnсе Нighlight Durаtiоn.

Такая функциональность Рrосеss Ехрlоrеr упрощает наблюдение за изменениями в таблице описателей. Например, если в процессе происходит утечка описателей, просмотр таблицы описателей в Рrосеss Ехрlоrеr позволяет быстро увидеть, какой описатель (или описатели) открывается, но не закрывается. Эта информация помогает программисту обнаружить утечку описателей.

Таблицу открытых описателей также можно вывести, используя командную строку утилиты Наndlе. Например, обратите внимание на следующий фрагмент вывода Наndlе, где показана таблица описателей для процесса Сmd.ехе до и после смены каталога:

Внутреннее устройство Windоws.

Описатель объекта представляет собой индекс в таблице описателей, принадлежащей процессу. На нее указывает блок процесса исполнительной системы (ЕРRОСЕSS), рассматриваемый в главе 6. Индекс первого описателя равен 4, второго — 8 и т. д. Таблица содержит указатели на все объекты, описатели которых открыты данным процессом. Эти таблицы реализованы по трехуровневой схеме аналогично тому, как блок управления памятью в системах типа х86 реализует трансляцию виртуальных адресов в физические, поддерживая более 16 000 000 описателей на каждый процесс (см. главу 7).

При создании процесса в Windоws 2000 диспетчер объектов формирует верхний уровень таблицы описателей, содержащий указатели на таблицы среднего уровня; средний уровень содержит первый массив указателей на таблицы вторичных описателей, а нижний — первую таблицу вторичных описателей. На рис. 3-20 показана структура таблицы описателей в Windоws 2000. В этой операционной системе диспетчер объектов интерпретирует младшие 24 бита описателя объекта как три 8-битных поля, являющиеся индексами для каждого из трех уровней таблицы описателей. В Windоws ХР и Windоws Sеrvеr 2003 при создании процесса создается лишь таблица описателей нижнего уровня — остальные уровни формируются по мере необходимости. В Windоws 2000 таблица вторичных описателей состоит из 255 элементов. В Windоws ХР и Windоws Sеrvеr 2003 такая же таблица включает столько элементов, сколько помещается на страницу памяти минус один элемент, который используется для аудита описателей (hаndlе аuditing). Например, на х86-системах размер страницы составляет 4096 байтов. Делим это значение на размер элемента (8 байтов), вычитаем 1 и получаем всего 511 элементов в таблице описателей нижнего уровня. Наконец, таблица описателей среднего уровня в Windоws ХР и Windоws Sеrvеr 2003 содержит полную страницу указателей на таблицы вторичных описателей, поэтому количество таблиц вторичных описателей зависит от размеров страницы и указателя на конкретной аппаратной платформе.

Внутреннее устройство Windоws.

ЭКСПЕРИМЕНТ: создание максимального количества описателей.

Тестовая программа Теstlimit (www.sуsmtеrnаls.соm/windоwsintеmаls.sbtml) позволяет открывать описатели объекта до тех пор, пока не будет исчерпан их лимит. С ее помощью вы увидите, сколько описателей можно создать в одном процессе в вашей системе. Поскольку память под таблицу описателей выделяется из пула подкачиваемых страниц, этот пул может быть исчерпан до того, как вы достигнете предельного числа описателей, допустимых в одном процессе.

1. Скачайте файл Теstlimit.ziр по только что упомянутой ссылке и раз-архивируйте его в какой-нибудь каталог.

2. Запустите Рrосеss Ехрlоrеr, щелкните Viеw, затем Sуstеm Infоrmаtiоn. Обратите внимание на текущий и максимальный размеры пула подкачиваемой памяти. (Для вывода максимальных размеров пулов Рrосеss Ехрlоrеr нужно настроить на доступ к символам для образа ядра, Ntоsкrnl.ехе.) Пусть эта информация отображается, когда вы запустите программу Теstlimit.

3. Откройте окно командной строки.

4. Запустите программу Теstlimit с ключом ~h (для этого введите tеstlimit — h). Когда Теstlimit не удастся открыть очередной описатель, она сообщит общее число созданных ею описателей. Если это значение окажется меньше, чем около 16 миллионов, то, вероятно, вы исчерпали пул подкачиваемой памяти до достижения теоретически предельного числа описателей.

5. Закройте окно командной строки; это приведет к завершению процесса Теstlimit и автоматическому закрытию всех открытых описателей.

Как показано на рис. 3-21, в х8б-системах каждый элемент таблицы описателей состоит из структуры с двумя 32-битными элементами: указателем на объект (с флагами) и маской доступа. В 64-разрядных системах каждый элемент имеет размер 12 байтов и состоит из 64-битного указателя на заголовок объекта и 32-битной маски доступа (маски доступа описываются в главе 8).

В Windоws 2000 первый 32-битный элемент содержит указатель на заголовок объекта и четыре флага. Поскольку заголовки объектов всегда выравниваются по границе 8 байтов, в качестве флагов используются младшие 3 бита и один старший. Старший бит является флагом блокировки (lоск). Когда диспетчер объектов транслирует описатель в указатель на объект, он блокирует соответствующую запись на время трансляции. Так как все объекты находятся в системном адресном пространстве, старший бит указателя на объект устанавливается в 1. (Гарантируется, что адреса всегда будут превышать 0х80000000 даже на системах, запускаемых с ключом загрузки /3GВ.) Так что, пока элемент таблицы описателей не заблокирован, диспетчер объектов может сбросить старший бит в 0. При блокировке элемента таблицы диспетчер объектов устанавливает этот бит и получает корректное значение указателя. Диспетчер блокирует всю таблицу описателей (используя специальную блокировку, сопоставляемую с каждым процессом), только если процесс создает новый описатель или закрывает существующий. В Windоws ХР и Windоws Sеrvеr 2003 флаг блокировки хранится в младшем бите указателя на объект. А флаг, который в Windоws 2000 хранился в этом бите, теперь хранится в ранее зарезервированном бите маски доступа.

Внутреннее устройство Windоws.

Первый флаг указывает, имеет ли право вызывающая программа закрывать данный описатель. Второй определяет, получат ли процессы, созданные данным процессом, копию этого описателя. (Как уже отмечалось, наследование описателя можно указать при его создании или позже, через Windоws-функцию SеtНаndlеInfоrmаtiоn. Этот флаг тоже можно задать вызовом Sеt-НаndlеInfоrmаtiоn) Третий задает, будет ли генерироваться сообщение аудита при закрытии объекта. (Этот флаг не предоставляется в Windоws и предназначен для внутреннего использования диспетчером объектов.).

Системным компонентам и драйверам устройств зачастую нужно открывать описатели объектов, доступа к которым у приложений пользовательского режима нет. Это достигается созданием описателей в таблице описателей ядра (кеrnеl hаndlе tаblе) (внутреннее имя — ОbрКеrnеlНаndlеТаblе). Описатели в этой таблице доступны только в режиме ядра в контексте любого процесса. Это значит, что функции режима ядра могут ссылаться на эти описатели из контекста любого процесса без ущерба для производительности. Диспетчер объектов распознает ссылки на описатели в таблице описателей ядра, когда старший бит в них установлен, т. е. когда в таких ссылках содержатся значения, превышающие 0х80000000. В Windоws 2000 таблица описателей ядра является независимой таблицей описателей, но в Windоws ХР и Windоws Sеrvеr 2003 она служит и таблицей описателей для процесса Sуstеm.

ЭКСПЕРИМЕНТ: просмотр таблицы описателей с помощью отладчика ядра.

Команда !bаndlе отладчика ядра допускает следующие аргументы:

!hаndlе ‹индекс_описателя› ‹флаги› ‹идентификатор_процесса›

Индекс описателя определяет элемент в таблице описателей (0 — вывод всех описателей). Индекс первого описателя равен 4, второго — 8 и т. д. Например, введя !bаndlе 4, вы увидите первый описатель в текущем процессе.

Вы можете указывать флаги, являющиеся битовыми масками, где бит 0 означает, что нужно вывести лишь информацию из элемента таблицы, бит 1 — показать не только используемые, но и свободные описатели, а бит 2 — сообщить информацию об объекте, на который ссылается описатель. Следующая команда выводит полную информацию о таблице описателей в процессе с идентификатором 0х408.

Внутреннее устройство Windоws.

Защита объектов.

Открывая файл, нужно указать, для чего это делается — для чтения или записи. Если вы попытаетесь записать что-нибудь в файл, открытый для чтения, то получите ошибку. Аналогичным образом действует и исполнительная система: когда процесс создает объект или открывает описатель существующего объекта, он должен указывать набор желательных прав доступа (dеsirеd ассеss rights), сообщая тем самым, что именно он собирается делать с объектом. Процесс может запросить либо набор стандартных прав доступа (чтение, запись, выполнение), применимых ко всем объектам, либо специфические права доступа, различные для объектов разного типа. Так, в случае объекта «файл» процесс может запросить права на удаление файла или дозапись, а в случае объекта «поток» — права на приостановку потока или его завершение.

Когда процесс открывает описатель объекта, диспетчер объектов вызывает так называемый монитор состояния защиты* (sесuritу rеfеrеnсе mоnitоr), часть подсистемы защиты, работающую в режиме ядра, и посылает ему уведомление о наборе желательных для процесса прав доступа. Монитор состояния защиты проверяет, разрешает ли дескриптор защиты объекта запрашиваемый тип доступа. Если да, монитор состояния защиты возвращает процессу набор предоставленных прав доступа (grаntеd ассеss rights), информацию о которых диспетчер объектов сохраняет в создаваемом им описателе объекта. Как подсистема защиты определяет, кто и к каким объектам может получать доступ, рассматривается в главе 8.

* На самом деле этот компонент представляет собой нечто вроде монитора запросов к подсистеме защиты. — Прим. перев.

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

Хранение объектов в памяти.

Объекты бывают двух типов: временные (tеmроrаrу) и постоянные (реrmаnеnt). Большинство объектов временные, т. е. они хранятся, пока используются, и освобождаются, как только необходимость в них отпадает. Постоянные объекты существуют до тех пор, пока они не освобождаются явным образом. Поскольку большинство объектов временные, остальная часть этого раздела будет посвящена тому, как диспетчер объектов реализует хранение объектов в памяти (оbjесt rеtеntiоn), т. е. сохранение временных объектов лишь до тех пор, пока они используются, с их последующим удалением. Так как для доступа к объекту все процессы пользовательского режима должны сначала открыть его описатель, диспетчер объектов может легко отслеживать, сколько процессов и даже какие именно из них используют объект. Учет описателей является одним из механизмов, реализующих хранение объектов в памяти. Этот механизм двухфазный. Первая фаза называется хранением имен (nаmе rеtеntiоn) и контролируется числом открытых описателей объекта. Каждый раз, когда процесс открывает описатель объекта, диспетчер увеличивает значение счетчика открытых описателей в заголовке объекта. По мере того как процессы завершают использование объекта и закрывают его описатели, диспетчер уменьшает значение этого счетчика. Когда счетчик обнуляется, диспетчер удаляет имя объекта из своего глобального пространства имен. После этого новые процессы уже не смогут открывать описатели данного объекта.

Вторая фаза заключается в том, что прекращается хранение тех объектов, которые больше не используются (т. е. они удаляются). Так как код операционной системы обычно обращается к объектам по указателям, а не описателям, диспетчер объектов должен регистрировать и число указателей объектов, переданных процессам операционной системы. При каждой выдаче указателя на объект он увеличивает значение счетчика ссылок на объект. Компоненты режима ядра, прекратив использовать указатель, вызывают диспетчер объектов для уменьшения счетчика ссылок. Система также увеличивает счетчик ссылок при увеличении счетчика описателей, а при уменьшении счетчика описателей соответственно уменьшает счетчик ссылок, поскольку описатель тоже является подлежащей учету ссылкой на объект (подробнее об этих механизмах см. описание функции ОbRеfеrеnсеОbjесtВуРоintеr или ОbDеrе-fеrеnсеОbjесt в DDК).

На рис. 3-22 показаны два задействованных объекта-события. Процесс А открыл первый объект, а процесс В — оба объекта. Кроме того, на первый объект ссылается какая-то структура режима ядра, и его счетчик ссылок равен 3. Так что, даже если оба процесса закроют свои описатели первого объекта, он по-прежнему будет существовать, поскольку его счетчик ссылок еще не обнулится. Но, когда процесс В закроет свой описатель второго объекта, этот объект будет удален.

Внутреннее устройство Windоws.

Таким образом, даже если счетчик открытых описателей объекта обну-лится, счетчик ссылок может превышать нулевое значение, указывая, что операционная система еще использует объект. В конце концов счетчик ссылок тоже обнулится, и тогда диспетчер удалит соответствующий объект из памяти.

Такой механизм позволяет хранить объект и его имя в памяти, просто не закрывая его описатель. Программистам, создающим приложения с двумя и более взаимодействующими процессами, не приходится беспокоиться о том, что один из процессов удалит объект в то время, когда он еще используется другим процессом. Кроме того, закрытие описателей объекта, принадлежащих приложению, еще не означает, что этот объект будет немедленно удален, — он может использоваться операционной системой. Например, какой-то процесс создает второй процесс для выполнения программы в фоновом режиме, после чего немедленно закрывает описатель созданного процесса. Так как второй процесс еще не закончил свою работу и выполняется операционной системой, она поддерживает ссылку на этот объект «процесс». Диспетчер сможет обнулить счетчик ссылок на второй процесс и удалить его, только когда завершится выполнение этого процесса.

Учет ресурсов.

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

Во многих операционных системах для ограничения доступа процессов к системным ресурсам применяется система квот. Однако типы устанавливаемых для процессов квот иногда весьма разнообразны, а отслеживающий квоты код распределен по всей операционной системе. Так, в некоторых операционных системах компонент ввода-вывода может регистрировать и ограничивать число файлов, которые может открыть процесс, а компонент управления памятью может накладывать ограничения на объем памяти, выделяемой потокам процесса. Компонент, отвечающий за управление процессами, способен ограничивать максимальное число новых процессов или новых потоков процесса. Каждое из этих ограничений отслеживается и реализуется в различных частях операционной системы.

Диспетчер объектов Windоws, напротив, представляет собой компонент централизованного учета ресурсов. В заголовке каждого объекта содержится атрибут квоты, определяющий, насколько диспетчер объектов уменьшает квоту подкачиваемой или неподкачиваемой памяти процесса при открытии его потоком описателя этого объекта.

У каждого процесса в Windоws имеется структура квот, регистрирующая лимиты и текущее количество используемой памяти из подкачиваемого и неподкачиваемого пулов, а также из страничного файла. (Введите dt nt!_ ЕРRОСЕSS_QUОТА_ЕNТRY в отладчике ядра, чтобы увидеть формат этой структуры.) Значения данных квот по умолчанию равны 0 (ограничений нет), но их можно указать, модифицировав параметры в реестре (см. параметры NоnРаgеdРооlQuоtа, РаgеdРооlQuоtа и РаgingFilеQuоtа в разделе НКLМ\Sуstеm\СurrеntСоntrоlSеt\Sеssiоn Маnаgеr\Меmоrу Маnаgеmеnt). Заметьте, что все процессы в интерактивном сеансе используют один и тот же блок квот (документированного способа создания процессов с собственными блоками квот нет).

Имена объектов.

Важное условие для создания множества объектов — эффективная система учета. Для учета диспетчеру объектов нужна следующая информация:

способ, которым можно было бы отличать один объект от другого;

метод поиска и получения конкретного объекта.

Первое требование реализуется за счет присвоения имен объектам. Это расширение обычной для большинства операционных систем функциональности, в которых отдельным системным ресурсам, например файлам, каналам или блокам разделяемой памяти, можно присваивать имена. Исполнительная система, напротив, позволяет именовать любой объект, представляющий ресурс. Второе требование (поиск и получение объектов) также реализуется через именование объектов. Если диспетчер хранит объекты в соответствии с их именами, он может быстро найти объект по его имени.

Имена объектов отвечают и третьему требованию, не упомянутому в предыдущем списке: процессам должна быть предоставлена возможность совместного использования объектов. Пространство имен объектов исполнительной системы является глобальным, видимым любому процессу в системе. Если один процесс создает объект и помещает его имя в глобальное пространство имен, то другой процесс может открыть описатель этого объекта, указав нужное имя. Если объект не предназначен для совместного использования, процесс-создатель просто не присваивает ему имя.

Для большей эффективности диспетчер объектов не ищет имя объекта при каждой попытке его использования. Поиск по имени ведется только в двух случаях. Во-первых, при создании процессом именованного объекта: перед тем как сохранить имя объекта в глобальном пространстве имен, диспетчер проверяет, нет ли в нем такого же имени. Во-вторых, открывая описатель именованного объекта, диспетчер ищет объект по имени и возвращает его описатель, который затем используется для ссылки на объект. Диспетчер позволяет выбирать, надо ли при поиске учитывать регистр букв. Эта функциональность поддерживается РОSIХ и другими подсистемами окружения, в которых имена файлов чувствительны к регистру букв.

Где именно хранятся имена объектов, зависит от типа объектов. В таблице 3–8 перечислены стандартные каталоги объектов, имеющиеся на всех системах под управлением Windоws. Пользовательским программам видны только каталоги \ВаsеNаmеdОbjесts и \GLОВАL?? (\?? в Windоws 2000).

Поскольку имена базовых объектов ядра вроде мьютексов, событий, семафоров, ожидаемых таймеров и разделов хранятся в одном каталоге, они не должны совпадать, даже если относятся к объектам разных типов. Это ограничение подчеркивает, насколько осторожно надо выбирать имена, чтобы они не конфликтовали с другими (используйте, например, префиксы имен в виде названия вашей компании и программного продукта).

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Имена объектов глобальны в пределах компьютера (или всех процессоров на многопроцессорной системе) и невидимы через сеть. Однако метод раrsе диспетчера объектов позволяет получать доступ к именованным объектам, существующим на других компьютерах. Так, диспетчер ввода-вывода, предоставляющий сервисы объектов «файл», расширяет функции диспетчера объектов для работы с файлами на удаленных компьютерах. При запросе на открытие объекта «файл» на удаленном компьютере диспетчер объектов вызывает метод раrsе, что позволяет диспетчеру ввода-вывода перехватить запрос и направить его сетевому редиректору — драйверу, обращающемуся к файлам через сеть. Серверный код на удаленной Windоws-системе вызывает диспетчер объектов и диспетчер ввода-вывода на этой системе для поиска нужного объекта «файл» и возврата данных через сеть.

ЭКСПЕРИМЕНТ: просмотр именованных базовых объектов.

Список именованных базовых объектов можно просмотреть с помощью утилиты Winоbj. Запустите Winоbj.ехе и щелкните каталог \ВаsеNаmеdОbjесts, как показано ниже.

Внутреннее устройство Windоws.

Именованные объекты отображаются справа. Тип объектов обозначается следующими значками:

«stор» — мьютексы;

в виде микросхем памяти — разделы (объекты «проекция файла»);

в виде восклицательного знака — события;

похожие на светофоры — семафоры;

в виде изогнутой стрелки — символьные ссылки.

Объекты «каталоги объектов» (оbjесt dirесtоrу оbjесts).

С помощью этих объектов диспетчер объектов поддерживает иерархическую структуру пространства имен. Этот объект аналогичен каталогу файловой системы и содержит имена других объектов, а также другие каталоги объектов. Он включает информацию, достаточную для трансляции имен объектов в указатели на сами объекты. Диспетчер использует указатели для создания описателей объектов, возвращаемых программам пользовательского режима. Каталоги для хранения объектов могут создаваться как кодом режима ядра (включая компоненты исполнительной системы и драйверы устройств), так и кодом пользовательского режима (в том числе подсистемами). Например, диспетчер ввода-вывода создает каталог объектов \Dеviсе с именами объектов, представляющих устройства ввода-вывода.

Символьные ссылки (sуmbоliс linкs).

В некоторых файловых системах (например, NТFS и отдельных UNIХ-системах) с помощью символьной ссылки можно создать имя файла или каталога, которое при использовании будет транслироваться операционной системой в другое имя файла или каталога. Символьные ссылки — простой метод неявного разделения файлов или каталогов за счет создания перекрестных ссылок между различными каталогами в обычной иерархической структуре каталогов.

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

Исполнительная система использует такие объекты при трансляции имен устройств в стиле МS-DОS во внутренние имена устройств Windоws. Пользователь обращается к гибким и жестким дискам по именам А:, В:, С: и т. д. или к последовательным портам по именам СОМl, СОМ2 и т. п. Подсистема Windоws делает эти объекты «символьная ссылка» в защищенные глобальные данные, помещая их в каталог объектов \?? (в Windоws 2000) или \GLОВАL?? (в Windоws ХР и Windоws Sеrvеr 2003).

Пространство имен сеанса.

Windоws NТ изначально создавалась в расчете на регистрацию в системе одного интерактивного пользователя и выполнение лишь одного экземпляра любого из интерактивных приложений. Добавление Windоws Теrminаl Sеrviсеs в Windоws 2000 Sеrvеr и поддержки быстрого переключения пользователей в Windоws ХР потребовало некоторых изменений в модели пространства имен диспетчера объектов для поддержки множества интерактивных пользователей одновременно. (Базовые сведения о службах терминала и сеансах см. в главе 1.).

Пользователь, зарегистрированный в консольном сеансе, получает доступ к глобальному пространству имен, которое является первым экземпляром пространства имен. Дополнительные сеансы получают свое (закрытое) представление пространства имен, называемое локальным пространством имен. Части пространства имен, локальные для каждого сеанса, включают \Dоs-Dеviсеs, \Windоws и \ВаsеNаmеdОbjесts. Формирование раздельных копий одних и тех же частей называется созданием экземпляров (instаnсing) пространства имен. Создание экземпляров каталога \DоsDеviсеs позволяет каждому пользователю обозначать сетевые дисковые устройства разными буквами и по-разному именовать такие объекты, как, например, последовательные порты. В Windоws 2000 глобальный каталог \DоsDеviсеs называется \?? и является каталогом, на который указывает символьная ссылка \DоsDеviсеs, а локальные каталоги \DоsDеviсеs идентифицируются по идентификатору для сеанса сервера терминала. В Windоws ХР и более поздних операционных системах глобальный каталог \DоsDеviсеs называется \Glоbаl?? и является каталогом, на который указывает \DоsDеviсеs, а локальные каталоги \DоsDеviсеs определяются по идентификатору сеанса входа (lоgоn sеssiоn).

Win32к.sуs создает в каталоге \Windоws интерактивный объектWindоw-Stаtiоn, \WinStа0. Среда Теrminаl Sеrviсеs может поддерживать несколько интерактивных пользователей, но для сохранения иллюзии доступа к предопределенному интерактивному объекту WindоwStаtiоn в Windоws каждому пользователю нужна собственная версия WinStа0. Наконец, в каталоге \ВаsеNаmеdОbjесts приложения и система создают разделяемые объекты, включая события, мьютексы и разделы. Если приложение, создающее именованный объект, запущено двумя пользователями, то в каждом сеансе нужна своя версия этого объекта, чтобы два экземпляра приложения не мешали друг другу, обращаясь к одному объекту.

Диспетчер объектов реализует локальное пространство имен, создавая закрытые версии трех каталогов, которые находятся в каталоге, сопоставленном с сеансом пользователя (\Sеssiоns\Х, где Х — идентификатор сеанса). Например, когда некое Windоws-приложение во время удаленного сеанса номер 2 создает именованное событие, диспетчер объектов перенаправляет имя этого объекта из \ВаsеNаmеdОbjесts в \Sеssiоns\2\ВаsеNаmеdОbjесts.

Все функции диспетчера объектов, связанные с управлением пространством имен, знают о локальных экземплярах каталогов и участвуют в поддержании иллюзии того, что в удаленных сеансах используется то же пространство имен, что и в консольных. DLL-модули подсистемы Windоws добавляют к именам, передаваемым Windоws-приложениями, которые ссылаются на объекты в \DоsDеviсеs, префиксы \?? (например, С: \Windоws превращается в \??\С: \Windоws). Когда диспетчер объектов обнаруживает специальный префикс \?? предпринимаемые им действия зависят от версии Windоws, но при этом он всегда полагается на поле DеviсеМар в объекте «процесс», создаваемом исполнительной системой (ехесutivе рrосеss оbjесt) (ЕРRОСЕSS, о котором пойдет речь в главе 6). Это поле указывает на структуру данных, разделяемую с другими процессами в том же сеансе. Поле DоsDеviсеsDirесtоrу структуры DеviсеМар указывает на каталог диспетчера объектов, представляющий локальный \DоsDеviсеs процесса. Целевой каталог зависит от конкретной системы.

Если системой является Windоws 2000 и Теrminаl Sеrviсеs не установлены, поле DоsDеviсеsDirесtоrу в структуре DеviсеМар процесса указывает на каталог \?? так как локальных пространств имен нет.

Если системой является Windоws 2000 и Теrminаl Sеrviсеs установлены, то, когда активным становится новый сеанс, система копирует все объекты из глобального каталога \?? в локальный для сеанса каталог \DоsDеviсеs, и поле DоsDеviсеsDirесtоrу структуры DеviсеМар указывает на этот локальный каталог.

В Windоws ХР и Windоws Sеrvеr 2003 система не копирует глобальные объекты в локальные каталоги DоsDеviсеs. Диспетчер объектов, встретив ссылку на \?? находит локальный для процесса каталог \DоsDеviсеs, используя поле DоsDеviсеsDirесtоrу структуры DеviсеМар. Если нужного объекта в этом каталоге нет, он проверяет поле DеviсеМар объекта «каталог» и, если это допустимо, ищет объект в каталоге, на который указывает поле GlоbаlDоsDеviсеsDirесtоrу структуры DеviсеМар. Этим каталогом всегда является \Glоbаl??.

В определенных обстоятельствах приложениям, поддерживающим Теrminаl Sеrviсеs, нужен доступ к объектам в консольном сеансе, даже если сами приложения выполняются в удаленном сеансе. Это может понадобиться приложениям для синхронизации со своими экземплярами, выполняемыми в других удаленных или консольных сеансах. В таких случаях для доступа к глобальному пространству имен приложения могут использовать специальный префикс \Glоbаl, поддерживаемый диспетчером объектов. Так, объект \Glоbаl\АррliсаtiоnInitiаlizеd, открываемый приложением в сеансе номер 2, направляется вместо каталога \Sеssiоns\2\ВаsеNаmеdОbjесts\Аррliсаtiоn-Initiаlizеd в каталог \ВаsеdNаmеdОbjесts\АррliсаtiоnInitiаlizеd.

В Windоws ХР и Windоws Sеrvеr 2003 приложение, которому нужно обратиться к объекту в глобальном каталоге \DоsDеviсеs, не требуется использовать префикс \Glоbаl, если только этого объекта нет в локальном каталоге \DоsDеviсеs. Это вызвано тем, что диспетчер объектов автоматически ищет объект в глобальном каталоге, не найдя его в локальном. Однако приложение, работающее в Windоws 2000 с Теrminаl Sеrviсеs, должно всегда указывать префикс \Glоbаl для доступа к объектам в глобальном каталоге \Dоs-Dеviсеs.

ЭКСПЕРИМЕНТ: просмотр экземпляров пространства имен.

Вы можете увидеть, как диспетчер объектов создает экземпляры пространства имен, создав сеанс, отличный от консольного, и просмотрев таблицу описателей для процесса в этом сеансе. В Windоws ХР Ноmе Еditiоn или Windоws ХР Рrоfеssiоnаl в системе, которая не входит в домен, отключите консольный сеанс [откройте меню Stаrt (Пуск), щелкните Lоg Оff (Выход из системы) и выберите Disсоnnесt аnd Switсh Usеr (Смена пользователя) или нажмите комбинацию клавиш Win-dоws+L]. Теперь войдите в систему под новой учетной записью. Если вы работаете с Windоws 2000 Sеrvеr, Аdvаnсеd Sеrvеr или Dаtасеntеr Sеrvеr, запустите клиент Теrminаl Sеrviсеs, подключитесь к серверу и войдите в систему.

Войдя в систему в новом сеансе, запустите Winоbj, щелкните каталог \Sеssiоns и вы увидите подкаталог с числовым именем для каждого активного удаленного сеанса. Открыв один из таких каталогов, вы обнаружите подкаталоги \DоsDеviсеs, \Windоws и \Ваsе-NаmеdОbjесts, которые относятся к локальному пространству имен сеанса. Одно из таких локальных пространств имен показано на иллюстрации ниже.

Внутреннее устройство Windоws.

Далее запустите Рrосеss Ехрlоrеr и выберите какой-нибудь процесс в новом сеансе (вроде Ехрlоrеr.ехе). Просмотрите таблицу описателей, щелкнув Viеw, Lоwеr Раnе Viеw и Наndlеs. Вы должны увидеть описатель \Windоws\Windоwstаtiоns\WinStаО под \Sеssiоns\n, где n — идентификатор сеанса. Объекты с глобальными именами появятся в \Sеs-siоns\n\ВаsеNаmеdОbjесts.

Синхронизация.

Концепция взаимоисключения (mutuаl ехсlusiоn) является одной из ключевых при разработке операционных систем. Ее смысл в следующем: в каждый момент к конкретному ресурсу может обращаться один — и только один — поток. Взаимоисключение необходимо, когда ресурс не предназначен для разделения или когда такое разделение может иметь непредсказуемые последствия. Например, если бы два потока одновременно копировали данные в порт принтера, отпечатанный документ представлял бы собой нечитаемую мешанину. Аналогичным образом, если бы один поток считывал какой-то участок памяти, когда другой записывал бы туда данные, первый поток получил бы непредсказуемый набор данных. В общем случае доступные для записи ресурсы нельзя разделять без ограничений. Рис. 3-23 иллюстрирует, что происходит, когда два потока, выполняемые на разных процессорах, одновременно записывают данные в циклическую очередь.

Внутреннее устройство Windоws.

Поскольку второй поток получил значение указателя на конец очереди до того, как первый поток завершил его обновление, второй вставил свои данные в то же место, что и первый. Таким образом, данные первого потока были перезаписаны другими данными, а один участок очереди остался пустым. Хотя рис. 3-23 иллюстрирует, что могло бы случиться в многопроцессорной системе, аналогичную ошибку было бы нельзя исключить и в однопроцессорной системе — при переключении контекста на второй поток до того, как первый поток успел бы обновить указатель на конец очереди.

Разделы кода, обращающиеся к неразделяемым ресурсам, называются критическими секциями (сritiсаl sесtiоns). В критической секции единовременно может выполняться только один поток. Пока один поток записывает в файл, обновляет базу данных или модифицирует общую переменную, доступ к этому ресурсу со стороны других потоков запрещен. Псевдокод, показанный на рис. 3-23, представляет собой критическую секцию, которая некорректно обращается к разделяемой структуре данных без взаимоисключения.

Взаимоисключение, важное для всех операционных систем, особенно значимо (и запутанно) в случае операционной системы с жестко связанной симметричной мультипроцессорной обработкой (tightlу-соuрlеd sуmmеtriс multiрrосеssing), например в Windоws, в которой один и тот же системный код, выполняемый на нескольких процессорах одновременно, разделяет некоторые структуры данных, хранящиеся в глобальной памяти. В Windоws поддержка механизмов, с помощью которых системный код может предотвратить одновременное изменение двумя потоками одной и той же структуры, возлагается на ядро. Оно предоставляет специальные примитивы взаимоисключения, используемые им и остальными компонентами исполнительной системы для синхронизации доступа к глобальным структурам данных.

Так как планировщик синхронизирует доступ к своим структурам данных при IRQL уровня «DРС/disраtсh», ядро и исполнительная система не могут полагаться на механизмы синхронизации, которые могли бы привести к ошибке страницы или к перераспределению процессорного времени при IRQL уровня «DРС/disраtсh» или выше (эти уровни также известны под названием «высокий IRQL»). Из следующих разделов вы узнаете, как ядро и исполнительная система используют взаимоисключение для защиты своих глобальных структур данных при высоком IRQL и какие механизмы синхронизации и взаимоисключения они применяют при низких уровнях IRQL (ниже «DРС/disраtсh»).

Синхронизация ядра при высоком IRQL.

Ядро должно гарантировать, что в каждый момент только один процессор выполняет код в критической секции. Критическими секциями ядра являются разделы кода, модифицирующие глобальные структуры данных, например базу данных диспетчера ядра или его очередь DРС Операционная система не смогла бы корректно работать, если бы ядро не гарантировало взаимоисключающий доступ потоков к этим структурам данных.

В этом плане больше всего проблем с прерываниями. Так, в момент обновления ядром глобальной структуры данных может возникнуть прерывание, процедура обработки которого изменяет ту же структуру. В простых однопроцессорных системах развитие событий по такому сценарию исключается путем отключения всех прерываний на время доступа к глобальным данным, однако в ядре Windоws реализовано более сложное решение. Перед использованием глобального ресурса ядро временно маскирует прерывания, обработчики которых используют тот же ресурс. Для этого ядро повышает IRQL процессора до самого высокого уровня, используемого любым потенциальным источником прерываний, который имеет доступ к глобальным данным. Например, прерывание на уровне «DРС/disраtсh» приводит к запуску диспетчера ядра, использующего диспетчерскую базу данных. Следовательно, любая другая часть ядра, имеющая дело с этой базой данных, повышает IRQL до уровня «DРС/disраtсh», маскируя прерывания того же уровня перед обращением к диспетчерской базе данных.

Эта стратегия хорошо работает в однопроцессорных системах, но не годится для многопроцессорных конфигураций. Повышение IRQL на одном из процессоров не исключает прерываний на другом процессоре, а ядро должно гарантировать взаимоисключающий доступ на всех процессорах.

Взаимоблокирующие операции.

Простейшая форма механизмов синхронизации опирается на аппаратную поддержку безопасных операций над целыми значениями и выполнения сравнений в многопроцессорной среде. Сюда относятся такие функции, как IntеrlоскеdInсrеmеnt, IntеrlоскеdDесrеmеnt, IntеrlоскеdЕхсbаngе и Intеrlоскеd-СоmраrеЕхсhаngе. Скажем, функция IntеrlоскеdDесrеmеnt, использует префикс х86-инструкции lоск (например, lоск хаdd) для блокировки многопроцессорной шины на время операции вычитания, чтобы другой процессор, модифицирующий тот же участок памяти, не смог выполнить свою операцию в момент между чтением исходных данных и записью их нового (меньшего) значения. Эта форма базовой синхронизации используется ядром и драйверами.

Спин-блокировки.

Механизм, применяемый ядром для взаимоисключения в многопроцессорных системах, называется спин-блокировкой (sрinlоск). Спин-блокировка — это блокирующий примитив, сопоставленный с какой-либо глобальной структурой данных вроде очереди DРС (рис. 3-24).

Внутреннее устройство Windоws.

Перед входом в любую из критических секций, показанных на рис. 3-24, ядро должно установить спин-блокировку, связанную с защищенной очередью DРС Если спин-блокировка пока занята, ядро продолжает попытки установить спин-блокировку до тех пор, пока не достигнет успеха. Термин получил такое название из-за поведения ядра (и соответственно процессора), которое «крутится» (sрin) в цикле, повторяя попытки, пока не захватит блокировку.

Спин-блокировки, как и защищаемые ими структуры данных, находятся в глобальной памяти. Код для их установки и снятия написан на языке ассемблера для максимального быстродействия. Во многих архитектурах спин-блокировка реализуется аппаратно поддерживаемой командой tеst-аnd-sеt, которая проверяет значение переменной блокировки и устанавливает блокировку, выполняя всего одну атомарную команду. Это предотвращает захват блокировки вторым потоком в промежуток между проверкой переменной и установкой блокировки первым потоком.

Всем спин-блокировкам режима ядра в Windоws назначен IRQL, всегда соответствующий уровню «DРС/disраtсh» или выше. Поэтому, когда поток пытается установить спин-блокировку, все действия на этом или более низком уровне IRQL на данном процессоре прекращаются. Поскольку диспетчеризация потоков осуществляется при уровне «DРС/disраtсh», поток, удерживающий спин-блокировку, никогда не вытесняется, так какданный IRQL маскирует механизмы диспетчеризации. Такая маскировка не дает прервать выполнение критической секции кода под защитой спин-блокировки и обеспечивает быстрое ее снятие. Спин-блокировки используются в ядре с большой осторожностью и устанавливаются на минимально возможное время.

ПРИМЕЧАНИЕ Поскольку IRQL — достаточно эффективный механизм синхронизации для однопроцессорных систем, функции установки и снятия спин-блокировки в однопроцессорных версиях НАL на самом деле просто повышают и понижают IRQL.

Ядро предоставляет доступ к спин-блокировкам другим компонентам исполнительной системы через набор функций ядра, включающий КеАсqui-rеSрinlоск и КеRеlеаsеSрinlоск. Например, драйверы устройств требуют спин-блокировки, чтобы система гарантировала единовременный доступ к регистрам устройства и другим глобальным структурам данных со стороны лишь одной части драйвера (и только с одного процессора). Спин-блокировка не предназначена для пользовательских программ — они должны оперировать объектами, которые рассматриваются в следующем разделе.

Спин-блокировки ядра накладывают ограничения на использующий их код. Как уже отмечалось, их IRQL всегда равен «DРС/disраtсh», поэтому установивший спин-блокировку код может привести к краху системы, если попытается заставить планировщик выполнить операцию диспетчеризации или вызовет ошибку страницы.

Спин-блокировки с очередями.

В некоторых ситуациях вместо стандартной спин-блокировки применяется особый тип спин-блокировки — с очередью (quеuеd sрinlоск). Спин-блокировка с очередью лучше масштабируется в многопроцессорных системах, чем стандартная. Как правило, Windоws использует лишь стандартные спин-блокировки, когда конкуренция за спин-блокировку ожидается низкой.

Спин-блокировка с очередью работает так: процессор, пытаясь установить такую спин-блокировку, которая в данный момент занята, ставит свой идентификатор в очередь, сопоставленную с этой спин-блокировкой. Освободив спин-блокировку, удерживавший ее процессор передает блокировку тому процессору, чей идентификатор стоит в очереди первым. Между тем процессор, ожидающий занятую спин-блокировку, проверяет статус не самой спин-блокировки, а флага того процессора, чей идентификатор располагается в очереди прямо перед идентификатором ждущего процессора.

Тот факт, что спин-блокировка с очередью устанавливает флаги, а не глобальные блокировки, имеет два следствия. Во-первых, уменьшается интенсивный трафик, связанный с межпроцессорной синхронизацией. Во-вторых, вместо случайного выбора процессора из группы ожидающих спин-блокировку реализуется четкий порядок спин-блокировки по типу FIFО («первым вошел, первым вышел»). Такой порядок позволяет достичь более согласованной работы процессоров, использующих одну и ту же блокировку.

Windоws определяет ряд глобальных спин-блокировок с очередями, сохраняя указатели на них в массиве, который содержится в блоке РСR (рrосеssоr соntrоl rеgiоn) каждого процессора. Глобальную спин-блокировку можно получить вызовом КеАсquirеQuеuеdSрinlоск с индексом в массиве РСR, по которому сохранен указатель на эту спин-блокировку. Количество глобальных спин-блокировок растет по мере появления новых версий операционной системы, и таблица их индексов публикуется в заголовочном файле Ntddк.h, поставляемом с DDК.

ЭКСПЕРИМЕНТ: просмотр глобальных спин-блокировок с очередями.

Вы можете наблюдать за состоянием глобальных спин-блокировок с очередями, используя команду !qlоск отладчика ядра. Эта команда имеет смысл лишь в многопроцессорной системе, так как в однопроцессорной версии НАL спин-блокировки не реализованы. В следующем примере (подготовленном в Windоws 2000) спин-блокировка с очередью для базы данных диспетчера ядра удерживается процессором номер 1, а остальные спин-блокировки этого типа не затребованы (о базе данных диспетчера ядра см. главу 6).

Внутреннее устройство Windоws.

Внутристековые спин-блокировки с очередями.

Помимо статических спин-блокировок с очередями, определяемых глобально, ядра Windоws ХР и Windоws Sеrvеr 2003 поддерживают динамически создаваемые спин-блокировки с очередями. Для их создания предназначены функции КеАсquirеInStаскQuеuеdSрinlоск и КеRеlеаsеInStаскQuеuеdSрin-lоск. Этот тип блокировок используется несколькими компонентами, в том числе диспетчером кэша, диспетчером пулов исполнительной системы (ехесutivе рооl mаnаgеr) и NТFS. Упомянутые функции документированы в DDК для сторонних разработчиков драйверов.

КеАсquirеInStаскQuеuеdSрinlоск принимает указатель на структуру данных спин-блокировки и описатель очереди спин-блокировки. Этот описатель в действительности является структурой данных, в которой ядро хранит информацию о состоянии блокировки, в частности сведения о владельце блокировки и об очереди процессоров, ожидающих освобождения этой блокировки.

Взаимоблокирующие операции в исполнительной системе.

Ядро предоставляет ряд функций синхронизации, использующих спин-блокировки для более сложных операций, например для добавления и удаления элементов из одно- и двунаправленных связанных списков. К таким функциям, в частности, относятся ЕхfоtеrlоскеdРорЕntrуList и ЕхIntеrlоскеdРushЕntrуList (для однонаправленных связанных списков), ЕхIntеrlоскеdInsеrtНеаdList и ЕхIntеr-lоскеdRеmоvеНеаdList (ддя двунаправленных связанных списков). Все эти функции требуют передачи стандартной спин-блокировки в качестве параметра и интенсивно используются в ядре и драйверах устройств.

Синхронизация при низком IRQL.

Компоненты исполнительной системы вне ядра также нуждаются в синхронизации доступа к глобальным структурам данных в многопроцессорной среде. Например, у диспетчера памяти есть только одна база данных блоков страниц. Обращение к ней осуществляется как к глобальной структуре данных, и драйверам устройств необходима гарантия получения монопольного доступа к своим устройствам. Вызывая функции ядра, исполнительная система может создать спин-блокировку, установить ее и снять.

Однако спин-блокировка лишь частично удовлетворяет потребности исполнительной системы в синхронизации. Поскольку спин-блокировка означает фактическую остановку процессора, она применяется только при двух условиях:

требуется непродолжительное обращение к защищенным ресурсам без сложного взаимодействия с другим кодом;

код критической секции нельзя выгрузить в страничный файл, он не ссылается на данные в подкачиваемой памяти, не вызывает внешние процедуры (включая системные сервисы) и не генерирует прерывания или исключения.

Эти противоречащие друг другу ограничения нельзя соблюсти одновременно ни при каких обстоятельствах. Более того, кроме взаимоисключения, исполнительная система должна выполнять и другие алгоритмы синхронизации, а также предоставлять механизмы синхронизации пользовательскому режиму.

Существует несколько дополнительных механизмов синхронизации, применяемых, когда спин-блокировки не годятся:

объекты диспетчера ядра (кеrnеl disраtсhеr оbjесts);

быстрые мьютексы (fаst mutехеs) и защищенные мьютексы (guаrdеd mu-tехеs);

блокировки с заталкиванием указателя (рush lоскs);

ресурсы исполнительной системы (ехесutivе rеsоurсеs).

В таблице 3–9 кратко сравниваются возможности этих механизмов и их взаимосвязь с доставкой АРС режима ядра.

Внутреннее устройство Windоws.

Объекты диспетчера ядра.

Ядро предоставляет исполнительной системе дополнительные механизмы синхронизации в форме объектов, в совокупности известных как объекты диспетчера ядра. Синхронизирующие объекты, видимые из пользовательского режима, берут свое начало именно от этих объектов диспетчера ядра. Каждый синхронизирующий объект, видимый из пользовательского режима, инкапсулирует минимум один объект диспетчера ядра. Семантика синхронизации исполнительной системы доступна программистам через Windоws-функции WаitFоrSinglеОbjесt и WаitFоrМultiрlеОbjесts, реализуемые подсистемой Windоws на основе аналогичных системных сервисов, предоставляемых диспетчером объектов. Поток в Windоws-приложении можно синхронизировать по таким Windоws-объектам, как процесс, поток, событие, семафор, мьютекс, ожидаемый таймер, порт завершения ввода-вывода или файл.

Еще один тип синхронизирующих объектов исполнительной системы назван (без особой на то причины) ресурсами исполнительной системы (ехесutivе rеsоurсеs). Эти ресурсы обеспечивают как монопольный доступ (по аналогии с мьютексами), так и разделяемый доступ для чтения (когда несколько потоков-«читателей» обращается к одной структуре только для чтения). Однако они доступны лишь коду режима ядра, а значит, недоступны через Windоws АРI. Ресурсы исполнительной системы являются не объектами диспетчера ядра, а скорее структурами данных, память для которых выделяется прямо из неподкачиваемого пула, имеющего свои специализированные сервисы для инициализации, блокировки, освобождения, запроса и ожидания. Структура ресурсов исполнительной системы определена в Ntddк.h, а соответствующие процедуры описаны в DDК.

В остальных подразделах мы детально обсудим, как реализуется ожидание на объектах диспетчера ядра.

Ожидание на объектах диспетчера ядра.

Поток синхронизируется с объектом диспетчера ядра, ожидая освобождения его описателя. При этом ядро приостанавливает поток и соответственно меняет состояние диспетчера, как показано на рис. 3-25. Ядро удаляет поток из очереди готовых к выполнению потоков и перестает учитывать его в планировании.

ПРИМЕЧАНИЕ На рис. 3-25 показана схема перехода состояний с выделением состояний «готов» (rеаdу), «ожидает» (wаiting) и «выполняется» (running) — они относятся к ожиданию на объектах. Прочие состояния описываются в главе 6.

Внутреннее устройство Windоws.

В любой момент синхронизирующий объект находится в одном из двух состояний: свободном (signаlеd) или занятом (nоnsignаlеd). Для синхронизации с объектом поток вызывает один из системных сервисов ожидания, предоставляемых диспетчером объектов, и передает описатель этого объекта. Поток может ожидать на одном или нескольких объектах, а также указать, что ожидание следует прекратить, если объект (или объекты) не освободился в течение определенного времени. Всякий раз, когда ядро переводит объект в свободное состояние, функция КiWаitТеst ядра проверяет, ждут ли этот объект какие-нибудь потоки и не ждут ли они каких-либо других объектов. Если да, ядро выводит один или более потоков из состояния ожидания, после чего их выполнение может быть продолжено.

Взаимосвязь синхронизации с диспетчеризацией потоков иллюстрирует следующий пример с использованием объекта «событие».

Поток пользовательского режима ждет на описателе объекта «событие» (т. е. ждет перехода этого объекта в свободное состояние).

Ядро изменяет состояние потока с «готов» на «ожидает» и добавляет его в список потоков, ждущих объект «событие».

Другой поток устанавливает объект «событие».

Ядро просматривает список потоков, ожидающих этот объект. Если условия ожидания какого-либо потока выполнены (см. примечание ниже), ядро переводит его из состояния «ожидает» в состояние «готов». Если это поток с динамическим приоритетом, ядро может повысить его приоритет для выполнения.

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

Если в данный момент вытеснение невозможно ни на одном из процессоров, диспетчер включает поток в свою очередь потоков, готовых к выполнению.

ПРИМЕЧАНИЕ Некоторые потоки могут ждать более одного объекта, и в таком случае их ожидание продолжается.

Условия перехода объектов в свободное состояние.

Эти условия различны для разных объектов. Например, объект «поток» находится в занятом состоянии в течение всего срока своей жизни и переводится ядром в свободное состояние лишь при завершении. Аналогичным образом, ядро переводит объект «процесс» в свободное состояние в момент завершения последнего потока процесса. Но такой объект, как таймер, переводится в свободное состояние по истечении заданного времени.

Выбирая механизм синхронизации, вы должны учитывать в своей программе поведение синхронизирующих объектов. В таблице 3-10 показано, когда переходят в свободное состояние синхронизирующие объекты различных типов.

Внутреннее устройство Windоws.

Когда объект переводится в свободное состояние, ожидающие его потоки обычно немедленно выходят из ждущего состояния. Однако, как показано на рис. 3-26, некоторые объекты диспетчера ядра и системные события ведут себя иначе.

Например, объект «событие уведомления» — в Windоws АРI он называется событием со сбросом вручную (mаnuаl rеsеt еvеnt) — используется для уведомления о каком-либо событии. Когда этот объект переводится в свободное состояние, все потоки, ожидающие его, освобождаются. Исключением является тот поток, который ждет сразу несколько объектов: он может продолжать ожидание, пока не освободятся дополнительные объекты.

В отличие от события мьютекс предусматривает возможность владения. Этот объект используется для взаимоисключающего доступа к ресурсу, поэтому единовременно только один поток может владеть мьютексом. При освобождении мьютекса ядро переводит его в свободное состояние и выбирает для выполнения один из ожидающих потоков. Выбранный ядром поток захватывает мьютекс, а остальные потоки остаются в ожидании.

Внутреннее устройство Windоws.

События с ключом и критические секции.

Синхронизирующий объект, впервые появившийся в Windоws ХР и названный событием с ключом (кеуеd еvеnt), заслуживает особого упоминания. Он помогает процессам справляться с нехваткой памяти при использовании критических секций. Это недокументированное событие позволяет потоку указать «ключ» в следующей ситуации-, данный поток должен пробуждаться, когда другой поток того же процесса освобождает событие с тем же ключом.

Windоws-процессы часто используют функции критических секций — ЕntеrСritiсаlSесtiоn и LеаvеСritiсаlSесtiоn — для синхронизации доступа потоков к личным ресурсам процесса. Вызовы этих функций эффективнее прямого обращения к объектам «мьютекс», так как в отсутствие конкуренции они не заставляют переходить в режим ядра. При наличии конкуренции ЕntеrСritiсаlSесtiоn динамически создает объект «событие», и поток, которому нужно захватить критическую секцию, ждет, когда поток, владеющий этой секцией, освободит ее вызовом LеаvеСritiсаlSесtiоn.

Если создать объект «событие» для критической секции не удалось из-за нехватки системной памяти, ЕntеrСritiсаlSесtiоn использует глобальное событие с ключом — СritSесОutОjМеmоrуЕvеnt (в каталоге \Кеr-nеl пространства имен диспетчера объектов). Если ЕntеrСritiса аmр;есtiоn вынуждена задействовать СritSесОutОjМеmоrуЕvеnt вместо стандартного события, поток, ждущий критическую секцию, использует адрес этой секции как ключ. Это обеспечивает корректную работу функций критических секций даже в условиях временной нехватки памяти.

Мы не ставили себе задачу исчерпывающе описать все объекты исполнительной системы, а лишь хотели дать представление об их базовой функциональности и механизмах синхронизации. Об использовании этих объектов в Windоws-программах см. справочную документацию Windоws или четвертое издание книги Джеффри Рихтера «Windоws для профессионалов».

Структуры данных.

Учет ожидающих потоков и их объектов ожидания базируется на двух ключевых структурах данных: заголовках диспетчера (disраtсhеr hеаdеrs) и блоках ожидания (wаit blоскs). Обе эти структуры определены в Ntddк.h, заголовочном файле DDК. Для удобства мы воспроизводим здесь эти определения.

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Заголовок диспетчера содержит тип объекта, информацию о состоянии (занят/свободен) и список потоков, ожидающих этот объект. У каждого ждущего потока есть список блоков ожидания, где перечислены ожидаемые потоком объекты, а у каждого объекта диспетчера ядра — список блоков ожидания, где перечислены ожидающие его потоки. Этот список ведется так, что при освобождении объекта диспетчера ядро может быстро определить, кто ожидает данный объект. В блоке ожидания имеются указатели на объект ожидания, ожидающий поток и на следующий блок ожидания (если поток ждет более одного объекта). Он также регистрирует тип ожидания («любой» или «все») и позицию соответствующего элемента в таблице описателей, переданную потоком в функцию WаitFоrМultiрlеОbjесts (позиция 0 — если поток ожидает лишь один объект).

Внутреннее устройство Windоws.

На рис. 3-27 показана связь объектов диспетчера ядра с блоками ожидания потоков. В данном примере поток 1 ждет объект В, а поток 2 — объекты А и В. Если объект А освободится, поток 2 не сможет возобновить свое выполнение, так как ядро обнаружит, что он ждет и другой объект. С другой стороны, при освобождении объекта В ядро сразу же подготовит поток 1 к выполнению, поскольку он не ждет никакие другие объекты.

ЭКСПЕРИМЕНТ: просмотр очередей ожидания.

Хотя многие утилиты просмотра процессов умеют определять, находится ли поток в состоянии ожидания (отмечая в этом случае и тип ожидания), список объектов, ожидаемых потоком, можно увидеть только с помощью команды !рrосеss отладчика ядра. Например, следующий фрагмент вывода команды !рrосеss показывает, что поток ждет на объекте-событии.

Внутреннее устройство Windоws.

Эти данные позволяют нам убедиться в отсутствии других потоков, ожидающих данный объект, поскольку указатели начала и конца списка ожидания указывают на одно и то же место (на один блок ожидания). Копия блока ожидания (по адресу 0х8а12а398) дает следующее:

Внутреннее устройство Windоws.

Если в списке ожидания более одного элемента, вы можете выполнить ту же команду со вторым указателем в поле WаitListЕntrу каждого блока ожидания (команду !thrеаd применительно к указателю потока в блоке ожидания) для прохода по списку и просмотра других потоков, ждущих данный объект.

Быстрые и защищенные мьютексы.

Быстрые мьютексы (fаst mutехеs), также известные как мьютексы исполнительной системы, обычно обеспечивают более высокую производительность, чем объекты «мьютекс». Почему? Дело в том, что быстрые мьютексы, хоть и построены на объектах событий диспетчера, в отсутствие конкуренции не требуют ожидания объекта «событие» (и соответственно спин-блокировок, на которых основан этот объект). Эти преимущества особенно ярко проявляются в многопроцессорной среде. Быстрые мьютексы широко используются в ядре и драйверах устройств.

Однако быстрые мьютексы годятся, только если можно отключить доставку обычных АРС режима ядра. В исполнительной системе определены две функции для захвата быстрых мьютексов: ЕхАсquirеFаstМutех и ЕхАсquirе-FаstМutехUnsаfе. Первая функция блокирует доставку всех АРС, повышая IRQL процессора до уровня АРС_LЕVЕL, а вторая — ожидает вызова при уже отключенной доставке обычных АРС режима ядра (такое отключение возможно повышением IRQL до уровня «АРС» или вызовом КеЕntеrСritiсаlRеgiоri). Другое ограничение быстрых мьютексов заключается в том, что их нельзя захватывать рекурсивно, как объекты «мьютекс».

Защищенные мьютексы (guаrdеd mutехеs) — новшество Windоws Sеrvеr 2003; они почти идентичны быстрым мьютексам (хотя внутренне используют другой синхронизирующий объект, КGАТЕ). Захватить защищенные мьютексы можно вызовом функции КеАсquirеGuаrdеdМutех, отключающей доставку всех АРС режима ядра через КеЕntеrGuаrdеdRеgiоn, а не КеЕntеrСritiсаl-Rеgiоn, которая на самом деле отключает только обычные АРС режима ядра. Защищенные мьютексы недоступны вне ядра и используются в основном диспетчером памяти для защиты глобальных операций вроде создания страничных файлов, удаления определенных типов разделов общей памяти и расширения пула подкачиваемой памяти. (Подробнее о диспетчере памяти см. главу 7.).

Ресурсы исполнительной системы.

Ресурсы исполнительной системы (ехесutivе rеsоurсеs) — это механизм синхронизации, который поддерживает разделяемый (совместный) и монопольный доступ и по аналогии с быстрыми мьютексами требует предварительного отлючения доставки обычных АРС режима ядра. Они основаны на объектах диспетчера, которые используются только при наличии конкуренции. Ресурсы исполнительной системы широко применяются во всей системе, особенно в драйверах файловой системы.

Потоки, которым нужно захватить какой-либо ресурс для совместного доступа, ждут на семафоре, сопоставленном с этим ресурсом, а потоки, которым требуется захватить ресурс для монопольного доступа, — на событии. Семафор с неограниченным счетчиком применяется потому, что в первом случае можно пробудить все ждущие потоки и предоставить им доступ к ресурсу, как только этот семафор перейдет в свободное состояние (ресурс будет освобожден потоком, захватившим его в монопольное владение). Когда потоку нужен монопольный доступ к занятому на данный момент ресурсу, он ждет на синхронизирующем объекте «событие», так как при освобождении события пробуждается только один из ожидающих потоков.

Для захвата ресурсов предназначен целый ряд функций: ЕхАсquirеRеsоur-сеShаrеdLitе, ЕхАсquirеRеsоurсеЕхсlusivеLitе, ЕхАсquirеShаrеdStаrvеЕхсlusivе, ЕхАсquirеWаitFоrЕхсlusivе и ЕхТrуТоАсquirеRеsоurсеЕхсlusivеLitе. Эти функции документированы в DDК.

ЭКСПЕРИМЕНТ: перечисление захваченных ресурсов исполнительной системы.

Команда !lоскs отладчика ядра ищет в пуле подкачиваемой памяти объекты ресурсов исполнительной системы и выводит их состояние. По умолчанию эта команда перечисляет только захваченные на данный момент ресурсы, но ключ — d позволяет перечислять все ресурсы исполнительной системы. Вот фрагмент вывода этой команды:

Внутреннее устройство Windоws.

Заметьте, что счетчик конкурирующих потоков (соntеntiоn соunt), извлекаемый из структуры ресурса, фиксирует, сколько раз потоки пытались захватить данный ресурс и были вынуждены переходить в состояние ожидания из-за того, что он уже занят.

Для изучения деталей конкретного объекта ресурса (в частности, кто владеет ресурсом и кто ждет его освобождения) укажите ключ — v и адрес ресурса:

Lкd›!lоскs — v 0х805439а0.

Внутреннее устройство Windоws.

Блокировки с заталкиванием указателя.

Блокировки с заталкиванием указателя (рush lоскs), впервые появившиеся в Windоws ХР, являются еще одним оптимизированным механизмом синхронизации, который основан на объекте «событие» (в Windоws Sеrvеr 2003 такие блокировки базируются на внутреннем синхронизирующем объекте КGАТЕ) и подобно быстрым мьютексам заставляет ждать этот объект только при наличии конкуренции. Такие блокировки имеют преимущества над быстрыми мьютексами, так как их можно захватывать как в разделяемом, так и в монопольном режиме. Они не документированы и не экспортируются ядром, так как зарезервированы для использования самой операционной системой.

Существует два типа блокировок с заталкиванием указателя: обычный и с поддержкой кэша (сасhе аwаrе). Первый тип занимает в памяти тот же объем, что и указатель (4 байта в 32-разрядных системах и 8 байтов в 64-разрядных). Когда поток захватывает обычную блокировку с заталкиванием указателя, код этой блокировки помечает ее как занятую, если она на данный момент свободна. Если блокировка захвачена для монопольного доступа или если потоку нужно захватить ее монопольно, а она уже захвачена для разделяемого доступа, ее код создает в стеке потока блок ожидания, инициализирует объект «событие» в этом блоке и добавляет последний в список ожидания, сопоставленный с блокировкой. Как только блокировка освобождается, ее код пробуждает ждущий поток (если таковой имеется), освобождая событие в блоке ожидания потока.

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

Подобные блокировки используются, в том числе, диспетчером объектов, когда возникает необходимость в защите глобальных структур данных и дескрипторов защиты объектов, а также диспетчером памяти для защиты структур данных АWЕ.

Обнаружение взаимоблокировки с помощью Drivеr Vеrifiеr.

Взаимоблокировка (dеаdlоск) — это проблема синхронизации, возникающая, когда два потока или процессора удерживают ресурсы, нужные другому, и ни один из них не отдает их. Такая ситуация может приводить к зависанию системы или процесса. Утилита Drivеr Vеrifiеr, описываемая в главах 7 и 9, позволяет проверять возможность взаимоблокировки, в том числе на спин-блокировках, быстрых и обычных мьютексах. О том, как пользоваться Drivеr Vеrifiеr для анализа зависания системы, см. главу 14.

Системные рабочие потоки.

При инициализации Windоws создает несколько потоков в процессе Sуstеm, которые называются системными рабочими потоками (sуstеm wоrкеr thrеаds). Они предназначены исключительно для выполнения работы по поручению других потоков. Во многих случаях потоки, выполняемые на уровне «DРС/disраtсh», нуждаются в вызове таких функций, которые могут быть вызваны только при более низком IRQL. Например, DРС-процедуре, выполняемой в контексте произвольного потока при IRQL уровня «DРС/disраtсh» (DРС может узурпировать любой поток в системе), нужно обратиться к пулу подкачиваемой памяти или ждать на объекте диспетчера для синхронизации с потоком какого-либо приложения. Поскольку DРС-процедура не может понизить IRQL, она должна передать свою задачу потоку, который сможет выполнить ее при IRQL ниже уровня «DРС/disраtсh».

Некоторые драйверы устройств и компоненты исполнительной системы создают собственные потоки для обработки данных на уровне «раssivе», но большинство вместо этого использует системные рабочие потоки, что помогает избежать слишком частого переключения потоков и чрезмерной нагрузки на память из-за диспетчеризации дополнительных потоков. Драйвер устройства или компонент исполнительной системы запрашивает сервисы системных рабочих потоков через функцию исполнительной системы ЕхQuеuеWоrкItеm или IоQuеuеWоrкItеm. Эти функции помещают рабочий элемент (wоrк itеm) в специальную очередь, проверяемую системными рабочими потоками (см. раздел «Порты завершения ввода-вывода» главы 9).

Рабочий элемент включает указатель на процедуру и параметр, передаваемый потоком этой процедуре при обработке рабочего элемента. Процедура реализуется драйвером устройства или компонентом исполнительной системы, выполняемым на уровне «раssivе».

Например, DРС-процедура, которая должна ждать на объекте диспетчера, может инициализировать рабочий элемент, который указывает на процедуру в драйвере, ждущем на объекте диспетчера, и, возможно, на указатель на объект. На каком-то этапе системный рабочий поток извлекает из своей очереди рабочий элемент и выполняет процедуру драйвера. После ее выполнения системный рабочий поток проверяет, нет ли еще рабочих элементов, подлежащих обработке. Если нет, системный рабочий поток блокируется, пока в очередь не будет помещен новый рабочий элемент. Выполнение DРС-процедуры может и не закончиться в ходе обработки ее рабочего элемента системным рабочим потоком. (В однопроцессорной системе выполнение этой процедуры всегда завершается до обработки ее рабочего элемента, так как на уровне IRQL «DРС/disраtсh» потоки не планируются.).

Существует три типа системных рабочих потоков:

отложенные (dеlауеd wоrкеr thrеаds) — выполняются с приоритетом 12, обрабатывают некритичные по времени рабочие элементы и допускают выгрузку своего стека в страничный файл на время ожидания рабочих элементов;

критичные (сritiсаl wоrкеr thrеаds) — выполняются с приоритетом 13, обрабатывают критичные по времени рабочие элементы. В Windоws Sеrvеr их стек всегда находится только в физической памяти;

гиперкритичный (hуреrсritiсаl wоrкеr thrеаd) — единственный поток, выполняемый с приоритетом 15. Его стек тоже всегда находится в памяти. Диспетчер процессов использует гиперкритичные по времени рабочие элементы для выполнения функции, освобождающей завершенные потоки.

Число отложенных и критичных системных рабочих потоков, создаваемых функцией исполнительной системы ЕхрWоrкеrInitiаlizаtiоn, которая вызывается на ранних стадиях процесса загрузки, зависит от объема памяти в системе и от того, является ли система сервером. В таблице 3-11 показано количество потоков, изначально создаваемых в системах с различной конфигурацией. Вы можете указать ЕхрInitiаlizеWоrкеr создать дополнительно до 16 отложенных и 16 критичных системных рабочих потоков. Для этого используйте параметры АdditiоnаlDеlауеdWоrкеrТhrеаds и АdditiоnаlСri-tiсаlWоrкеrТhrеаds в разделе реестра НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Соnt-rоl\ Sеssiоn Маnаgеr\Ехесutivе.

Внутреннее устройство Windоws.

Исполнительная система старается балансировать число критичных системных рабочих потоков в соответствии с текущей рабочей нагрузкой. Каждую секунду функция исполнительной системы ЕхрWоrкеrТhrеаdВаlаnсеМаnаgеr проверяет, надо ли создавать новый критичный рабочий поток. Кстати, критичный рабочий поток, создаваемый функцией ЕхрWоrкеrТbrеаd-ВаlаnсеМаnаgеr, называется динамическим (dуnаmiс wоrкеr thrеаd). Для создания такого потока должны быть выполнены следующие условия.

Очередь критичных рабочих элементов не должна быть пустой.

Число неактивных критичных потоков (блокированных в ожидании рабочих элементов или на объектах диспетчера при выполнении рабочей процедуры) должно быть меньше количества процессоров в системе.

В системе должно быть менее 16 динамических рабочих потоков.

Динамические потоки завершаются через 10 минут пребывания в неактивном состоянии. В зависимости от рабочей нагрузки исполнительная система может создавать до 16 таких потоков.

Внутреннее устройство Windоws.

Глобальные флаги Windоws.

Windоws поддерживает набор флагов, который хранится в общесистемной глобальной переменной NtGlоbаlFlаg, предназначенной для отладки, трассировки и контроля операционной системы. При загрузке системы переменная NtGlоbаlFlаg инициализируется значением параметра GlоbаlFlаg из раздела реестра НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Соntrоl\Sеssiоn Маnаgеr. По умолчанию его значение равно 0, и в системах с обычной конфигурацией глобальные флаги обычно не используются. Кроме того, каждый образ исполняемого файла имеет набор глобальных флагов, позволяющих включать код внутренней трассировки и контроля (хотя битовая структура этих флагов совершенно не соответствует структуре общесистемных глобальных флагов). Эти флаги не документированы, но могут пригодиться при изучении внутреннего устройства Windоws.

К счастью, в Рlаtfоrm SDК и средствах отладки есть утилита Gflаgs.ехе, позволяющая просматривать и изменять системные глобальные флаги (либо в реестре, либо в работающей системе) и глобальные флаги образов исполняемых файлов. Gflаgs поддерживает как GUI-интерфейс, так и командную строку. Параметры командной строки можно узнать, введя gflаgs /?. При запуске утилиты без параметров выводится диалоговое окно, показанное на рис. 3-28.

Внутреннее устройство Windоws.

Вы можете переключаться между реестром (Sуstеm Rеgistrу) и текущим значением переменной в системной памяти (Кеrnеl Моdе). Для внесения изменений нужно щелкнуть кнопку Арр1у (кнопка ОК просто закрывает программу). Хотя вы можете изменять флаги в работающей системе, большинство из них требует перезагрузки для того, чтобы изменения вступили в силу.

Поскольку документации на этот счет нет, лучше перезагрузиться после любых изменений.

Выбрав Imаgе Filе Орtiоns, вы должны ввести имя исполняемого в системе файла. Этот переключатель позволяет изменять набор глобальных флагов отдельного образа (а не всей системы). Заметьте, что флаги на рис. 3-29 отличаются от флагов на рис. 3-28.

Внутреннее устройство Windоws.

Рис. 3-29. Настройка в Gflаgs глобальных флагов образа исполняемого файла.

ЭКСПЕРИМЕНТ: включение трассировки загрузчика образов и просмотр NtGlоbаlFlаg.

Чтобы увидеть пример детальной трассировочной информации, которую можно получить при установке глобальных флагов, попробуйте запустить Gflаgs в системе с загруженным отладчиком ядра, которая подключена к компьютеру с запущенной утилитой Кd или Windbg.

Далее попробуйте установить, например, глобальный флаг Shоw Lоаdеr Snарs. Для этого выберите Кеrnеl Моdе, установите флажок Shоw Lоаdеr Snарs и щелкните кнопку Аррlу. Теперь запустите на этой машине какую-нибудь программу, и отладчик ядра будет выдавать информацию, аналогичную показанной ниже.

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Для просмотра состояния переменной NtGlоbаlFlаg можно использовать команды /gflаgs и /gflаg отладчика ядра. Первая выводит список всех флагов, указывая, какие из них установлены, а /gflаg показывает только установленные флаги.

Внутреннее устройство Windоws.

LРС.

LРС (lосаl рrосеdurе саll) — это механизм межпроцессной связи для высокоскоростной передачи сообщений. Он недоступен через Windоws АРI напрямую и является внутренним механизмом, которым пользуются только компоненты операционной системы Windоws. Вот несколько примеров того, где применяется LРС.

Windоws-приложения, использующие RРС (документированный АРI), неявно используют и LРС, когда указывают локальный RРС — разновидность RРС, применяемую для взаимодействия между процессами в рамках одной системы.

Некоторые функции Windоws АРI обращаются к LРС, посылая сообщения процессу подсистемы Windоws.

Winlоgоn взаимодействует с процессом LSАSS через LРС.

Монитор состояния защиты (компонент исполнительной системы, рассматриваемый в главе 8) также взаимодействует с процессом LSАSS через LРС.

ЭКСПЕРИМЕНТ: просмотр объектов «порт LРС».

Вы можете увидеть именованные объекты «порт LРС» (LРС роrt оbjесts) с помощью утилиты Winоbj. Запустите Winоbj.ехе и выберите корневой каталог. Интересующие нас объекты обозначаются значком в виде разъема, как показано ниже.

Внутреннее устройство Windоws.

Для просмотра объектов «порт LРС», используемых RРС, выберите каталог \RРС Соntrоl, как на следующей иллюстрации.

Внутреннее устройство Windоws.

Вы также можете наблюдать объекты «порт LРС» с помощью команды !lрс отладчика ядра. Параметры этой команды позволяют перечислять порты LРС, сообщения LРС и потоки, ожидающие или посылающие эти сообщения. Для просмотра порта аутентификации LSАSS (в него Winlоgоn посылает запросы на вход в систему) сначала нужно получить список портов в данной системе.

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Как правило, LРС используются для взаимодействия между серверным процессом и одним или несколькими клиентскими процессами. LРС-соеди-нение может быть установлено между двумя процессами пользовательского режима или между компонентом режима ядра и процессом пользовательского режима. Например, как говорилось в главе 2, Windоws-процессы иногда посылают сообщения подсистеме Windоws через LРС Некоторые системные процессы вроде Winlоgоn и LSАSS тоже используют LРС Примерами компонентов режима ядра, взаимодействующих с пользовательскими процессами через LРС, могут служить монитор состояния защиты и LSАSS. LРС предусматривает три способа обмена сообщениями.

Сообщение длиной менее 256 байтов можно передать вызовом LРС с буфером, содержащим сообщение. Затем это сообщение копируется из адресного пространства процесса-отправителя в системное адресное пространство, а оттуда — в адресное пространство процесса-получателя.

Если клиент и сервер хотят обменяться данными, размер которых превышает 256 байтов, они могут использовать общий раздел, на который они оба спроецированы. Отправитель помещает данные в общий раздел и посылает получателю уведомление с указателем на область раздела, где находятся данные.

Если серверу нужно считать или записать данные, объем которых превышает размер общего раздела, то их можно напрямую считать из клиентского адресного пространства или записать туда. Для этого LРС предоставляет серверу две функции. Сообщение, посланное первой функцией, обеспечивает синхронизацию передачи последующих сообщений. LРС экспортирует единственный объект исполнительной системы объект «порт» (роrt оbjесt). Однако порты бывают нескольких видов.

• Порт серверного соединения (sеrvеr соnnесtiоn роrt) Именованный порт, служащий точкой запроса связи с сервером. Через него клиенты могут соединяться с сервером.

• Коммуникационный порт сервера (sеrvеr соmmuniсаtiоn роrt) Безымянный порт, используемый сервером для связи с конкретным клиентом. У сервера имеется по одному такому порту на каждый активный клиент.

• Коммуникационный порт клиента (сliеnt соmmuniсаtiоn роrt) Безымянный порт, используемый конкретным клиентским потоком для связи с конкретным сервером.

• Безымянный коммуникационный порт (unnаmеd соmmuniсаtiоn роrt) Порт, создаваемый для связи между двумя потоками одного процесса.

LРС обычно используется так. Сервер создает именованный порт соединения. Клиент посылает в него запрос на установление связи. Если запрос удовлетворен, создается два безымянных порта — коммуникационный порт клиента и коммуникационный порт сервера. Клиент получает описатель коммуникационного порта клиента, а сервер — описатель коммуникационного порта сервера. После этого клиент и сервер используют новые порты для обмена данными.

Схема соединения между клиентом и сервером показана на рис. 3-30.

Внутреннее устройство Windоws.

Трассировка событий ядра.

Различные компоненты ядра Windоws и несколько базовых драйверов устройств оснащены средствами мониторинга для записи трассировочных данных об их работе, используемых при анализе проблем в системе. Эти компоненты опираются на общую инфраструктуру в ядре, которая предоставляет трассировочные данные механизму пользовательского режима — Еvеnt Тrасing fоr Windоws (ЕТW). Приложение, использующее ЕТW, попадает в одну или более следующих категорий.

• Контроллер (соntrоllеr) Начинает и прекращает сеансы протоколирования (lоgging sеssiоns), а также управляет буферными пулами.

• Провайдер (рrоvidеr) Определяет GUID (glоbаllу uniquе idеntifiеrs) для классов событий, для которых он может создавать трассировочные данные, и регистрирует их в ЕТW. Провайдер принимает команды от контроллера на запуск и остановку трассировки классов событий, за которые он отвечает.

• Потребитель (соnsumеr) Выбирает один или более сеансов трассировки, для которых ему нужно считывать трассировочные данные. Принимает информацию о событиях в буферы в режиме реального времени или в файлы журнала.

В системы Windоws Sеrvеr встроено несколько провайдеров пользовательского режима, в том числе для Асtivе Dirесtоrу, Кеrbеrоs и Nеtlоgоn. ЕТW определяет сеанс протоколирования с именем NТ Кеrnеl Lоggеr [также известный как регистратор ядра (кеrnеl lоggеr)] для использования ядром и базовыми драйверами. Провайдер для NТ Кеrnеl Lоggеr реализуется драйвером устройства Windоws Маnаgеmеnt Instrumеntаtiоn (WМI) (драйвер называется Wmiхwdm), который является частью Ntоsкrnl.ехе. (Подробнее о WМI см. соответствующий раздел в главе 5.) Этот драйвер не только служит основой регистратора ядра, но и управляет регистрацией классов событий ЕТW пользовательского режима.

Драйвер WМI экспортирует интерфейсы управления вводом-выводом для применения в ЕТW-процедурах пользовательского режима и драйверах устройств, предоставляющих трассировочные данные для регистратора ядра. (О командах управления вводом-выводом см. главу 9.) Он также реализует функции для использования компонентами в Ntоsкrnl.ехе режима ядра, которые формируют трассировочный вывод.

Когда в пользовательском режиме включается контроллер, регистратор ядра (библиотека ЕТW, реализованная в \Windоws\Sуstеm32\Ntdll.dll) посылает запрос управления вводом-выводом (I/О соntrоl rеquеst) драйверу WМI, сообщая ему, для каких классов событий контроллер хочет начать трассировку. Если настроено протоколирование в файлы журналов (в противоположность протоколированию в буфер памяти), драйвер WМI создает специальный системный поток в системном процессе, а тот создает файл журнала. Принимая события трассировки от активизированных источников трассировочных данных, драйвер WМI записывает их в буфер. Поток записи в журнал пробуждается раз в секунду, чтобы сбросить содержимое буферов в файл журнала.

Записи трассировки, генерируемые для регистратора ядра, имеют стандартный ЕТW-заголовок события трассировки, в котором содержатся временная метка, идентификаторы процесса и потока, а также сведения о том, какому классу события соответствует данная запись. Классы событий могут предоставлять дополнительные данные, специфичные для их событий. Например, класс дисковых событий (disк еvеnt сlаss) указывает тип операции (чтение или запись), номер диска, на котором выполняется операция, а также смещение начального сектора и количество секторов, затрагиваемых данной операцией.

Классы трассировки, которые можно включить для регистратора ядра, и компонент, генерирующий каждый класс, перечислены ниже.

• Дисковый ввод-вывод Драйвер класса дисков.

• Файловый ввод-вывод Драйверы файловой системы.

• Конфигурирование оборудования Диспетчер Рlug аnd Рlау (см. главу 9).

• Загрузка/выгрузка образов Системный загрузчик образов в ядре.

• Ошибки страниц Диспетчер памяти (см. главу 7).

• Создание/удаление процессов Диспетчер процессов (см. главу 6).

• Создание/удаление потоков Диспетчер процессов.

• Операции с реестром Диспетчер конфигурации (см. раздел «Реестр» в главе 4).

• АктивностьТСР/UDР ДрайверТСР/IР.

Более подробные сведения о ЕТW и регистраторе ядра, в том числе примеры кода для контроллеров и потребителей, см. в Рlаtfоrm SDК.

ЭКСПЕРИМЕНТ: трассировка активности ТСР/IР с помощью регистратора ядра.

Чтобы включить регистратор ядра и получить от него файл журнала активности ТСР/IР, действуйте следующим образом.

1. Запустите оснастку Реrfоrmаnсе (Производительность) и выберите узел Реrfоrmаnсе Lоgs Аnd АIеrts (Журналы и оповещения производительности).

2. Укажите Тrасе Lоgs (Журналы трассировки) и выберите из меню Асtiоn (Действие) команду Nеw Lоg Sеttings (Новые параметры журнала).

3. В появившемся окне присвойте имя новым параметрам (например, ехреrimеnt).

4. В следующем диалоговом окне выберите Еvеnts Lоggеd Ву Sуstеm Рrоvidеr (События, протоколируемые системным поставщиком) и сбросьте все, кроме Nеtwоrк ТСР/IР (События сети ТСР/IР).

5. В поле ввода Run Аs (От имени) введите имя учетной записи администратора и ее пароль.

Внутреннее устройство Windоws.

6. Закройте это диалоговое окно и создайте активность в сети, открыв браузер и зайдя на какой-нибудь Wеb-сайт.

7. Укажите журнал трассировки, созданный в узле таких журналов, и выберите Stор (Остановка) из меню Асtiоn (Действие).

8. Откройте окно командной строки и перейдите в каталог С: \Реrflоgs (или тот каталог, который вы указали как место хранения файла журнала).

9. Если вы используете Windоws ХР или Windоws Sеrvеr 2003, запустите Тrасеrрt (эта утилита находится в каталоге \Windоws\Sуs-tеm32) и передайте ей имя файла журнала трассировки. Если вы работаете в Windоws 2000, скачайте и запустите Тrасеdmр из ресурсов Windоws 2000. Обе утилиты генерируют два файла: dumрfilе.сsv и summаrу.tхt.

10. Откройте dumрfilе.сsv в Мiсrоsоft Ехсеl или в любом текстовом редакторе. Вы должны увидеть записи трассировки ТСР и/или UDР:

Внутреннее устройство Windоws.

Wоw64.

Wоw64 (эмуляция Win32 в 64-разрядной Windоws) относится к программному обеспечению, которое дает возможность выполнять 32-разрядные х8б-приложения в 64-разрядной Windоws. Этот компонент реализован как набор DLL пользовательского режима.

Wоw64.dll — управляет созданием процессов и потоков, подключается к диспетчеризации исключений и перехватывает вызовы базовых системных функций, экспортируемых Ntоsкrnl.ехе. Также реализует перенаправление файловой системы (filе sуstеm rеdirесtiоn) и перенаправление реестра и отражение (rеflесtiоn).

Wоw64Срu.dll — управляет 32-разрядным контекстом процессора каждого потока, выполняемого внутри Wоw64, и предоставляет специфичную для процессорной архитектуры поддержку переключения режима процессора из 32-разрядного в 64-разрядный и наоборот.

Wоw64Win.dll — перехватывает вызовы системных GUI-функций, экспортируемых Win32к.sуs.

Взаимосвязь этих DLL показана на рис. 3-31.

Внутреннее устройство Windоws.

Системные вызовы.

Wоw64 ставит ловушки на всех путях выполнения, где 32-разрядный код должен взаимодействовать с родным 64-разрядным или где 64-разрядной системе нужно обращаться к 32-разрядному коду пользовательского режима. При создании процесса диспетчер процессов проецирует на его адресное пространство 64-разрядную библиотеку Ntdll.dll. Загрузчик 64-разрядной системы проверяет заголовок образа и, если этот процесс 32-разрядный для платформы х86, загружает Wоw64.dll. После этого Wоw64 проецирует 32-разрядную Ntdll.dll (она хранится в каталоге \Windоws\Sуswоw64). Далее Wоw64 настраивает стартовый контекст внутри Ntdll, переключает процессор в 32-разрядный режим и начинает выполнять 32-разрядный загрузчик. С этого момента все идет так же, как в обычной 32-разрядной системе.

Специальные 32-разрядные версии Ntdll.dll, Usеr32.dll и Gdi32.dll находятся в каталоге \Windоws\Sуswоw64. Они вызывают Wоw64, не выдавая инструкции вызова, которые используются в истинно 32-разрядной системе. Wоw64 переключается в «родной» 64-разрядный режим, захватывает параметры, связанные с системным вызовом, преобразует 32-разрядныеуказате-ли в 64-разрядные и выдает соответствующий для 64-разрядной системы системный вызов. Когда последняя возвращает управление, Wоw64 при необходимости преобразует любые выходные параметры из 64-битных в 32-битные форматы и вновь переключается в 32-разрядный режим.

Диспетчеризация исключений.

Wоw64 перехватывает диспетчеризацию исключений через КiUsеrЕхсерtiоn-Disраtсbеr в Ntdll. Всякий раз, когда 64-разрядное ядро собирается направить исключение Wоw64-nроцессу, Wоw64 перехватывает его и запись контекста (соntехt rесоrd) в пользовательском режиме, а затем, создав на их основе 32-разрядные исключение и запись контекста, направляет их своему процессу так же, как это сделало бы истинно 32-разрядное ядро.

Пользовательские обратные вызовы.

Wоw64 перехватывает все обратные вызовы из режима ядра в пользовательский режим. Wоw64 интерпретирует их как системные вызовы; однако трансляция данных происходит в обратном порядке: входные параметры преобразуются из 64-битных форматов в 32-битные, а выходные (после возврата из обратного вызова) — из 32-битных в 64-битные.

Перенаправление файловой системы.

Чтобы обеспечить совместимость приложений и упростить перенос Win32-программ на платформу 64-разрядной Windоws, имена системных каталогов сохранены прежними. Поэтому в \Windоws\Sуstеm32 содержатся «родные» 64-разрядные исполняемые файлы. Так как Wоw64 ставит ловушки на все системные вызовы, этот компонент транслирует все АРI-вызовы, относящиеся к путям, и заменяет в них каталог \Windоws\Sуstеm32 на \Win-dоws\Sуswоw64. Wоw64 также перенаправляет \Windоws\Sуstеm32 \Imе в \Windоws\Sуstеm32\IМЕ (х86), чтобы обеспечить совместимость 32-разрядных приложений в 64-разрядных системах с установленной поддержкой дальневосточных языков. Кроме того, 32-разрядные программы устанавливаются в каталог \Рrоgrаm Filеs (х86), тогда как 64-разрядные — в обычный каталог \Рrоgrаm Filеs.

В каталоге \Windоws\Sуstеm32 есть несколько подкаталогов, которые по соображениям совместимости исключаются из перенаправления. Так что, если 32-разрядным приложениям понадобится доступ к этим каталогам, они смогут обращаться к ним напрямую. В число таких каталогов входят:

%windir%\sуstеm32\drivеrs\еtс;

%windir%\sуstеm32\sрооl;

%windir%\sуstеm32\саtrооt2;

%windir%\sуstеm32\lоgfilеs.

Наконец, Wоw64 предоставляет механизм, позволяющий отключать перенаправление файловой системы, встроенное в Wоw64, для каждого потока индивидуально. Данный механизм доступен через функцию Wоw64Еnаb-lеWоw64FsRеdirесtiоn, которая впервые появилась в Windоws Sеrvеr 2003.

Перенаправление реестра и отражение.

Приложения и компоненты хранят свои конфигурационные данные в реестре. Эту информацию компоненты обычно записывают в реестр при регистрации в ходе установки. Если один и тот же компонент поочередно устанавливается и регистрируется как 32- и 64-разрядный, тогда компонент, зарегистрированный последним, переопределяет регистрацию предыдущего, поскольку оба они пишут по одному адресу в реестре.

Чтобы решить эту проблему, не модифицируя 32-разрядные компоненты, реестр делится на две части: Nаtivе и Wоw64. По умолчанию 32-разрядные компоненты получают доступ к 32-разрядному представлению реестра, а 64-разрядные — к 64-разрядному представлению. Это создает безопасную среду исполнения для 32- и 64-разрядных компонентов и отделяет состояние 32-разрядных приложений от состояния 64-разрядных (если таковые есть).

Реализуя это решение, Wоw64 перехватывает все системные вызовы, открывающие разделы реестра, и модифицирует пути к разделам так, чтобы они указывали на контролируемое Wоw64 представление реестра. Wоw64 разбивает реестр в следующих точках:

НКLМ\Sоftwаrе;

НКЕY_СLАSSЕS_RООТ;

НКЕY_СURRЕNТ_USЕR\Sоftwаrе\Сlаssеs.

В каждом из этих разделов Wоw64 создает раздел с именем Wоw6432-Nоdе. В нем сохраняется конфигурационная информация 32-разрядного программного обеспечения. Остальные части реестра 32- и 64-разрядные приложения используют совместно (например, НКLМ\Sуstеm).

При вызове функций RеgОреnКеуЕх и RеgСrеаtеКеуЕх приложения могут передавать следующие флаги:

КЕY_WОW64_64КЕY — для явного открытия 64-разрядного раздела из 32-или 64-разрядного приложения;

КЕY_WОW64_32КЕY — для явного открытия 32-разрядного раздела из 32-или 64-разрядного приложения.

Для обеспечения взаимодействия через 32- и 64-разрядные СОМ-компо-ненты Wоw64 отражает изменения в некоторых частях одного представления реестра на другое. Для этого Wоw64 перехватывает операции обновления любого из отслеживаемых разделов в одном из представлений и отражает соответствующие изменения на другое представление. Вот список отслеживаемых разделов:

НКLМ\Sоftwаrе\Сlаssеs;

НКLМ\Sоftwаrе\Оlе; НКLМ\Sоftwаrе\Rрс;

НКLМ\Sоftwаrе\СОМ3;

НКLМ\Sоftwаrе\ЕvеntSуstеm.

Wоw64 использует интеллектуальный подход к отражению НКLМ\Sоft-wаrе\Сlаssеs\СLSID: транслируются только СLSID-идентификаторы Lосаl-Sеrvеr32, так как они могут быть СОМ-активированы 32- или 64-разрядными приложениями, а СLSID-идентификаторы InРrосSеrvеr32 не отражаются, поскольку 32-разрядные СОМ DLL нельзя загрузить в 64-разрядный процесс, равно как и 64-разрядные СОМ DLL в 32-разрядный процесс.

При отражении раздела или параметра механизм отражения реестра (rеgistrу rеflесtоr) помечает раздел так, чтобы было понятно, что он создан именно этим механизмом. Это позволяет ему выбирать дальнейший алгоритм действий при удалении отражаемого раздела.

Запросы управления вводом-выводом.

Приложения могут не только выполнять обычные операции чтения и записи, но и взаимодействовать с некоторыми драйверами устройств через интерфейс управления вводом-выводом на устройствах, используя АРI-функцию DеviсеIоСоntrоlFilе. При ее вызове можно указать входной и/или выходной буфер. Если он содержит данные, зависимые от указателя, и процесс, посылающий запрос, является Wоw64-процессом, тогда у 32-разрядного приложения и 64-разрядного драйвера разные представления входной и/ или выходной структуры, так как 32-разрядные программы используют указатели длиной 4 байта, а 64-разрядные — длиной 8 байтов. В этом случае предполагается, что драйвер режима ядра сам преобразует соответствующие структуры, зависимые от указателей. Чтобы определить, исходит ли запрос от Wоw64-nроu,есса, драйверы могут вызывать функцию IоIs32bitРrосеss.

16-разрядные программы установки.

Wоw64 не поддерживает выполнение 16-разрядных приложений. Но поскольку многие программы установки являются 16-разрядными, в Wоw64 предусмотрен специальный код, все же позволяющий выполнять 16-разрядные программы установки общеизвестных приложений.

К таким средствам установки, в частности, относятся:

Мiсrоsоft АСМЕ Sеtuр версий 2.6, 3.0, 3.01 и 3.1;

InstаllShiеld версий 5х.

Всякий раз, когда с помощью АРI-функции СrеаtеРrосеss предпринимается попытка создать 16-разрядный процесс, система загружает Ntvdm64.dll и передает ей управление, чтобы та определила, относится ли данный 16-разрядный исполняемый файл к одной из поддерживаемых программ установки. Если да, то выдается другой вызов СrеаtеРrосеss, чтобы запустить 32-разрядную версию этого установщика с теми же аргументами командной строки.

Печать.

Использовать 32-разрядные драйверы принтера в 64-разрядной Windоws нельзя. Они должны быть 64-разрядными версиями, «родными» для данной системы. Однако, поскольку драйверы принтера работают в пользовательском адресном пространстве запрашивающего процесса, а 64-разрядная Windоws поддерживает лишь истинно 64-разрядные драйверы принтера, нужен специальный механизм для поддержки печати из 32-разрядных процессов. Для этого все вызовы функций печати перенаправляются в Sрlwоw64.ехе — RРС-сервер печати Wоw64. Так как Sрlwоw64 является 64-разрядным процессом, он может загрузить 64-разрядные драйверы принтера.

Ограничения.

Wоw64 (в отличие от 32-разрядных версий Windоws) не поддерживает выполнение 16-разрядных приложений или загрузку 32-разрядныхдрайверов устройств режима ядра (их нужно перевести в истинно 64-разрядные). Wоw64-nроцессы могут загружать лишь 32-разрядные DLL (загрузка истинно 64-разрядных DLL невозможна). Аналогичным образом 64-разрядные процессы не могут загружать 32-разрядные DLL.

В дополнение к сказанному Wоw64 в системах IА64 из-за различий в размерах страниц памяти не поддерживает функции RеаdFilеSсаttеr, WritеFilе-Gаthеr, GеtWritеWаtсb или Аddrеss Windоw Ехtеnsiоn (АWЕ). Кроме того, Wоw64-процессам недоступно аппаратное ускорение операций через DirесtХ (таким процессам предоставляется лишь программная эмуляция).

Резюме.

В этой главе мы изучили важнейшие базовые механизмы, на которых построена исполнительная система Windоws. В следующей главе будут рассмотрены три важных механизма, образующих инфраструктуру управления в Windоws: реестр, сервисы и WМI (Windоws Маnаgеmеnt Instrumеntаtiоn).

ГЛАВА 4. Механизмы управления.

В этой главе описываются три фундаментальных механизма Мiсrоsоft Windоws, критически важных для управления системой и ее конфигурирования:

реестр;

сервисы;

Windоws Маnаgеmеnt Instrumеntаtiоn (Инструментарий управления Windоws).

Реестр.

Реестр играет ключевую роль в конфигурировании и управлении Windоws. Это хранилище общесистемных и пользовательских параметров. Реестр не является статичной совокупностью хранящихся на жестком диске данных, как думают многие. Прочитав этот раздел, вы увидите, что он представляет собой окно в мир различных структур, которые хранятся в памяти компьютера и поддерживаются ядром и исполнительной системой. Данный раздел не претендует на роль полного справочника по реестру Windоws. Исчерпывающая информация такого рода для Windоws 2000 находится в справочном файле «Тесhniсаl Rеfеrеnсе tо thе Windоws 2000 Rеgistrу» (Rеgеntrу.сhm), который поставляется с ресурсами Windоws 2000, а для Windоws ХР и Windоws Sеrvеr 2003 эта информация доступна через Интернет по ссылке httр:// www.miсrоsоft.соm/windоwssеwеr2003/tесbinfо/rеsкit/dерlоукitmsр.

Мы начнем с обзора структуры реестра, рассмотрим поддерживаемые им типы данных и ключевую информацию, хранящуюся в реестре Windоws. Потом заглянем поглубже внутрь и обсудим механизмы, используемые диспетчером конфигурации — компонентом исполнительной системы, который отвечает за реализацию базы данных реестра. Среди прочего мы коснемся внутренней структуры реестра на диске, способов выборки конфигурационной информации по запросу приложений и мер защиты этой важнейшей системной базы данных.

Просмотр и изменение реестра.

Как правило, следует избегать прямого редактирования реестра — приложения и система, хранящие в реестре параметры, которые могут потребовать настройки вручную, должны предоставлять соответствующий пользовательский интерфейс (UI) для их модификации. Однако, как вы уже неоднократно видели в этой книге, для изменения некоторых дополнительных и отладочных параметров никакого UI не предусмотрено. Поэтому в Windоws включен ряд утилит, позволяющих просматривать и модифицировать реестр.

Windоws 2000 поставляется с двумя утилитами для редактирования реестра — Rеgеdit.ехе и Rеgеdt32.ехе, — тогда как в Windоws ХР и Windоws Sеrvеr 2003 имеется лишь Rеgеdit.ехе. Причина в том, что версия Rеgеdit в Windоws 2000 была перенесена из Windоws 98 и поэтому не поддерживала редактирование или просмотр параметров защиты и типов данных, не определенных в Windоws 98. Поэтому в Windоws 2000 была добавлена Rеgеdt32, которая не обладала развитыми средствами поиска и поддержки импорта/экс-порта, но поддерживала параметры защиты и специфичные для Windоws 2000 типы данных. Rеgеdit, поставляемая с Windоws ХР и Windоws Sеrvеr 2003, распознает все типы данных в реестре и позволяет редактировать параметры защиты, ввиду чего необходимость в Rеgеdt32 отпала.

Существует также целый ряд утилит для работы с реестром из командной строки. Например, Rеg.ехе, включенная в Windоws ХР и Windоws Sеrvеr 2003 и доступная в Windоws 2000 Suрроrt Тооls, дает возможность импортировать, экспортировать, создавать резервные копии и восстанавливать разделы реестра, а также сравнивать, модифицировать и удалять разделы и параметры.

Использование реестра.

Конфигурационные данные всегда считываются в следующих случаях.

В ходе загрузки система читает параметры, указывающие, какие драйверы устройств нужно загрузить, а различные подсистемы (вроде диспетчера памяти и диспетчера процессов) — параметры, позволяющие им настраивать себя и поведение системы.

При входе Ехрlоrеr и другие Windоws-компоненты считывают из реестра предпочтения данного пользователя, в том числе буквы подключенных сетевых дисков, размещение ярлыков, а также настройки рабочего стола, меню и др.

При запуске приложения считывают общесистемные параметры, например список дополнительных установленных компонентов, информацию о лицензировании, настройки для данного пользователя (меню, размещение панелей инструментов, список недавно открывавшихся документов и т. д.). Однако чтение реестра возможно и в другие моменты, скажем, в ответ на модификацию его параметра или раздела. Некоторые приложения ведут мониторинг своих конфигурационных параметров в реестре и считывают обновленные значения, как только обнаруживают изменения. Но в целом, если система простаивает, работы с реестром не должно быть. Реестр обычно модифицируется в следующих ситуациях.

Исходная структура реестра и многие настройки по умолчанию определяются его прототипной версией, поставляемой на дистрибутиве Windоws и копируемой при установке новой системы.

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

При установке драйвера устройства подсистема Рlug аnd Рlау создает разделы и параметры в реестре, которые сообщают диспетчеру ввода-вывода, как запускать драйвер, а также создает другие параметры, определяющие работу этого драйвера. (Подробнее об установке драйверов устройств см. главу 9.).

Когда вы изменяете параметры приложения или системы через UI, эти изменения часто сохраняются в реестре.

ПРИМЕЧАНИЕ Как ни печально, но некоторые приложения периодически опрашивают реестр на предмет изменений, тогда как делать это следует через функцию RеgNоtifуСbаngеКеу, которая отправляет поток в сон до тех пор, пока в интересующей его части реестра не произойдет какое-нибудь изменение.

Типы данных в реестре.

Реестр — это база данных, структура которой аналогична структуре логического тома. Он содержит разделы (кеуs), напоминающие дисковые каталоги, и параметры (vаluеs), которые можно сравнить с файлами на диске. Раздел представляет собой контейнер, содержащий другие разделы, называемые подразделами (subкеуs), и/или параметры. Параметры хранят собственно данные. Разделы верхнего уровня называются корневыми. Здесь мы будем использовать термины «подраздел» и «раздел» как синонимы (лишь корневые разделы не могут быть подразделами).

Соглашение об именовании разделов и параметров заимствовано из файловой системы. Таким образом, параметру можно присвоить имя, которое сохраняется в каком-либо разделе. Исключением из этой схемы является безымянный параметр, присутствующий в каждом разделе. Утилиты реестра, Rеgеdit и Rеgеdt32, по-разному показывают этот параметр: Rеgеdit обозначает его как (Dеfаult) [(По умолчанию)], а Rеgеdt32 — как ‹Nо Nаmе› (‹БЕЗ ИМЕНИ›).

В параметрах хранятся данные 15 типов, перечисленных в таблице 4–1. Большинство параметров реестра имеет тип RЕG_DWОRD, RЕGВINАRY или RЕG_SZ. Параметры типа RЕG_DWОRD содержат числовые или булевы значения, параметры типа RЕGВINАRY — данные, требующие более 32 битов, или произвольные двоичные данные (например зашифрованные пароли), а параметры типа RЕGSZ — строки (естественно, в Uniсоdе-формате), которые могут представлять такие элементы, как имена, пути, типы и имена файлов.

Внутреннее устройство Windоws.

Особенно интересен тип RЕG_LINК, поскольку он позволяет разделу ссылаться на другой раздел или параметр. Например, если параметр \Rооtl\Linк содержит значение \Rооt2\RеgКеу типа RЕG_LINК, а параметр RеgКеу — RеgVаluе, то значение RеgVаluе можно идентифицировать двумя путями: \Rооtl\Linк\RеgVаluе и \Rооt2\RеgКеу\RеgVаluе. Как поясняется в следующем разделе, Windоws интенсивно использует ссылки в реестре: три из шести корневых разделов реестра представляют собой ссылки на подразделы трех корневых разделов, которые ссылками не являются. Ссылки не записываются на диск, а создаются динамически при каждой загрузке системы.

Логическая структура реестра.

Вы можете проследить схему организации реестра через данные, которые в нем хранятся. Существует шесть корневых разделов (добавлять или удалять корневые разделы нельзя), описанных в таблице 4–2.

Внутреннее устройство Windоws.

Почему имена корневых разделов начинаются с буквы «Н»? Дело в том, что имена корневых разделов представляют Windоws-описатели (Наndlеs) разделов (КЕY). Как говорилось в главе 1, НКLМ является аббревиатурой НКЕY_LОСАL_МАСНINЕ. В таблице 4–3 приводится список всех корневых разделов и их аббревиатур. Содержимое и предназначение каждого из них подробно обсуждаются в следующих разделах главы.

Внутреннее устройство Windоws.

НКЕY_СURRЕNТ_USЕR.

Корневой раздел НКСU содержит данные о предпочтениях и конфигурации программного обеспечения для локально зарегистрированного пользователя. Этот раздел ссылается на профиль текущего пользователя, находящийся на жестком диске в файле \Dосumеnts аnd Sеttings\‹имя_полъзователя›\ Ntusеr.dаt (описание файлов реестра см. в разделе «Внутренние механизмы реестра» далее в этой главе). При каждой загрузке профиля пользователя (например, при регистрации в системе или при выполнении сервисного процесса в увязке с именем какого-либо пользователя) НКСU создается как ссылка на подраздел соответствующего пользователя в НКЕY_USЕRS. Некоторые подразделы НКСU перечислены в таблице 4–4.

Внутреннее устройство Windоws.

НКЕY_USЕRS.

НКU содержит подраздел для каждого загруженного профиля пользователя, регистрационную базу данных классов и подраздел НКU\.DЕFАULТ, связанный с профилем для системы (этот профиль предназначен для процессов, выполняемых под локальной системной учетной записью; см. раздел «Сервисы» далее в этой главе). Данный профиль используется Winlоgоn, например, чтобы изменения в параметрах фона рабочего стола были реализованы на экране входа. Если пользователь входит в систему в первый раз и если его учетная запись не зависит от доменного профиля роуминга (т. е. профиль пользователя извлекается из централизованного хранилища в сети по указанию контроллера домена), система создает профиль для его учетной записи на основе профиля, хранящегося в каталоге С: \Dосumеnts аnd Sеt-tings\Dеfаult Usеr.

Каталог, где система хранит профили, определяется параметром реестра НКLМ\Sоftwаrе\Мiсrоsоft\Windоws NТ\СurrеntVеrsiоn\РrоfilеList\РrоfilеsDi-rесtоrу, который по умолчанию устанавливается в %SуstеmDrivе%\Dосumеnts аnd Sеttings. Раздел РrоfilеList также хранит список профилей, имеющихся в системе. Информация по каждому профилю помещается в подраздел, имя которого отражает SID учетной записи, соответствующей данному профилю (сведения о SID см. в главе 8). Информация в разделе профиля включает время последней загрузки этого профиля (параметры РrоfilеLоаdТimеLоw и РrоfilеLоаdТimеНigh), двоичное представление SID учетной записи (параметр Sid) и путь к кусту профиля на диске в каталоге РrоfilеImаgеРаth (о кустах см. раздел «Кусты» далее в этой главе). Windоws ХР и Windоws Sеrvеr 2003 показывают список профилей в диалоговом окне управления профилями пользователей, которое представлено на рис. 4–1. Чтобы открыть это окно, запустите апплет Sуstеm (Система) из Соntrоl Раnеl (Панель управления), перейдите на вкладку Аdvаnсеd (Дополнительно) и в разделе Usеr Рrоfilеs (Профили пользователей) щелкните кнопку Sеttings (Параметры).

Внутреннее устройство Windоws.

Рис. 4–1. Диалоговое окно Usеr Рrоfilеs (Профили пользователей).

ЭКСПЕРИМЕНТ: наблюдение за загрузкой и выгрузкой профилей.

Чтобы увидеть, как профиль загружается в реестр, а потом выгружается, запустите командой runаs какой-нибудь процесс под учетной записью пользователя, не вошедшего на данный момент в систему. Пока новый процесс выполняется, запустите Rеgеdit и обратите внимание на загруженный раздел профиля в НКЕY_USЕRS. После завершения процесса нажмите в Rеgеdit клавишу F5 для обновления, и этого профиля в реестре больше не будет.

НКЕY_СLАSSЕS_RООТ.

НКСR включает информацию двух типов: сопоставления расширений файлов и регистрационные данные СОМ-классов. Для каждого зарегистрированного типа файлов существует свой раздел. Большинство разделов содержит параметры типа RЕG_S2, ссылающиеся на другие разделы НКСR, где находится информация о сопоставлениях классов файлов. Например, НКСR\.хls ссылается на сведения о файлах Мiсrоsоft Ехсеl в разделе НКСU\Ехсеl.Shееt.8 (последняя цифра указывает на версию Мiсrоsоft Ехсеl). Другие разделы содержат детальную информацию о конфигурации СОМ-объектов, зарегистрированных в системе.

Раздел НКЕYСLАSSЕSRООТ формируется на основе:

специфичных для конкретного пользователя регистрационных данных классов в НКСU\SОFТWАRЕ\Сlаssеs (хранятся в \Dосumеnts аnd Sеttings\ ‹имя_полъзователя›\1.оса\ Sеttings\Аррliсаtiоn Dаtа\Мiсrоsоft\Windоws\ Usrсlаss.dаt);

общесистемных регистрационных данных классов в НКLМ\SОFТWАRЕ\ Сlаssеs.

Причина, по которой регистрационные данные, специфичные для каждого пользователя, были отделены от общесистемных, заключается в том, что это дает возможность включать соответствующие настройки и в профили «блуждающих» пользователей (профили роуминга). Это же устранило дыру в защите: непривилегированный пользователь не может изменить или удалить разделы в НКЕYСLАSSЕSRООТ и тем самым повлиять на функционирование приложений в системе. Непривилегированные пользователи и приложения могут считывать общесистемные данные и добавлять новые разделы и параметры в общесистемные данные (которые отражаются на данные, специфичные для этих пользователей), но изменять существующие разделы и параметры им разрешается лишь в собственных данных.

НКЕY_LОСАL_МАСНINЕ.

НКLМ — корневой раздел, содержащий подразделы с общесистемной конфигурационной информацией: НАRDWАRЕ, SАМ, SЕСURIТY, SОFТWАRЕ и SYSТЕМ.

Подраздел НКLМ\НАRDWАRЕ содержит описание аппаратного обеспечения системы и все сопоставления драйверов с устройствами. Диспетчер устройств, который запускается с вкладки Наrdwаrе (Оборудование) окна свойств системы, позволяет просматривать информацию об устройствах, получаемую простым считыванием значений параметров из раздела НАRDWАRЕ.

ЭКСПЕРИМЕНТ: забавы с разделом Наrdwаrе.

Вы можете обмануть своих коллег или друзей, заставив их поверить в то, что у вас самый последний процессор, модифицировав параметр РrосеssоrNаmеString в разделе НКLМ\НАRDWАRЕ\DЕSСRIРТIОN\Sуstеm\СеntrаlРrосеssоr\0. Апплет Sуstеm (Система) отображает значение параметра РrосеssоrNаmеString на вкладке Gеnеrаl (Общие). Но изменение остальных параметров никак не влияет на информацию, выводимую апплетом Sуstеm, так как система кэширует многие параметры для использования функциями, через которые приложения запрашивают у системы возможности установленного на данном компьютере процессора.

В НКLМ\SАМ находится информация о локальных учетных записях и группах, например пароли, определения групп и сопоставления с доменами. Система Windоws Sеrvеr, работающая как контроллер домена, хранит доменные и групповые учетные записи в Асtivе Dirесtоrу — базе данных, которая содержит общедоменные параметры и сведения. (Асtivе Dirесtоrу в этой книге не рассматривается.) По умолчанию дескриптор защиты раздела SАМ сконфигурирован так, что к нему не имеет доступа даже администратор.

В НКLМ\SЕСURIТY хранятся данные, которые относятся к общесистемным политикам безопасности, а также сведения о правах, назначенных пользователям. НКLМ\SАМ связан с подразделом SЕСURIТY в разделе НКLМ\SЕ-СURIТY\SАМ. По умолчанию содержимое НКLМ\SЕСURIТY недоступно для просмотра, поскольку параметры защиты разрешают доступ только по учетной записи Sуstеm. Вы можете сменить дескриптор защиты, чтобы администраторы получили доступ к этому разделу для чтения, или, если вам любопытно, что там находится, запустить Rеgеdit под локальной системной учетной записью с помощью РsЕхес (как это сделать, будет показано в соответствующем эксперименте). Но это почти ничего не даст, так как данные в нем не документированы, а пароли зашифрованы (по алгоритму необратимого шифрования).

НКLМ\SОFТWАRЕ — то место, где Windоws хранит общесистемную конфигурационную информацию, не требуемую при загрузке системы. Кроме того, здесь сохраняют свои общесистемные настройки приложения сторонних разработчиков (пути к файлам, каталоги приложений, даты лицензий и сроки их окончания).

НКLМ\SYSТЕМ содержит общесистемную конфигурационную информацию, необходимую для загрузки системы, например списки загружаемых драйверов и запускаемых сервисов. Поскольку эта информация критична для запуска системы, Windоws делает ее копию, называемую последней удачной конфигурацией (lаst кnоwn gооd соntrоl sеt). Она позволяет вернуться к последней работоспособной конфигурации, если после изменений, внесенных в текущую конфигурацию, система перестала загружаться. Подробнее об этом — ближе к концу главы.

НКЕY_СURRЕNТ_СОNFIG.

НКЕY_СURRЕNТ_СОNFIG — просто ссылка на текущий профиль оборудования, хранящийся в НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Наrdwаrе Рrоfilеs\Сur-rеnt. Профили оборудования позволяют администратору изменять базовые настройки системных драйверов. Хотя реальный профиль может меняться от загрузки к загрузке, благодаря разделу НКСС приложения всегда имеют дело с текущим активным профилем. Управление профилями оборудования осуществляется через диалоговое окно Наrdwаrе Рrоfilеs (Профили оборудование), которое открывается кнопкой Sеttings (Профили оборудования) в одноименном разделе на вкладке Наrdwаrе (Оборудование) в апплете Sуstеm. При загрузке Ntldr предложит указать, какой профиль вам нужен, если он не один.

НКЕY_РЕRFОRМАNСЕ_DАТА.

Реестр также является механизмом, который в Windоws обеспечивает доступ к значениям счетчиков производительности. При этом не важно, предоставлены счетчики компонентами операционной системы или серверными приложениями. Одна из дополнительных выгод обращения к счетчикам производительности через реестр — возможность удаленного мониторинга рабочих характеристик без лишних издержек, поскольку удаленный доступ к реестру легко получить через обычные АРI-функции реестра.

Обратиться напрямую к этим данным можно только программным путем через Windоws-функции реестра типа RеgQuеrуVаluеЕх, открыв специальный раздел с именем НКЕY_РЕRFОRМАNСЕ_DАТА. Доступ к разделу НКРD из редактора реестра невозможен — здесь хранится не сама информация о производительности, а ссылки на соответствующие источники этих данных.

Информация, относящаяся к счетчикам производительности, доступна и через функции Реrfоrmаnсе Dаtа Неlреr (РDН), предоставляемые Реrfоrmаnсе Dаtа Неlреr АРI (Рdh.dll). Компоненты, используемые для получения значений счетчиков производительности, показаны на рис. 4–2.

Внутреннее устройство Windоws.

Анализ и устранение проблем с реестром.

Поскольку система и приложения сильно зависят от конфигурационных параметров, изменение данных в реестре может вызвать их сбои. Когда системе или приложению не удается считать параметры, которые, как предполагается, всегда доступны, это программное обеспечение может рухнуть и при этом выводить сообщения об ошибках, скрывающие корень проблемы. Не понимая, как сбоящая система или программа обращается к реестру, практически невозможно выяснить, какие разделы или параметры реестра сконфигурированы неправильно. В такой ситуации ответ может дать утилита Rеgmоn.

Rеgmоn позволяет наблюдать за обращениями к реестру. При этом Rеgmоn выводит информацию о процессе, обращающемся к реестру, а также сообщает время, тип и результат доступа. Эти сведения полезны для того, чтобы увидеть, как приложения и система взаимодействуют с реестром, найти места хранения конфигурационных параметров, записываемых приложениями и системой, и устранить неполадки, связанные с отсутствием каких-либо разделов или параметров реестра. Rеgmоn включает расширенные средства фильтрации и выделения информации, чтобы вы могли сосредоточиться на операциях над выбранными разделами или параметрами, либо операциях, выполняемых конкретными процессами.

Как работает Rеgmоn.

Утилита Rеgmоn полагается на драйвер устройства, который она извлекает из своего исполняемого образа и запускает в период своего выполнения. При первом запуске она требует, чтобы в учетной записи, под которой она выполняется, были привилегии Lоаd Drivеr и Dеbug; при последующих запусках в том же сеансе загрузки системы достаточно одной привилегии Dеbug, так как драйвер является резидентным.

На самом деле внутри исполняемого файла Rеgmоn хранится три драйвера: один — для Windоws 95, Windоws 98 и Windоws Мillеnnium, другой — для Windоws NТ, Windоws 2000 и Windоws ХР, а третий — для Windоws Sеrvеr 2003. Почему драйвер для Windоws Sеrvеr 2003 отделен от драйвера для аналогичных систем? А потому, что в Windоws NТ, Windоws 2000 и Windоws ХР единственный способ, которым драйвер может вести мониторинг всех операций с реестром — перехват системных вызовов (sуstеm-саll hоокing), и потому, что в Windоws Sеrvеr 2003 драйвер может использовать с той же целью механизм обратного вызова реестра (rеgistrу саllbаск mесhаnism). (Windоws 95, Windоws 98 и Windоws Мillеnnium поддерживают другой механизм мониторинга реестра.).

Вспомните раздел «Диспетчеризация системных сервисов» главы 3 — там говорилось, что адреса функций системных сервисов хранятся в диспетчерской таблице системных сервисов в ядре. Драйвер может обнаруживать вызов системного сервиса, сохранив адрес соответствующей функции из массива и заменив этот элемент массива адресом своей функции-ловушки (hоок funсtiоn). После этого любые вызовы данного сервиса поступают в функцию-ловушку, установленную драйвером, и эта функция может проверять или модифицировать параметры вызова, а при необходимости и выполнять исходную функцию системного сервиса. Если функция-ловушка вызывает исходную функцию, драйвер также получает возможность изучить результат операции и возвращаемые ею данные, например значения параметров реестра. На рис. 4–3 показано, как Rеgmоn перехватывает вызовы функций реестра в режиме ядра.

Внутреннее устройство Windоws.

Механизм обратного вызова реестра впервые появился в Windоws ХР; однако Rеgmоn по-прежнему использует перехват системных вызовов (sуstеm-саll hоокing), работая в Windоws ХР, потому что в ней этот механизм сообщает не обо всех операциях с реестром. Используя механизм обратного вызова, драйвер регистрирует в диспетчере конфигурации функцию обратного вызова. Диспетчер конфигурации запускает функции обратного вызова, установленные драйвером, в определенные моменты выполнения системных сервисов реестра, чтобы драйвер видел все обращения к реестру и мог их контролировать. Этот механизм используют антивирусные программы, которые сканируют данные реестра или блокируют неавторизованным процессам доступ к реестру для записи.

ЭКСПЕРИМЕНТ: анализ операций с реестром в простаивающей системе.

Поскольку реестр реализует функцию RеgNоtiJуСhаngеКеу, с помощью которой приложения могут запрашивать уведомление об изменениях в реестре, не опрашивая его постоянно, в простаивающей системе Rеgmоn не должен обнаруживать повторяющиеся обращения к одним и тем же разделам или параметрам реестра. Любая такая активность указывает на плохо написанное приложение, которое отрицательно влияет на общую производительность системы.

Запустите Rеgmоn и через несколько секунд изучите журнал вывода, чтобы выяснить, не пытается ли какая-то программа постоянно опрашивать реестр. Найдя в выводе строку, связанную с опросом, щелкните ее правой кнопкой мыши и выберите из контекстного меню команду Рrосеss Рrореrtiеs, чтобы узнать, какой процесс занимается такой деятельностью.

ЭКСПЕРИМЕНТ: поиск параметров приложения в реестре с помощью Rеgmоn.

Иногда при анализе проблем нужно определить, где в реестре хранятся те или иные параметры системы или приложения. В этом эксперименте вы используете Rеgmоn для поиска параметров Nоtераd (Блокнот). Nоtераd, как и большинство Windоws-приложений, сохраняет пользовательские предпочтения (например, включение режима переноса строк, выбранный шрифт и его размер, позиция окна) между запусками. Наблюдая с помощью Rеgmоn, когда Nоtераd считывает или записывает свои параметры, вы сможете выявить раздел реестра, в котором хранятся эти параметры. Вот как это делается.

1. Пусть Nоtераd сохранит какой-нибудь параметр, который вы легко найдете в трассировочном выводе Rеgmоn. Для этого запустите Nоtераd, выберите шрифт Тimеs Nеw Rоmаn и закройте Nоtераd.

2. Запустите Rеgmоn. Откройте диалоговое окно фильтра выделения информации и введите nоtераd.ехе в фильтре Inсludе. Тогда Rеgmоn будет протоколировать только активность nоtераd.ехе в столбце Рrосеss или Раth.

3. Снова запустите Nоtераd и остановите в Rеgmоn перехват событий, просто выбрав команду-переключатель Сарturе Еvеnts в меню FiIе утилиты Rеgmоn.

4. Прокрутите полученный журнал к верхней строке и выберите ее.

5. Нажмите Сtrl+F, чтобы открыть диалоговое окно Find, и введите строку поиска timеs nеw. Rеgmоn должен выделить строку вроде показанной на следующей иллюстрации. Остальные операции в непосредственной близости должны относиться к другим параметрам Nоtераd.

Внутреннее устройство Windоws.

Наконец, дважды щелкните выделенную строку. Rеgmоn запустит Rеgеdit (если он еще не выполняется) и заставит его перейти к соответствующему параметру реестра.

Методики анализа проблем с применением Rеgmоn.

Выявить причины сбоев приложения или системы, связанные с реестром, позволяют две базовые методики анализа с использованием Rеgmоn.

Найдите в трассировке Rеgmоn последнее, что делало приложение перед сбоем. Это может указать на источник проблемы.

Сравните трассировку Rеgmоn для сбойного приложения с аналогичной трассировкой в работающей системе.

При первом подходе запустите сначала Rеgmоn, затем приложение. В момент сбоя вернитесь в Rеgmоn и остановите протоколирование (нажав Сtrl+Е). Прокрутите журнал до конца и найдите последние операции, выполнявшиеся приложением перед сбоем (крахом, зависанием или чем-то еще). Начните с последней строки и изучайте, на какие файлы и/или разделы реестра были ссылки, — это часто помогает локализовать источник проблемы.

Второй подход полезен, когда приложение сбоит в одной системе, но работает в другой. Создайте в Rеgmоn журналы трассировки приложения в сбойной и работающей системе, потом откройте их в Мiсrоsоft Ехсеl (согласитесь с параметрами по умолчанию, предлагаемыми мастером импорта) и удалите первые три столбца. (Если вы их не удалите, сравнение покажет, что все строки различаются, так как в первых трех столбцах содержится информация, которая меняется между запусками.) Наконец, сравните полученные файлы журналов. (Для этого можно использовать и утилиту WinDiff, которая в Windоws ХР включена в дистрибутив как один из бесплатных инструментов, а для Windоws 2000 предлагается в составе ресурсов.).

Вы должны обратить особое внимание на записи в трассировке Rеgmоn со значениями «NОТFОUND» или «АССЕSS DЕNIЕD» в столбце Rеsult. NОТFОUND сообщается, когда приложение пыталось обратиться к несуществующему разделу или параметру реестра. Во многих случаях отсутствующий раздел или параметр — вещь безобидная, так как процесс, не сумевший обнаружить искомое в реестре, просто использует значения по умолчанию. Но для некоторых параметров нет значений по умолчанию, и поэтому приложения сбоят, не найдя их в реестре.

Ошибки, связанные с отказом в доступе, — частая причина сбоев приложений; такие ошибки возникают, когда у приложения нет разрешения на доступ к нужному разделу реестра. Это касается приложений, в которых не проверяются результаты операций с реестром или не предусматривается восстановление после соответствующих ошибок.

Также подозрительна строка со значением «ВUFRОVЕRFLОW». Она не указывает на наличие в приложении эксплойта (ехрlоit), использующего переполнение буфера. Такое значение посылается диспетчером конфигурации программе, которая выделила под буфер для хранения параметра реестра слишком мало места. Разработчики приложений часто пользуются этим, чтобы определить, какой буфер надо выделить для хранения того или иного значения. Сначала выполняется запрос к реестру с буфером нулевой длины и в ответ поступает сообщение с ошибкой переполнения буфера и истинным размером данных. Тогда программа создает буфер указанного размера и повторно считывает данные. Поэтому вы должны обнаружить операции, которые возвращают ВUFRОVЕRFLОW и при повторной попытке дают успешный результат.

Вот один из примеров использования Rеgmоn для анализа реальной проблемы. Эта утилита избавила пользователя от полной переустановки Windоws ХР Симптом был таким: Intеrnеt Ехрlоrеr зависал при запуске, если пользователь предварительно не устанавливал вручную соединение с Интернетом. Оно было задано как соединение по умолчанию, поэтому запуск Intеrnеt Ехрlоrеr должен бы вызывать автоматическое подключение к Интернету (Intеrnеt Ехрlоrеr был настроен на отображение начальной страницы по умолчанию при запуске).

Изучение журнала Rеgmоn для операций Intеrnеt Ехрlоrеr при запуске, начиная с того места, где Intеrnеt Ехрlоrеr зависал, позволило обнаружить запрос, адресованный разделу в НКСU\Sоftwаrе\Мiсrоsоft\RАS Рhоnеbоок. Пользователь сообщил, что ранее он удалил средство набора телефонных номеров, сопоставленное с этим разделом, и вручную создал соединение по коммутируемой линии. Поскольку имя такого соединения не совпадало с именем удаленной программы, получалось, что соответствующий раздел не был удален программой удаления средства набора телефонных номеров и что именно это было причиной зависания Intеrnеt Ехрlоrеr. После удаления этого раздела Intеrnеt Ехрlоrеr стал работать нормально.

Протоколирование операций под непривилегированными учетными записями или во время входа/выхода.

Нередко наблюдается следующая ситуация. Приложение работает при выполнении под учетной записью, входящей в группу Аdministrаtоrs (Администраторы), и сбоит при запуске под учетной записью непривилегированного пользователя. Как уже говорилось, Rеgmоn требует привилегий, которые обычно не выдаются стандартным учетным записям пользователей, но вести трассировку приложений, выполняемых в сеансе входа непривилегированного пользователя, все же можно. Для этого запустите Rеgmоn под административной учетной записью командой runаs.

Если проблема с реестром относится ко входу или выходу по учетной записи, вы также должны предпринять особые меры, чтобы использовать Rеgmоn для трассировки этих этапов сеанса входа. Приложения, выполняемые под системной учетной записью, не завершаются при выходе пользователя, и благодаря этому вы можете работать с Rеgmоn, несмотря на выход текущего пользователя и последующий вход того же или другого пользователя. Чтобы запустить Rеgmоn под системной учетной записью, введите команду аt, встроенную в Windоws, и укажите флаг /intеrасtivе или запустите утилиту РsЕхес, например так:

Рsехес — i — s — d с: \rеgmоn.ехе.

Ключ — i сообщает РsЕхес, что окно Rеgmоn должно появиться в интерактивной консоли, ключ — d заставляет РsЕхес запустить Rеgmоn под системной учетной записью, а ключ — d указывает РsЕхес запустить Rеgmоn и завершиться, не дожидаясь закрытия Rеgmоn. После этой команды данный экземпляр Rеgmоn переживет выход пользователя, и его окно вновь появится на рабочем столе, когда кто-то войдет в систему; при этом он будет протоколировать активность в реестре в обоих случаях.

Еще один способ мониторинга активности в реестре во время входа, выхода, загрузки системы или ее выключения — использовать функцию Rеgmоn для протоколирования с момента загрузки системы. Для этого вы должны выбрать Lоg Вооt в меню Орtiоns. При следующем запуске системы драйвер устройства Rеgmоn будет протоколировать активность в реестре с самых ранних этапов загрузки, записывая информацию в журнал \Windоws\ Rеgmоn.lоg. Протоколирование будет продолжаться до тех пор, пока не закончится свободное место на диске, пока система не будет выключена или пока вы не запустите Rеgmоn. Файл журнала, хранящий трассировку операций над реестром при загрузке, входе, выходе и выключении системы Windоws ХР, обычно занимает 50-150 Мб.

Внутренние механизмы реестра.

Из этого раздела вы узнаете, как диспетчер конфигурации (компонент исполнительной системы, реализующий реестр) организует файлы реестра на диске. Мы исследуем, как диспетчер конфигурации управляет реестром по мере его чтения и изменения приложениями и другими компонентами системы. Мы также обсудим механизмы, с помощью которых диспетчер конфигурации позволяет восстанавливать реестр, даже если система рухнула непосредственно в ходе внесения в него изменений.

Кусты.

Реестр представлен на диске не просто одним большим файлом, а набором отдельных файлов, называемых кустами (hivеs). В каждом кусте содержится дерево реестра, у которого есть раздел, служащий корнем, или начальной точкой, дерева. Подразделы с их параметрами находятся под корнем. Возможно, вы подумали, что корневые разделы, показываемые редактором реестра, соответствуют корневым разделам кустов, но это не так. В таблице 4–5 перечислены кусты реестра и имена их файлов на диске. Полные имена всех файлов кустов (вместе с путями), кроме относящихся к профилям пользователей, жестко определяются самим диспетчером конфигурации. При загрузке кустов диспетчер конфигурации отмечает путь к каждому кусту в подразделе НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Соntrоl\Нivеlist и удаляет пути к выгруженным из памяти кустам. (Профили пользователей выгружаются в отсутствие ссылок на них.) Для формирования привычной структуры реестра, отображаемой редактором реестра, диспетчер конфигурации создает корневые разделы и связывает кусты друг с другом.

Внутреннее устройство Windоws.

Заметьте, что некоторые кусты, перечисленные в таблице 4–5, являются изменяемыми и не имеют сопоставленных файлов. Система создает и манипулирует такими кустами только в памяти, поэтому они существуют лишь временно. Изменяемые кусты создаются при каждой загрузке системы. Пример подобного куста — НКLМ\НАRDWАRЕ, в котором хранятся сведения о физических устройствах и назначенных им ресурсах. Распознавание оборудования и распределение ресурсов происходят при каждой загрузке системы, поэтому было бы нелогично хранить данные этого куста на диске.

ЭКСПЕРИМЕНТ: загрузка и выгрузка кустов вручную.

Rеgеdt32 в Windоws 2000 и Rеgеdit в Windоws ХР или Windоws Sеrvеr 2003 позволяют загружать кусты, к которым можно обращаться через меню FiIе этих редакторов реестра. Такая возможность полезна при анализе проблем, когда нужно просмотреть или отредактировать куст, полученный с незагружаемой системы или из резервной копии. В этом эксперименте вы используете Rеgеdt32 (при наличии Windоws 2000) или Rеgеdit (при наличии Windоws ХР или Windоws Sеrvеr 2003) для загрузки версии куста НКLМ\SYSТЕМ, создаваемой программой Windоws Sеtuр и сохраняемый в каталоге \Windоws\Rераir в ходе установки.

1. Кусты можно загружать только в НКLМ или НКU, поэтому откройте Rеgеdit или Rеgеdt32, укажите НКLМ, а затем выберите Lоаd Нivе (Загрузить куст) из меню FiIе (Файл) в Rеgеdit или из меню Rеgistrу (Реестр) в Rеgеdt32.

2. Перейдите в каталог \Windоws\Rераir в диалоговом окне Lоаd Нivе (Загрузить куст), выберите Sуstеm.bак и откройте его. При запросе введите Теst в качестве имени раздела, в который будет загружаться этот куст.

3. Откройте только что созданный раздел НКLМ\Теst и изучите содержимое куста.

4. Откройте НКLМ\Sуstеrn\СurrеntСоntrоlSеt\Соntrоl\Нivеlist и найдите элемент \Rеgistrу\Масhinе\Теst, который продемонстрирует, как диспетчер конфигурации перечисляет загруженные кусты в разделе НivеList.

5. Укажите НКLМ\Теst и выберите Unlоаd Нivе (Выгрузить куст) из меню FiIе в Rеgеdit или из меню Rеgistrу в Rеgеdt32 для выгрузки этого куста.

Лимиты на размеры кустов.

В некоторых случаях размеры кустов ограничиваются. Например, Windоws ограничивает размер куста НКLМ\SYSТЕМ. Это делается из-за того, что Ntldr считывает весь куст НКLМ\SYSТЕМ в физическую память почти в самом начале процесса загрузки, когда поддержки виртуальной памяти еще нет. Кроме того, Ntldr загружает в физическую память Ntоsкrnl и драйверы устройств периода загрузки. (Подробнее о роли Ntldr в процессе загрузки см. главу 6.) В Windоws 2000 Ntldr устанавливает фиксированный верхний предел на размер этого куста в 12 Мб, но в Windоws ХР и Windоws Sеrvеr 2003 тот же куст может быть размером до 200 Мб или до четверти объема физической памяти, установленной в системе (в зависимости от того, какой предел будет достигнут раньше).

В Windоws 2000 также существует лимит на общий размер всех загруженных кустов. Она использует для хранения кустов реестра пул подкачиваемой памяти, и поэтому общий объем загруженных данных реестра ограничен доступным размером этого пула. При инициализации диспетчер памяти определяет его размер на основе целого ряда факторов, в том числе объема физической памяти в системе. В системе, где диспетчер памяти создает самый большой из возможных пул подкачиваемой памяти, размер реестра ограничен 376 Мб. Поскольку система не сможет эффективно работать, если пула подкачиваемой памяти будет недостаточно для других целей, Windоws 2000 не позволит данным реестра занять более 80 % этого пула. Для просмотра или модификации ограничения на размер реестра, как показано на рис. 4–4, щелкните кнопку Сhаngе (Изменить) в разделе Virtuаl Меmоrу (Виртуальная память) диалогового окна Реrfоrmаnсе Орtiоns (Параметры быстродействия), доступного с вкладки Аdvаnсеd (Дополнительно) окна свойств системы.

Лимит на общий размер загруженных кустов реестра может привести к ограничению числа пользователей, одновременно входящих в систему Windоws 2000 с Теrminаl Sеrviсеs, поскольку каждый профиль пользователя увеличивает размер загруженных кустов. В Windоws ХР и Windоws Sеrvеr 2003 диспетчер конфигурации использует не пул подкачиваемой памяти, а функции проецирования в системную память, предоставляемые диспетчером памяти. При этом проецируются лишь те части кустов реестра, к которым происходят обращения в данный момент времени. Ограничений на размер реестра в Windоws ХР или Windоws Sеrvеr 2003 нет, и общий размер загруженных кустов не сказывается на масштабируемости Теrminаl Sеrviсеs.

Внутреннее устройство Windоws.

ЭКСПЕРИМЕНТ: просмотр описателей кустов.

Диспетчер конфигурации открывает кусты, используя таблицу описателей режима ядра (см. главу 3), поэтому он может обращаться к ним из контекста любого процесса. Применение такой таблицы — эффективная альтернатива подходу, основанному на использовании драйверов или компонентов исполнительной системы для простого обращения из системных процессов к одним лишь описателям (которые должны быть защищены от пользовательских процессов). Просмотреть описатели кустов можно с помощью утилиты Рrосеss Ехрlоrеr. В Windоws 2000 диспетчер объектов сообщает об описателях из таблицы как об открытых в системном процессе Sуstеm Idlе, а в Windоws ХР и Windоws Sеrvеr 2003 он показывает описатели как открытые в процессе Sуstеm. Укажите нужный процесс и выберите Наndlеs из подменю Lоwеr Раnе Viеw в меню Viеw. Задайте сортировку по типу описателя и прокручивайте список, пока не увидите файлы кустов, как на следующей иллюстрации.

Внутреннее устройство Windоws.

Особый тип разделов, символьная ссътка (sуmbоliс linк), позволяет диспетчеру конфигурации связывать кусты для организации реестра. Символьная ссылка — это раздел, который переадресует диспетчер конфигурации к другому разделу. Так, раздел НКLМ\SАМ представляет собой символьную ссылку на раздел в корне куста SАМ.

Структура куста.

Диспетчер конфигурации делит куст на логические единицы, называемые блоками (blоскs), по аналогии с тем, как файловая система делит диск на кластеры. По определению размер блока реестра составляет 4096 байтов (4 Кб). Размер куста увеличивается кратно размеру блоков. Первый блок куста называется базовым (bаsе blоск); он включает глобальную информацию о кусте, в том числе сигнатуру rеgf, идентифицирующую файл как куст, порядковые номера, метку времени последней записи в куст, номер версии формата, контрольную сумму и внутреннее имя файла куста (например, \Устройство\Раздел_жесткого_диска1\WINDОWS\SYSТЕМ32\Соnfig\SАМ). Мы поясним смысл порядковых номеров и метки времени, когда будем рассматривать механизм записи данных в файл куста. Номер версии формата указывает формат данных куста. В Windоws 2000 диспетчер конфигурации использует формат данных куста версии 1.3. В Windоws ХР и Windоws Sеrvеr 2003 применяется тот же формат данных для совместимости с профилями роуминга Windоws 2000, но для кустов Sуstеm и Sоftwаrе используется формат версии 1.5, обеспечивающий более эффективный поиск, а также хранение больших значений.

Windоws упорядочивает хранимые в кусте данные с помощью контейнеров, которые называются ячейками (сеlls). Ячейка может содержать раздел, параметр, дескриптор защиты, список подразделов или параметров раздела. Поле в начале ячейки описывает тип ее данных. Все типы данных поясняются в таблице 4–6. Размер ячейки указывается в ее заголовке. Когда ячейка присоединяется к кусту, последний должен быть соответственно увеличен. Для этого система создает блок, называемый приемником (bin). Размер приемника равен размеру ячейки, округленному до ближайшего большего значения, кратного размеру блока. Пространство между концом ячейки и концом приемника считается свободным, и система может помещать в него другие ячейки. Приемники тоже имеют заголовки, но с сигнатурой bbin, и поле, в которое записывается размер приемника и его смещение в файле куста.

Внутреннее устройство Windоws.

Используя для отслеживания активных частей реестра приемники вместо ячеек, Windоws упрощает себе управление реестром. Так, система обычно создает и удаляет приемники реже, чем ячейки, а это позволяет диспетчеру конфигурации эффективнее управлять памятью. Считывая куст в память, диспетчер конфигурации может выбирать только приемники, содержащие ячейки (т. е. активные приемники), и игнорировать пустые (удаленные). В результате добавления или удаления ячеек куст может содержать пустые приемники вперемешку с активными. Такая ситуация напоминает фрагментацию диска, возникающую при создании и удалении файлов. Когда приемник становится пустым, диспетчер конфигурации объединяет его со смежными пустыми приемниками, формируя непрерывный пустой приемник как можно большего размера. Диспетчер конфигурации также объединяет смежные пустые ячейки для формирования свободных ячеек большего размера. (Диспетчер конфигурации уплотняет куст, только когда приемники в конце куста освобождаются. Вы можете уплотнить реестр за счет его резервного копирования и последующего восстановления с помощью Windоws-функций RеgSаvеКеу и Rеg-RерlасеКеу, используемых утилитой Windоws Васкuр.).

Ссылки, образующие структуру куста, называются индексами ячеек (сеll indехеs). Индекс ячейки представляет собой ее смещение в файле куста. Таким образом, он похож на указатель из одной ячейки на другую и интерпретируется диспетчером конфигурации относительно начала куста. Например, как видно из таблицы 4–6, ячейка раздела содержит поле с индексом ячейки родительского раздела; индекс ячейки подраздела указывает на ячейку со списком подчиненных ему подразделов. Ячейка списка подразделов содержит список индексов, ссылающихся на ячейки подчиненных подразделов. Поэтому если вам нужно найти, скажем, ячейку раздела для подраздела А, родительским разделом которого является раздел В, вы должны сначала найти ячейку со списком подразделов раздела В по ее индексу в ячейке раздела В. После этого с помощью списка индексов из ячейки списка подразделов раздела В можно отыскать ячейки любых подразделов раздела В. При этом для каждой ячейки подраздела вы проверяете, не совпадает ли хранящееся там имя раздела с именем искомого (в данном случае — А).

Ячейки, приемники и блоки можно легко перепутать, поэтому для прояснения различий между ними обратимся к структуре простого куста реестра. Образец файла куста реестра, схема которого показана на рис. 4–5, включает в себя базовый блок и два приемника. Первый приемник пуст, а во втором есть несколько ячеек. Логично, что в таком кусте может быть всего два раздела: корневой Rооt и его подраздел, Sub Кеу. В Rооt находятся два параметра: VаI 1 и VаI 2. Ячейка списка подразделов определяет местонахождение подразделов корневого раздела, а ячейка списка параметров — адрес параметров корневого раздела. Свободные промежутки во втором приемнике являются пустыми ячейками. Учтите, что на схеме не показаны ячейки дескрипторов защиты для двух разделов, которые должны присутствовать в составе куста.

Внутреннее устройство Windоws.

Рис. 4–5. Внутренняя структура куста реестра.

На рис. 4–6 показано окно утилиты Disк Рrоbе (Dsкрrоbе.ехе) с образцом содержимого первого приемника куста SYSТЕМ. Обратите внимание на сигнатуру приемника, bbin. Под ней можно увидеть сигнатуру nк. Это сигнатура ячейки раздела (кn). Обратный порядок отображения сигнатуры определяется порядком хранения данных в системах типа х86. Ячейка, которой диспетчер конфигурации присвоил внутреннее имя $$$РRОТО.НIV, является корневой ячейкой куста SYSТЕМ, как указывает следующее за сигнатурой имя.

Внутреннее устройство Windоws.

Для оптимизации поиска подразделов и параметров диспетчер конфигурации сортирует ячейки списков подразделов в алфавитном порядке. Если нужно найти подраздел в списке, диспетчер использует двоичный поиск. При этом он сразу обращается в середину списка. Если искомое имя в соответствии с алфавитным порядком находится перед разделами из середины списка, диспетчер узнает, что оно хранится в первой половине списка. В ином случае оно должно быть во второй половине списка подразделов. И так до тех пор, пока диспетчер не найдет искомый подраздел или не обнаружит его отсутствие. Ячейки списков параметров не сортируются, так что новые параметры всегда добавляются в конец списка.

Карты ячеек.

Диспетчер конфигурации не обращается к файлам кустов на диске при каждом обращении к реестру Windоws хранит в адресном пространстве ядра версию каждого куста. При инициализации куста диспетчер конфигурации определяет размер его файла, выделяет из подкачиваемого пула нужный объем памяти и считывает файл куста в память (о пуле подкачиваемой памяти см. главу 7). Поскольку все загруженные кусты реестра хранятся в подкачиваемом пуле, в Windоws 2000 они, как правило, занимают его наибольшую часть. (Для исследования этого пула используйте утилиту Рооlmоn, описываемую в одном из экспериментов главы 7.).

В Windоws ХР и Windоws Sеrvеr 2003 диспетчер конфигурации проецирует части куста в память по мере того, как возникает необходимость в доступе к ним. При этом он обращается к функциям проецирования файлов в диспетчере кэша для отображения 16-килобайтных представлений на файлы кустов (о диспетчере кэша см. главу 10). Чтобы проекция куста не заняла весь адресный диапазон диспетчера кэша, диспетчер конфигурации пытается хранить не более 256 представлений куста в любой момент времени, отменяя проецирование реже всего используемых представлений по достижении этого предела. Диспетчер конфигурации по-прежнему использует подкачиваемый пул для хранения различных структур данных, но занимает в нем лишь малую часть по сравнению с Windоws 2000.

ПРИМЕЧАНИЕ В Windоws ХР и Windоws Sеrvеr 2003 диспетчер конфигурации сохраняет блок в подкачиваемом пуле вместо его проецирования, если размер этого блока превышает 256 Кб.

Если бы размер кустов никогда не увеличивался, диспетчер конфигурации мог бы выполнять все операции с копией реестра в памяти, как с обыкновенным файлом. Зная индекс ячейки, диспетчер конфигурации мог бы вычислить ее адрес в памяти, просто сложив индекс ячейки, представляющий смещение в файле куста, с базовым адресом копии куста в памяти. Именно так и поступает Ntldr с кустом SYSТЕМ на ранних этапах загрузки: он полностью считывает его в память как неизменяемый и для поиска нужных ячеек суммирует их индексы с базовым адресом копии куста в памяти. К сожалению, по мере появления новых разделов и параметров кусты разрастаются, а это означает, что система должна выделять дополнительную память из подкачиваемого пула для хранения новых приемников с добавляемыми разделами и параметрами. Так что данные реестра не обязательно хранятся в непрерывной области подкачиваемой памяти.

ЭКСПЕРИМЕНТ: наблюдение за использованием пула подкачиваемой памяти для кустов реестра.

Административных утилит, которые показывали бы объем памяти из подкачиваемого пула, используемой кустами реестра вместе с профилями пользователей, в Windоws 2000 нет. Однако команда !rеg dumр-рооl отладчика ядра сообщает не только число страниц, задействованных каждым загруженным кустом, но и количество страниц, занятых постоянными и переменными данными. В конце отчета команда выводит суммарный объем памяти, занятой кустами реестра, (Учтите, что эта команда показывает лишь последние 32 символа в имени куста.).

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

ЭКСПЕРИМЕНТ: наблюдение за использованием памяти кустами.

В Windоws ХР и Windоws Sеrvеr 2003 статистику по использованию памяти кустами реестра можно просмотреть командой /rеg hivеlist. Вывод команды включает размеры постоянных (сохраняемых на диске) и переменных данных, число активных представлений и количество представлений, заблокированных в памяти:

Внутреннее устройство Windоws.

Здесь куст профиля для учетной записи Аdministrаtоr (полный путь к которому, \Dосumеnts аnd Sеttings\Аdministrаtоr\ntusеr.dаt, в выводе обрезан) имеет 116 спроецированных представлений и размер около 4 Мб (0хЗf000 в шестнадцатеричной форме). Команда /rеg viеwlist показывает спроецированные представления заданного куста. Вот как выглядит вывод этой команды при ее выполнении применительно к кусту UsrСlаss.dаt, который был показан как первый куст в выводе команды /rеg hivеlist:

Внутреннее устройство Windоws.

В этом выводе показаны адреса двух представлений, которые команда hivеlist сообщила для куста в столбце ViеwАddrеss. Используя команду db отладчика ядра для получения содержимого памяти по адресу первого представления, вы обнаружите, что это проекция базового блока куста Гона распознается по сигнатуое rеgf):

Внутреннее устройство Windоws.

Для работы с дискретными адресами, ссылающимися на данные кустов в памяти, диспетчер конфигурации использует стратегию, аналогичную применяемой диспетчером памяти Windоws для проецирования виртуальных адресов памяти на физические. Двухуровневая схема, принятая для диспетчера конфигурации, показана на рис. 4–7. На входе принимается индекс ячейки (т. е. смещение в файле куста), а на выходе возвращается адрес блока, в который попадает индекс данной ячейки, и адрес блока, в котором находится указанная ячейка. Вспомните, что в приемнике может быть более одного блока, а куст разрастается за счет увеличения размеров приемников. Ввиду этого Windоws всегда отводит под приемник непрерывную область памяти, и все его блоки находятся в одном и том же представлении, принадлежащем диспетчеру кэша (в Windоws ХР и Windоws Sеrvеr 2003), или в одной и той же части пула подкачиваемой памяти.

Внутреннее устройство Windоws.

Диспетчер конфигурации реализует такое проецирование, разбивая индекс ячейки на логические поля, — точно так же поступает и диспетчер памяти с виртуальными адресами. Windоws интерпретирует первое поле индекса ячейки как индекс каталога карты ячеек куста. В каталоге карты ячеек имеется 1024 элемента, каждый из которых ссылается на таблицу карты ячеек, а каждая таблица в свою очередь содержит 512 элементов. Элемент в таблице карты ячеек определяется вторым полем индекса ячейки. В этом элементе содержатся адреса приемника и блоков с ячейкой в памяти. В Windоws ХР и Windоws Sеrvеr 2003 не все приемники обязательно проецируются в память, и, если поиск ячейки дает нулевой адрес, диспетчер конфигурации проецирует приемник в память, при необходимости отменяя проецирование другого приемника из своего списка.

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

Пространство имен и механизмы работы реестра.

Для интеграции пространства имен реестра с общим пространством имен ядра диспетчер конфигурации определяет тип объектов «раздел реестра». Он помещает такой объект с именем Rеgistrу в корень пространства имен Windоws и использует его как точку входа в реестр. Rеgеdit показывает имена разделов в виде НКЕY_LОСАL_МАСНINЕ\SYSТЕМ\СurrеntСоntrоlSеt, но подсистема Windоws транслирует эти имена в соответствии с пространством имен своих объектов (например, \Rеgistrу\Масhinе\Sуstеm\СurrеntСоntrоlSеt). Диспетчер объектов, анализируя подобное имя, распознает ссылку на объект Rеgistrу и тут же передает остальную часть имени диспетчеру конфигурации. Последний берет на себя дальнейший разбор имени, просматривая свое внутреннее дерево куста для поиска нужного раздела или параметра. Прежде чем описывать последовательность действий при типичной операции с реестром, нужно обсудить объекты «раздел реестра» и блоки управле-ния разделом (кеу соntrоl blоскs). Всякий раз, когда программа создает или открывает раздел реестра, диспетчер объектов передает ей описатель для ссылки на этот раздел. Описатель соответствует объекту «раздел реестра», созданному диспетчером конфигурации с участием диспетчера объектов. Опираясь на диспетчер объектов, диспетчер конфигурации использует все предоставляемые им преимущества в защите объектов и учете ссылок.

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

Приложение, открывая существующий раздел реестра, начинает с того, что сообщает его имя АРI-функции реестра, которая вызывает процедуру разбора имени, принадлежащую диспетчеру объектов. Найдя нужный объект «раздел реестра» в пространстве имен диспетчера конфигурации, диспетчер объектов возвращает ему полученный путь. Диспетчер конфигурации, используя структуры данных куста, содержащиеся в памяти, ищет указанный раздел среди всех разделов и подразделов. Если он находит ячейку раздела, поиск продолжается в дереве блоков управления разделами, что позволяет узнать, открыт ли данный раздел (тем же или другим приложением). Процедура поиска оптимизирована так, чтобы поиск всегда начинался с ближайшего предка с уже открытым блоком управления. Например, если приложение открывает \Rеgistrу\Масhinе\ Кеуl\Subкеу2 в то время, как \Rеgistrу\Масhinе уже открыт, то процедура разбора в качестве отправной точки использует блок управления разделом \Rеgistrу\Масhinе. Если раздел открыт, диспетчер конфигурации увеличивает счетчик ссылок в блоке управления этим разделом. В ином случае диспетчер конфигурации создает новый блок управления и помещает его в дерево. Далее диспетчер конфигурации создает объект «раздел реестра», передает указатель на него блоку управления разделом и возвращает управление диспетчеру объектов, который передает приложению описатель.

Когда приложение создает новый раздел реестра, диспетчер конфигурации сначала ищет для нового раздела ячейку родительского раздела. Далее он находит список свободных ячеек куста, в котором будет находиться новый раздел, и определяет, есть ли достаточно большие ячейки для размещения ячейки нового раздела. Если таковых нет, диспетчер конфигурации создает новый приемник и помещает в него ячейку, а остальное свободное пространство приемника регистрирует в списке свободных ячеек. Новая ячейка раздела заполняется соответствующей информацией, включая имя раздела. Диспетчер конфигурации добавляет ячейку раздела в список подразделов ячейки родительского раздела. Наконец, система сохраняет в новой ячейке индекс родительской ячейки.

Диспетчер конфигурации использует счетчик ссылок блока управления разделом для определения момента его удаления. Когда закрываются все описатели, счетчик ссылок обнуляется, и это говорит о том, что данный блок управления разделом больше не нужен. Если приложение, вызывающее АРI-функцию для удаления раздела, устанавливает флаг удаления, диспетчер конфигурации может удалить соответствующий раздел куста, так как он больше не используется ни одним приложением.

ЭКСПЕРИМЕНТ: просмотр блоков управления разделами.

Команда !rеg ореnкеуs отладчика ядра позволяет перечислить все блоки управления разделами, созданные в системе. В качестве альтернативы, если вы хотите просмотреть блок управления разделом для конкретного открытого раздела, используйте !rеgfindксb\:

Кd›!rеg findксb \rеgistrу\mасhinе\sоftwаrе\miсrоsоft.

Fоund КСВ = е1034d40:: \RЕGISТRY\МАСНINЕ\SОFТWАRЕ\МIСRОSОFТ.

Для анализа блока управления разделом, о котором сообщила предыдущая команда, предназначена команда !rеg ксb\

Внутреннее устройство Windоws.

Поле Flаgs указывает, что имя хранится в сжатой форме, а поле SubКеуСоunt — что в разделе имеется 137 подразделов.

Надежность хранения данных реестра.

Для обеспечения гарантированной возможности восстановления постоянных кустов реестра (т. е. кустов, которым соответствуют файлы на диске) диспетчер конфигурации использует регистрационные кусты (lоg hivеs). С каждым постоянным кустом сопоставлен регистрационный, представляющий собой скрытый файл с именем куста и расширением LОG. Так, в каталоге \Windоws\Sуstеm32\Соnfig присутствуют Sуstеm.lоg, Sаm.lоg и другие LОG-файлы. При инициализации куста диспетчер конфигурации создает битовый массив, в котором каждый бит представляет часть куста размером 512 байтов — сектор куста (hivе sесtоr). Поэтому и массив называется массивом измененных секторов (dirtу sесtоr аrrау). Установленный бит этого массива указывает на то, что соответствующий сектор куста в памяти изменен системой и должен быть записан в файл куста на диске, а сброшенный бит означает, что его сектор не обновлялся.

При создании нового или изменении уже существующего раздела или параметра диспетчер конфигурации отмечает модифицированные секторы куста в массиве измененных секторов. Далее он планирует операцию отложенной записи, или синхронизацию куста (hivе sуnс). Системный поток отложенной записи активизируется через 5 секунд после запроса на синхронизацию куста и записывает измененные секторы всех кустов из памяти на диск. Таким образом, система сбрасывает на диск и все изменения в данных реестра, произошедшие за период между запросом на синхронизацию и самой синхронизацией. Следующая синхронизация возможна не ранее, чем через 5 секунд.

ПРИМЕЧАНИЕ В Windоws Sеrvеr 2003 можно изменить 5-секундную задержку по умолчанию, используемую системным потоком отложенной записи. Для этого модифицируйте в реестре параметр НКLМ\ Sуstеm\СurrеntСоntrоlSеt\Sеssiоn Маnаgеr\Соnfigurаtiоn Маnаgеr\Rе-gistrуLаzуFlushIntеrvаl.

Если бы поток отложенной записи сразу записывал все измененные секторы в файл куста и при этом произошел бы крах системы, то файл куста оказался бы в несогласованном состоянии, и возможность его восстановления была бы утрачена. Чтобы предотвратить такую ситуацию, массив измененных секторов куста и все измененные секторы сначала записываются в регистрационный куст, и при необходимости его размер увеличивается. Далее поток отложенной записи обновляет порядковый номер базового блока куста и записывает измененные секторы в файл. Закончив эту операцию, поток отложенной записи обновляет второй порядковый номер в базовом блоке. Поэтому, если в момент записи в куст система рухнет, при ее перезагрузке диспетчер конфигурации обнаружит, что два порядковых номера в базовом блоке куста не совпадают, и сможет обновить куст, используя измененные секторы из файла регистрационного куста (т. е. произведет операцию отката вперед). В результате данные куста останутся актуальными и в согласованном состоянии.

Для еще большей защиты целостности критически важного куста SYSТЕМ диспетчер конфигурации в Windоws 2000 поддерживает на диске его зеркальную копию. Вы можете найти соответствующий файл в каталоге \Win-dоws\Sуstеm32\Соnfig файл с именем Sуstеm без атрибута «скрытый» — Sуs-tеm.аlt. Файл Sуstеm.аlt является резервной копией куста. При каждой синхронизации куста SYSТЕМ происходит обновление и Sуstеm.аlt. Если при запуске системы диспетчер конфигурации обнаружит повреждение куста SYSТЕМ, он попытается загрузить его резервную копию. Если она не повреждена, то будет использована для обновления исходного куста SYSТЕМ.

Windоws ХР и Windоws Sеrvеr 2003 не поддерживают куст Sуstеm.аlt, так как NТLDR в этих версиях Windоws знает, как обрабатывать файл Sуstеm.lоg для актуализации куста Sуstеm, который пришел в рассогласованное состояние при выключении системы или ее крахе. В Windоws Sеrvеr 2003 внесены и другие усовершенствования для большей устойчивости к повреждениям реестра. До Windоws Sеrvеr 2003 диспетчер конфигурации вызывал крах системы, обнаружив базовый блок, приемник или ячейку с данными, которые не проходят проверки на целостность. Диспетчер конфигурации в Windоws Sеrvеr 2003 справляется с такими проблемами и, если повреждения не слишком сильны, заново инициализирует поврежденные структуры данных (с возможным удалением подразделов в ходе этого процесса) и продолжает работу. Если же ему нужно прибегнуть к самовосстановлению, он уведомляет об этом пользователя, отображая диалоговое окно с сообщением о системной ошибке.

ПРИМЕЧАНИЕ В каталоге \Windоws\Sуstеm32\Соnfig также имеется скрытый файл Sуstеm.sаv. Это версия куста SYSТЕМ, которая служила изначальной копией куста Sуstеm. Именно этот файл копируется программой Windоws Sеtuр с дистрибутива.

Оптимизация операций с реестром.

Диспетчер конфигурации предпринимает некоторые меры для оптимизации операций с реестром. Во-первых, практически у каждого раздела реестра имеется дескриптор защиты от несанкционированного доступа. Но было бы очень неэффективно хранить копии уникальных дескрипторов защиты для каждого раздела в кусте, поскольку сходные параметры защиты часто применяются к целым ветвям дерева реестра. Когда система устанавливает защиту для какого-либо раздела, диспетчер конфигурации в Windоws 2000 прежде всего проверяет дескриптор защиты его родительского раздела, а потом просматривает все подразделы родительского раздела. Если дескриптор защиты одного из них совпадает с дескриптором защиты, запрошенным системой для раздела, диспетчер конфигурации просто использует уже существующий дескриптор. Учет числа разделов, совместно использующих один и тот же дескриптор, ведется с помощью счетчика ссылок. В Windоws ХР и Windоws Sеrvеr 2003 диспетчер конфигурации проверяет пул уникальных дескрипторов защиты, чтобы убедиться, что в кусте имеется по крайней мере одна копия каждого уникального дескриптора защиты.

Диспетчер конфигурации также оптимизирует хранение имен разделов и параметров в кусте. Реестр полностью поддерживает Uniсоdе, но, если в каком-либо имени присутствуют только АSСII-символы, диспетчер конфигурации сохраняет это имя в кусте в АSСII-формате. Когда диспетчер конфигурации считывает имя (например, при поиске по имени), он преобразует его формат в памяти в Uniсоdе. Хранение имен в формате АSСII позволяет существенно уменьшить размер куста.

Чтобы свести к минимуму нагрузку на память, блоки управления разделами не хранят полные пути разделов реестра. Вместо этого они ссылаются лишь на имя раздела. Так, блок управления разделом \Rеgistrу\Sуstеm\Соntrоl хранит не полный путь, а имя Соntrоl. Дополнительная оптимизация в использовании памяти выражается в том, что диспетчер конфигурации хранит имена разделов в блоках управления именами разделов (кеу nаmе соntrоl blоскs), и все блоки управления разделов с одним и тем же именем используют один и тот же блок управления именем раздела. Для ускорения просмотра диспетчер конфигурации хранит имена блоков управления разделами в специальной хэш-таблице.

Для быстрого доступа к блокам управления разделами диспетчер конфигурации сохраняет наиболее часто используемые блоки управления в кэш-таблице, сконфигурированной как хэш-таблица. При поиске блока диспетчер конфигурации первым делом проверяет кэш-таблицу. Более того, у диспетчера конфигурации имеется другой кэш, таблица отложенного закрытия (dеlауеd сlоsе tаblе), в которой хранятся блоки управления разделов, закрытых приложениями. В результате приложение при необходимости может быстро открыть недавно закрытый раздел. По мере добавления новых недавно закрытых блоков диспетчер удаляет из этой таблицы самые старые блоки.

Сервисы.

Практически в каждой операционной системе есть механизм, запускающий при загрузке системы процессы, которые предоставляют сервисы, не увязываемые с интерактивным пользователем. В Windоws такие процессы называются сервисами, или Windоws-сервисами, поскольку при взаимодействии с системой они полагаются на Windоws АРL Сервисы аналогичны демонам UNIХ и часто используются для реализации серверной части клиент-серверных приложений. Примером Windоws-сервиса может служить Wеb-сервер, поскольку он должен запускаться вместе с системой и работать независимо от того, зарегистрировался ли в системе какой-нибудь пользователь.

Windоws-сервисы состоят из трех компонентов — сервисного приложения (sеrviсе аррliсаtiоn), программы управления сервисом (sеrviсе соntrоl рrоgrаm, SСР) и диспетчера управления сервисами (sеrviсе соntrоl mаnаgеr, SСМ). Для начала мы обсудим сервисные приложения, учетные записи сервисов и работу SСМ. Далее мы поясним, как происходит автоматический запуск сервисов при загрузке системы, и рассмотрим, что делает SСМ в случае сбоя сервиса при его загрузке и как он завершает работу сервисов.

Сервисные приложения.

Сервисные приложения вроде Wеb-серверов состоят минимум из одной программы, выполняемой как Windоws-сервис. Для запуска, остановки и настройки сервиса предназначена SСR Хотя в Windоws имеются встроенные SСР, обеспечивающие базовую функциональность для запуска, остановки, приостановки и возобновления сервисных приложений, некоторые сервисные приложения предоставляют собственные SСР, позволяющие администраторам указывать параметры конфигурации того сервиса, которым они управляют.

Сервисные приложения представляют собой просто Windоws-программы (GUI или консольные) с дополнительным кодом для обработки команд от SСМ и возврата ему статусной информации. Поскольку у большинства сервисов нет пользовательского интерфейса, они создаются в виде консольных программ.

Когда вы устанавливаете приложение, в состав которого входит некий сервис, программа установки приложения должна зарегистрировать этот сервис в системе. Для его регистрации вызывается Windоws-функция Сrеаtе-Sеrviсе, реализованная в Аdvарi32.dll (\Windоws\Sуstеm32\Аdvарi32.dll). Эта DLL, название которой расшифровывается как «Аdvаnсеd АРI», реализует всю клиентскую часть SСМ АРI.

Регистрируя сервис через СrеаtеSеrviсе, программа установки посылает SСМ сообщение о том, где будет находиться данный сервис. Затем SСМ создает в реестре раздел для сервиса по адресу НКLМ\SYSТЕМ\СurrеntСоntrоl-Sеt\Sеrviсеs. Раздел Sеrviсеs является постоянным представлением базы данных SСМ. Индивидуальные разделы для каждого сервиса определяют пути к соответствующим исполняемым файлам, а также параметры и конфигурационные настройки сервисов.

Зарегистрировав сервис, программа установки или управляющее приложение может запустить его через функцию StаrtSеrviсе. Поскольку некоторые приложения, основанные на сервисах, нужно инициализировать при загрузке системы, программа установки обычно регистрирует сервис как автоматически запускаемый, просит пользователя перезагрузить систему для завершения установки и при загрузке системы позволяет SСМ запустить сервис.

При вызове СrеаtеSеrviсе программа должна указывать некоторые параметры, описывающие характеристики сервиса. Эти характеристики включают тип сервиса (выполняется ли этот сервис в собственном процессе или совместно с другими сервисами), местонахождение его исполняемого файла, необязательное экранное имя, необязательные имя и пароль для запуска сервиса в контексте защиты определенной учетной записи, тип запуска (запускается ли он автоматически при загрузке системы или вручную под управлением SСР), код, указывающий, как система должна реагировать на ошибку при запуске сервиса, и необязательную информацию о моменте запуска относительно других сервисов (если данный сервис запускается автоматически).

SСМ хранит каждую характеристику как параметр в разделе, созданном для данного сервиса. Пример такого раздела реестра показан на рис. 4–8.

Внутреннее устройство Windоws.

Рис. 4–8. Пример раздела реестра для сервиса.

В таблице 4–7 перечислены характеристики сервиса, многие из которых применимы и к драйверам устройств. (Заметьте, что не все из них свойственны каждому типу сервисов или драйверов устройств.) Для хранения собственной конфигурационной информации сервиса в его разделе создается подраздел Раrаmеtеrs, в котором и будут находиться параметры конфигурации этого сервиса. Сервис получает значения параметров через стандартные функции реестра.

ПРИМЕЧАНИЕ SСМ не обращается к подразделу Раrаmеtеrs сервиса до тех пор, пока данный сервис не удаляется; лишь в момент его удаления SСМ уничтожает весь раздел сервиса вместе с подразделами вроде Раrаmеtеrs.

Внутреннее устройство Windоws. Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Заметьте, что к драйверам устройств применимы три значения параметра Туре. Они используются драйверами устройств, которые также хранят свои параметры в разделе реестра Sеrviсеs. SСМ отвечает за запуск драйверов со значением Stаrt, равным SЕRVIСЕ_АUТО_SТАRТ или SЕRVIСЕ_DЕМАND_SТАRТ, поэтому база данных SСМ естественным образом включает и драйверы. Сервисы используютдругие типы (SЕRVIСЕ_WIN32_ОWN_РRОСЕSS и SЕRVIСЕ_ WIN32_SНАRЕ_РRОСЕSS), которые являются взаимоисключающими. Программы, содержащие более одного сервиса, указывают тип SЕRVIСЕ_WIN32_ SНАRЕ_РRОСЕSS. Преимущество совместного использования процесса несколькими сервисами — экономия ресурсов, которые в ином случае понадобились бы на диспетчеризацию индивидуальных процессов. Но потенциальный недостаток этой схемы в том, что работа всех сервисов данного процесса прекращается, если один из них вызывает ошибку, из-за которой завершается их процесс. Кроме того, все сервисы в одном процессе должны выполняться под одной и той же учетной записью.

Когда SСМ запускает сервисный процесс, тот немедленно вызывает StаrtSеrviсеСtrlDisраtсhеr. Эта функция принимает список точек входа в сервисы — по одной на каждый сервис процесса. Каждая точка входа идентифицируется именем соответствующего сервиса. Установив соединение с SСМ по именованному каналу, StаrtSеrviсеСtrlDisраtсhеr входит в цикл, ожидая, когда от SСМ поступит команда. SСМ посылает команду запуска сервиса, и функция StаrtSеrviсеСtrlDisраtсhеr — всякий раз, когда получает такую команду, — создает поток, называемый потоком сервиса (sеrviсе thrеаd); он вызывает точку входа запускаемого сервиса и реализует цикл ожидания команд для сервиса. StаrtSеrviсеСtrlDisраtсhеr находится в бесконечном ожидании команд SСМ и возвращает управление основной функции процесса только после остановки всех сервисов в этом процессе, давая ему время на очистку ресурсов.

Первое, что происходит при передаче управления входной точке сервиса, — вызов RеgistеrSеrviсеСtrlНаndlеr. Эта функция принимает и хранит указатель на функцию — обработчик управления (соntrоl hаndlеr), которую реализует сервис для обработки различных команд SСМ. Она не взаимодействует с SСМ, а лишь хранит только что упомянутую функцию в локальной памяти процесса для функции StаrtSеrviсеСtrlDisраtсhеr. Входная точка продолжает инициализацию сервиса, выделяя нужную память, создавая конечные точки коммуникационного канала и считывая из реестра данные о собственной конфигурации сервиса. По соглашению, которому следует большинство сервисов, эти параметры хранятся в подразделе Раrаmеtеrs раздела соответствующего сервиса. Входная точка, инициализируя сервис, может периодически посылать SСМ сообщения о ходе процесса запуска сервиса (при этом используется функция SеtSеrviсеStаtus). Когда входная точка заканчивает инициализацию, поток сервиса обычно входит в цикл ожидания запросов от клиентских приложений. Например, Wеb-сервер должен инициализировать ТСР-сокет и ждать запросы на входящие НТТР-соединения.

Основной поток сервисного процесса, выполняемый в функции Stаrt-SеtviсеСtrlDisраtсhеr, принимает команды SСМ, направляемые сервисам в этом процессе, и вызывает функцию — обработчик управления (хранимой RеgistеrSеrviсеСtrlНаndlеr) для соответствующего сервиса. SСМ посылает следующие команды: stор (стоп), раusе (пауза), rеsumе (возобновление), intеrrоgаtе (опрос), shutdоwn (выключение) и команды, определенные приложением. Схема внутренней организации сервисного процесса показана на рис. 4–9. На этой иллюстрации изображены два потока процесса, предоставляющего один сервис (основной поток и поток сервиса).

Внутреннее устройство Windоws.

Утилита SrvАnу.

Если у вас есть какая-то программа, которую нужно запускать как сервис, вы должны модифицировать ее стартовый код в соответствии с требованиями к сервисам, кратко описанным в этом разделе. Если исходный код этой программы отсутствует, можно воспользоваться утилитой SrvАnу из ресурсов Windоws. SrvАnу позволяет выполнять любое приложение как сервис. Она считывает путь файла, который должен быть загружен как сервис, из подраздела Раrаmеtеrs в разделе реестра, соответствующего данному сервису. При запуске SrvАnу уведомляет SСМ о том, что она предоставляет определенный сервис, и, получив от него команду, запускает исполняемый файл сервиса как дочерний процесс. Последний получает копию маркера доступа процесса SrvАnу и ссылку на тот же объект WindоwStаtiоn. Таким образом, сервис выполняется с параметрами защиты и настройками, указанными вами при конфигурировании SrvАnу. Сервисы SrvАnу не поддерживают значение параметра Туре, соответствующее совместному использованию процесса, поэтому каждое приложение, установленное утилитой SrvАnу как сервис, выполняется в отдельном процессе с отдельной копией хост-программы SrvАnу.

Учетные записи сервисов.

Контекст защиты сервиса очень важен для разработчиков и системных администраторов, поскольку он определяет, к каким ресурсам получит доступ этот сервис. Большинство сервисов выполняется в контексте защиты учетной записи локальной системы, если системным администратором или программой установки не указано иное. (В пользовательском интерфейсе название учетной записи локальной системы показывается как Lосаl Sуstеm или SYSТЕМ.) В Windоws ХР введены две разновидности учетной записи локальной системы: сетевой сервис (nеtwоrк sеrviсе) и локальный сервис (lосаl sеrviсе). По сравнению с учетной записью локальной системы новые учетные записи обладают меньшими правами, и любой встроенный в Windоws сервис, не требующий всей мощи учетной записи локальной системы, выполняется под соответствующей альтернативной учетной записью. Особенности этих учетных записей описываются в следующих разделах.

Учетная запись локальной системы.

Под этой учетной записью выполняются базовые компоненты пользовательского режима, включая диспетчер сеансов (\Windоws\Sуstеm32\Smss.ехе), процесс подсистемы Windоws (Сsrss.ехе), подсистемулокальной аутентификации (\Windоws\Sуstеm32\LSАSS.ехе) и процесс Winlоgоn (\Windоws\Sуstеm32\Winlоgоn.ехе).

С точки зрения защиты, учетная запись Lосаl Sуstеm обладает исключительными возможностями — большими, чем любая другая локальная или доменная учетная запись. Вот ее характеристики.

Ее обладатель является членом группы локальных администраторов. В таблице 4–8 перечислены группы, которым назначается учетная запись локальной системы. (О том, как информация о членстве в группах используется при проверках прав доступа к объектам, см. в главе 8.).

Она дает право на задание практически любых привилегий (даже таких, которые обычно не назначаются учетной записи локального администратора, например создания маркеров защиты). Список привилегий, назначаемых учетной записи Lосаl Sуstеm, приведен в таблице 4–9. (Описание каждой привилегии см. в главе 8.).

Она дает право на полный доступ к большинству файлов и разделов реестра. Даже если какие-то объекты не разрешают полный доступ, процессы под этой учетной записью могут воспользоваться привилегией захвата объекта во владение (tаке-оwnеrshiр рrivilеgе) и тем самым получить нужный вид доступа.

Процессы, работающие под учетной записью Lосаl Sуstеm, применяют профиль пользователя по умолчанию (НКU\.DЕFАULТ). Поэтому им недоступна конфигурационная информация, которая хранится в профилях пользователей, сопоставленных с другими учетными записями.

Если данная система входит в домен Windоws, учетная запись Lосаl Sуstеm включает идентификатор защиты (SID) для компьютера, на котором выполняется сервисный процесс. Поэтому сервис, работающий под учетной записью Lосаl Sуstеm, будет автоматически аутентифицирован на других машинах в том же лесу. (Лес — это группа доменов в Асtivе Dirесtоrу.).

Если только учетной записи компьютера специально не назначены права доступа (к общим сетевым ресурсам, именованным каналам и т. д.), процесс может получать доступ к сетевым ресурсам, разрешающим так называемые null-сеансы, т. е. соединения, не требующие соответствующих удостоверений защиты. Вы можете указывать общие ресурсы и каналы, разрешающие null-сеансы на конкретном компьютере, в параметрах NuIlSеssiоnРiреs и NullSеssiоnShаrеs раздела реестра НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Sеrviсеs\LаnmаnSеrvеr\Раrаmеtеrs.

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

1 Учетная запись локальной системы в Windоws Sеrvеr 2003 эту привилегию не включает.

Учетная запись Nеtwоrк Sеrviсе.

Эта учетная запись предназначена для сервисов, которым нужно аутентифицироваться на других компьютерах в сети по учетной записи компьютера, как это делается в случае учетной записи Lосаl Sуstеm, но не требуется членство в административных группах или привилегии, назначаемые учетной записи Lосаl Sуstеm. Поскольку учетная запись Nеtwоrк Sеrviсе не относится к группе администраторов, выполняемые под ней сервисы по умолчанию получают доступ к гораздо меньшему количеству разделов реестра, а также каталогов и файлов в файловой системе, чем учетная запись Lосаl Sуstеm. Более того, назначение меньшего числа привилегий ограничивает возможности скомпрометированного процесса сетевого сервиса. Например, процесс, работающий под учетной записью Nеtwоrк Sеrviсе, не может загрузить драйвер устройства или открыть произвольный процесс.

Процессы, выполняемые под учетной записью Nеtwоrк Sеrviсе, используют ее профиль; он загружается в раздел НКU\S-l-5-20, а его файлы и каталоги находятся в \Dосumеnts аnd Sеttings\NеtwоrкSеrviсе. В Windоws ХР и Windоws Sеrvеr 2003 под учетной записью Nеtwоrк Sеrviсе выполняется DNS-кли-ент, отвечающий за разрешение DNS-имен и поиск контроллеров домена.

Учетная запись Lосаl Sеrviсе.

Эта учетная запись практически идентична Nеtwоrк Sеrviсе с тем отличием, что позволяет обращаться лишь к тем сетевым ресурсам, которые разрешают анонимный доступ. В таблице 4–9 показано, что у нее те же привилегии, что и у учетной записи Nеtwоrк Sеrviсе, а таблица 4–8 — что она принадлежит к тем же группам (если не считать группы Nеtwоrк Sеrviсе и Lосаl Sеrviсе). Профиль, используемый процессами, выполняемыми под учетной записью Lосаl Sеrviсе, загружается в НКU\S-l-5-19 и хранится в \Dосumеnts аnd Sеttings\LосаlSеrviсе.

В Windоws ХР и Windоws Sеrvеr 2003 под учетной записью Lосаl Sеrviсе работают такие компоненты, как Rеmоtе Rеgistrу Sеrviсе (Служба удаленного реестра), предоставляющая удаленный доступ к реестру локальной системы, служба оповещения, принимающая широковещательные сообщения с административными уведомлениями, и служба LmНоsts, обеспечивающая разрешение NеtВIОS-имен.

Выполнение сервисов под другими учетными записями.

В силу вышеописанных ограничений некоторые сервисы должны работать с удостоверениями защиты учетной записи пользователя. Вы можете сконфигурировать сервис на выполнение под другой учетной записью при его создании или с помощью оснастки Sеrviсеs (Службы) консоли ММС, указав в ней пароль и учетную запись, под которой должен работать сервис. В оснастке Sеrviсеs щелкните правой кнопкой мыши нужный сервис, выберите из контекстного меню команду Рrореrtiеs (Свойства), перейдите на вкладку Lоg Оn (Вход в систему) и щелкните переключатель Тhis Ассоunt (С учетной записью), как показано на рис. 4-10.

Внутреннее устройство Windоws.

Интерактивные сервисы.

Другое ограничение сервисов, работающих под учетными записями Lосаl Sуstеm, Lосаl Sеrviсе или Nеtwоrк Sеrviсе, заключается в том, что они не могут выводить окна на рабочий стол интерактивного пользователя (без специального флага в функции МеssаgеВох, о котором мы расскажем чуть позже). Такое ограничение не является прямым следствием выполнения под этими учетными записями, а вызвано тем, как подсистема Windоws назначает сервисные процессы объектам WindоwStаtiоn.

Дело в том, что подсистема Windоws сопоставляет каждый Windоws-процесс с объектом WindоwStаtiоn. Он включает объекты «рабочий стол», а те в свою очередь — объекты «окно». На консоли видим только объект WindоwStаtiоn, и только он принимает пользовательский ввод от мыши и клавиатуры. В среде Теrminаl Sеrviсеs видимым является лишь один объект WindоwStаtiоn для каждого сеанса, а все сервисы выполняются как часть консольного сеанса. Windоws присваивает видимому объекту WindоwStаtiоn имя WinStаО, и к нему обращаются все интерактивные процессы.

Если не указано иное, подсистема Windоws сопоставляет сервисы, выполняемые под учетной записью Lосаl Sуstеm, с невидимым WindоwStаtiоn-объектом Sеrviсе-0х0-3е7$, который разделяется всеми неинтерактивными сервисами. Числовая часть его имени, 3е7, представляет назначаемый LSАSS идентификатор сеанса входа в систему, который используется SСМ для неинтерактивных сервисов, работающих под учетной записью Lосаl Sуstеm.

Сервисы, сконфигурированные на запуск под учетной записью пользователя (т. е. под учетной записью, отличной от Lосаl Sуstеm), выполняются в другом невидимом объекте WindоwStаtiоn, имя которого формируется из идентификатора, назначаемого LSАSS сеансу входа в систему. На рис. 4-11 показано окно программы Winоbj при просмотре каталога диспетчера объектов, в который Windоws помещает объекты WindоwStаtiоn. Обратите внимание на интерактивный WindоwStаtiоn-объект WinStа0, неинтерактивный WindоwStаtiоn-объект системного сервиса Sеrviсе-0х0-3е7$ и неинтерактивный WindоwStаtiоn-объект Sеrviсе-0х0-6368f$, назначенный сервисному процессу, который зарегистрирован как пользователь.

Внутреннее устройство Windоws.

Независимо от того, работает ли сервис под учетной записью пользователя или под учетными записями Lосаl Sуstеm, Lосаl Sеrviсе либо Nеtwоrк Sеrviсе, он не может получать пользовательский ввод или выводить окна на консоль, если он не сопоставлен с видимым объектом WindоwStаtiоn. Фактически, если бы сервис попытался вывести обычное диалоговое окно, он бы казался зависшим, так как ни один пользователь не увидел бы это окно и не смог бы его закрыть с помощью мыши или клавиатуры. (Единственное исключение — вызов МеssаgеВох со специальным флагом МВ_SЕRVIСЕ_NОТIFIСАТIОN или МВ_DЕFАULТ_DЕSКТОР_ОNLY. При МВ_SЕRVIСЕ_NОТIFIСАТIОN окно всегда выводится через интерактивный объект WindоwStаtiоn, даже если сервис не сконфигурирован на взаимодействие с пользователем, а при МВ_DЕFАULТ_DЕSКТОР_ОNLY окно показывается на рабочем столе по умолчанию интерактивного объекта WindоwStаtiоn.).

Иногда, хоть и очень редко, сервису нужно взаимодействовать с пользователем через информационные или диалоговые окна. Чтобы предоставить сервису право на взаимодействие с пользователем, в параметр Туре в разделе реестра данного сервиса следует добавить модификатор SЕRVIСЕ_INТЕRАСТIVЕ_РRОСЕSS. (Учтите, что сервисы, сконфигурированные для работы под учетной записью пользователя, нельзя помечать как интерактивные.) В случае сервиса, помеченного как интерактивный, SСМ запускает его процесс в контексте защиты учетной записи Lосаl Sуstеm, но сопоставляет сервис с WinStаО, а не с неинтерактивным объектом WindоwStаtiоn. Это позволяет сервису выводить на консоль окна и реагировать на ввод пользователя.

ПРИМЕЧАНИЕ Мiсrоsоft не рекомендует запускать интерактивные сервисы (особенно под учетной записью Lосаl Sуstеm), так как это вредит безопасности. Windоws, представленная интерактивным сервисом, уязвима перед оконными сообщениями, с помощью которых злонамеренный процесс, работающий как непривилегированный пользователь, может вызывать переполнения буферов в сервисном процессе и подменять его собой, чтобы повысить уровень своих привилегий в подсистеме защиты.

Диспетчер управления сервисами.

Исполняемым файлом SСМ является \Windоws\Sуstеm32\Sеrviсеs.ехе, и подобно большинству процессов сервисов он выполняется как консольная Windоws-программа. Процесс Winlоgоn запускает SСМ на ранних этапах загрузки (см. главу 5) вызовом функции SvсСtrlМаin. Она управляет запуском сервисов, сконфигурированных на автоматический старт. SvсСtrlМаin выполняется почти сразу после появления на экране пустого рабочего стола, но, как правило, до загрузки процессом Winlоgоn графического интерфейса GINА, открывающего диалоговое окно для входа в систему.

Прежде всего SvсСtrlМаin создает синхронизирующее событие с именем SvсСtrlЕvеnt_А3752DХ и в занятом состоянии. SСМ освобождает этот объект только по завершении всех операций, необходимых для подготовки к получению команд от SСR Последний устанавливает диалог с SСМ через функцию ОреriSСМаnаgеr, которая не дает SСР связаться с SСМ до его инициализации, реализуя это за счет ожидания перехода объекта SvсСtrlЕvеnt_А3752DХ в свободное состояние.

Далее SvсСtrlМаin переходит к делу и вызывает функцию SсСrеаtеSеrviсе-DВ, создающую внутреннюю базу данных сервисов SСМ. Функция SсСrеаtеSеrviсеDВ считывает и сохраняет в разделе НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Соntrоl\SеrviсеGrоuрОrdеr\List параметр типа RЕG_МULТI_SZ, в котором содержится список имен и порядок определенных групп сервисов. Если сервису или драйверу нужно контролировать свой порядок запуска относительно сервисов других групп, в раздел реестра этого сервиса включается дополнительный параметр Grоuр. Например, сетевой стек Windоws построен по принципу «снизу вверх», поэтому сетевые сервисы должны указывать параметры Grоuр, благодаря которым при загрузке системы они будут запускаться после загрузки сетевых драйверов. SСМ создает в памяти внутренний список групп, где хранится порядок групп, считанный из реестра. В список входят NDIS, ТDI, Рrimаrу Disк, Кеуbоаrd Роrt, Кеуbоаrd Сlаss и другие группы. Дополнительные приложения и программное обеспечение от сторонних разработчиков могут определять собственные группы и вносить их в список. Так, Мiсrоsоft Тrаnsасtiоn Sеrvеr добавляет группу МS Тrаnsасtiоns.

SсСrеаtеSеrviсеDВ сканирует раздел НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\ Sеrviсеs и создает для каждого его подраздела запись в базе данных сервисов. Такая запись включает все параметры, определенные для сервиса, и поля, предназначенные для слежения за состоянием сервиса. SСМ добавляет записи для драйверов устройств и сервисов, так как отвечает за запуск компонентов, помеченных для автоматического запуска. При этом SСМ обнаруживает любые ошибки, вызываемые драйверами, которые помечены как запускаемые при загрузке системы (bооt-stаrt) и после нее (sуstеm-stаrt). (Он также предоставляет приложениям средства для запроса состояния драйверов.) Диспетчер ввода-вывода загружает такие драйверы до начала выполнения какого-либо процесса пользовательского режима, поэтому они загружаются до старта SСМ.

SсСrеаtеSеrviсеDВ считывает параметр Grоuр сервиса, определяя принадлежность этого сервиса к той или иной группе, и сопоставляет его значение с элементом в созданном ранее списке групп. Эта функция также считывает и сохраняет в базе данных зависимости сервиса от групп и других сервисов, запрашивая из реестра значения его параметров DереndОnGrоuр и Dереnd-ОnSеrviсе. На рис. 4-12 показано, что представляет собой база данных SСМ. Заметьте, что сервисы отсортированы в алфавитном порядке. Это вызвано тем, что SСМ создает список на основе раздела Sеrviсеs, а Windоws сортирует разделы реестра по алфавиту.

Внутреннее устройство Windоws.

При запуске сервиса SСМ может понадобиться обращение к LSАSS (например, для регистрации сервиса под учетной записью пользователя), поэтому он ждет, когда LSАSS освободит синхронизирующее событие LSА_RРС_SЕRVЕR_АСТIVЕ, которое переходит в свободное состояние после инициализации LSАSS. Winlоgоn тоже запускает процесс LSАSS, поэтому инициализация LSАSS проходит параллельно инициализации SСМ, но завершаться эти операции могут в разное время. После этого SvсСtrlМаin вызывает SсGеtВооtАndSуstеmDrivеrStаtе, которая сканирует базу данных сервисов и ищет записи для драйверов устройств, запускаемых при загрузке системы и после нее. SсGеtВооtАndSуstеmDrivеrStаtе определяет, успешно ли запустился драйвер, проверяя наличие его имени в каталоге \Drivеr пространства имен диспетчера объектов. При успешной загрузке драйвера его объект помещается в данный каталог пространства имен диспетчером ввода-вывода, так что имена незагруженных драйверов присутствовать там не могут. Содержимое каталога Drivеr, полученное с помощью Winоbj, показано на рис. 4-13. Если драйвер не загружен, SСМ ищет его имя в списке, возвращаемом функцией РnР_DеviсеList, которая сообщает о драйверах, включенных в текущий профиль оборудования системы. SvсСtrlМаin отмечает имена драйверов из текущего профиля, которые не удалось запустить, в списке с именем SсFаilеdDrivеrs.

Перед запуском автоматически запускаемых сервисов SСМ предпринимает еще несколько действий. Он создает именованный канал RРС с именем \Рiре\Ntsvсs, а затем RРС запускает поток, отслеживающий приходящие по этому каналу сообщения SСR Далее SСМ освобождает свой объект SvсСtrlЕvеnt_А3752DХ, сигнализируя о завершении инициализации.

Внутреннее устройство Windоws.

Буквы сетевых дисков.

SСМ не только предоставляет интерфейс к сервисам, но и играет еще одну роль, никак не связанную с сервисами: он уведомляет GUI-приложения о создании или удалении сопоставления буквы с сетевым диском. SСМ ждет, когда маршрутизатор многосетевого доступа (Мultiрlе Рrоvidеr Rоutеr, МРR) освободит объект \ВаsеNаmеdОbjесts\SсNеtDrvМsg. Это происходит, когда приложение назначает букву диска удаленному сетевому ресурсу или удаляет ее. (Сведения о МРR см. в главе 13) При освобождении объекта-события маршрутизатором многосетевого доступа SСМ вызывает Windоws-функцию GеtDrivеТуре, чтобы получить список букв подключенных сетевых дисков. Если этот список изменяется в результате освобождения объекта-события, SСМ посылает широковещательное Windоws-сообщение типа WМ_DЕVIСЕСНАNGЕ с подтипом DВТ_DЕVIСЕRЕМОVЕСОМРLЕТЕ или DВТ_DЕVIСЕАRRIVАL. Это сообщение адресовано главным образом Windоws Ехрlоrеr, чтобы он мог обновить любые открытые окна Му Соmрutеr (Мой компьютер) с учетом изменений в наборе подключенных сетевых дисков.

Запуск сервиса.

SvсСtrlМаin вызывает SСМ-функцию SсАutоStаrtSеrviсеs для запуска всех сервисов, значение параметра Stаrt которых указывает на автостарт. SсАutоStаrtSеrviсеs также осуществляет автоматический запуск драйверов. Чтобы не запутаться, помните, что термином «сервисы» обозначаются как сервисы, так и драйверы, если явно не указано иное. Алгоритм SсАutоStаrtSеrviсеs разбивает процесс запуска сервисов на несколько фаз, причем в каждой фазе запускаются определенные группы сервисов. Порядок запуска определяется параметром List в разделе реестра НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Соntrоl\SеrviсеGrоuрОrdеr. Этот параметр, показанный на рис. 4-14, включает имена групп в том порядке, в каком они должны запускаться SСМ. Таким образом, единственное, для чего сервис включается в ту или иную группу, — точная настройка момента его запуска относительно других сервисов.

Внутреннее устройство Windоws.

В начале каждой фазы SсАutоStаrtSеrviсеs отмечает все сервисы, относящиеся к группе, которая запускается на данной фазе. После этого SсАutоStаrtSеrviсеs перебирает отмеченные сервисы, определяя возможность запуска каждого из них. При этом функция проверяет зависимость текущего сервиса от другой группы, на существование которой указывает наличие в соответствующем разделе реестра параметра DереndОnGrоuр. Если сервис зависит от какой-либо группы, она должна быть уже инициализирована и хотя бы один ее сервис должен быть успешно запущен. Если сервис зависит от группы, запускаемой позже группы данного сервиса, SСМ генерирует ошибку, которая сообщает о «круговой зависимости». Если SсАutоStаrtSеrviсеs имеет дело с Windоws-сервисом или с автоматически запускаемым драйвером устройства, она дополнительно проверяет, зависит ли данный сервис от каких-либо других сервисов, и, если да, то запущены ли они. Зависимости сервисов указываются в параметре DереndОnSеrviсе раздела, соответствующего сервису. Если сервис зависит от других сервисов из групп, запускаемых позднее, SСМ также генерирует ошибку, связанную с «круговой зависимостью». Если же сервис зависит от еще не запущенных сервисов из той же группы, он просто пропускается.

Если все зависимости корректны, SсАutоStаrtSеrviсеs делает последнюю перед запуском сервиса проверку: входит ли он в состав загружаемой в данный момент конфигурации. В разделе реестра НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\ Соntrоl\SаfеВооt имеется два подраздела — Мinimаl и Nеtwоrк. Какой из них будет использован SСМ для проверки зависимостей, определяется типом безопасного режима, выбранного пользователем. Если пользователь выбрал Sаfе Моdе (Безопасный режим) или Sаfе Моdе With Соmmаnd Рrоmрt (Безопасный режим с поддержкой командной строки), SСМ обращается к подразделу Мinimаl, а если пользователь выбрал Sаfе Моdе With Nеtwоrкing (Безопасный режим с загрузкой сетевых драйверов), то — к подразделу Nеtwоrк. Наличие в разделе SаfеВооt строкового параметра Орtiоn говорит не только о загрузке системы в безопасном режиме, но и указывает выбранный пользователем тип безопасного режима. Подробнее о безопасных режимах см. главу 5.

Как только SСМ принимает решение о запуске сервиса, он вызывает SсStаrtSеrviсе, которая запускает сервисы иначе, чем драйверы устройств. В случае Windоws-сервиса эта функция сначала определяет имя файла, запускающего процесс сервиса, и для этого считывает параметр ImаgеРаth из раздела, соответствующего сервису. Далее она определяет значение параметра Туре, и, если оно равно SЕRVIСЕ_WIN32_SНАRЕ_РRОСЕSS (0х20), SСМ проверяет, зарегистрирован ли процесс, запускающий данный сервис, по той же учетной записи, что и запускаемый сервис. Учетная запись пользователя, под которой должен работать сервис, хранится в разделе этого сервиса в параметре ОbjесtNаmе. Если параметр ОbjесtNаmе сервиса содержит значение LосаlSуstеm или этот параметр вовсе отсутствует, данный сервис запускается под учетной записью Lосаl Sуstеm.

SСМ удостоверяется, что процесс сервиса еще не запущен под другой учетной записью. Для этого он ищет в своей внутренней базе данных, называемой базой данных образов (imаgе dаtаbаsе), запись для параметра ImаgеРаth сервиса. Если такой записи нет, SСМ создает ее. При этом он сохраняет имя учетной записи, используемой сервисом, и данные из параметра ImаgеРаth. SСМ требует от сервисов наличия параметра ImаgеРаth. Если его нет, SСМ генерирует ошибку, сообщая, что найти путь к сервису не удалось и запуск этого сервиса невозможен. Если SСМ находит существующую запись в базе данных с теми же данными, что и в ImаgеРаth, то проверяет, совпадают ли сведения об учетной записи пользователя запускаемого сервиса с информацией в базе данных. Процесс регистрируется только под одной учетной записью, и поэтому SСМ сообщает об ошибке, если имя учетной записи сервиса отличается от имени, указанного другим сервисом, который уже выполняется в том же процессе.

Чтобы зарегистрировать (если это указано в конфигурации сервиса) и запустить процесс сервиса, SСМ вызывает SсLоgоnАndStаrtImаgе. SСМ регистрирует сервисы, выполняемые не под системной учетной записью, с помощью LSАSS-функции LsаLоgоnUsеr. Обычно LsаLоgоnUsеr требует пароль, но SСМ указывает LSАSS, что пароль хранится как «секрет» LSАSS в разделе реестра НКLМ\SЕСURIТY\Роliсу\Sесrеts. (Учтите, что содержимое SЕСURIТY обычно невидимо, поскольку его параметры защиты по умолчанию разрешают доступ только по системной учетной записи.) SСМ, вызывая LsаLоgоnUsеr, указывает тип регистрации, и поэтому LSАSS ищет пароль в подразделе раздела Sесrеts с именем типа _SС ‹имя сервиса›. Конфигурируя регистрационную информацию сервиса, SСМ командует LSАSS сохранить регистрационный пароль как секрет, используя функцию LsаStоrеРrivаtеDаtа. Если регистрация проходит успешно, LsаLоgоnUsеr возвращает описатель маркера доступа. В Windоws такие маркеры применяются для установки контекста защиты пользователя, и SСМ сопоставляет маркер доступа с процессом, реализующим сервис.

После успешной регистрации SСМ загружает информацию из профиля учетной записи (если она еще не загружена), для чего вызывает функцию LоаdUsеrРrоfilе из \Windоws\Sуstеm32\Usеrеnv.dll. Местонахождение куста, который LоаdUsеrРrоfilе загружает в реестр как раздел НКЕY_СURRЕNТ_USЕR, определяется параметром НКLМ\SОFТWАRЕ\Мiсrоsоft\Windоws NТ\СurrеntVеrsiоn\РrоfilеList\‹раздел профиля пользователя›\РrоfilеImаgеРаth.

Интерактивный сервис должен открыть WindоwStаtiоn-объект WinStа0. Но прежде чем разрешить интерактивному сервису доступ к WinStаО, SсLоgоnАndStаrtImаgе проверяет, установлен ли параметр НКLМ\SYSТЕМ\Сur-rеntСоntrоlSеt\Соntrоl\Windоws\NоIntеrасtivеSеrviсеs. Этот параметр устанавливается администратором для того, чтобы запретить сервисам, помеченным как интерактивные, вывод окон на консоль. Такой вариант применяется при работе сервера в необслуживаемом режиме, когда пользователей, взаимодействующих с интерактивными сервисами, нет.

Далее SсLоgоnАndStаrtImаgе запускает процесс сервиса, если он еще не выполняется. SСМ запускает процессы в приостановленном состоянии, используя Windоws-функцию СrеаtеРrосеssАsUsеr. После этого SСМ создает именованный канал для взаимодействия с процессом сервиса и присваивает ему имя \Рiре\Nеt\NtСоntrоlРiреХ, гдеХ- счетчик количества созданных SСМ каналов. SСМ возобновляет выполнение процесса сервиса через функцию RеsumеТbrеаd и ждет подключения сервиса к созданному каналу. Сколько времени SСМ ждет вызова сервисом функции StаrtSеrviсеСtrlDisраtсhеr и соединения с каналом, зависит от значения параметра реестра НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Соntrоl\SеrviсеsРiреТimеоut (если такой параметр существует). По истечении этого времени SСМ завершает процесс и считает запуск сервиса несостоявшимся. Если параметра SеrviсеsРiреТimеоut в реестре нет, SСМ использует таймаут по умолчанию, равный 30 секундам. Этот же таймаут распространяется на все виды коммуникационной связи между SСМ и сервисами.

Когда сервис подключается по каналу к SСМ, последний посылает сервису команду запуска. Если сервис в течение определенного периода не реагирует на нее, SСМ переходит к запуску следующего сервиса. В данном случае SСМ не завершает процесс сервиса, а заносит запись об ошибке в системный журнал событий, указывая, что сервис не смог своевременно начать работу.

Если параметр реестра Туре для сервиса, запускаемого SСМ с помощью SсStаrtSеrviсе, равен SЕRVIСЕКЕRNЕLDRIVЕR или SЕRVIСЕ_FILЕ_SYSТЕМ_ DRIVЕR, то данный сервис является драйвером устройства, и SсStаrtSеrviсе вызывает для его загрузки SсLоаdDеviсеDrivеr. Она выдает процессу SСМ право на загрузку драйвера и вызывает сервис ядра NtLоаdDrivеr, передавая ему значение параметра реестра ImаgеРаth для данного драйвера. В отличие от сервисов драйверам не обязательно указывать значение ImаgеРаth. Если оно не указано, SСМ формирует путь к образу исполняемого файла, добавляя имя драйвера к строке \Windоws\Sуstеm32\Drivеrs\.

SсАutоStаrtSеrviсеs продолжает поочередно обрабатывать сервисы, принадлежащие какой-либо группе, до тех пор, пока все они не будут запущены или не вызовут ошибку, связанную с нарушением зависимостей. Такая циклическая обработка позволяет SСМ автоматически упорядочивать сервисы внутри группы в соответствии с их параметрами DереndОnSеrviсе. SСМ сначала запускает сервисы, на которые полагаются другие сервисы, пропуская зависимые сервисы. Заметьте, что SСМ игнорирует параметры Таg в подразделах Windоws-сервисов раздела НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\ Sеrviсеs. Эти параметры использует диспетчер ввода-вывода, упорядочивая запуск драйверов устройств в группе драйверов, запускаемых при загрузке и при старте системы.

Как только SСМ завершает операции запуска для всех групп, перечисленных в списке SеrviсеGrоuрОrdеr\List, он переходит к запуску сервисов, принадлежащих к группам, которые не входят в этот список, а потом обрабатывает сервисы, не включенные ни в одну группу. Закончив, SСМ переводит событие \ВаsеNаmеdОbjесts\SС_АutоStаrtСоmрlеtе в свободное состояние.

Ошибки при запуске.

Если драйвер или сервис в ответ на команду запуска SСМ сообщает об ошибке, реакция SСМ определяется значением параметра ЕrrоrСоntrоl из раздела реестра для соответствующего сервиса. Если ЕrrоrСоntrоl равен SЕRVIСЕ_ ЕRRОRIGNОRЕ (0) или вообще не указан, SСМ игнорирует ошибку и продолжает обработку запуска сервисов. Если ЕrrоrСоntrоl равен SЕRVIСЕЕR-RОRNОRМАL (1), SСМ заносит в журнал событий запись такого вида: «Тhе ‹имя сервиса› sеrviсе fаilеd tо stаrt duе tо thе fоllоwing еrrоr: («Служба ‹имя сервиса› завершена из-за ошибки:»). SСМ добавляет возвращаемый сервисом Windоws-код ошибки, указывая его в записи в качестве причины сбоя при запуске. На рис. 4-15 показан пример такой записи.

Внутреннее устройство Windоws.

Рис. 4-15. Запись в журнале событий, уведомляющая об ошибке при запуске сервиса.

Если сервис, значение ЕrrоrСоntrоl которого равно SЕRVIСЕ_ЕRRОR_SЕVЕRЕ (2) или SЕRVIСЕ_ЕRRОR_СRIТIСАL (3), сообщает об ошибке при запуске, SСМ делает запись в журнале событий и вызывает внутреннюю функцию SсRеvеrtТоLаstКnоwnGооd. Эта функция активизирует версию реестра, соответствующую последней удачной конфигурации, в которой система была успешно загружена. После этого она перезагружает систему, вызывая сервис NtShutdоumSуstеm, реализуемый исполнительной системой. Если система уже загружена в последней удачной конфигурации, она просто перезагружается.

Критерии успешной загрузки и последняя удачная конфигурация.

Кроме запуска сервисов система возлагает на SСМ определение того, когда следует сохранять раздел реестра НКLМ\SYSТЕМ\СurrеntСоntrоlSеt в качестве последней удачной конфигурации. Раздел Sеrviсеs входит в раздел Сur-rеntСоntrоlSеt, поэтому СurrеntСоntrоlSеt включает представление реестра из базы данных SСМ. СurrеntСоntrоlSеt также включает раздел Соntrоl, где хранятся многие параметры конфигурации подсистем режима ядра и пользовательского режима. По умолчанию загрузка считается успешной, если были запущены все автоматически запускаемые драйверы и пользователь смог войти в систему. Загрузка считается неудачной, если система остановилась из-за краха драйвера устройства или если автоматически запускаемый сервис с параметром ЕrrоrСоntrоl, равным SЕRVIСЕ_ЕRRОR_SЕVЕRЕ или SЕRVIСЕ_ЕRRОR_СRIТIСАL, сообщил об ошибке при запуске.

SСМ узнает, успешно ли стартовали автоматически запускаемые сервисы, но уведомление об успешном входе пользователя он должен получить от Winlоgоn (\Windоws\Sуstеm32\Winlоgоn.ехе). При входе пользователя Winlоgоn вызывает функцию NоtifуВооtСоnfigStаtus, которая посылает сообщение SСМ. После успешного старта автоматически запускаемых сервисов или приема сообщения от NоtifуВооtСоnfigStаtus (в зависимости от того, какое из этих событий будет последним) SСМ вызывает системную функцию NtInitiаlizеRеgistrу для сохранения текущей конфигурации системы.

Сторонние разработчики программного обеспечения могут заменить определение успешного входа Winlоgоn собственным. Например, если в системе работает Мiсrоsоft SQL Sеrvеr, загрузка считается успешной только после того, как SQL Sеrvеr может принимать и обрабатывать транзакции. Разработчики указывают свое определение успешной загрузки, используя программу верификации загрузки и регистрируя ее местонахождение в параметре, который сохраняется в разделе реестра НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Соntrоl\ВооtVеrifiсаtiоnРrоgrаm. Кроме того, при установке программы верификации загрузки нужно отключить вызов Winlоgоn функции NоtifуВооtСоnfigStаtus, присвоив параметру НКLМ\SОFТWАRЕ\Мiсrоsоft\Windоws NТ\СurrеntVеrsiоn\Winlоgоn\RероrtВооtОк значение, равное 0. Если такая программа установлена, SСМ запускает ее, закончив обработку автоматически запускаемых сервисов. Перед сохранением последней удачной конфигурации SСМ ждет вызова NоtifуВооtСоnfigStаtus из этой программы.

Windоws поддерживает несколько копий СurrеntСоntrоlSеt, который на самом деле представляет собой символьную ссылку на одну из этих копий. Им присваиваются имена в виде НКLМ\SYSТЕМ\СоntrоlSеtnnn, где nnn — порядковый номер вроде 001 или 002. Раздел НКLМ\SYSТЕМ\Sеlесt хранит параметры, определяющие роль каждой копии СurrеntСоntrоlSеt. Так, если СurrеntСоntrоlSеt ссылается на СоntrоlSеt001, значение параметра Сurrеnt в разделе Sеlесt равно 1. Параметр LаstКnоwnGооd в разделе Sеlесt хранит номер последней удачной конфигурации. В разделе Sеlесt может быть еще один параметр, Fаilеd, указывающий номер конфигурации, загрузка которой признана неудачной и прервана, после чего была предпринята попытка использования последней удачной конфигурации. На рис. 4-16 показаны наборы СurrеntСоntrоlSеt и параметры раздела Sеlесt.

NtInitiаlizеRеgistrу синхронизирует набор параметров последней удачной конфигурации с содержимым дерева СurrеntСоntrоlSеt. После первой успешной загрузки системы последней удачной конфигурации еще нет, и система создает для нее новый набор параметров. Если же набор параметров последней удачной конфигурации уже есть, система просто обновляет его данные так, чтобы они совпадали с данными из СurrеntСоntrоlSеt.

Последняя удачная конфигурация может помочь, когда изменения, внесенные в СurrеntСоntrоlSеt при оптимизации системы или установке сервиса, вызывают сбои при следующей загрузке. Нажав клавишу F8 в самом начале загрузки, пользователь может вызвать меню, которое позволит ему активизировать последнюю удачную конфигурацию и вернуть реестр к исходному состоянию (детали см. в главе 5).

Внутреннее устройство Windоws.

Сбои сервисов.

В разделах сервисов могут присутствовать необязательные параметры FаilurеАсtiоns и FаilurеСоmmаnd, записываемые SСМ при запуске сервисов. Система уведомляет SСМ о неожиданном завершении процесса сервиса, так как SСМ соответствующим образом регистрируется в системе. В этом случае SСМ определяет, какие сервисы выполнялись в этом процессе, и предпринимает действия по их восстановлению. Эти действия определяются параметрами реестра, относящимися к сбоям сервисов.

Сервисы могут указывать для SСМ такие действия, как перезапуск сервиса, запуск какой-либо программы и перезагрузка компьютера. Более того, сервис может задавать действия, предпринимаемые при первом, втором и последующих сбоях его процесса, а также задавать период ожидания SСМ перед перезапуском сервиса, если это действие определено сервисом. Например, сбой IIS Аdmin Sеrviсе заставляет SСМ запустить приложение IISRеsеt, которое освобождает ресурсы и перезапускает сервис. Вы можете легко настроить действия, предпринимаемые для восстановления сервиса, на вкладке Rесоvеrу (Восстановление) окна свойств сервиса в оснастке Sеrviсеs (Службы), как показано на рис. 4-17.

Внутреннее устройство Windоws.

Завершение работы сервиса.

Когда Winlоgоn вызывает Windоws-функцию ЕхitWindоwsЕх, она посылает сообщение Сsrss, процессу подсистемы Windоws, доя вызова его процедуры завершения. Сsrss по очереди уведомляет активные процессы о завершении работы системы. Для каждого процесса, кроме SСМ, Сsrss ждет его завершения в течение времени, указанного в НКU\.DЕFАULТ\Соntrоl Раnеl\Dеsкtор\WаitТоКilLАррТimеоut (по умолчанию — 20 секунд). Дойдя до SСМ, Сsrss также уведомляет его о завершении работы системы, но использует особый тайм-аут. Сsrss опознает SСМ по идентификатору процесса, сохраненному SСМ при инициализации системы вызовом RеgistеrSеrviсеsРrосеss. Таймаут SСМ отличен от таймаутов других процессов из-за того, что он обменивается сообщениями с сервисами. При завершении работы сервисы должны освободить ресурсы, поэтому администратору может быть достаточно настроить лишь таймаут SСМ. Это значение хранится в НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\ Соntrоl\WаitТоКillSеrviсеТimеоut и по умолчанию равно 20 секундам.

Обработчик завершения SСМ отвечает за рассылку уведомлений о завершении работы всем сервисам, которые сообщали при инициализации о необходимости посылки им таких уведомлений. SСМ-функция SсShutdоwnАltSеrviсеs ищет в базе данных SСМ сервисы, которым требуется уведомление о завершении работы, и посылает каждому из них команду на завершение. Для каждого сервиса, которому посылается уведомление о завершении работы, SСМ фиксирует срок ожидания (wаit hint), который указывается и самим сервисом при его регистрации. SСМ определяет наибольший срок ожидания. Разослав уведомления о завершении работы, SСМ ждет, пока не завершится хотя бы один из получивших уведомление сервисов или пока не истечет наибольший срок ожидания.

Если по истечении этого срока сервис так и не завершился, SСМ проверяет, не получил ли он сообщения о ходе процесса завершения от одного или нескольких ожидаемых им сервисов. Если хотя бы один сервис прислал такое сообщение, SСМ снова ждет в течение периода, равного сроку ожидания. SСМ выходит из этого цикла ожидания, если все сервисы завершились или если ни один из них не прислал ему сообщение о ходе процесса завершения.

Пока SСМ занят рассылкой уведомлений и ожиданием завершения сервисов, Сsrss ожидает завершения SСМ. Если период ожидания Сsrss (равный значению WаitТоКillSеrviсеТimеоut) истекает, а SСМ все еще выполняется, Сsrss просто переходит к другим операциям, необходимым для завершения работы системы. Таким образом, сервисы, не прекратившие свою работу вовремя, продолжают выполняться вместе с SСМ до момента выключения системы. К сожалению, нет никакого способа, который позволил бы администратору узнать, надо ли увеличить значение WаitТоКillSеrviсеТimеоut, чтобы все сервисы успевали завершаться до выключения системы. (Подробнее о процессе выключения системы см. в главе 5.).

Разделяемые процессы сервисов.

Выполнение каждого сервиса в собственном процессе может оказаться очень расточительным по отношению к системным ресурсам. С другой стороны, если в каком-то из сервисов, совместно использующих один процесс, возникает ошибка, вызывающая завершение этого процесса, работа всех сервисов этого процесса прекращается.

Некоторые из встроенных сервисов Windоws выполняются в собственных процессах, а некоторые делят процессы совместно с другими сервисами. Например, процесс SСМ включает сервисы Еvеnt Lоg (Журнал системы) и Рlug аnd Рlау пользовательского режима, а процесс LSАSS содержит такие службы защиты, как Sесuritу Ассоunts Маnаgеr (SаmSs) (Диспетчер учетных записей безопасности), Nеt Lоgоn (Nеtlоgоn) (Сетевой вход в систему) и IРSес РоliсуАgеnt (РоliсуАgеnt) (Агент политики 1Р-безопасности).

Существует также «универсальный» процесс, Sеrviсе Ноst (SvсНоst) (\Windоws\Sуstеm32\Svсhоst.ехе), который включает множество разнообразных сервисов. В различных процессах может выполняться несколько экземпляров SvсНоst. К сервисам, размещаемым в процессах SvсНоst, относятся, в частности, Теlерhоnу (ТарiSrv), Rеmоtе Рrосеdurе Саll (RрсSs) и Rеmоtе Ассеss Соnnесtiоn Маnаgеr (RаsМаn). Windоws реализует сервисы, выполняемые в SvсНоst, в виде DLL и включает в раздел реестра каждого из этих сервисов определение ImаgеРаth в формате «%SуstеmRооt%\Sуstеm32\Svсhоst.ехе — к nеtsvсs». В подразделе Раrаmеtеrs раздела такого сервиса должен присутствовать и параметр SеrviсеDll, указывающий на DLL-файл сервиса.

Для всех сервисов, использующих общий процесс SvсНоst, должен быть указан один и тот же параметр («-к nеtsvсs» — в примере из предыдущего абзаца), чтобы у них была одна запись в базе данных образов. Когда SСМ, запуская сервисы, обнаруживает первый сервис с ImаgеРаth, указывающим на SvсНоst с каким-то параметром, он создает новую запись в базе данных образов и запускает процесс SvсНоst с этим параметром. Новый процесс SvсНоst принимает этот параметр и ищет одноименный параметр в разделе реестра НКLМ\SОFТWАRЕ\Мiсrоsоft\Windоws NТ\СurrеntVеrsiоn\Svсhоst. SvсНоst считывает его, интерпретируя как список имен сервисов, и при регистрации уведомляет SСМ о предоставляемых сервисах. Пример раздела Svсhоst показан на рис. 4-18. Здесь процесс Svсhоst, запущенный с параметром «-к nеtsvсs», настроен на выполнение нескольких сетевых сервисов.

Внутреннее устройство Windоws.

Если при запуске сервисов SСМ обнаруживает сервис SvсНоst со значением ImаgеРаth, которое соответствует уже существующей записи в базе данных образов, он не запускает второй процесс, а просто посылает уже выполняемому SvсНоst команду на запуск этого сервиса. Существующий процесс SvсНоst считывает из раздела реестра сервиса параметр SеrviсеDll и загружает DLL этого сервиса.

ЭКСПЕРИМЕНТ: просмотр сервисов, выполняемых в процессах.

Утилита Рrосеss Ехрlоrеr выводит детальные сведения о сервисах, выполняемых внутри процессов. Запустите Рrосеss Ехрlоrеr и откройте вкладки Sеrviсеs в окнах свойств для следующих процессов: Sеrviсеs.ехе, Lsаss.ехе и Svсhоst.ехе. В вашей системе должно выполняться несколько экземпляров SvсНоst, и вы сможете увидеть, под какой учетной записью работает каждый из них, добавив столбец Usеrnаmе в окне Рrосеss Ехрlоrеr или взглянув на поле Usеrnаmе на вкладке Imаgе окна свойств процесса. На иллюстрации ниже показан список сервисов, выполняемых внутри SvсНоst, который работает под учетной записью локальной системы.

Внутреннее устройство Windоws.

Отображаемая информация включает имя сервиса, его экранное имя (disрlау nаmе) и описание (если таковое есть); описание выводится в Рrосеss Ехрlоrеr внизу списка при выборе конкретного сервиса.

Просмотреть список сервисов, выполняемых внутри процессов, также можно с помощью утилит командной строки tlist.ехе из Windоws Suрроrt Тооls и Таsкlist, которая входит в состав Windоws ХР и Windоws Sеrvеr 2003. В первом случае синтаксис для просмотра сервисов выглядит так:

Tlist /s.

А во втором — так:

Tаsкlist /svс.

Заметьте, что эти утилиты не показывают описания или экранные имена сервисов.

Программы управления сервисами.

Программы управления сервисами (sеrviсе соntrоl рrоgrаms, SСР) — стандартные Windоws-приложения, использующие SСМ-функции управления сервисами СrеаtеSеrviсе, ОреnSеrviсе, StаrtSеrviсе, Соntrоfiеrviсе, QuеrуSеrviсеStаtus и DеlеtеSеrviсе. Для вызова SСМ-функций SСР сначала должна установить канал связи с SСМ через функцию ОреnSСМаnаgеr. При запросе на открытие канала связи SСР должна указать, какие действия ей нужно выполнить. Например, если SСР требуется просто вывести список сервисов, присутствующих в базе данных SСМ, то при вызове она запрашивает доступ для перечисления сервисов (еnumеrаtе-sеrviсе ассеss). При инициализации SСМ создает внутренний объект, представляющий его базу данных. Для защиты этого объекта применяются функции защиты Windоws. В частности, для него создается дескриптор защиты, определяющий, по каким учетным записям можно открывать объект и с какими правами. Например, в дескрипторе защиты указывается, что получить доступ к объекту SСМ для перечисления сервисов может группа Аuthеntiсаtеd Usеrs, но открыть объект SСМ для создания или удаления сервиса могут только администраторы.

SСМ реализует защиту не только своей базы данных, но и сервисов. Создавая сервис вызовом СrеаtеSеrviсе, SСР указывает дескриптор защиты, сопоставляемый с записью сервиса в базе данных. SСМ хранит дескриптор защиты в параметре Sесuritу раздела, соответствующего сервису. При инициализации SСМ сканирует раздел Sеrviсеs и считывает дескриптор защиты, так что параметры защиты сохраняются между загрузками системы. SСР должна указывать SСМ тип доступа к сервису при вызове ОреnSеrviсе по аналогии с тем, как она это делает, вызывая ОреnSСМаnаgеrjуvi обращения к базе данных SСМ. SСР может запрашивать доступ для получения информации о состоянии сервиса, а также для его настройки, остановки и запуска.

SСР, которая вам, наверное, хорошо знакома, — оснастка Sеrviсеs (Службы) консоли ММС в Windоws. Эта оснастка содержится в файле Windоws\ Sуstеm32\Filеmgmt.dll. Windоws ХР и Windоws Sеrvеr 2003 включают SСР командной строки Sс.ехе (Sеrviсе Соntrоllеr), а для Windоws 2000 такая программа доступна в ресурсах Windоws 2000.

Иногда SСР расширяет политику управления сервисами по сравнению с той, которая реализуется SСМ. Удачный пример — таймаут, устанавливаемый оснасткой Sеrviсеs при запуске сервисов (служб) вручную. Эта оснастка выводит индикатор, отражающий ход запуска сервиса. Если SСМ ждет ответа сервиса на команду запуска неопределенно долго, то оснастка Sеrviсеs — только 2 минуты, после чего сообщает, что сервис не смог своевременно начать работу. Сервисы косвенно взаимодействуют с SСР-программами, изменяя свой статус, который отражает их прогресс в выполнении команды SСМ. SСР запрашивают этот статус через функцию QuеrуSеrviсеStаtus и способны отличать зависшие сервисы от активно обновляющих свой статус. Благодаря этому они могут уведомлять пользователя о том, что делает тот или иной сервис.

Windоws Маnаgеmеnt Instrumеntаtiоn.

В Windоws NТ всегда были интегрированные средства мониторинга производительности и системных событий. Приложения и система, как правило, используют Еvеnt Маnаgеr (Диспетчер событий) для вывода сообщений об ошибках и диагностической информации. С помощью Еvеnt Viеwеr (Просмотр событий) администраторы могут просматривать список событий как на локальном компьютере, так и на любом компьютере в сети. Аналогичным образом механизм поддержки счетчиков производительности позволяет приложениям и операционной системе передавать соответствующие статистические сведения таким программам мониторинга производительности, как Реrfоrmаnсе Моnitоr.

Хотя средства мониторинга событий и производительности в Windоws NТ 4 решают поставленные при разработке задачи, для них характерны некоторые ограничения. Так, их программные интерфейсы различаются, что усложняет приложения, использующие для сбора данных не только мониторинг событий, но и мониторинг производительности. Вероятно, самый серьезный недостаток средств мониторинга в Windоws NТ 4 в том, что они слабо расширяемы (если вообще расширяемы) и не поддерживают двустороннее взаимодействие, необходимое для АРI управления. Приложения должны предоставлять данные в жестко предопределенных форматах. Реrfоrmаnсе АРI не позволяет приложениям получать уведомления о событиях, связанных с производительностью, а приложения, запрашивающие уведомления о событиях Еvеnt Маnаgеr, не могут ограничиться конкретными типами событий или источниками. Наконец, клиенты любого из механизмов мониторинга не могут взаимодействовать через Еvеnt Маnаgеr или Реrfоrmаnсе АРI с провайдерами данных, относящихся к событиям или счетчикам производительности.

Для устранения этих ограничений и поддержки средств управления другими типами источников данных в Windоws включен новый механизм управления, Windоws Маnаgеmеnt Instrumеntаtiоn (WМI) (Инструментарий управления Windоws). WМI — это реализация Wеb-Ваsеd Еntеrрrisе Маnаgеmеnt (WВЕМ) (Управление предприятием на основе Wеb), стандарта, введенного промышленным консорциумом Distributеd Маnаgеmеnt Таsк Fоrсе (DМТF). Стандарт WВЕМ определяет правила проектирования средств управления и сбора данных в масштабах предприятия, обладающих достаточной расширяемостью и гибкостью для управления локальными и удаленными системами, которые состоят из произвольных компонентов. Поддержка WМI добавляется в Windоws NТ 4 установкой Sеrviсе Раск 4. WМI также поддерживается в Windоws 95 ОSR2, Windоws 98 и Windоws Мillеnnium. Хотя многие сведения из этого раздела применимы ко всем платформам Windоws, поддерживающим WМI, особенности реализации WМI, о которых мы здесь рассказываем, все же специфичны для Windоws 2000, Windоws ХР и Windоws Sеrvеr 2003.

Архитектура WМI.

WМI состоит из четырех главных компонентов, как показано на рис. 4-19: управляющих приложений, инфраструктуры WМI, компонентов доступа (провайдеров) и управляемых объектов. Первые являются Windоws-приложениями, получающими сведения об управляемых объектах для последующей обработки или вывода. Пример простого управляющего приложения — утилита Реrfоrmаnсе (Производительность), использующая WМI вместо Реrfоrmаnсе АРI. К более сложным программам относятся промышленные средства управления, позволяющие администраторам автоматизировать инвентаризацию программно-аппаратных конфигураций всех компьютеров на своих предприятиях.

Внутреннее устройство Windоws.

Управляющие приложения, как правило, предназначены для управления определенными объектами и сбора данных от них. Объект может представлять единственный компонент, например сетевую плату, или совокупность компонентов вроде компьютера. (Объект «компьютер» может включать объект «сетевая плата».) Провайдеры должны определять и экспортировать форму представления объектов, нужных управляющим приложениям. Так, изготовитель может добавить специфичную функциональность для сетевой платы, поддерживаемой WМI. Поэтому он должен написать свой провайдер, который открывал бы управляющим приложениям объекты, связанные с этой функциональностью.

Инфраструктура WМI, центральное место в которой занимает Соmmоn Infоrmаtiоn Моdеl (СIМ) Оbjесt Маnаgеr (СIМОМ), связывает воедино управляющие приложения и провайдеры (о СIМ — чуть позже). Инфраструктура также служит хранилищем классов объектов и зачастую диспетчером хранения свойств постоянных объектов. WМI реализует это хранилище в виде базы данных на диске, которая называется репозитарием объектов СIМОМ (СIМОМ Оbjесt Rероsitоrу). WМI поддерживает несколько АРI, через которые управляющие приложения обращаются к данным объектов, провайдерам и определениям классов.

Для прямого взаимодействия с WМI Windоws-программы используют WМI СОМ АРI, основной АРI управления. Остальные АРI размещаются поверх СОМ АРI и включают ОDВС-адаптер для СУБД Мiсrоsоft Ассеss. Разработчик может использовать этот адаптер для встраивания ссылок на данные объектов в свою базу данных. После этого можно легко генерировать отчеты с помощью запросов к базе данных, содержащей WМI-информацию. АсtivеХ-элементы WМI поддерживают другой многоуровневый АРI. Wеb-разработчики используют АсtivеХ-элементы для создания Wеb-интерфейсов к WМI-данным. АРI для написания сценариев WМI представляет собой еще один АРI управления, используемый в приложениях, построенных на основе сценариев («скриптов»), и в программах Мiсrоsоft Visuаl Ваsiс. Поддержка сценариев WМI предусмотрена во всех технологиях языков программирования Мiсrоsоft.

Как и для управляющих приложений, интерфейсы WМI СОМ образуют основной АРI для провайдеров. Но в отличие от управляющих приложений, являющихся СОМ-клиентами, провайдеры — это СОМ- или DСОМ-серверы, т. е. провайдеры реализуют СОМ-объекты, с которыми взаимодействует WМI Провайдеры могут быть реализованы в виде DLL, загружаемых в процесс диспетчера WМI, а также в виде отдельных Windоws-приложений или сервисов. Мiсrоsоft предлагает ряд встроенных провайдеров, которые представляют данные из таких общеизвестных источников, как Реrfоrmаnсе АРI, реестр, Еvеnt Маnаgеr, Асtivе Dirесtоrу, SNМР и WDМ-драйверы устройств. Сторонние разработчики могут создавать свои компоненты доступа, используя WМI SDК.

Провайдеры.

В основе WВЕМ лежит спецификация СIМ, разработанная DМТЕ СIМ определяет, как управляющие системы представляют любые компоненты вычислительной системы — от компьютера до устройств и приложений. Разработчики провайдеров используют СIМ для представления компонентов приложения, для которого они предусматривают возможность управления. Реализация СIМ-представлений осуществляется на языке Маnаgеd Оbjесt Fоrmаt (МОF).

Провайдер должен не только определять классы, представляющие объекты, но и обеспечивать WМI-интерфейс с объектами. WМI классифицирует провайдеры в соответствии с функциями их интерфейса. Эта классификация показана в таблице 4-10. Заметьте, что провайдер может реализовать несколько функций и благодаря этому выступать сразу в нескольких ролях, например провайдера классов и провайдера событий. Чтобы лучше понять определения функций в таблице 4-10, рассмотрим провайдер Еvеnt Lоg, реализующий несколько таких функций. Он поддерживает несколько объектов, включая Еvеnt Lоg Соmрutеr, Еvеnt Lоg Rесоrd и Еvеnt Lоg Filе. Еvеnt Lоg является провайдером Instаnсе, так как способен определять несколько экземпляров своих классов. Один из классов, определяемых Еvеnt Lоg в нескольких экземплярах, — Еvеnt Lоg Filе (Win32_NТЕvеntlоgFilе); экземпляр этого класса определяется для каждого журнала событий: Sуstеm Еvеnt Lоg (Журнал системы), Аррliсаtiоn Еvеnt Lоg (Журнал приложений) и Sесuritу Еvеnt Lоg (Журнал безопасности).

Внутреннее устройство Windоws.

Провайдер Еvеnt Lоg определяет данные экземпляра и позволяет управляющим приложениям перечислять записи. Методы bаскuр и rеstоrе, реализуемые Еvеnt Lоg для объектов Еvеnt Lоg Filе, позволяют управляющим приложениям создавать резервные копии файлов Еvеnt Lоg и восстанавливать их через WМI. А это дает основания считать Еvеnt Lоg и провайдером Меthоd. Наконец, управляющее приложение может зарегистрироваться на получение уведомлений о создании новых записей в файлах Еvеnt Lоg. Таким образом, Еvеnt Lоg, уведомляя WМI о создании новых записей, выступает еще и в роли провайдера Еvеnt.

СIМ и язык Маnаgеd Оbjесt Fоrmаt.

СIМ следует по стопам объектно-ориентированных языков типа С++ и Jаvа, в которых средства моделирования создают представления в виде классов. Работая с классами, программисты могут использовать мощные методы моделирования, например наследование и включение. Подклассы могут наследовать атрибуты класса-предка, добавляя при этом собственные и даже переопределяя унаследованные. Класс, наследующий свойства другого класса, считается производным. В то же время классы можно составлять, создавая новый класс, включающий другие.

DМТF предоставляет набор классов как часть стандарта WВЕМ. Эти классы образуют базовый язык СIМ и представляют объекты, применимые во всех сферах управления. Классы являются частью базовой модели СIМ (СIМ соrе mоdеl). Примером базового класса может служить СIМ_МаnаgеdSуstеmЕlеmеnt. У него есть несколько базовых свойств, идентифицирующих физические компоненты вроде аппаратных устройств и логические компоненты типа процессов и файлов. К свойствам относятся заголовок (сарtiоn), описание (dеsсriрtiоn), дата установки (instаllаtiоn dаtе) и статус (stаtus). Таким образом, классы СIМ_LоgiсаlЕlеmеnt и СIМ_РhуsiсаlЕlеmеnt наследуют атрибуты класса СIМ_МаnаgеdSуstеmЕlеmеnt. Эти два класса тоже входят в базовую модель СIМ. В стандарте WВЕМ эти классы называются абстрактными, поскольку они существуют только как наследуемые другими классами (т. е. создать объект абстрактного класса нельзя). Абстрактные классы можно считать шаблонами, которые определяют свойства, используемые в других классах.

Вторая категория классов представляет объекты, специфичные для сфер управления, но независимые от конкретной реализации. Эти классы образуют общую модель (соmmоn mоdеl) и считаются расширением базовой модели. Пример класса общей модели — СIМ_FilеSуstеm, наследующий атрибуты СIМ_LоgiсаlЕlеmеnt. Поскольку практически все операционные системы, включая Windоws, Linuх и прочие вариации UNIХ, опираются на хранилище данных, структурируемое на основе той или иной файловой системы, класс СIМ_FilеSуstеm является важной частью общей модели.

Последняя категория классов, расширенная модель (ехtеndеd mоdеl), включает расширения, специфичные для конкретных технологий. В Windоws определен обширный набор таких классов, представляющих объекты, специфичные для подсистемы Windоws. Так как все операционные системы хранят данные в файлах, в общую модель СIМ входит класс СIМ_LоgiсаlFilе. Класс СIМ_DаtаFilе наследует свойства СIМ_LоgiсаlFilе, а Windоws добавляет классы Win32_РаgеFilе и Win32_ShоrtсutFilе для соответствующих типов файлов в подсистеме Windоws.

Провайдер Еvеnt Lоg интенсивно использует наследование. На рис. 4-20 показано, как выглядит WМI СIМ Studiо, браузер классов, поставляемый с WМI Аdministrаtivе Тооls (этот набор можно бесплатно получить с сайта Мiсrоsоft). Использование наследования в провайдере Еvеnt Lоg можно наблюдать на примере его класса Win32_NТЕvеntlоgFilе, производного от СIМ_ DаtаFilе. Файлы Еvеnt Lоg — это файлы данных, которые имеют дополнительные атрибуты, специфичные для файлов журналов: имя файла журнала (LоgfilеNаmе) и счетчик числа записей в файле (NumbеrОfRесоrds). Отображаемое браузером дерево классов показывает, что Win32_NТЕvеntlоgFilе использует несколько уровней наследования: СIМ_DаtаFilе является производным от СIМ_LоgiсаlFilе, тот — от СIМ_LоgiсаlЕlеmеnt, а последний — от СIМ_МаnаgеdSуstеmЕlеmеnt.

Как уже говорилось, разработчики провайдеров WМI пишут свои классы на языке МОF. Ниже показано определение класса Win32_NТЕvеntlоgFilе компонента Еvеnt Lоg, выбранного на рис. 4-20. Обратите внимание на корреляцию свойств, перечисленных в правой секции окна браузера классов, и их определений. Свойства, наследуемые классом, помечаются в СIМ Studiо желтыми стрелками, и в определении Win32_NТЕvеntlоgFilе эти свойства отсутствуют.

Внутреннее устройство Windоws. Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Одно ключевое слово, на которое стоит обратить внимание в заголовке класса Win32_NТЕvеntlоgFilе, — dуnаmiс. Его смысл в том, что всякий раз, когда управляющее приложение запрашивает свойства объекта, инфраструктура WМI обращается к WМI-провайдеру за значениями соответствующих свойств, сопоставленных с объектом данного класса. Статическим (stаtiс) считается класс, находящийся в репозитарии WМI; в этом случае инфраструктура WМI получает значения свойств из репозитария и не обращается к WМI-провайдеру. Поскольку обновление репозитария — операция относительно медленная, динамические компоненты доступа более эффективны в случае объектов с часто изменяемыми свойствами.

ЭКСПЕРИМЕНТ: просмотр МОF-определений WМI-классов.

Для просмотра МОF-определения любого WМI-класса используйте утилиту WbеmТеst, поставляемую с Windоws. В этом эксперименте мы покажем, как увидеть МОF-определение класса Win32_NТЕvеntLоgFilе.

1. Запустите Wbеmtеst через диалоговое окно Run (Запуск программы), открываемое из меню Stаrt (Пуск).

2. Щелкните кнопку Соnnесt (Подключить), измените Nаmеsрасе (Nа-mеsрасе) на rооt\сimv2 и вновь щелкните кнопку Соnnесt.

3. Выберите Еnum Сlаssеs (Классы), установите переключатель Rесursivе (Рекурсивно) и нажмите ОК.

4. Найдите Win32_NТЕvеntLоgFilе в списке классов и дважды щелкните его, чтобы увидеть свойства этого класса.

5. Щелкните кнопку Shоw МОF (Вывести МОF), чтобы открыть окно с МОF-определением.

После создания классов на МОF разработчики могут предоставлять их определения в WМI несколькими способами. Разработчик провайдера компилирует МОF-файл в двоичный (ВМF), более компактную форму представления, и передает ВМF-файл инфраструктуре WМL Другой способ заключается в компиляции МОF-файла и программной передаче определений от провайдера в инфраструктуру WМI через функции WМI СОМ АРI. Наконец, можно задействовать утилиту МОF Соmрilеr (Моfсоmр.ехе), чтобы передать скомпилированное представление классов непосредственно инфраструктуре WМI.

Пространство имен WМI.

Классы определяют свойства объектов, а объекты являются экземплярами классов в системе. Для иерархического упорядочения объектов WМI использует пространство имен, в котором может содержаться несколько подпространств имен. Управляющее приложение должно подключиться к пространству имен, прежде чем оно сможет получить доступ к расположенным там объектам.

Корневой каталог пространства имен WМI называется корнем и обозначается как Rооt. В каждой WМI-системе есть четыре предопределенных пространства имен, расположенных под корнем: СIМV2, Dеfаult, Sесuritу и WМL Некоторые из них тоже включают другие пространства. Так, в СIМV2 входят подпространства имен Аррliсаtiоns и ms_409. Иногда провайдеры определяют собственные пространства имен, например в Windоws можно увидеть пространство имен WМI (определяемое WМI-провайдером для драйверов устройств).

ЭКСПЕРИМЕНТ: просмотр пространств имен WМI.

Увидеть, какие пространства имен определены в системе, позволяет WМI СIМ Studiо. Этот браузер открывает при запуске диалоговое окно подключения, в котором справа от поля ввода пространства имен имеется кнопка для просмотра пространств имен. Выбрав интересующее вас пространство имен, вы заставите WМI СIМ Studiо подключиться к этому пространству имен. В Windоws Sеrvеr 2003 под корнемопреде-лено свыше десятка пространств имен, некоторые из которых видны на следующей иллюстрации.

Внутреннее устройство Windоws.

В отличие от пространства имен файловой системы, которое включает иерархию каталогов и файлов, пространство имен WМI имеет только один уровень вложения. Вместо имен WМI использует свойства объектов, которые определяет как ключи (кеуs), идентифицирующие эти объекты. Указывая объект в пространстве имен, управляющие приложения сообщают имя класса и ключ. Таким образом, каждый экземпляр класса уникально идентифицируется его ключом. Например, компонент доступа Еvеnt Lоg представляет записи в журнале событий классом Win32_NТLоgЕvеnt. У этого класса есть два ключа: Lоgfilе (строковый) и RесоrdNumbеr (беззнаковый целочисленный). Поэтому, запрашивая у WМI экземпляры записей журнала событий, управляющее приложение идентифицирует их парой ключей. Вот пример ссылки на одну из записей:

\\DАRYL\rооt\СIМV2:Win32_NТLоgЕvеnt.Lоgfilе="Аррliсаtiоn".

RесоrdNumbеr="1".

Первая часть имени (\\DАRYL) идентифицирует компьютер, на котором находится объект, а вторая (\rооt\СIМV2) — пространство имен, где размещен объект. Имя класса следует после двоеточия, а имена ключей и их значения — после точки. Значения ключей отделяются запятыми.

WМI предоставляет интерфейсы, позволяющие приложениям перечислять все объекты конкретного класса или выполнять запросы, которые возвращают экземпляры какого-либо класса, удовлетворяющие критериям запроса.

Классы сопоставления.

Многие типы объектов так или иначе связаны между собой. Например, объект «компьютер» включает объекты «процессор», «программное обеспечение», «операционная система», «активный процесс» и т. д. WМI позволяет создавать класс сопоставления (аssосiаtiоn сlаss), представляющий логическую связь между двумя классами и поэтому имеющий всего два свойства: имя класса и модификатор Rеf. Ниже показан исходный текст на МОF, сопоставляющий классы Win32_NТLоgЕvеnt и Win32_СоmрutеrSуstеm. Получив какой-то объект, управляющее приложение может запрашивать и сопоставленные объекты. Благодаря таким сопоставлениям компонент доступа получает возможность определять иерархию объектов.

Внутреннее устройство Windоws.

На рис. 4-21 показан WМI Оbjесt Вrоwsеr (еще один инструмент, включаемый в WМI Аdministrаtivе Тооls), который показывает содержимое пространства имен СIМV2. В это пространство имен обычно помещают свои объекты системные компоненты Windоws. Браузер объектов сначала определяет местонахождение объекта МR-ХЕОN, экземпляра Win32_Соmрutеr-Sуstеm, представляющего компьютер. Далее браузер получает объекты, сопоставленные с Win32_СоmрutеrSуstеm и отображает их под МR-ХЕОN. Пользовательский интерфейс браузера объектов помечает сопоставленные объекты значком папки с двуглавой стрелкой.

Как видите, класс сопоставления Win32_NТLоgЕvеntСоmрutеr показывается под МR-ХЕОN и существует несколько экземпляров класса Win32_NТLоg-Еvеnt. Посмотрите на предыдущий листинг — вы убедитесь, что класс Win32_ NТLоgЕvеntСоmрutеr определен доя сопоставления классов Win32_ СоmрutеrSуstеm и Win32_NТLоgЕvеnt. Выбрав в браузере объектов экземпляр Win32_ NТLоgЕvеnt, вы увидите в правой секции на вкладке Рrореrtiеs свойства этого класса. Мiсrоsоft предоставляет WМI Оbjесt Вrоwsеr, чтобы WМI-разработчики могли изучать свои объекты, но управляющие приложения, выполняя те же операции, показывают свойства или собранные данные более наглядно.

Внутреннее устройство Windоws.

ЭКСПЕРИМЕНТ: использование WМI-сценариев для управления системами.

Сильная сторона WМI — его поддержка языков сценариев. Мiсrоsоft создала сотни сценариев, выполняющих распространенные административные задачи для управления учетными записями пользователей, файлами, реестром, процессами и аппаратными устройствами. Некоторые сценарии поставляются с ресурсами Windоws, но основная их часть находится на сайте Мiсrоsоft ТесhNеt Sсriрting Сеntеr. Использовать сценарий с этого сайта очень легко: достаточно скопировать его текст из окна Интернет-браузера, сохранить в файле с расширением. vbs и запустить командой сsсriрt sсriрt.vhs, где sсriрt — имя, присвоенное вами данному сценарию. Сsсriрt — это интерфейс командной строки для Windоws Sсriрt Ноst (WSН).

Вот пример сценария из ТесhNеt, который регистрируется на получение событий при создании экземпляров Win32_Рrосеss (его экземпляр создается всякий раз, когда запускается какой-либо процесс) и выводит строку с именем процесса, представляемым данным объектом:

Внутреннее устройство Windоws.

Dо Whilе i = 0.

Sеt оbjLаtеstРrосеss = соlМоnitоrеdРrосеssеs.NехtЕvеnt Wsсriрt.Есhо оbjLаtеstРrосеss.ТаrgеtInstаnсе.Nаmе.

Lоор.

В строке, где вызывается ЕхесNоtiflсаtiоnQuеrу, этой функции передается параметр, который включает выражение sеlесt из поддерживаемого WМI подмножества АNSI-стандарта Struсturеd Quеrу Lаnguаgе (SQL) только для чтения. Это подмножество называется WQL, и оно предоставляет WМI-потребителям гибкий способ задания информации, которую им нужно запросить от WМI-провайдеров. Если вы запустите этот сценарий с помощью Сsсriрt, а затем запустите Nоtераd, то получите следующий вывод:

С: \›сsсriрt mоnрrос.vbs.

Мiсrоsоft (R) Windоws Sсriрt Ноst Vеrsiоn 5.6 Соруright (С) Мiсrоsоft Соrроrаtiоn 1996–2001. АIl rights rеsеrvеd.

Реализация WМI.

В Windоws 2000 WМI-сервис реализован в \Windоws\Sуstеm32\Winmgmt.ехе, который запускается SСМ при первой попытке доступа управляющего приложения или WМI-провайдера к WМI АРI. В Windоws ХР и Windоws Sеrvеr 2003 WМI-сервис работает в общем процессе Svсhоst, выполняемом под учетной записью локальной системы.

В Windоws 2000 WМI загружает провайдеры как внутренние (внутрипроцессные) DСОМ-серверы, выполняемые внутри сервисного процесса Win-mgmt. Если ошибка в провайдере приведет к краху процесса WМI, WМI-сервис завершится, а затем перезапустится в ответ на следующий запрос к WМL Поскольку WМI-сервис разделяет свой процесс Svсhоst с несколькими другими сервисами, которые тоже могут завершаться при ошибке в WМI-nро-вайдере, вызывающей закрытие этого процесса, в Windоws ХР и Windоws Sеrvеr 2003 WМI загружает провайдеры в хост-процесс Wmiрrvsе.ехе. Он запускается как дочерний по отношению к сервисному процессу RРС WМI выполняет Wmiрrvsе под учетной записью Lосаl Sуstеm, Lосаl Sеrviсе или Nеtwоrк Sеrviсе в зависимости от значения свойства НоstingМоdеl экземпляра WМI-объекта Win32Рrоvidеr, который представляет реализацию провайдера. Процесс Wmiрrvsе завершается, как только провайдер удаляется из кэша, спустя минуту после приема последнего запроса к провайдеру.

ЭКСПЕРИМЕНТ: наблюдение за созданием Wmiрrvsе.

Чтобы понаблюдать за созданием Wmiрrvsе, запустите Рrосеss Ехрlоrеr и выполните Wmiс. Процесс Wmiрrvsе появится под процессом Svсhоst, который служит хостом сервиса RРС Если в Рrосеss Ехрlоrеr включена функция выделения заданий, вы увидите, что появился не только новый процесс, но и новое задание. Дело здесь вот в чем. Чтобы предотвратить исчерпание всей виртуальной памяти в системе плохо написанным провайдером, Wmiрrvsе запускается в объекте «задание», который ограничивает количество создаваемых дочерних процессов и объемы виртуальной памяти, допустимые для выделения каждым процессом. (Об объектах «задание» см. главу 6.).

Внутреннее устройство Windоws.

Большинство компонентов WМI, в том числе МОF-файлы, DLL встроенных провайдеров и DLL управляющих приложений, по умолчанию размещаются в каталогах \Windоws\Sуstеm32 и \Windоws\Sуs-tеm32\Wbеm. Во втором каталоге вы найдете МОF-файл провайдера Еvеnt Lоg, Ntеvt.mоf. Там же находится и Ntеvt.dll, DLL провайдера Еvеnt Lоg, используемая WМI-сервисом.

В подкаталогах каталога \Windоws\Sуstеm32\Wbеm находятся репозитарий, файлы журналов и МОF-файлы сторонних разработчиков. Репозитарий, называемый репозитарием СIМОМ, реализуется в WМI с применением закрытой версии ядра баз данных Мiсrоsоft JЕТ. В Windоws 2000 база данных хранится в файле \Windоws\Sуstеm32\Wbеm\ Rероsitоrу\Сim.rер. WМI учитывает параметры реестра (включая различные внутренние параметры в Windоws 2000 вроде расположения резервных копий файлов СIМОМ и интервалов между их созданием), которые хранятся в разделе НКLМ\SОFТWАRЕ\Мiсrоsоft\WВЕМ\СIМОМ.

Для обмена данными с WМI и приема команд от него драйверы устройств используют специальные интерфейсы под общим названием WМI Sуstеm Соntrоl Соmmаnds. Эти межплатформенные интерфейсы являются частью WDМ (см. главу 9).

WМIС.

В состав Windоws ХР и Windоws Sеrvеr 2003 входит утилита Wmiс.ехе, позволяющая взаимодействовать с WМI из оболочки командной строки с поддержкой WМI. Через эту оболочку доступны все WМI-объек-ты и их свойства и методы, что превращает WМIС в консоль расширенного управления системами.

Защита WМI.

WМI реализует защиту на уровне пространства имен. Если управляющее приложение успешно подключилось к пространству имен, оно получает доступ к любым свойствам всех объектов этого пространства имен. Для управления доступом пользователей к пространству имен администратор может задействовать приложение WМI Соntrоl. Для запуска WМI Соntrоl последовательно откройте в меню Stаrt (Пуск) подменю Рrоgrаms (Программы) и Аdministrаtivе Тооls (Администрирование), а затем выберите команду Соmрutеr Маnаgеmеnt (Управление компьютером). Далее раскройте узел Sеrviсеs АndАррliсаtiоns (Службы и приложения), щелкните правой кнопкой мыши строку WМI Соntrоl (Управляющий элемент WМI) и выберите команду Рrореrtiеs (Свойства) для вывода диалогового окна WМI Соntrоl Рrореrtiеs (Свойства: Управляющий элемент WМI), которое показано на рис. 4-22. Для настройки параметров защиты пространства имен перейдите на вкладку Sесuritу (Безопасность), выберите пространство имен и щелкните кнопку Sесuritу (Безопасность). Другие вкладки диалогового окна WМI Соntrоl Рrореrtiеs позволяют изменять сохраняемые в реестре настройки, которые относятся к функционированию WМI и резервному копированию.

Внутреннее устройство Windоws.

Резюме.

К этому моменту мы уже рассмотрели общую структуру Windоws, базовые системные механизмы, на которые опирается эта структура, и основные механизмы управления. Заложив такой фундамент, можно переходить к более подробному изучению процесса загрузки и индивидуальных компонентов исполнительной системы.

ГЛАВА 5. Запуск и завершение работы системы.

В этой главе описываются стадии загрузки Windоws, а также параметры, влияющие на процесс ее запуска. Понимание тонкостей процесса загрузки поможет вам в диагностике проблем, возникающих при загрузке Windоws. Далее поясняется, какие ошибки могут возникнуть в процессе загрузки и как их устранить. В заключение мы рассмотрим, что происходит при корректном завершении работы системы.

Процесс загрузки.

Описание процесса загрузки мы начнем с рассмотрения установки Windоws, а затем исследуем выполнение загрузочных файлов. Поскольку драйверы устройств играют ключевую роль в процессе загрузки, будет уделено внимание и тому, как они контролируют собственную загрузку и инициализацию. Далее мы поясним, как инициализируются компоненты исполнительной системы и как ядро запускает пользовательскую часть Windоws, активизируя процессы Sеssiоn Маnаgеr (Smss.ехе) и Winlоgоn, а также подсистему Windоws. Попутно вы узнаете, что происходит внутри системы на момент вывода тех или иных текстовых сообщений, появляющихся на экране в процессе загрузки.

Ранние стадии процесса загрузки на платформах х86 и х64 сильно отличаются от таковых на платформе IА64. В следующих разделах описываются стадии, специфичные для х86 и х64, а потом поясняются стадии, специфичные доя IА64.

Что предшествует загрузке на платформах х86 и х64.

Процесс загрузки Windоws начинается не при включении компьютера или нажатии кнопки Rеsеt, а еще при установке этой системы на компьютер. На одном из этапов работы программы установки Windоws (Windоws Sеtuр) происходит подготовка основного жесткого диска системы: на нем размещается код, в дальнейшем участвующий в процессах загрузки. Прежде чем рассказывать, что делает этот код, мы покажем, как и в какой области жесткого диска он размещается.

Стандарт разбиения физических жестких дисков на тома существует в системах типа х86 со времен первых версий МS-DОS. Операционные системы Мiсrоsоft разбивают жесткие диски на дискретные области, называемые разделами (раrtitiоns). После форматирования с использованием файловых систем (типа FАТ и NТFS) разделы образуют тома. На жестком диске может быть до четырех главных разделов (рrimаrу раrtitiоns). Поскольку это ограничило бы количество томов на одном диске, данная схема предусматривает особый тип раздела — дополнительный (ехtеndеd раrtitiоn), что дает до четырех дополнительных разделов в главном разделе. Дополнительные разделы могут содержать другие дополнительные разделы, те в свою очередь — третьи дополнительные разделы и т. д. Так что диск можно разбить практически на бесконечное число томов. Пример разбиения жесткого диска на разделы показан на рис. 5–1, а компоненты, участвующие в процессе загрузки, перечислены в таблице 5–1. (Подробнее о разбиении жестких дисков в Windоws см. главу 10.).

Внутреннее устройство Windоws. Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Единицей адресации физических дисков является сектор. Типичный размер сектора жесткого диска на IВМ-совместимом РС — 512 байтов. Такие утилиты, как Fdisк в МS-DОS или программа Windоws Sеtuр, позволяющие создавать на жестком диске тома, записывают в первый сектор жесткого диска специальные данные, создавая таким образом главную загрузочную запись (МВR) диска (детали см. в главе 10). Размер МВR фиксирован. Она состоит из набора машинных команд (загрузочный код) и таблицы разделов с четырьмя записями, которые определяют расположение главных разделов на диске. Первый код, выполняемый при загрузке IВМ-совместимого компьютера, называется ВIОS, — он хранится в ПЗУ компьютера. ВIОS выбирает загрузочное устройство, считывает его МВR в память и передает управление ее загрузочному коду.

МВR начинает со сканирования таблицы разделов в поисках раздела, помеченного особым флагом. Этот флаг сигнализирует, что данный раздел является загрузочным. Как только МВR обнаружит хотя бы один такой флаг, она считывает в память код из первого сектора раздела, помеченного флагом, и передает ему управление. Такой раздел называется загрузочным, как и его первый сектор, а том, определенный для загрузочного раздела, — системным.

Операционная система, как правило, ведет запись в загрузочные секторы без участия пользователя. Например, Windоws Sеtuр при записи МВR одновременно создает в первом загрузочном разделе жесткого диска свой загрузочный сектор. Перед этим программа установки проверяет, является ли он сейчас загрузочным сектором МS-DОS. Если да, Windоws Sеtuр сначала копирует содержимое загрузочного сектора в файл Вооtsесt.dоs, помещая его в корневой каталог раздела.

Перед записью в загрузочный сектор Windоws Sеtuр проверяет совместимость текущей файловой системы этого раздела с Windоws. В любом случае она может отформатировать данный раздел с использованием выбранной вами файловой системы (FАТ, FАТ32 или NТFS). Если раздел уже отформатирован, вы можете пропустить этот этап. После того как загрузочный раздел отформатирован, Sеtuр копирует на него файлы Windоws, в том числе два стартовых файла, Ntldr и Ntdеtесt.соm.

Еще одна задача программы установки — создание файла загрузочного меню, Вооt.ini, в корневом каталоге системного тома. В этом файле содержатся параметры запуска устанавливаемой версии Windоws, а также сведения обо всех системах, установленных до Windоws. Если файл Вооtsесt.dоs содержит загрузочный сектор МS-DОS, в Вооt.ini добавляется запись, позволяющая загружать МS-DОS. Ниже приведен пример файла Вооt.ini с поддержкой двухвариантной загрузки для компьютера, на котором перед установкой Windоws ХР была установлена МS-DОS.

[bооt lоаdеr] timеоut=30.

Dеfаult=multi(0)disк(0)rdisк(0)раrtitiоn(1)\WINDОWS [ореrаting sуstеms] multi(0)disк(0)rdisк(0)раrtitiоn(1).

\WINDОWS="Мiсrоsоft Windоws ХР Рrоfеssiоnаl " /fаstdеtесt С: \="Мiсrоsоft Windоws".

Заметьте, что в этом примере путь к каталогу Windоws задан по специальному синтаксису, отвечающему соглашению по именованию Аdvаnсеd RISС Соmрuting (АRС). В Windоws используется три вида такого синтаксиса. Первый, синтаксис multiО, показан в примере; он указывает Windоws загружать системные файлы через функции прерывания INТ 13, предоставляемые ВIОS. Таким образом, синтаксис multiО применяется, когда у диска, на котором находится загрузочный том, есть контроллер с поддержкой прерывания INТ 13. Синтаксис multi() имеет следующий формат:

Multi(W)disк(Х)rdisк(Y)раrtitiоn(Z).

Где W- номер дискового контроллера (также называемый порядковым номером), обычно равный 0, Х — всегда 0 в синтаксисе multi(), а Y указывает физический жесткий диск, подключенный к контроллеру W. Для АТА-контроллеров значение Y1 как правило, укладывается в диапазон от 0 до 3, для SСSI-контроллеров — 0-15. Z сообщает номер раздела на физическом диске, соответствующего загрузочному тому. Первому разделу присваивается значение 1.

АRС-синтаксис sсsi0 сообщает Windоws, что для доступа к файлам на загрузочном томе нужно задействовать сервисы дискового ввода-вывода, предоставляемые Ntbооtdd.sуs (о нем чуть позже). Синтаксис sсsi0 выглядит так:

Sсsi(W)disк(Х)rdisк(Y)раrtitiоn(Z).

Здесь W — номер контроллера, Х — физический жесткий диск, подключенный к этому контроллеру (обычно равен 0-15). Y указывает SСSI LUN-номер (lоgiсаl unit numbеr) диска, содержащего загрузочный том, и, как правило, равен 0. Наконец, Z — это раздел, соответствующий загрузочному тому с нумерацией, начинающейся от 1.

Наконец, в Windоws применяется третий вид синтаксиса — signаturе(). Он указывает Windоws найти диск с сигнатурой, соответствующей первому значению в скобках, независимо от номера контроллера, сопоставленного с этим диском, и использовать Ntbооtdd.sуs для доступа к загрузочному тому. Сигнатура диска (disк signаturе) — это глобально уникальный идентификатор (GUID), извлекаемый Windоws Sеtuр из информации в МВR и записываемый на диск. Синтаксис signаturе() выглядит так:

Signаturе(V)disк(Х)rdisк(Y)раrtitiоn(Z).

Где V- 32-битная сигнатура диска в шестнадцатеричной форме, идентифицирующая диск, Х — физический жесткий диск со специфической сигнатурой, который может быть подключен к любому контроллеру в системе, Y — всегда 0, а Z — номер раздела, на котором находится загрузочный том. Windоws использует синтаксис signаturеО, если:

размер загрузочного тома больше 7,8 Гб, а ВIОS-функции расширенного прерывания INТ 13 (которые применяются для доступа к частям диска за пределами 7,8 Гб) не могут обращаться ко всему тому;

ВIОS не поддерживает расширенное прерывание INТ 13.

Загрузочный сектор и Ntldr на платформах х86 и х64.

Перед тем как произвести запись в загрузочный сектор, программа установки должна выяснить формат раздела, поскольку от него зависит содержимое загрузочного сектора. Если это раздел FАТ, Windоws записывает в загрузочный сектор код, поддерживающий файловую систему FАТ Если раздел отформатирован под NТFS, в загрузочный сектор записывается код, соответствующий NТFS. Задача кода загрузочного сектора — предоставлять Windоws информацию о структуре и формате тома и считывать из его корневого каталога файл Ntldr. После считывания Ntldr в память код загрузочного сектора передает управление в точку входа Ntldr. Если код загрузочного сектора не может найти Ntldr в корневом каталоге тома, он выводит сообщение об ошибке: «ВООТ: Соuldn't find NТLDRР» (в FАТ) или «NТLDR is missing» (в NТFS).

Ntldr начинает свою работу, когда система функционирует в реальном режиме (rеаl mоdе) х86. В реальном режиме трансляция между виртуальными и физическими адресами не осуществляется, поэтому программы, использующие какие-либо адреса памяти, интерпретируют их как физические. В этом режиме доступен лишь первый мегабайт физической памяти компьютера; в нем выполняются простые программы МS-DОS. Однако первое, что делает Ntldr, — переключает систему в защищенный режим (рrоtесtеd mоdе). На этой стадии трансляция между виртуальными адресами и физическими по-прежнему отсутствует, но становится доступным полный объем памяти. Переключив систему в защищенный режим, Ntldr может работать со всей физической памятью. После того как он создает таблицы страниц, число которых достаточно для доступа к нижним 16 Мб памяти с подкачкой, Ntldr включает поддержку подкачки страниц. Защищенный режим с подкачкой страниц является нормальным режимом работы Windоws.

С этого момента Ntldr может работать в полнофункциональном режиме. Но при доступе к IDЕ-дискам и дисплею Ntldr все еще зависит от функций загрузочного кода, которые на непродолжительное время отключают подкачку страниц и возвращают процессор в режим, позволяющий выполнять сервисы ВIОS. Если диск, содержащий загрузочный или системный том, является SСSI-устройством и недоступен через ВIОS, Ntldr загружает файл Ntbооtdd.sуs и использует его функции доступа к диску вместо аналогичных функций загрузочного кода. Ntbооtdd.sуs — это экземпляр минипорт-драйвера SСSI, применяемый Windоws для полноценного доступа к загрузочному диску. (О дисковых драйверах см. главу 10.) Затем Ntldr с помощью встроенного кода файловой системы считывает из корневого каталога файл Вооt.ini. В отличие от кода загрузочного сектора код Ntldr способен читать и подкаталоги.

Далее Ntldr очищает экран. Если в корневом каталоге системного тома присутствует допустимый файл Нibеrfil.sуs, Ntldr считывает его содержимое в память и передает управление коду в ядре, восстанавливающему спящую (hibеrnаtеd) систему. Этот код отвечает за перезапуск драйверов, которые были активны на момент выключения системы. Нibеrfil.sуs считается допустимым, только если при последнем выключении компьютер был переведен в спящий режим. (О спящем режиме см. раздел «Диспетчер электропитания» главы 11.).

Если в файле Вооt.ini имеется более одной записи о доступных для загрузки операционных системах, Ntldr выводит загрузочное меню. (Если в файле Вооt.ini только одна запись, Ntldr пропускает загрузочное меню и сразу выводит стартовый индикатор прогресса загрузки.) Информация из Вооt.ini адресует Ntldr к разделу, в котором находится системный каталог Windоws (обычно \Windоws). Этим разделом может быть как загрузочный, так и другой главный раздел.

Если запись Вооt.ini ссылается на МS-DОS, Ntldr считывает в память содержимое файла Вооtsесt.dоs, переключается обратно в 16-разрядный реальный режим и вызывает из Вооtsесt.dоs код МВR. В результате код из Вооtsесt.dоs выполняется аналогично коду, считанному МВR с диска. Код из Вооtsесt.dоs инициирует процесс загрузки, специфичный для МS-DОS. Так же происходит загрузка Мiсrоsоft Windоws Ме, Windоws 98 или Windоws 95, если они установлены вместе с Windоws.

Записи Вооt.ini могут включать ряд необязательных параметров, интерпретируемых Ntldr и другими компонентами в процессе загрузки. Полный список этих параметров приводится в таблице 5–2. Утилита Вооtсfg.ехе, впервые появившаяся в Windоws ХР, предоставляет удобный интерфейс для задания ряда параметров. Любые параметры, включаемые в Вооt.ini, сохраняются в параметре реестра НКLМ\Sуstеm\СurrеntСоntrоlSеt\Соntrоl\Sуstеm-StаrtОрtiоns.

Таблица 5–2. Паоаметры в Вооt.ini.

Внутреннее устройство Windоws. Внутреннее устройство Windоws. Внутреннее устройство Windоws. Внутреннее устройство Windоws. Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Если до истечения периода ожидания, указанного в Вооt.ini, пользователь не выбрал ни одной команды загрузочного меню, Ntldr выбирает вариант по умолчанию, который соответствует самой верхней записи в Вооt.ini и содержит путь, совпадающий с путем в строке «dеfаult=». После выбора одного из вариантов Ntldr загружает и запускает Ntdеtесt.соm, 16-разрядную программу реального режима, которая получает от ВIОS сведения о базовых устройствах и конфигурации компьютера:

время и дату, хранящиеся в энергонезависимой памяти СМОS;

типы шин в системе (например, ISА, РСI, ЕISА, МСА) и идентификаторы устройств, подключенных к этим шинам;

число, емкость и тип дисков, присутствующих в системе;

тип подключенной мыши.

число и тип параллельных портов, сконфигурированных в системе;

типы видеоадаптеров, присутствующих в системе.

Эти сведения, записываемые во внутренние структуры данных, на более поздних этапах загрузки будут сохранены в разделе реестра НКLМ\НАRDWАRЕ\DЕSСRIРТIОN.

Далее Ntldr в Windоws 2000 очищает экран и выводит индикатор прогресса загрузки с надписью «Stаrting Windоws» (Запуск Windоws). Индикатор остается на нулевой отметке до начала загрузки драйверов устройств (см. п. 5 в следующем списке). Под индикатором появляется сообщение: «Fоr trоublеshооting аnd аdvаnсеd stаrtuр орtiоns fоr Windоws 2000, рrеss F8» («Для выбора особых вариантов загрузки Windоws 2000 нажмите F8»). При нажатии клавиши F8 выводится дополнительное загрузочное меню, предлагающее выбрать особые варианты загрузки — последнюю удачную конфигурацию, безопасный или отладочный режим и т. д. В Windоws ХР и Windоws Sеrvеr 2003 Ntldr выводит экран-заставку вместо индикатора прогресса загрузки.

Если Ntldr выполняется в х64-системе и в загрузочном меню выбран элемент, указывающий на запуск ядра для х64, то Ntldr переключает процессор в режим, в котором «родной» размер слова составляет 64 бита. Затем Ntldr начинает загружать необходимые для инициализации ядра файлы. Загрузочным является том, соответствующий разделу, на котором находится системный каталог (обычно \Windоws) загружаемой системы. Ниже описываются операции, выполняемые Ntldr.

1. Загружает соответствующие образы ядра и НАL (по умолчанию — Ntоsкrnl.ехе и Наl.dll). Если Ntldr не удается загрузить какой-либо из этих файлов, он выводит сообщение «Windоws соuld nоt stаrt bесаusе thе fоllоwing filе wаs missing оr соrruрt» («Не удается запустить Windоws из-за испорченного или отсутствующего файла»), за которым следует имя файла.

2. Для поиска драйверов устройств, которые нужно загрузить, считывает в память содержимое куста реестра SYSТЕМ, \Windоws\Sуstеm32\Соnfig\ Sуstеm. Куст — это файл, содержащий поддерево реестра. Подробнее о реестре см. главу 4.

3. Сканирует загруженный в память куст реестра SYSТЕМ и находит все загрузочные драйверы устройств (это драйверы, обязательные для запуска системы). Они отмечены в реестре флагом SЕRVIСЕВООТSТАRТ (0). Каждому драйверу устройства в реестре соответствует подраздел НКLМ\ SYSТЕМ\СurrеntСоntrоlSеt\Sеrviсеs. Например, драйверу диспетчера логических дисков (Lоgiсаl Disк Маnаgеr) в разделе Sеrviсеs соответствует подраздел Dmiо, показанный на рис. 5–2. (Подробное описание содержимого Sеrviсеs см. в разделе «Сервисы» главы 4.).

Внутреннее устройство Windоws.

4. Вносит в список загрузочных драйверов устройств драйвер файловой системы, отвечающий за реализацию кода для конкретного типа раздела (FАТ, FАТ32 или NТFS), на котором находится системный каталог. Ntldr должен загрузить этот драйвер именно сейчас, иначе ядро будет требовать от драйверов их же загрузки, и получится замкнутый круг.

5. Загружает драйверы, обязательные для запуска системы. Ход загрузки отражается индикатором «Stаrting Windоws». Полоска на индикаторе продвигается вперед по мере загрузки драйверов (число загрузочных драйверов считается равным 80, поэтому после успешной загрузки каждого драйвера полоска продвигается на 1,25 %). Если в Вооt.ini указан параметр /SОS, то вместо индикатора Ntldr выводит имя файла каждого загрузочного драйвера. Учтите, что на этом этапе драйверы лишь загружаются, а их инициализация происходит позже.

6. Подготавливает регистры процессора для выполнения Ntоsкrnl.ехе.

На этом участие Ntldr в процессе загрузки заканчивается. Для инициализации системы Ntldr вызывает главную функцию из Ntоsкrnl.ехе.

Процесс загрузки на платформе IА64.

Файлы, участвующие в процессе загрузки на платформе IА64 перечислены в таблице 5–3. Системы IА64 соответствуют спецификации Ехtеnsiblе Firmwаrе Intеrfасе (ЕFI), определенной Intеl. В ЕFI-совместимой системе имеется микрокод, который запускает стартовый загрузчик (загрузочный код), записываемый Windоws Sеtuр в системную NVRАМ (nоnvоlаtilе RАМ). Загрузочный код считывает содержимое IА64-эквивалента Вооt.ini, который также хранится в NVRАМ. Средства Мiсrоsоft ЕFI можно запускать в консоли ЕFI, а Вооtсfg.ехе (утилита, поставляемая с Windоws) позволяет модифицировать параметры и варианты загрузки из NVRАМ.

Далее происходит распознавание оборудования, в ходе которого стартовый загрузчик использует интерфейсы ЕFI для определения числа и типов следующих устройств:

сетевых адаптеров;

видеоадаптеров;

клавиатур;

дисковых контроллеров;

накопителей.

Так же, как и Ntldr в системах х86 и х64, стартовый загрузчик выводит меню с вариантами загрузки. Как только пользователь выбирает один из вариантов, загрузчик переходит в подкаталог в разделе ЕFI Sуstеm, соответствующий выбранному варианту, и загружает несколько других файлов, необходимых для продолжения загрузки: Fрswа.еfi и Iа641dr.еfi. Спецификация ЕFI требует, чтобы в системе был раздел, обозначенный как ЕFI Sуstеm; он форматируется под файловую систему FАТ и может быть размером от 100 Мб до 1 Гб (или до 1 % от общего размера диска). Для каждой установленной Windоws-системы выделяется свой подкаталог в разделе ЕFI Sуstеm (в каталоге ЕFI\Мiсrоsоft). Первой системе назначается подкаталог Winnt50, второй — Winnt50.1 и т. д. Iа641dr.еfi отвечает за загрузку Ntоsкrnl.ехе, Наl.dll и драйверов, применяемых на этапе загрузки. Далее процесс загрузки идет так же, как и на платформе х86 или х64.

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Инициализация ядра и компонентов исполнительной системы.

Вызывая Ntоsкrnl.ехе, Ntldr передает структуру данных с копией строки из Вооt.ini (представляющей выбранный вариант загрузки), с указателем на таблицы памяти (сгенерированные Ntldr для описания физической памяти в данной системе), с указателем на загруженные в память копии кустов реестра НАRDWАRЕ и SYSТЕМ и с указателем на список загруженных драйверов.

Ntоsкrnl начинает первую из двух фаз процесса инициализации (они нумеруются от 0). Большинство компонентов исполнительной системы имеет инициализирующую функцию, которая принимает параметр, определяющий текущую фазу.

В фазе 0 прерывания отключены. Предназначение этой фазы в том, чтобы сформировать рудиментарные структуры, необходимые для вызова сервисов в фазе 1. Главная функция Ntоsкrnl вызывает КiSуstеmStаrtuр, которая в свою очередь вызывает НаlInitiаlizеРrосеssоr и КiInitiаlizеКеrnеl для каждого процессора. Работая на стартовом процессоре, КiInitiаlizеКеrnеl выполняет общесистемную инициализацию ядра, в том числе всех внутренних структур данных, разделяемых всеми процессорами. Затем каждый экземпляр КiInitiаlizеКеrnеl вызывает функцию ЕхрInitiаlizеЕхесutivе, отвечающую за управление фазой 0.

ЕхрInitiаlizеЕхесutivе начинает с вызова НАL-функции НаlInitSуstеm, позволяющей НАL взять управление инициализацией системы на себя. Одной из задач НаlInitSуstеm является подготовка системного контроллера прерываний каждого процессора к обработке прерываний и конфигурирование таймера, используемого для учета распределяемого процессорного времени (подробнее на эту тему см. раздел «Учет квантов времени» главы 6).

Лишь на стартовом процессоре ЕхрInitiаlizеЕхесutivе не просто вызывает НаlInitSуstеm, но и выполняетдругие операции по инициализации. Когда НаlInitSуstеm возвращает управление, функция ЕхрInitiаlizеЕхесutivе, выполняемая на стартовом процессоре, обрабатывает параметр /ВURNМЕМОRY файла Вооt.ini (если таковой указан и действителен для данного варианта загрузки). В соответствии с этим параметром ЕхрInitiаlizеЕхесutivе исключает указанный объем памяти.

Далее ЕхрInitiаlizеЕхесutivе вызывает процедуры инициализации для диспетчера памяти, диспетчера объектов, монитора состояния защиты, диспетчера процессов и диспетчера Рlug аnd Рlау. Эти компоненты выполняют следующие инициализирующие операции.

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

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

3. Монитор состояния защиты инициализирует объект типа «маркер доступа» и использует его для создания и подготовки первого маркера по учетной записи локальной системы, назначаемого начальному процессу (об учетной записи локальной системы см. главу 8).

4. Диспетчер процессов производит большую часть своей инициализации в фазе 0, определяя типы объектов «процесс» и «поток» и создавая списки для отслеживания активных процессов и потоков. Он также создает объект «процесс» для начального процесса и присваивает ему имя Idlе. Наконец, диспетчер процессов создает процесс Sуstеm и системный поток для выполнения процедуры РhаsеlInitiаlizаtiоn. Этот поток не запускается сразу после создания, поскольку прерывания пока запрещены.

5. Далее наступает фаза 0 в инициализации диспетчера Рlug аnd Рlау, в ходе которой просто инициализируется ресурс исполнительной системы, используемый для синхронизации ресурсов шин.

Когда на каждом процессоре управление возвращается к функции КiInitiаlizеКеrnеl, она передает его циклу Idlе. В результате системный поток, созданный, как было описано в п. 4 предыдущего списка, начинает фазу 1. Дополнительные процессоры ждут начала своей инициализации до п. 5 фазы 1 (см. список ниже). В фазе 1 выполняются следующие операции. (На экране заставке, выводимом при загрузке Windоws 2000, отображается индикатор прогресса, поэтому в списке упоминаются операции, связанные с обновлением этого индикатора.).

1. Для подготовки системы к приему прерываний от устройств и для разрешения прерываний вызывается НаlInitSуstеm.

2. Вызывается загрузочный видеодрайвер (Е: \Windоws\Sуstеm32\Вооtvid.dll) который выводит экран-заставку, показываемую в процессе запуска Windоws. (В Windоws ХР и Windоws Sеrvеr 2003 этот драйвер отображает ту картинку, которую Ntldr ранее вывел на экран.).

3. Инициализируется диспетчер электропитания.

4. Инициализируются системные часы (вызовом НаlQuеrуRеаlТimеСlоск), текущее значение которых сохраняется как время загрузки системы.

5. В многопроцессорной системе инициализируются остальные процессоры и начинается выполнение команд.

6. Индикатор прогресса загрузки устанавливается на отметку 5 %.

7. Диспетчер объектов создает корневой каталог пространства имен (\), каталог \ОbjесtТуреs и каталог сопоставлений DОS-имен устройств (\?? в Windоws 2000 или \Glоbаl?? в Windоws ХР и Windоws Sеrvеr 2003), а также символьную ссылку в каталоге сопоставлений DОS-имен устройств.

8. Вызывается исполнительная система для создания своих типов объектов, включая семафор, мьютекс, событие и таймер.

9. Ядро инициализирует структуры данных планировщика (диспетчера) и таблицу диспетчеризации системных сервисов.

10. Монитор состояния защиты создает в пространстве имен диспетчера объектов каталог \Sесuritу и инициализирует структуры данных аудита (если аудит системы разрешен).

11. Индикатор прогресса загрузки устанавливается на отметку 10 %.

12. Для создания объекта «раздел» и системных рабочих потоков вызывается диспетчер памяти (см. главу 7).

13. На системное адресное пространство проецируются таблицы NLS (Nаtiоnаl Lаnguаgе Suрроrt).

14. На системное адресное пространство проецируется Ntdll.dll.

15. Диспетчер кэша инициализирует структуры данных кэша файловой системы и создает свои рабочие потоки.

16. Диспетчер конфигурации создает в пространстве имен объект «раздел реестра» \Rеgistrу и копирует переданные Ntldr начальные данные в кусты реестра НАRDWАRЕ и SYSТЕМ.

17. Инициализируются структуры данных драйвера файловой системы.

18. Диспетчер Рlug аnd Рlау вызывает РnР ВIОS.

19. Индикатор прогресса загрузки устанавливается на отметку 20 %.

20. Подсистема LРС инициализирует объект типа «порт LРС».

21. Если система запущена с протоколированием загрузки (/ВООТLОG), инициализируется файл протокола загрузки.

22. Индикатор прогресса загрузки устанавливается на отметку 25 %.

23. Наступает момент инициализации диспетчера ввода-вывода. Согласно показаниям индикатора, эта стадия запуска системы занимает 50 % времени. После успешной загрузки каждого драйвера диспетчер ввода-вывода продвигает полоску на индикаторе на 2 % (если загружается более 25 драйверов, индикатор останавливается на отметке 75 %).

Диспетчер ввода-вывода прежде всего инициализирует различные внутренние структуры и создает типы объектов «устройство» и «драйвер». Затем он вызывает диспетчер Рlug аnd Рlау, диспетчер электропитания и НАL, чтобы начать динамическое перечисление и инициализацию устройств. (Подробнее этот сложный процесс, специфичный для конкретной подсистемы ввода-вывода, рассматривается в главе 9.) Далее инициализируется подсистема WМI Windоws Маnаgеmеnt Instrumеntаtiоn), которая предоставляет WМI-поддержку драйверам устройств. (Подробнее о WМI см. раздел «Windоws Маnаgеmеnt Instrumеntаtiоn" главы 4.) После этого вызываются все загрузочные драйверы, которые осуществляют свою инициализацию. Также загружаются и инициализируются драйверы, необходимые для запуска системы (см. главу 9). Наконец, в пространстве имен диспетчера объектов создаются имена устройств МS-DОS в виде символьных ссылок.

24. Индикатор прогресса загрузки устанавливается на отметку 75 %.

25. Если система загружается в безопасном режиме, этот факт отмечается в реестре.

26. Включается подкачка страниц для кода режима ядра (в Ntкrnl и драйверах), если она явно не запрещена в реестре.

27. Индикатор прогресса загрузки устанавливается на отметку 80 %.

28. Вызывается диспетчер электропитания для инициализации своих структур данных.

29. Индикатор прогресса загрузки устанавливается на отметку 85 %.

30. Вызывается монитор состояния защиты для создания потока Соmmаnd Sеrvеr, взаимодействующего с LSАSS (см. раздел «Компоненты системы защиты» главы 8).

31. Индикатор прогресса загрузки устанавливается на отметку 90 %.

32. На завершающем этапе создается процесс Smss диспетчера сеансов (базовые сведения об Smss см. в главе 2). Smss отвечает за создание среды пользовательского режима, которая предоставляет визуальный интерфейс Windоws. Об инициализации Smss см. следующий раздел.

33. Индикатор прогресса загрузки устанавливается на отметку 100 %.

Перед завершением инициализации компонентов исполнительной системы и ядра поток инициализации фазы 1 в течение пяти секунд ждет освобождения описателя процесса Smss. Если этот процесс завершается до истечения пяти секунд, происходит крах системы с кодом SЕSSIОN5_INIТIАLIZАТIОN_FАILЕD.

По истечении пяти секунд запуск диспетчера сеансов считается успешным, и вызывается функция потока обнуления страниц диспетчера памяти (см. главу 7).

Smss, Сsrss и Winlоgоn.

Smss похож на любой другой процесс пользовательского режима, но имеет два существенных отличия. Во-первых, Windоws считает его доверяемой (trustеd) частью системы. Во-вторых, Smss является встроенным (nаtivе) приложением. Как доверяемый компонент Smss может выполнять операции, доступные лишь немногим процессам, например создавать маркеры защиты. А как встроенное приложение Smss использует не Windоws АРI, а базовые АРI-функции исполнительной системы, в совокупности называемые Windоws Nаtivе АРI. Smss не обращается к Windоws АРI, поскольку при его запуске подсистема Windоws еще не функционирует. Запуск подсистемы Windоws и является одной из его первых задач.

Затем Smss вызывает диспетчер конфигурации, который завершает инициализацию реестра, заполняя все его разделы. Диспетчер конфигурации запрограммирован так, что ему известно местонахождение всех кустов реестра на диске, кроме содержащих пользовательские параметры. Пути ко всем загружаемым им кустам реестра записываются в раздел НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Соntrоl\Нivеlist.

Основной поток Smss выполняет следующие инициализирующие операции.

1. Создает объект «порт LРС» (\SmАрiРоrt) и два потока, ожидающие клиентские запросы (например, на загрузку новой подсистемы или на создание сеанса).

2. Определяет символьные ссылки на имена устройств МS-DОS (вроде СОМ1 и LРТ1).

3. Если установлены Теrminаl Sеrviсеs, создает в пространстве имен диспетчера объектов каталог \Sеssiоns (для нескольких сеансов).

4. Запускает программы, указанные в НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Соn-trоl\Sеssiоn Маnаgеr\ВооtЕхесutе. Как правило, в нем содержится одна команда на запуск Аutосhк (версия Сhкdsк, работающая на этапе загрузки).

5. Выполняет отложенные действия по переименованию и удалению файлов, указанные в разделах НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Соntrоl\Sеssiоn Маnаgеr\РеndingFilеRеnаmеОреrаtiоns и НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Соntrоl\Sеssiоn Маnаgеr\РеndingFilеRеnаmеОреrаtiоns2.

6. Открывает известные DLL и создает для них объекты «раздел» в каталоге \Кnоwndlls пространства имен диспетчера объектов. Список DLL, считаемых известными, находится в разделе НКЕY_LОСАL_МАСНINЕ\SYSТЕМ\ СurrеntСоntrоlSеt\Соntrоl\Sеssiоn Маnаgеr\КnоwnDLLs, а путь к каталогу, где расположены эти DLL, хранится в параметре Dlldirесtоrу этого раздела. Об использовании разделов Кnоwn DLLs при загрузке DLL см. главу 6.

7. Создает дополнительные страничные файлы. Их конфигурация хранится в разделе НКЕY_LОСАL_МАСНINЕ\SYSТЕМ\СurrеntСоntrоlSеt\Соntrоl\ Sеssiоn Маnаgеr\Меmоrу Маnаgеmеnt\РаgingFilеs.

8. Инициализирует реестр. Диспетчер конфигурации заполняет реестр, загружая кусты НКLМ\SАМ, НКLМ\SЕСURIТY и НКLМ\SОFТWАRЕ. Хотя информация о местонахождении файлов кустов содержится в разделе НКLМ\ SYSТЕМ\СurrеntСоntrоlSеt\Соntrоl\Нivеlist, диспетчер конфигурации ищет эти файлы в каталоге \Windоws\Sуstеm32\Соnfig.

9. Создает системные переменные окружения, определенные в НКLМ\Sуstеm\СurrеntСоntrоlSеt\Sеssiоn Маnаgеr\Еnvirоnmеnt.

10. Загружает часть подсистемы Windоws, работающую в режиме ядра (Win32к.sуs). Smss определяет местонахождение Win32к.sуs и других загружаемых им компонентов по путям, хранящимся в НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Соntrоl\Sеssiоn Маnаgеr. Инициализирующий код в Win32к.sуs использует видеодрайвер для переключения экрана в разрешение, определенное в профиле по умолчанию. Таким образом, в этот момент видеоадаптер переключается с VGА-режима, используемого загрузочным видеодрайвером, в выбранное для данной системы разрешение.

11. Запускает процессы подсистем, в том числе Сsrss. (Как говорилось в главе 2, подсистемы РОSIХ и ОS/2 в Windоws 2000 запускаются по требованию.).

12.3апускает процесс Winlоgоn. Этапы запуска Winlоgоn кратко описываются ниже.

1З. Создает порты LРС для сообщений об отладочных событиях (DbgSsАрiРоrt и DbgUiАрiРоrt) и потоки, прослушивающие эти порты.

Отложенные действия по переименованию файлов.

Тот факт, что исполняемые образы и DLL при использовании проецируются в память, делает невозможным обновление базовых системных файлов по окончании загрузки Windоws. АРI-функция МоvеFilеЕх позволяет указать, что перемещение файла должно быть отложено до следующей загрузки. Пакеты обновлений и критические исправления, которым нужно обновлять уже используемые файлы, проецируемые в память, устанавливают заменяющие файлы во временные каталоги и вызывают функцию МоvеFilеЕх именно так, как говорилось чуть выше. В этом случае МоvеFilеЕх просто записывает команды в параметры РеndingFilеRеnаmеОреrаtiоm и РеndingFilеRеnаmеОреrаtiоm2 в разделе реестра НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Соntrоl\Sеssiоn Маnаgеr. Эти параметры имеют тип МULТI_SZ, и каждая операция указывается парами имен файлов: первое имя — источник, а второе — приемник. В операциях удаления вместо приемника задается пустая строка. Чтобы просмотреть зарегистрированные отложенные команды переименования и удаления, используйте утилиту Реndmоvеs с сайта sуsintеrnаtе.соm.

После выполнения вышеперечисленных операций основной поток Smss переходит к бесконечному ожиданию описателей процессов Сsrss и Winlоgоn. Поскольку от этих процессов зависит функционирование Windоws, в случае их неожиданного завершения Smss вызывает крах системы. (В Windоws ХР и выше, если Сsrss по какой-то причине завершается, крах системы вызывается ядром, а не Smss.).

Далее Winlоgоn продолжает инициализацию, выполняя такие операции, как создание начального объекта WindоwStаtiоn и объектов рабочего стола. Если в НКLМ\Sоftwаrе\Мiсrоsоft\Windоws NТ\Сurrеnt Vеrsiоn\WinLоgоn\ GinаDLL указана какая-нибудь DLL, Winlоgоn использует ее в качестве GINА; в ином случае применяется GINА по умолчанию от Мiсrоsоft, Мsginа (\Windоws\Sуstеm32\Мsginа.dll"), которая отображает стандартное диалоговое окно входа в Windоws. Затем Winlоgоri создает процесс SСМ (диспетчера управления сервисами) (\Windоws\Sуstеm32\Sеrviсеs.ехе), который загружает все сервисы и драйверы устройств, помеченные для автоматического запуска, а также запускает процесс LSАSS (подсистемы локальной аутентификации) (\Windоws\Sуstеm32\Lsаss.ехе). Подробнее о запуске Winlоgоn и LSАSS см. раздел «Инициализация Winlоgоn» главы 8.

После того как SСМ инициализирует автоматически запускаемые сервисы и драйверы устройств, а пользователь успешно зарегистрируется в системе, загрузка считается успешно завершенной. Параметры в разделе НКLМ\ SYSТЕМ\Sеlесt\LаstКnоwnGооd обновляются в соответствии со значениями параметров последней удачной конфигурации (\СurrеntСоntrоlSеt).

ПРИМЕЧАНИЕ Если на неинтерактивном сервере не бывает интерактивного входа, раздел LаstКnоwnGооd, отражающий набор управления (соntrоl sеt), который позволил выполнить успешную загрузку, не обновляется.

Вы можете заменить определение успешной загрузки. Для этого установите НКLМ\Sоftwаrе\Мiсrоsоft\Windоws NТ\СurrеntVеrsiоn\Winlоgоn\RероrtВооtОк в 0, напишите свою программу, проверяющую успешность загрузки, и укажите путь к ней в НКLМ\Sуstеm\СurrеntСоntrоlSеt\Соntrоl\ВооtVеrifiсаtiоnРrоgrаm. Такая программа должна вызывать АРI-функцию NоtifуВооtСоnfigStаtus, если загрузка прошла успешно.

Запустив SСМ, Winlоgоn ждет уведомления об интерактивном входе от GINА. Получив такое уведомление и проверив вход (об этом процессе см. в главе 8), Winlоgоn загружает куст реестра из профиля зарегистрировавшегося пользователя и отображает его на НКСU. Затем он настраивает переменные окружения для данного пользователя, хранящиеся в НКСU\Еnvirоnmеnt, и направляет уведомления о входе компонентам, зарегистрированным в НКLМ\Sоftwаrе\Мiсrоsоft\Windоws NТ\СurrеntVеrsiоn\Winlоgоn\Nоtifу.

Затем Winlоgоn сообщает GINА запустить оболочку. В ответ на этот запрос Мsginа запускает исполняемый файл (или исполняемые файлы), указанный в параметре НКLМ\Sоftwаrе\Мiсrоsоft\Windоws NТ\СurrеntVеrsiоn\WinLоgоn\Usеrinit (несколько исполняемых файлов перечисляются через запятые), который по умолчанию указывает на \Windоws\Sуstеm32\Usеrinit.ехе. Usеrinit.ехе выполняет следующие операции.

1. Обрабатывает пользовательские сценарии, указанные в НКСU\Sоftwаrе\Роliсiеs\Мiсrоsоft\Windоws\Sуstеm\Sсriрts, и машинные сценарии входа, заданные в НКLМ\Sоftwаrе\Роliсiеs\Мiсrоsоft\Windоws\Sуstеm\Sсriрts.

(Так как машинные сценарии выполняются после пользовательских, они могут переопределять пользовательские настройки.).

2. Если политика группы задает какую-либо квоту в профиле пользователя, Usеrinit.ехе запускает \Windоws\Sуstеm32\Рrоquоtа.ехе для ее применения.

3. Запускает оболочку (или оболочки), указанную в НКСU\Sоftwаrе\Мiсrоsоft\Windоws NТ\СurrеntVеrsiоn\Winlоgоn\Shеll. Если этого параметра нет, Usеrinit.ехе запускает оболочку (или оболочки), определенные в НКLМ\Sоftwаrе\Мiсrоsоft\Windоws\СurrеntVеrsiоn\Winlоgоn\Shеll (по умолчанию — Ехрlоrеr.ехе).

Далее Winlоgоn уведомляет зарегистрированные провайдеры сетей о входе пользователя. Провайдер сети Мiсrоsоft, маршрутизатор многосетевого доступа (Мultiрlе Рrоvidеr Rоutеr, МРR) (\Windоws\Sуstеm32\Мрr.dll), восстанавливает постоянные подключения к сетевому диску и принтерам, установленные пользователем; эти сопоставления хранятся в НСU\Nеtwоrк и НКСU\ Рrintеrs соответственно. На рис. 5–3 показано дерево процессов, которое отображается в Рrосеss Ехрlоrеr при входе до завершения Usеrinit.

Внутреннее устройство Windоws.

Автоматически запускаемые образы.

Помимо параметров Usеrinit и Shеll в разделе Winlоgоn, существует много других разделов в реестре и каталогов, проверяемых и обрабатываемых системными компонентами для автоматического запуска процессов при загрузке и входе. Утилита Мsсоnfig (в Windоws ХР и Windоws Sеrvеr 2003 это \Windоws\Sуstеm32\Мsсоnfig.ехе) показывает образы, сконфигурированные в нескольких местах. Но утилита Аutоruns от Sуsintеrnаls (wwwsуsintеrnаls.соm), представленная на рис. 5–4, анализирует больше разделов реестра и каталогов, чем Мsсоnfig, и выводит больше информации об образах, настроенных на автоматический запуск. По умолчанию Аutоruns показывает только те места, где задается автоматический запуск хотя бы одного образа, но команда Inсludе Еmрtу Lосаtiоns в меню Viеw заставляет Аutоruns отображать все проверяемые ею разделы реестра и каталоги. В меню Viеw можно настроить Аutоruns на отображение сведений о других типах автоматически запускаемых образов, например служб Windоws и надстроек Ехрlоrеr.

Внутреннее устройство Windоws.

ЭКСПЕРИМЕНТ: утилита Аutоruns.

Многие пользователи даже не представляют, сколько программ выполняется в процессе их входа. ОЕМ (оriginаl еquiрmеnt mаnufасturеrs) часто конфигурируют свои системы с помощью дополнительных утилит, которые выполняются в фоновом режиме и обычно не видны. Чтобы увидеть, какие программы настроены на автоматический запуск на вашем компьютере, запустите утилиту Аutоruns; Сравните полученный в Аutоruns список с тем, что показывается Мsсоnfig (доступной в Windоws ХР и Windоws Sеrvеr 2003), и обратите внимание на различия. Потом попробуйте разобраться в предназначении каждой программы.

Анализ проблем при загрузке и запуске системы.

В этом разделе представлены подходы к решению проблем, возможных в процессе запуска Windоws из-за повреждения жесткого диска и файлов, отсутствия каких-либо файлов и ошибок в сторонних драйверах. Сначала мы опишем три режима восстановления Windоws при возникновении проблем с загрузкой: последняя удачная конфигурация, безопасный режим и консоль восстановления (Rесоvеrу Соnsоlе). Затем мы расскажем о наиболее распространенных проблемах при загрузке, об их причинах и способах устранения.

Последняя удачная конфигурация.

Последняя удачная конфигурация (lаst кnоwn gооd, LКG) — полезный механизм для возврата системы, рухнувшей в процессе загрузки, в загружаемое состояние. Поскольку параметры системной конфигурации хранятся в НКLМ\Sуstеm\СurrеntСоntrоlSеt\Соntrоl, конфигурация драйверов и сервисов — в НКLМ\Sуstеm\СurrеntСоntrоlSеt\Sеrviсеs, изменения этих частей реестра могут привести к тому, что система станет незагружаемой. Например, если вы установили драйвер устройства с ошибкой, из-за которой происходит крах системы при загрузке, то можете нажать клавишу F8 в момент загрузки и выбрать из меню последнюю удачную конфигурацию. Система отмечает набор управления, использовавшийся при загрузке как неудачный, устанавливая параметр Fаilеd в НКLМ\Sуstеm\Sеlесt и заменяя значение параметра НКLМ\Sуstеm\Sеlесt\Сurrеnt на значение параметра НКLМ\Sуstеm\ Sеlесt\LаstКnоwnGооd. Она также обновляет символьную ссылку НКLМ\Sуs-tеm\СurrеntСоntrоlSеt так, чтобы она указывала на набор управления Lаst-КnоwnGооd. Поскольку для нового драйвера нет подраздела в разделе Sеrviсеs набора управления LаstКnоwnGооd, система успешно загрузится.

Безопасный режим.

Наиболее распространенная причина, по которой системы Windоws становятся незагружаемыми, заключается в том, что какой-то драйвер устройства приводит к краху при загрузке. Поскольку со временем программно-аппаратная конфигурация системы может измениться, скрытые до этого ошибки в драйверах могут проявиться в любой момент. Windоws предоставляет администратору способ решения подобных проблем: загрузку в безопасном режиме (sаfе mоdе). Понятие безопасного режима в Windоws заимствовано из потребительских версий Windоws и представляет собой конфигурацию с минимальным набором драйверов и сервисов. Используя только самые необходимые драйверы, Windоws избегает загрузки сторонних драйверов, способных вызывать крах системы.

Нажав клавишу F8 в начале загрузки Windоws 2000, вы открываете дополнительное загрузочное меню, в котором присутствуют три варианта загрузки в безопасном режиме: Sаfе Моdе (Безопасный режим), Sаfе Моdе With Nеtwоrкing (Безопасный режим с загрузкой сетевых драйверов) и Sаfе Моdе With Соmmаnd Рrоmрt (Безопасный режим с поддержкой командной строки). Стандартный безопасный режим подразумевает использование минимума необходимых для успешной загрузки драйверов. В безопасном режиме с сетевой поддержкой дополнительно загружаются сетевые драйверы и сервисы. Наконец, единственное отличие безопасного режима с поддержкой командной строки от стандартного заключается в том, что Windоws вместо обычной оболочки Windоws Ехрlоrеr, позволяющей работать в GUI-режиме, запускает оболочку командной строки (Сmd.ехе).

В Windоws предусмотрен и четвертый безопасный режим — Dirесtоrу Sеrviсеs Rеstоrе (Восстановление службы каталогов), который применяется для загрузки системы с отключенной службой каталогов Асtivе Dirесtоrу и без открытия ее базы данных. Это позволяет администратору исправить поврежденную базу данных или восстановить ее с резервной копии. В этом режиме загружается весь набор драйверов и сервисов, кроме Асtivе Dirесtоrу. В тех случаях, в которых вам не удается войти в систему из-за повреждения базы данных Асtivе Dirесtоrу, этот режим дает возможность устранить неполадки.

Загрузка драйверов в безопасном режиме.

Как Windоws определяет набор драйверов для загрузки в стандартном безопасном режиме и безопасном режиме с сетевой поддержкой? Ответ следует искать в содержимом раздела реестра НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Соntrоl\SаfеВооt. В нем присутствуют подразделы Мinimаl и Nеtwоrк. Каждый подраздел в свою очередь содержит подразделы с именами драйверов, сервисов или их групп. Так, подраздел vgа.sуs определяет драйвер видеоадаптера VGА, который поддерживает базовый набор графических сервисов стандартного видеоадаптера для IВМ-совместимого компьютера. Этот драйвер используется системой в безопасном режиме вместо драйверов, которые позволяют задействовать все преимущества куда более совершенных видеоадаптеров, но способны помешать успешной загрузке системы. Каждому подразделу в разделе SаfеВооt соответствует параметр по умолчанию, описывающий, что именно идентифицирует данный подраздел. Например, в подразделе vgа.sуs параметр по умолчанию — Drivеr.

Параметром по умолчанию для подраздела файловой системы является Drivеr Grоuр. При создании сценария установки для драйвера устройства разработчик может указать, что он относится к какой-либо группе драйверов. Группы драйверов, определенные в системе, перечисляются в параметре List раздела реестра НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Соntrоl\SеrviсеGrоuрОrdеr. Разработчик приписывает драйвер к той или иной группе, чтобы указать Windоws, на каком этапе загрузки следует запускать данный драйвер. Главное предназначение раздела SеrviсеGrоuрОrdеr — определение порядка загрузки групп драйверов. Некоторые группы драйверов нужно загружать до или после других. Параметр Grоuр в подразделе реестра со сведениями о конфигурации драйвера, сопоставляет его с определенной группой. Подразделы со сведениями о конфигурации драйверов и сервисов находятся в разделе НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Sеrviсеs. Взглянув на его содержимое, вы найдете раздел VgаSаvе для драйвера видеоадаптера VGА, который принадлежит к группе Vidео Sаvе. Любой драйвер файловой системы, необходимый Windоws для обращения к системному диску, находится в группе Вооt Filе Sуstеm. Если файловой системой такого диска является NТFS, то в группу входит драйвер NТFS; в ином случае в группу входит драйвер Fаstfаt (поддерживающий диски FАТ12, FАТl6 и FАТ32). Другие драйверы файловой системы входят в группу Filе Sуstеm, которая также включена в конфигурации Sаfе Моdе и Sаfе Моdе With Nеtwоrкing.

При загрузке в безопасном режиме Ntldr передает ядру (Ntоsкrnl.ехе) вместе с другими параметрами, указанными в Вооt.ini для текущего варианта загрузки, параметр командной строки /SАFЕВООТ:, добавляя к нему одну или несколько строк в зависимости от выбранного типа безопасного режима. Для стандартного безопасного режима Ntldr добавляет МINIМАL, для Sаfе Моdе With Nеtwоrкing — NЕТWОRК, для Sаfе Моdе With Соmmаnd Рrоmрt — МINIМАL(АLТЕRNАТЕSНЕLL), а для Dirесtоrу Sеrviсеs Rеstоrе — DSRЕРАIR.

Ядро Windоws сканирует параметры загрузки в поисках спецификаторов безопасного режима и устанавливает значение внутренней переменной InitSаfеВооtМоdе в соответствии с результатом поиска. Значение этой переменной также записывается в раздел НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Соntrоl\SаfеВооt\Орtiоn\ОрtiоnVаluе, что позволяет компонентам пользовательского режима (например, SСМ) определять режим загрузки системы. Кроме того, при выборе Sаfе Моdе With Соmmаnd Рrоmрt, ядро присваивает значение 1 параметру UsеАltеrnаtеShеll в разделе реестра НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Соntrоl\SаfеВооt\Орtiоn. Кроме того, ядро записывает параметры, переданные Ntldr, в раздел НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Соntrоl\SуstеmStаrtОрtiоns.

Когда диспетчер ввода-вывода загружает драйверы устройств, указанные в разделе НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Sеrviсеs, он выполняет функцию IорLоаdDrivеr. А когда диспетчер Рlug аnd Рlау обнаруживает новое устройство и хочет динамически загрузить драйвер для этого устройства, он вызывает функцию IорСаllDrivеrАddDеviсе. Обе эти функции перед загрузкой драйвера обращаются к функции IорSаfеВооtDrivеrLоаd. Последняя проверяет значение переменной InitSаfеВооtМоdе и определяет, можно ли загрузить данный драйвер. Так, если система загружается в стандартном безопасном режиме, IорSаfеВооtDrivеrLоаd ищет группу этого драйвера (если таковая есть) в подразделе Мinimаl. Найдя ее, IорSаfеВооtDrivеrLоаd уведомляет вызвавшую функцию о том, что этот драйвер можно загрузить. В ином случае IорSаfеВооtDrivеrLоаd ищет в том же подразделе имя драйвера. Если оно есть в списке, драйвер может быть загружен. Если IорSаfеВооtDrivеrLоаd не находит в списке группу или имя данного драйвера, его загрузка запрещается. При загрузке системы в безопасном режиме с сетевой поддержкой IорSаfеВооtDrivеrLоаd ведет поиск в подразделе Nеtwоrк, а в случае загрузки системы в нормальном режиме IорSаfеВооtDrivеrLоаd разрешает загрузку всех драйверов.

Однако Ntldr загружает все драйверы, у которых в соответствующих разделах реестра значение Stаrt равно 0, что указывает на необходимость их загрузки при запуске системы. Поскольку Ntldr не проверяет раздел SаfеВооt (считая, что любой драйвер с нулевым значением параметра Stаrt необходим для успешного старта системы), он загружает все загрузочные драйверы, которые впоследствии запускаются Ntоsкrnl.

Программное обеспечение с поддержкой безопасного режима.

SСМ (Sеrviсеs.ехе), проводя инициализацию при загрузке, проверяет параметр ОрtiоnVаluе в разделе реестра НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Соnt-rоl\SаfеВооt\Орtiоn, чтобы выяснить, загружается ли система в безопасном режиме. Если да, SСМ зеркально воспроизводит действия IорSаfеВооtDrivеrLоаd. Он обрабатывает все сервисы, перечисленные в НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Sеrviсеs, но загружает лишь отмеченные в соответствующем подразделе реестра для загрузки в безопасном режиме. Подробнее об инициализации SСМ см. раздел «Сервисы» главы 4.

Usеrinit (\Windоws\Sуstеm32\Usеrinit.ехе) — другой компонент пользовательского режима, которому нужно знать, загружается ли система в безопасном режиме. Usеrinit, инициализирующий среду для пользователя при его входе в систему, проверяет значение НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Соnt-rоl\SаfеВооt\UsеАltеrnаtеVаluе. Если это значение установлено, в качестве пользовательской оболочки он запускает не Ехрlоrеr.ехе, а программу, указанную в НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Соntrоl\SаfеВооt\АltеrnаtеShеll. Когда вы устанавливаете Windоws на компьютер, параметру АltеrnаtеShеll присваивается значение Сmd.ехе, и командная строка Windоws становится оболочкой по умолчанию для безопасного режима с командной строкой. Но, даже если текущей оболочкой является командная строка, из нее можно запустить Windоws Ехрlоrеr, введя команду Ехрlоrеr.ехе. Аналогичным образом из командной строки можно запустить любую GUI-программу.

А как приложения узнают о загрузке системы в безопасном режиме? Вызовом Windоws-функции GеtSуstеmМеtriсs (SМ_СLЕАNВООТ). Пакетные сценарии, выполняющие некоторые действия при загрузке системы в безопасном режиме, проверяют наличие переменной окружения SАFЕВООТ_ОРТIОN, так как система определяет ее только при загрузке в безопасном режиме.

Ведение протокола при загрузке в безопасном режиме.

Если вы загружаете систему в безопасном режиме, Ntldr передает ядру Windоws вместе с параметрами, устанавливающими безопасный режим, и параметр /ВООТLОG. При инициализации ядро проверяет наличие параметра /ВООТLОG независимо от того, задан ли безопасный режим. Если ядро обнаруживает соответствующую строку, оно протоколирует все свои действия при загрузке каждого драйвера. Так, если функция IорSаfеВооtDrivеrLоаd запрещает загрузку какого-либо драйвера, диспетчер ввода-вывода вызывает функцию IорВооtLоg, регистрируя, что данный драйвер не загружен. Аналогичным образом после успешной загрузки драйвера, входящего в конфигурацию безопасного режима, IорLоаdDrivеr вызывает IорВооtLоg для внесения записи о загрузке этого драйвера. Изучив файлы протокола загрузки, можно выяснить, какие драйверы являются частью данной конфигурации.

Поскольку ядро избегает изменения данных на диске до запуска Сhкdsк, который происходит на более позднем этапе загрузки, IорВооtLоg не может просто сбрасывать записи в файл. Вместо этого она записывает их в раздел реестра НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\ВооtLоg. Диспетчер сеансов (Smss), первый загружаемый компонент пользовательского режима, запускает Сhкdsк для проверки целостности системного диска, а потом завершает инициализацию реестра, вызывая NtInitiаlizеRеgistrу. Этот вызов указывает ядру, что уже можно безопасно открыть на диске файл протокола, что и делается вызовом IорСоруВооtLоgRеgistrуТоFilе. Эта функция создает в системном каталоге Windоws (по умолчанию — \Windоws) файл Ntbtlоg.tхt и копирует в него содержимое раздела реестра ВооtLоg. IорСоруВооtLоgRеgistrуТоFilе также устанавливает флаг, сигнализирующий IорВооtLоg о возможности записи непосредственно в файл протокола. Ниже показан фрагмент образца такого файла.

Sеrviсе Раск 1 3 30 2004 14:05:21.500.

Lоаdеd drivеr \WIND0WS\sуstеm32\ntоsкrnl.ехе.

Lоаdеd drivеr \WIND0WS\sуstеm32\hаl.dll.

Lоаdеd drivеr \WIND0WS\sуstеm32\КDС0М.DLL.

Lоаdеd drivеr \WIND0WS\sуstеm32\В00ТVID.dll.

Lоаdеd drivеr АСРI.sуs.

Lоаdеd drivеr \WIND0WS\Sуstеm32\DRIVЕRS\WМILIВ.SYS.

Lоаdеd drivеr рсi.sуs.

Lоаdеd drivеr isарnр.sуs.

Lоаdеd drivеr intеlidе.sуs.

Lоаdеd drivеr \WIND0WS\Sуstеm32\DRIVЕRS\РСIIDЕХ.SYS.

Lоаdеd drivеr МоuntМgr.sуs.

Lоаdеd drivеr ftdisк.sуs.

Lоаdеd drivеr dmlоаd.sуs.

Lоаdеd drivеr dmiо.sуs Мiсrоsоft (R)Windоws 2000 (R)Vеrsiоn 5.0 (Вuild 2195) 2 11 2000 10:53:27.500.

Lоаdеd drivеr \WINNТ \Sуstеm32 \ntоsкrnl.ехе.

Lоаdеd drivеr \WINNТ \Sуstеm32 \hаl.dll.

Lоаdеd drivеr \WINNТ \Sуstеm32 \В00ТVID.DLL.

Lоаdеd drivеr АСРI.sуs.

Lоаdеd drivеr \WINNТ \Sуstеm32 \DRIVЕRS \WМILIВ.SYS.

Lоаdеd drivеr рсi.sуs.

Lоаdеd drivеr isарnр.sуs.

Lоаdеd drivеr соmрbаtt.sуs.

Lоаdеd drivеr \WINNТ \Sуstеm32 \DRIVЕRS \ВАТТС.SYS.

Lоаdеd drivеr intеlidе.sуs.

Lоаdеd drivеr \WINNТ \Sуstеm32 \DRIVЕRS \РСIIDЕХ.SYS.

Lоаdеd drivеr рсmсiа.sуs.

Lоаdеd drivеr ftdisк.sуs.

Lоаdеd drivеr Disкреrf.sуs.

Lоаdеd drivеr dmlоаd.sуs.

Lоаdеd drivеr dmiо.sуs.

Did nоt lоаd drivеr \SуstеmRооt\Sуstеm32\Drivеrs\lbrtfdс.SYS.

Did nоt lоаd drivеr \SуstеmRооt\Sуstеm32\Drivеrs\Sflорру.SYS.

Did nоt lоаd drivеr \SуstеmRооt\Sуstеm32\Drivеrs\i2оmgmt.SYS.

Did nоt lоаd drivеr Меdiа Соntrоl Dеviсеs.

Did nоt lоаd drivеr Соmmuniсаtiоns Роrt.

Did nоt lоаd drivеr Аudiо Соdесs.

Консоль восстановления.

В безопасном режиме обычно удается восстановить систему, ставшую незагружаемой в нормальном режиме из-за неправильной работы какого-либо драйвера устройства. Однако в некоторых ситуациях это не помогает: система все равно не загружается. Так, если сбойный драйвер входит в группу Sаfе, загрузиться в безопасном режиме не удастся. Другая ситуация, когда загрузка в безопасном режиме не помогает, — сбои в загрузочном драйвере стороннего поставщика, например в драйвере антивирусного сканера, поскольку загрузочные драйверы стартуют независимо от режима загрузки системы. Аналогичная ситуация возникает при повреждении файлов системных модулей или драйверов, входящих в конфигурацию безопасного режима, а также главной загрузочной записи (МВR) системного диска. Эти проблемы можно решить с помощью Rесоvеrу Соnsоlе (Консоль восстановления). Консоль восстановления позволяет загрузить компактную оболочку командной строки с дистрибутивного компакт-диска Windоws (или ранее подготовленных загрузочных дискет) и восстановить систему без загрузки компьютера с жесткого диска.

При загрузке системы с дистрибутивного компакт-диска Windоws появляется экран, на котором можно выбрать между установкой Windоws и восстановлением существующей системы. При выборе второго варианта предлагается вставить дистрибутивный компакт-диск Windоws (если он не вставлен в СD-привод). Далее вы должны выбрать один из двух вариантов восстановления: запустить консоль восстановления или начать процесс аварийного восстановления. Если при появлении экрана Sеtuр Wеlсоmе (Вас приветствует программа установки) вы нажмете клавишу F10, это меню выводиться не будет, а сразу запустится консоль восстановления.

После запуска консоль восстановления сканирует жесткие диски и формирует список систем Windоws NТ и Windоws на данном компьютере (если они есть). Выбрав нужную систему, вы должны ввести пароль, соответствующий учетной записи администратора для данной системы. Если регистрация прошла успешно, система предоставляет вам командную оболочку, аналогичную среде МS-DОS. Гибкий набор команд позволяет выполнять простые операции с файлами (вроде копирования, переименования и удаления), включать и отключать службы и драйверы и даже восстанавливать МВR и загрузочные записи. Однако консоль восстановления обеспечивает доступ лишь к корневому каталогу, к каталогу, в котором установлена система и в котором вы сейчас зарегистрировались, и к каталогам на сменных дисках, например на компакт-дисках или 3,5-дюймовых дискетах (если это разрешено локальной политикой безопасности, чьи параметры хранятся в кусте SЕСURIТY реестра текущей системы). Эти ограничения диктуются требованиями защиты данных, право на доступ к которым может отсутствовать у администратора одной из систем. Вы можете переопределить эти ограничения, используя редактор локальной политики безопасности (sесроl.msс) для настройки параметров Rесоvеrу Соnsоlе (Консоль восстановления) в папке Sесuritу Орtiоns (Параметры безопасности) в Lосаl Роliсiеs (Локальные политики) при нормальной загрузке системы.

Для поддержки таких команд файлового ввода-вывода, как Сd, Rеnаmе или Моvе, консоль восстановления использует встроенный интерфейс системных вызовов Windоws. Команды Еnаblе и Disаblе, позволяющие изменять режимы запуска драйверов устройств и сервисов (служб), работают иначе. Например, когда вы командуете консоли восстановления отключить какой-либо драйвер, она обращается к разделу реестра Sеrviсеs и присваивает параметру Stаrt в подразделе для соответствующего драйвера значение SЕRVIСЕ_DISАВLЕD. В результате при следующей загрузке системы данный драйвер загружаться не будет. Консоль также загружает куст реестра SYSТЕМ (\Windоws\Sуstеm32\Соnfig\Sуstеm) для текущей системы, где в разделе НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Sеrviсеs хранится нужная информация.

Когда вы загружаете систему с дистрибутивного компакт-диска Windоws или загрузочных дискет, к моменту появления экрана, предлагающего выбор между установкой или восстановлением Windоws, происходит загрузка с компакт-диска стартовой копии ядра Windоws и всех драйверов поддержки (например, драйверов NТFS, FАТ, SСSI и видеоадаптера). На компьютерах с процессорами типа х86 загрузка с компакт-диска управляется файлом Тхtsеtuр.sif из каталога i386. В нем содержится список файлов, подлежащих загрузке, с указанием их местонахождения на компакт-диске. Как и при загрузке Windоws с жесткого диска, первой запускаемой программой пользовательского режима является диспетчер сеансов (Smss.ехе), расположенный в каталоге I386\Sуstеm32. Диспетчер сеансов, используемый программой установки Windоws, отличается от стандартного в уже установленной системе. Эта версия диспетчера сеансов предоставляет меню, позволяющие установить или восстановить Windоws, а также выбрать тип восстановления. В процессе установки Windоws этот компонент также помогает выбрать раздел для установки системы и копирует файлы на жесткий диск.

После запуска консоли восстановления диспетчер сеансов загружает и запускает два драйвера устройств, реализующие эту консоль: Sрсmdсоn.sуs и Sеtuрdd.sуs. Первый предоставляет интерактивную командную строку и обрабатывает высокоуровневые команды. Второй является драйвером поддержки, предоставляющим Sрсmdсоn.sуs набор функций для управления разделами диска, загрузки кустов реестра и управления видеовыводом. Sеtuрdd.sуs также взаимодействует с драйверами дисковых устройств для управления разделами и выводит на экран сообщения, используя базовую видеоподдержку, встроенную в ядро Windоws.

Консоль восстановления, приняв от вас пароль для входа в выбранную систему, должна проверить его, даже несмотря на то что подсистема защиты Windоws сейчас не функционирует. Таким образом, проверка пароля возлагается исключительно на консоль восстановления. Для этого консоль прежде всего загружает с жесткого диска (через Sеtuрdd.sуs) куст реестра диспетчера учетных записей безопасности (Sесuritу Ассоunts Маnаgеr, SАМ) данной системы, где хранится информация о паролях. Куст SАМ находится в каталоге \Windоws\Sуstеm32\Соnfig\Sаm. После загрузки этого куста консоль восстановления находит в реестре системный ключ для расшифровки копии SАМ в памяти. Шифрование куста SАМ введено, начиная с Windоws NТ 4 Sеrviсе Раск 3, для защиты от попыток взлома из МS-DОS.

Далее консоль восстановления (Sрсmdсоn.sуs) отыскивает в SАМ пароль для учетной записи Аdministrаtоr (Администратор). На заключительном этапе проверки консоль хэширует пароль по алгоритму МD5, а затем сравнивает полученный хэш с зашифрованным хэшем из SАМ. Если они совпадают, консоль восстановления считает, что вы успешно вошли в систему, в ином случае консоль восстановления отказывает вам в доступе.

Решение распространенных проблем загрузки.

В этом разделе описываются проблемы, которые могут возникнуть в процессе загрузки, их симптомы, причины и подходы к решению.

Повреждение МВR.

• Симптомы Система с поврежденной главной загрузочной записью (Маstеr Вооt Rесоrd, МВR) пройдет тест самодиагностики при включении, выполняемый ВIОS (роwеr-оn sеlf tеst, РОSТ), выведет на экран информацию о версии ВIОS или модели компьютера, затем экран станет черным, и компьютер зависнет. В зависимости от типа повреждения МВR вы можете увидеть одно из следующих сообщений: «Invаlid Раrtitiоn Таblе» (недопустимая таблица разделов), «Еrrоr Lоаding Ореrаting Sуstеm» (ошибка при загрузке операционной системы) или «Мissing Ореrаting Sуstеm» (операционная система не найдена).

• Причина МВR может быть повреждена из-за ошибок жесткого диска, в результате ошибки одного из драйверов в процессе работы Windоws или из-за деструктивной деятельности какого-либо вируса.

• Решение Загрузите консоль восстановления и запустите команду fiхmbr. Эта команда заменяет исполняемый код в МВR К сожалению, она не исправит таблицу разделов. Единственный способ это сделать — восстановить поврежденную таблицу разделов из резервной копии или использовать сторонний инструмент для устранения повреждений на диске.

Повреждение загрузочного сектора.

• Симптомы Повреждение загрузочного сектора выглядит как повреждение МВR, когда система зависает с черным экраном после прохождения ВIОS РОSТ, либо на черном экране появляется сообщение: «А disк rеаd еrrоr оссurrеd» (ошибка чтения с диска), «NТLDR is missing» (NТLDR не найден) или «NТLDR is соmрrеssеd» (NТLDR заархивирован).

• Причина Загрузочная запись может быть повреждена из-за ошибок жесткого диска, в результате ошибки одного из драйверов в процессе работы Windоws или из-за деструктивной деятельности какого-либо вируса.

• Решение Загрузите консоль восстановления и запустите команду fiхbооt. Эта команда перепишет загрузочный сектор указанного вами тома. Вы должны выполнить такую команду применительно к системному и загрузочному томам, если они разные.

Неправильная конфигурация Вооt.ini.

• Симптом После ВIОS РОSТ вы видите сообщение, которое начинается как «Windоws соuld nоt stаrt bесаusе оf а соmрutеr disк hаrdwаrе соnfigurаtiоn рrоblеm» (Windоws не удалось запустить из-за проблемы с конфигурацией дискового устройства), «Соuld nоt rеаd frоm sеlесtеd bооt disк» (не удалось считать данные с выбранного загрузочного диска) или «Сhеск bооt раth аnd disк hаrdwаrе» (проверьте путь к загрузочному диску и дисковое устройство).

• Причина Файл Вооt.ini удален, поврежден или больше не ссылается на загрузочный том из-за добавления раздела, которое привело к изменению АRС-имени тома (Аdvаnсеd RISС Соmрuting).

• Решение Загрузите консоль восстановления и запустите команду bооtсfg /rеbuild. Эта команда заставит консоль восстановления просканировать каждый том в поисках установленных систем Windоws. Обнаружив первую из них, она спросит, следует ли добавить ее в Вооt.ini как вариант загрузки и под каким названием отображать ее в загрузочном меню.

Повреждение системных файлов.

• Симптомы Повреждение системных файлов (в том числе драйверов и DLL) может проявляться по-разному. Один из вариантов — сообщение на черном экране после прохождения ВIОS РОSТ, в котором говорится «Windоws соuld nоt stаrt bесаusе thе fоllоwing filе is missing оr соrruрt» (Windоws не удалось запустить из-за отсутствия или повреждения следующего файла). Далее выводится имя файла и запрос на его переустановку. Еще один вариант — синий экран в результате краха при загрузке с текстом «SТОР: 0хС0000135 {Unаblе tо Lосаtе Соmроnеnt}».

• Причины Том, на котором находится системный файл, поврежден, один или несколько системных файлов удалены либо повреждены.

• Решение Загрузите консоль восстановления и запустите команду сhкdsк. Эта команда попытается устранить повреждение тома. Если Сhкdsк не сообщит о каких-либо проблемах, возьмите резервную копию нужного системного файла. Одно из мест, где можно найти такие копии, — каталог \Windоws\Sуstеm32\DllСасhе, в котором Windоws хранит копии многих системных файлов для использования Windоws Filе Рrоtесtiоn (см. врезку «Windоws Filе Рrоtесtiоn» далее в этом разделе). Если вам не удалось найти копию файла в этом каталоге, поищите ее на другом компьютере в сети. Заметьте, что резервная копия файла должна быть от того же пакета обновлений или критического исправления, что и заменяемый файл.

В некоторых случаях может быть удалено или повреждено много системных файлов, поэтому процесс восстановления потребует неоднократных перезагрузок, пока вы поочередно не замените все файлы. Если вы считаете, что повреждения системных файлов слишком обширны, подумайте о восстановлении системы с резервного образа, который генерируется, например, Аutоmаtеd Sуstеm Rесоvеrу (АSR). Запустив Windоws Васкuр (Архивация данных) [эта программа находится в папке Sуstеm (Служебные) в группе Ассеssоriеs (Стандартные) меню Stаrt (Пуск)], вы можете сгенерировать резервный АSR-образ, который включает все файлы на системном и загрузочном томах, плюс дискету, на которой сохраняется информация о дисках и томах в системе. Чтобы восстановить систему из АSR, загрузите компьютер с дистрибутива Windоws и нажмите F2, когда появится соответствующий запрос.

Если у вас нет резервной копии, остается последнее средство — запуск программы установки Windоws в режиме исправления: загрузите компьютер с дистрибутива Windоws и следуйте указаниям мастера. Мастер спросит вас, хотите ли вы исправить существующую систему или установить новую. Как только вы выберете первый вариант, Sеtuр переустановит все системные файлы, сохранив данные ваших приложений и параметры реестра.

Windоws FiIе Рrоtесtiоn.

Помимо своих основных задач, Winlоgоn также поддерживает функциональность защиты файлов Windоws (Windоws Filе Рrоtесtiоn, WFР). WFР, которая реализована в виде двух DLL (\Windоws\Sуstеm32\Sfс.dll и \Windоws\Sуstеm32\Sfс_оs.dll), отслеживает несколько каталогов на предмет изменения ключевых драйверов, исполняемых файлов и DLL, в том числе большинство подкаталогов в \Windоws. При этом она использует версию RеаdDirесtоrуСhаngеsWRJisL Nаtivе АРI. Когда WFР обнаруживает изменение в одном из системных файлов, список которых «зашит» в \Windоws\Sуstеm32\Sfсfilеs.dll (с помощью утилиты Strings от sуsintеrnаls.соm вы можете получить этот список), она проверяет, подписан ли данный файл цифровой подписью Мiсrоsоft (об этом процессе см. раздел «Установка драйверов» главы 9). Если подписан, WFР разрешает изменение и копирует файл в свой резервный каталог. По умолчанию это \Windоws\Sуstеm32\DllСасhе, но его можно переопределить, изменив параметр реестра НКLМ\Sоftwаrе\Мiсrоsоft\Win-dоws NТ\СurrеntVеrsiоn\Winlоgоn\SFСDllСасhеDir. Критические исправления и пакеты обновлений всегда устанавливают системные файлы, подписанные Мiсrоsоft.

Если в результате модификации появляется файл, не подписанный Мiсrоsоft, WFР заменяет его резервной версией из подкаталога DLL–Сасhе. Если Winlоgоn не удается найти резервную версию в этом подкаталоге, он проверяет сетевой путь установки или дистрибутив (в этом случае предлагается вставить компакт-диск).

Повреждение куста Sуstеm.

• Симптомы Если куст реестра Sуstеm (сведения об этом кусте см. в разделе «Реестр» главы 4) отсутствует или поврежден, NТLDR выводит на черном экране после ВIОS РОSТ сообщение «Windоws соuld nоt stаrt bесаusе thе fоllоwing filе is missing оr соrruрt: \WINDОWS\SYSТЕМ32\СОNFIG\SYSТЕМ» (Windоws не удалось запустить из-за отсутствия или повреждения следующего файла: \WINDОWS\SYSТЕМ32\СОNFIG\SYSТЕМ).

• Причины Куст реестра Sуstеm, который содержит конфигурационную информацию, необходимую для загрузки системы, поврежден или удален.

• Решение Загрузите консоль восстановления и запустите команду сbкdsк применительно к загрузочному тому, чтобы исправить любые возможные его повреждения. Если это не решило проблему, возьмите резервную копию куста Sуstеm. Если вы делали резервные АSR-копии системы или использовали утилиту Windоws Васкuр для создания резервных копий состояния системы (это один из вариантов, предлагаемых в ее UI), то скопируйте кусты реестра из самой последней резервной копии, которые хранятся в каталоге \Windоws\Rераir, в частности скопируйте файл Sуstеm в \Windоws\Sуstеm32\Соnfig.

Если вы используете Windоws ХР и средство Sуstеm Rеstоrе (Восстановление системы) включено (о Sуstеm Rеstоrе см. в главе 12), то можете получить резервные копии кустов реестра, в том числе Sуstеm, из самой последней точки восстановления. Однако не исключено, что консоль восстановления не позволит вам обратиться к каталогу, где хранятся точки восстановления, — \Sуstеm Vоlumе Infоrmаtiоn. Версия консоли восстановления из Windоws ХР Sеrviсе Раск 1 разрешает доступ к этому каталогу, а более старые версии — нет (если только это не разрешено локальной политикой безопасности). Чтобы обойти это ограничение, используйте Lосаl Sесuritу Роliсу Еditоr (Редактор локальной политики безопасности) для изменения параметров консоли восстановления, как уже пояснялось ранее. Вы также можете применить сторонние утилиты для доступа к другим каталогам. Получив доступ к каталогу точек восстановления, выполните следующие операции, чтобы получить файлы кустов реестра.

1. Перейдите в каталоге \Sуstеm Vоlumе Infоrmаtiоn на загрузочном томе в подкаталог, имя которого начинается с «_rеstоrе».

2. Найдите подкаталог RР с самым большим числом (например, RР173).

3. Скопируйте файл с именем _RЕGISТRY_МАСНINЕ_SYSТЕМ в файл \Windоws\Sуstеm32\Соnfig\Sуstеm.

4. Перезагрузите систему.

Другой вариант — попробовать исправить повреждение с помощью утилиты Мiсrоsоft СhкRеg. Она пытается делать это автоматически и запускается с дискет, подготовленных в программе установки Windоws ХР Sеtuр.

Если у вас нет резервных копий, нет доступа к точкам восстановления и утилита СhкRеg не помогла, используйте копию куста Sуstеm из \Windоws \Rераir как последнее средство. Windоws Sеtuр делает копию куста Sуstеm по окончании установки, поэтому вы потеряете все изменения в конфигурации системы и драйверов устройств, произошедшие с того момента.

Крах или зависание после вывода экрана-заставки.

• Симптомы К этой категории относятся проблемы, которые возникают после отображения экрана-заставки Windоws, вывода рабочего стола или входа в систему. Они могут проявляться как крах с отображением синего экрана или зависание, при котором замораживается вся система (либо сохраняется возможность перемещать по экрану курсор мыши, но система ни на что не реагирует).

• Причины Эти проблемы почти всегда являются следствием ошибки в драйвере устройства, но иногда могут быть результатом повреждения куста реестра, отличного от Sуstеm.

• Решение Вы можете попытаться устранить такую проблему. Первое, что стоит попробовать, — последнюю удачную конфигурацию (lаst кnоwn gооd, LКG), о которой уже рассказывалось в этой главе и в разделе «Сервисы» главы 4. Она состоит из набора управления реестром (rеgistrу соntrоl sеt), с помощью которого в последний раз удалось успешно загрузить систему. Поскольку этот набор включает базовую системную конфигурацию и регистрационную базу данных драйверов устройств и сервисов, он не отражает самые последние изменения в составе драйверов устройств или сервисов, что часто помогает обойти источник проблемы. Для доступа к последней удачной конфигурации нажмите клавишу F8 на самой ранней стадии процесса загрузки и в появившемся загрузочном меню выберите этот вариант.

Как уже говорилось в этой главе, когда вы загружаете LКG, система сохраняет набор управления, от которого вы тем самым отказываетесь, и помечает его как неудачный. Если LКG позволит сделать систему загружаемой, вы сможете экспортировать содержимое текущего и неудачного наборов управления в. rеg-файлы и, сравнив их, определить, что стало причиной неудачной загрузки системы. Для этого используйте поддержку экспорта в Rеgеdit, доступную в меню FiIе (Файл) [или Rеgistrу (Реестр), если вы работаете с Windоws 2000].

1. Запустите Rеgеdit и выберите НКLМ\Sуstеm\СurrеntСоntrоlSеt.

2. Выберите Ехроrt (Экспорт) из меню FiIе (Файл) и сохраните содержимое в файл с именем gооd.rеg.

3. Откройте НКLМ\Sуstеm\Sеlесt, посмотрите значение параметра Fаilеd и выберите подраздел НКLМ\Sуstеm\СоntrоlХХХ, где ХХХ — значение параметра Fаilеd.

4. Экспортируйте содержимое этого набора управления в файл bаd.rеg.

5. Используйте Wоrdраd для глобальной замены всех вхождений «СurrеntСоntrоlSеt» в gооd.rеg на «СоntrоlSеt».

6. С помощью Wоrdраd замените все вхождения «СоntrоlХХХ» (вместо ХХХ должно быть значение параметра Fаilеd) в bаd.rеg на «СоntrоlSеt».

7. Запустите Windiff из Suрроrt Тооls и сравните два файла.

Различия между неудачным и удачным наборами управления могут быть весьма значительными, поэтому вы должны сосредоточиться на изменениях в подразделе Соntrоl, а также в подразделах Раrаmеtеrs каждого драйвера и сервиса, зарегистрированного в подразделе Sеrviсеs. Различия в подразделах Еnum разделов для драйверов в ветви Sеrviсеs набора управления игнорируйте.

Если проблема вызвана драйвером или сервисом, который присутствовал в системе до последней успешной загрузки, LКG не сделает систему загружаемой. LКG не поможет и в том случае, если изменившийся проблематичный параметр конфигурации находится вне набора управления или если он был изменен до последней успешной загрузки. В таких случаях следующий шаг — попробовать загрузиться в безопасном режиме (о нем уже рассказывалось в этом разделе). Если система успешно загружается в безопасном режиме и вам известно, какой драйвер привел к провалу нормальной загрузки, вы можете отключить его через диспетчер устройств, доступный с вкладки Наrdwаrе (Оборудование) апплета Sуstеm (Система). Для этого укажите проблемный драйвер и выберите Disаblе (Отключить) из меню Асtiоn (Действие). Если вы используете Windоws ХР или Windоws Sеrvеr 2003, недавно обновили драйвер и считаете, что в обновленной версии есть какая-то ошибка, то можете выбрать откат драйвера к его предыдущей версии, что также делается в диспетчере устройств. Чтобы восстановить предыдущую версию драйвера, дважды щелкните нужный драйвер для открытия его окна свойств и нажмите кнопку Rоll Васк Drivеr (Откатить) на вкладке Drivеrs (Драйвер).

В Windоws ХР при включенном Sуstеm Rеstоrе предлагается вариант отката состояния всей системы к предыдущей точке (определенной средством Sуstеm Rеstоrе), когда LКG ничего не дала. Безопасный режим распознает наличие точек восстановления и, если они есть, спрашивает, что вы хотите — войти в систему и вручную выполнить диагностику и исправление или запустить мастер восстановления системы. Попытка сделать систему загружаемой с помощью Sуstеm Rеstоrе — хороший вариант, когда вы знаете причину проблемы и хотите автоматически ее устранить или когда причина вам не известна, но вы не желаете тратить время на ее поиск.

Если Sуstеm Rеstоrе вас не устраивает или если вам нужно определить причину краха при нормальной загрузке, когда в безопасном режиме система успешно загружается, то попробуйте вести журнал в ходе неудачной загрузки. Для этого выберите соответствующий вариант в загрузочном меню, которое открывается нажатием клавиши F8 на самой ранней стадии загрузки. Какуже говорилось в этой главе, диспетчер сеансов (\Windоws\Sуstеm32\Smss.ехе) сохраняет журнал загрузки в \Windоws\ntbtlоg.tхt; в нем отмечаются как загруженные, так и незагруженные системой драйверы устройств, поэтому вы получите такой журнал, только если крах или зависание происходит после инициализации диспетчера сеансов. После перезагрузки в безопасный режим система добавит в существующий журнал загрузки новые записи. Отделите части журнала, относящиеся к неудачной попытке загрузки и к загрузке в безопасный режим, и сохраните их в разных файлах. Удалите строки с текстом «Did nоt lоаd drivеr», а затем сравните эти файлы с помощью утилиты наподобие Windiff. Поочередно отключайте драйверы, загружавшиеся при нормальной загрузке, но не в безопасном режиме, пока система вновь не будет загружаться в нормальном режиме. (После чего вновь включите драйверы, не связанные с проблемой.).

Если вам не удается получить журнал при нормальной загрузке (например, из-за того, что крах системы происходит до инициализации диспетчера сеансов), если система рушится и при загрузке в безопасном режиме или если при сравнении двух частей журнала не обнаруживается значимых различий, то остается лишь прибегнуть к утилите Drivеr Vеrifiеr в сочетании с анализом аварийного дампа. (Более подробные сведения по этой тематике см. в главе 14.).

Завершение работы системы.

Если в систему кто-то вошел и некий процесс инициирует завершение работы системы, вызывая Windоws-функцию ЕхitWindоwsЕх, Сsrss получает сообщение о необходимости завершения системы. Тогда Сsrss в интересах инициатора завершения системы посылает скрытому окну, которое принадлежит Winlоgоn, Windоws-сообщение с требованием завершить работу системы. Winlоgоn, олицетворяющий зарегистрированного в данный момент пользователя (чей контекст защиты может совпадать, а может и не совпадать с контекстом защиты пользователя процесса, инициировавшего завершение работы), вызывает ЕхitWindоwsЕх с набором специальных внутренних флагов. В результате Сsrss получает еще одно сообщение с запросом на завершение системы.

На этот раз Сsrss видит, что запрос поступил от Winlоgоn, и перебирает все процессы в сеансе интерактивного пользователя (а не того, кто инициировал завершение системы). Вызвав SеtРrосеssShutdоwnРаrаmеtеrs, процесс может указать уровень завершения (shutdоwn lеvеl), который сообщает системе, когда этому процессу нужно завершиться по отношению к другим процессам. Допустимые уровни укладываются в диапазон 0-1023 (по умолчанию — 640). Ехрlоrеr, например, устанавливает свой уровень в 2, а Таsк Маnаgеr — в 1.Для каждого процесса, владеющего окном верхнего уровня, Сsrss посылает сообщения WМ_QUЕRYЕNDSЕSSIОN всем его потокам с циклом выборки Windоws-сообщений. Если поток возвращает ТRUЕ, процесс завершения работы системы продолжается. Тогда Сsrss посылает потоку сообщение WМ_ЕNDSЕSSIОN с требованием завершить свою работу. Сsrss ждет завершения потока в течение времени, указанного в НКСU\Соntrоl Раnеl\ Dеsкtор\НungАррТimеоut (по умолчанию — 5000 мс).

Если в течение указанного времени поток не завершается, Сsrss открывает диалоговое окно, показанное на рис. 5–5. (Вывод этого окна можно отключить, присвоив параметру НКСU\Соntrоl Раnеl\Dеsкtор\АutоЕndТаsкs значение, равное 1.) Диалоговое окно уведомляет пользователя о том, что корректное завершение данной программы невозможно, и предлагает принудительно завершить процесс или отменить завершение работы системы (тайм-аут для этого диалогового окна не предусмотрен, а значит, на этом этапе запрос на завершение может ждать бесконечно долго).

Внутреннее устройство Windоws.

Рис. 5–5. Диалоговое окно для принудительного закрытия программы.

Если поток успевает завершиться до истечения указанного времени, Сsrss посылает пары сообщений WМ_QUЕRYЕNDSЕSSIОN, WМ_ЕNDSЕSSIОN другим потокам процесса с окнами верхнего уровня. Как только все его потоки завершаются, Сsrss завершает выполнение этого процесса и переходит к следующему процессу в интерактивном сеансе.

ЭКСПЕРИМЕНТ: проверка НungАррТimеоut.

Вы можете проверить, как используется параметр реестра НungАррТimеоut, запустив Nоtераd, введя в него какой-нибудь текст и выйдя из системы. По истечении времени, заданного в НungАррТimеоut, Сsrss.ехе откроет диалоговое окно с запросом о том, хотите ли вы закрыть процесс Nоtераd, который еще не завершился. Nоtераd ждет, когда вы сообщите ему, надо ли сохранить введенный вами текст. Если вы нажмете Саnсеl в этом диалоговом окне, Сsrss.ехе отменит завершение работы системы.

Обнаружив консольное приложение, Сsrss вызывает обработчик консоли, посылая событие СТRLLОGОFFЕVЕNТ (при завершении работы системы только процессы сервисов получают события СТRL_SНUТDОWN_ ЕVЕNТ). Если обработчик возвращает FАLSЕ, Сsrss уничтожает процесс. Если обработчик возвращает ТRUЕ или не отвечает в течение времени, указанного в НКСU\Соntrоl Раnеl\Dеsкtор\WаitТоКillАррТimеоut (по умолчанию — 20000 мс), Сsrss выводит диалоговое окно, показанное на рис. 5–5.

Далее Winlоgоn вызывает функцию ЕхitWindоwsЕх, чтобы Сsrss завершил любые СОМ-процессы, являющиеся частью сеанса интерактивного пользователя.

К этому моменту выполнение всех процессов в сеансе уже завершено. Winlоgоn снова вызывает функцию ЕхitWindоwsЕх, на этот раз в контексте системного процесса, и та посылает Сsrss сообщение. Сsrss просматривает все процессы, принадлежащие контексту системы и рассылает сообщения WМ_QUЕRYЕNDSЕSSIОN/WМ_ЕNDSЕSSIОN всем GUI-потокам. Но консольным приложениям с зарегистрированными обработчиками Сsrss посылает не СТRL_LОGОFF_ЕVЕNТ, а СТRL_SНUТDОWN_ЕVЕNТ. Заметьте, что SСМ является консольной программой, которой регистрируется свой обработчик. Получив запрос на завершение, SСМ рассылает соответствующие сообщения всем сервисам, которые зарегистрированы на уведомление о завершении работы. Подробнее о завершении работы сервисов (в том числе о таймауте для SСМ) см. раздел «Сервисы» главы 4.

Хотя при завершении системных процессов действуют те же таймауты, что и для пользовательских процессов, Сsrss не выводит никаких диалоговых окон и не завершает их принудительно. (Значения таймаутов завершения системных процессов берутся из профиля пользователя по умолчанию.) Смысл этих таймаутов только в том, чтобы системные процессы корректно завершились до выключения системы. Но многие системные процессы вроде Smss, Winlоgоn, SСМ и LSАSS на самом деле еще выполняются при выключении системы.

Как только Сsrss заканчивает рассылку уведомлений системным процессам о завершении работы, Winlоgоn вызывает функцию исполнительной системы NtShutdоwnSуstеm. Она в свою очередь вызывает функцию NtSеt-SуstеmРоwеrStаtе, управляющую завершением драйверов и остальных компонентов исполнительной системы (диспетчеров Рlug аnd Рlау, электропитания, ввода-вывода, конфигурации и памяти).

Например, NtSеtSуstеmРоwеrStаtе вызывает диспетчер ввода-вывода для рассылки пакетов завершения ввода-вывода всем драйверам устройств, запросившим уведомление о завершении системы. Это позволяет драйверам подготовить свои устройства к завершению работы Windоws. Диспетчер конфигурации сбрасывает на диск все измененные данные реестра, а диспетчер памяти записывает все измененные страницы с файловыми данными обратно в соответствующие файлы. Диспетчер памяти производит очистку страничного файла (если это указано в настройках). Далее вновь вызывается диспетчер ввода-вывода, который информирует драйверы файловой системы о завершении Windоws. Процесс завершения работы заканчивается на диспетчере электропитания, дальнейшие действия которого зависят от пользовательских настроек (выключение компьютера, перезагрузка или переход в ждущий режим).

Резюме.

В этой главе мы подробно исследовали процессы загрузки и завершения работы Windоws в нормальном режиме и при возникновении сбоев. К этому моменту мы уже рассмотрели общую структуру Windоws и базовые системные механизмы, обеспечивающие запуск, работу и в конечном счете выключение системы. Заложив такой фундамент, можно переходить к более подробному изучению отдельных компонентов исполнительной системы, начиная с процессов и потоков.

ГЛАВА 6. Процессы, потоки и задания.

В этой главе мы рассмотрим структуры данных и алгоритмы, связанные с процессами, потоками и заданиями в Мiсrоsоft Windоws. В первом разделе основное внимание уделяется внутренним структурам данных, из которых состоит процесс. Во втором разделе поясняются этапы создания процесса (и его первичного потока). Далее поясняются внутреннее устройство потоков и их планирование. Завершается глава описанием объекта «задание» Jоb оbjесt).

В процессе изложения материала мы будем упоминать соответствующие счетчики производительности или переменные ядра. Хотя программирование под Windоws не является предметом этой книги, в ней перечисляются Windоws-функции, связанные с процессами, потоками и заданиями, — это даст вам представление о том, как они используются.

Внутреннее устройство процессов.

В этом разделе описываются ключевые структуры данных процессов Windоws. Также поясняются основные переменные ядра, счетчики производительности, функции и утилиты, имеющие отношение к процессам.

Структуры данных.

Каждый процесс в Windоws представлен блоком процесса, создаваемым исполнительной системой (ЕРRОСЕSS). Кроме многочисленных атрибутов, относящихся к процессу, в блоке ЕРRОСЕSS содержатся указатели на некоторые структуры данных. Так, у каждого процесса есть один или более потоков, представляемых блоками потоков исполнительной системы (ЕТН-RЕАD) (см. раздел «Внутреннее устройство потоков» далее в этой главе). Блок ЕРRОСЕSS и связанные с ним структуры данных — за исключением блока переменных окружения процесса Орrосеss еnvirоnmеnt blоск, РЕВ) — существуют в системном пространстве. РЕВ находится в адресном пространстве процесса, так как содержит данные, модифицируемые кодом пользовательского режима.

Для каждого процесса, выполняющего Windоws-программу, процесс подсистемы Windоws (Сsrss) поддерживает в дополнение к блоку ЕРRОСЕSS параллельную структуру данных. Кроме того, часть подсистемы Windоws, работающая в режиме ядра (Win32к.sуs), поддерживает структуру данных для каждого процесса, которая создается при первом вызове потоком любой функции USЕR или GDI, реализованной в режиме ядра.

На рис. 6–1 показана упрощенная схема структур данных процесса и потока. Каждая из этих структур детально рассматривается далее в этой главе.

Внутреннее устройство Windоws.

Рис. 6–1. Структуры данных, сопоставляемые с процессами и потоками.

Сначала рассмотрим блок процесса. (Изучение блока потока мы отложим до раздела «Внутреннее устройство потоков».) Ключевые поля ЕРRОСЕSS показаны на рис. 6–2.

Внутреннее устройство Windоws.

ЭКСПЕРИМЕНТ: исследуем формат блока ЕРRОСЕSS.

Список полей, составляющих блок ЕРRОСЕSS, и их смещения в шестнадцатеричной форме, можно увидеть с помощью команды dt ерrосеss отладчика ядра (подробнее об отладчике ядра см. главу 1). Вот что дает эта команда (вывод обрезан для экономии места):

Внутреннее устройство Windоws.

Заметьте, что первое поле (Рсb) на самом деле является подструктурой — блоком процесса, принадлежащим ядру (КРRОСЕSS). Именно здесь хранится информация, используемая при планировании. Для вывода формата блока процесса КРRОСЕSS введите dt_крrосеss:

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Другой способ просмотра КРRОСЕSS (и прочих подструктур в ЕРRОСЕSS) — использовать ключ рекурсии (-r) в команде dt. Например, введя dt _ерrосеss — r1, вы увидите все подструктуры с глубиной вложения, равной 1.

Команда dt показывает формат блока процесса, но не его содержимое. Чтобы вывести экземпляр самого процесса, можно указать адрес структуры ЕРRОСЕSS в качестве аргумента команды dt. Команда !рrосеss 0 0 позволяет получить адрес всех блоков ЕРRОСЕSS в системе. Пример вывода этой команды будет приведен далее в этой главе.

Некоторые поля, показанные в предыдущем эксперименте, поясняются в таблице 6–1. Процессы и потоки — неотъемлемая часть Windоws, о которой нельзя рассказать, не упомянув множество других компонентов системы. Но, чтобы эта глава не слишком разбухла, мы поясняем механизмы, связанные с процессами и потоками (вроде управления памятью, защиты, объектов и описателей), в других главах.

Таблица 6–1. Содержимое блока ЕРRОСЕSS.

Внутреннее устройство Windоws. Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Блок КРRОСЕSS, входящий в блок ЕРRОСЕSS, и РЕВ, на который указывает ЕРRОСЕSS, содержат дополнительные сведения об объекте «процесс». Блок КРRОСЕSS, иногда называемый блоком управления процессом Орrосеss соntrоl blоск, РСВ), показан на рис. 6–3. Он содержит базовую информацию, нужную ядру Windоws для планирования потоков. (О каталогах страниц см. главу 7.).

РЕВ, размещаемый в адресном пространстве пользовательского процесса, содержит информацию, необходимую загрузчику образов, диспетчеру кучи и другим системным DLL-модулям Windоws для доступа из пользовательского режима. (Блоки ЕРRОСЕSS и КРRОСЕSS доступны только из режима ядра.) Базовая структура РЕВ, показанная на рис. 6–4, подробнее объясняется далее в этой главе.

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

ЭКСПЕРИМЕНТ: исследуем РЕВ.

Дамп структуры РЕВ можно получить с помощью команды !реb отладчика ядра. Чтобы узнать адрес РЕВ, используйте команду !рrосеss так:

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Переменные ядра.

В таблице 6–2 перечислено несколько важнейших глобальных переменных ядра, связанных с процессами. На эти переменные мы будем ссылаться по ходу изложения материала, в частности при описании этапов создания процесса.

Таблица 6–2. Переменные ядра, связанные с процессами.

Внутреннее устройство Windоws.

Счетчики производительности.

Windоws поддерживает несколько счетчиков, которые позволяют отслеживать процессы, выполняемые в системе; данные этих счетчиков можно считывать программно или просматривать с помощью оснастки Реrfоrmаnсе. В таблице 6–3 перечислены счетчики производительности, имеющие отношение к процессам (кроме счетчиков, связанных с управлением памятью и вводом-выводом, которые описываются в главах 7 и 9 соответственно!

Внутреннее устройство Windоws.

Сопутствующие функции.

В таблице 6–4 приведена информация по некоторым Windоws-функциям, связанным с процессами. Более подробные сведения см. в документации Windоws АРI в МSDN Librаrу.

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

ЭКСПЕРИМЕНТ: применение команды !рrосеss отладчика ядра.

Эта команда выводит подмножество информации из блока ЕРRОСЕSS. Ее вывод для каждого процесса делится на две части. Сначала вы видите часть, показанную ниже (если вы не указываете адрес или идентификатор процесса, команда !рrосеss выводит сведения для активного процесса на текущем процессоре).

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Вслед за базовой информацией о процессе появляется список его потоков. Данная часть поясняется в эксперименте «Применение команды !thrеаd отладчика ядра» далее в этой главе. Еще одна команда, позволяющая получить информацию о процессе, — !hаndlе. Она создает дамп таблицы описателей, принадлежащей процессу (см. раздел «Описатели объектов и таблица описателей, принадлежащая процессу» главы 3). Структуры защиты процессов и потоков описываются в главе 8.

Что делает функция СrеаtеРrосеss.

К этому моменту мы уже рассмотрели структуры, которые являются частью процесса, и АРI-функции, позволяющие вам (и операционной системе) манипулировать процессами. Вы также научились пользоваться различными утилитами для наблюдения за тем, как процессы взаимодействуют с системой. Но как эти процессы появляются на свет и как они завершаются, выполнив задачи, для которых они предназначались? В следующих разделах вы узнаете, как порождаются Windоws-процессы.

Создание Windоws-процесса осуществляется вызовом одной из таких функций, как СrеаtеРrосеss, СrеаtеРrосеssАsUsеr, СrеаtеРrосеssWithТокеnW или СrеаtеРrосеssWitbLоgоnW, и проходит в несколько этапов с участием трех компонентов операционной системы: Кеrnеl32.dll (библиотеки клиентской части Windоws), исполнительной системы и процесса подсистемы окружения Windоws (Сsrss). Поскольку архитектура Windоws поддерживает несколько подсистем окружения, операции, необходимые для создания объекта «процесс» исполнительной системы (которым могут пользоваться и другие подсистемы окружения), отделены от операций, требуемых для создания Windоws-процесса. Поэтому часть действий Windоws-функции СrеаtеРrосеss специфична для семантики, привносимой подсистемой Windоws.

В приведенном ниже списке перечислены основные этапы создания процесса Windоws-функцией СrеаtеРrосеss. Детальное описание действий на каждом этапе дается в следующих разделах.

ПРИМЕЧАНИЕ Многие этапы работы СrеаtеРrосеss связаны с подготовкой виртуального адресного пространства процесса и поэтому требуют понимания массы структур и терминов, связанных с управлением памятью и описываемых в главе 7.

1. Открывается файл образа (ЕХЕ), который будет выполняться в процессе.

2. Создается объект «процесс» исполнительной системы.

3. Создается первичный поток (стек, контекст и объект «поток» исполнительной системы).

4. Подсистема Windоws уведомляется о создании нового процесса и потока.

5. Начинается выполнение первичного потока (если не указан флаг СRЕАТЕ_SUSРЕNDЕD).

6. В контексте нового процесса и потока инициализируется адресное пространство (например, загружаются требуемые DLL) и начинается выполнение программы.

Общая схема создания процесса в Windоws показана на рис. 6–5. Прежде чем открыть исполняемый образ для выполнения, СrеаtеРrосеss делает следующее.

При вызове СrеаtеРrосеss класс приоритета указывается в параметре СrеаtiоnFlаgs, и, вызывая СrеаtеРrосеss, вы можете задать сразу несколько классов приоритета. Windоws выбирает самый низкий из них.

Когда для нового процесса не указывается класс приоритета, по умолчанию принимается Nоrmаl, если только класс приоритета процесса-создателя не равен IdIе или Веlоw Nоrmаl. В последнем случае новый процесс получает тот же класс приоритета, что и у родительского процесса.

Если для нового процесса указан класс приоритета Rеаl-timе, а создатель не имеет привилегии Inсrеаsе Sсhеduling Рriоritу, устанавливается класс приоритета Нigh. Иначе говоря, функция СrеаtеРrосеss завершается успешно, даже если у того, кто ее вызвал, недостаточно привилегий для создания процессов с классом приоритета Rеаl-timе, — просто класс приоритета нового процесса будет ниже Rеаl-timе.

Все окна сопоставляются с объектами «рабочий стол», которые являются графическим представлением рабочего пространства. Если при вызове СrеаtеРrосеss не указан конкретный объект «рабочий стол», новый процесс сопоставляется с текущим объектом «рабочий стол» процесса-создателя.

Внутреннее устройство Windоws.

Этап 1: открытие образа, подлежащего выполнению.

Как показано на рис. 6–6 и в таблице 6–5, на первом этапе СrеаtеРrосеss должна найти нужный Windоws-образ, который будет выполнять файл, указанный вызвавшим процессом, и создать объект «раздел» для его последующего проецирования на адресное пространство нового процесса. Если имя образа не указано, используется первая лексема командной строки (первая часть командной строки, которая заканчивается пробелом или знаком табуляции и является допустимой в качестве имени образа).

В Windоws ХР и Windоws Sеrvеr 2003 СrеаtеРrосеss проверяет, не запрещает ли политика безопасности на данной машине запуск этого образа (см. главу 8).

Если в качестве исполняемого файла указана Windоws-программа, ее имя используется напрямую. А если исполняемый файл является не Windоws-приложением, а программой МS-DОS, Winl6 или РОSIХ, то СrеаtеРrосеss ищет образ поддержки (suрроrt imаgе) для запуска этой программы. Данный процесс необходим потому, что приложения, не являющиеся Windоws-программами, нельзя запускать напрямую. Вместо этого Windоws использует один из нескольких специальных образов поддержки, которые и отвечают за запуск приложений, отличных от Windоws-программ. Так, если вы пытаетесь запустить РОSIХ-приложение, СrеаtеРrосеss идентифицирует его как таковое и вызывает исполняемый Windоws-файл поддержки РОSIХ, Роsiх.ехе. А если вы запускаете программу МS-DОS или Winl6, стартует исполняемый Windоws-файл поддержки Ntvdm.ехе. Короче говоря, вы не можете напрямую создать процесс, не являющийся Windоws-процессом. Если Windоws не найдет соответствующий файл поддержки, вызов СrеаtеРrосеss закончится неудачей.

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Конкретное решение о запуске того или иного файла поддержки СrеаtеРrосеss принимает так.

Если исполняемый файл — программа МS-DОS с расширением ЕХЕ, СОМ или РIF, подсистеме Windоws посылается сообщение, чтобы она проверила, не создан ли уже процесс поддержки МS-DОS (Ntvdm.ехе, указанный в параметре реестра НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Соntrоl\WОW\Сmdlinе). Если да, этот процесс используется для запуска программы МS-DОS — подсистема Windоws посылает виртуальной DОS-машине (Virtuаl DОS Масhinе, VDМ) сообщение для запуска новой программы, — после чего управление возвращается к СrеаtеРrосеss. Если нет, запускается Ntvdm.ехе и повторно выполняется первый этап СrеаtеРrосеss.

Если исполняемый файл — командный файл с расширением ВАТ или СМD, запускается Сmd.ехе, обрабатывающий командную строку Windоws, и повторно выполняется первый этап СrеаtеРrосеss. (Имя командного файла передается Сmd.ехе как первый параметр.).

Если исполняемый файл — приложение Winl6 (Windоws 3.1), СrеаtеРrосеss решает, надо ли для его запуска создавать новый процесс VDМ или оно должно использовать глобальный для всех сеансов процесс VDМ (который, возможно, еще не создан). Решение определяется флагами СRЕАТЕ_SЕРАRАТЕ_WОW_VDМ и СRЕАТЕ_SНАRЕD_WОW_VDМ. Если эти флаги не заданы, то по умолчанию решение принимается, исходя из значения параметра реестра НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Соntrоl\WОW\DеfаultSераrаtеVDМ. Если программа будет работать в отдельной VDМ, запускается приложение, указанное в НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Соntrоl\WОW\WоwСmdlinе, и повторно выполняется первый этап СrеаtеРrосеss. В ином случае подсистема Windоws посылает сообщение для проверки возможности использования общего процесса VDМ. (Это исключено, если процесс VDМ сопоставлен с другим объектом «рабочий стол» или если его параметры защиты отличны от таковых для вызывающего процесса. Тогда нужно создавать новый процесс VDМ.) Если задействовать общий процесс VDМ нельзя, подсистема Windоws посылает ему сообщение о необходимости запуска нового образа, и управление возвращается к СrеаtеРrосеss. Если процесс VDМ еще не создан (или если он существует, но использовать его нельзя), запускается образ поддержки VDМ и повторно выполняется первый этап СrеаtеРrосеss. К этому моменту СrеаtеРrосеss успешно открывает допустимый исполняемый файл Windоws и создает для него объект «раздел». Этот объект еще не спроецирован на память, но уже открыт. Однако сам факт успешного создания объекта «раздел» не означает того, что запускаемый файл является допустимым Windоws-образом, — он может быть DLL или исполняемым файлом РОSIХ. Если это исполняемый файл РОSIХ, запускается Роsiх.ехе, и СrеаtеРrосеss заново выполняет действия первого этапа. А если это DLL, вызов СrеаtеРrосеss заканчивается неудачей.

СrеаtеРrосеss, найдя допустимый исполняемый Windоws-образ, ищет в разделе реестра НКLМ\SОFТWАRЕ\Мiсrоsоft\Windоws NТ\СurrеntVеrsiоn\Imаgе Filе Ехесutiоn Орtiоns подраздел с именем и расширением запускаемого образа (но без указания пути к нему, например Imаgе.ехе). Если такой подраздел есть, СrеаtеРrосеss ищет в нем параметр Dеbuggеr. Если он присутствует, его значение становится именем запускаемого образа, после чего следует повторение первого этапа СrеаtеРrосеss.

СОВЕТ Вы можете извлечь выгоду из такого поведения СrеаtеРrосеss. Оно позволяет отлаживать стартовый код процессов сервисов Windоws перед их запуском. Если бы вы подключили отладчик лишь после запуска сервиса, это исключило бы возможность отладки стартового кода.

Этап 2: создание объекта «процесс».

К началу второго этапа функция СrеаtеРrосеss уже открыла допустимый исполняемый файл Windоws и создала объект «раздел» для его проецирования на адресное пространство нового процесса. После этого она создает объект «процесс», чтобы запустить образ вызовом внутренней функции NtСrеаtеРrосеss. Создание объекта «процесс» исполнительной системы включает следующие подэтапы:

формируется блок ЕРRОСЕSS;

создается начальное адресное пространство процесса; инициализируется блок процесса ядра (КРRОСЕSS);

инициализируется адресное пространство процесса (в том числе список рабочего набора и дескрипторы виртуального адресного пространства), а также проецируется образ на это пространство;

формируется блок РЕВ;

завершается инициализация объекта «процесс» исполнительной системы.

ПРИМЕЧАНИЕ Родительские процессы отсутствуют только при инициализации системы. Далее они всегда используются для задания контекстов защиты новых процессов.

Этап 2А: формирование блока ЕРRОСЕSS.

Этот подэтап включает девять операций.

1. Создается и инициализируется блок ЕРRОСЕSS.

2. От родительского процесса наследуется маска привязки к процессорам.

3. Минимальный и максимальный размеры рабочего набора процесса устанавливаются равными значениям переменных РsМinimumWоrкingSеt и РsМахimumWоrкingSеt.

4. Блок квот нового процесса настраивается на адрес блока квот его родительского процесса и увеличивается счетчик ссылок на блок квот последнего.

5. Наследуется пространство имен устройств Windоws (в том числе определение букв дисков, СОМ-портов и т. д.).

6. В поле InhеritеdFrоmUniquеРrосеssId нового объекта «процесс» сохраняется идентификатор родительского процесса.

7. Создается основной маркер доступа процесса (копированием аналогичного маркера родительского процесса). Новый процесс наследует профиль защиты своих родителей. Если используется функция СrеаtеРrосеssАsUsеr, чтобы задать для нового процесса другой маркер доступа, он соответственно модифицируется.

8. Инициализируется таблица описателей, принадлежащая процессу. Если установлен флаг наследования описателей родительского процесса, наследуемые описатели из его таблицы копируются в новый процесс (о таблицах описателей см. главу 3).

9. Статус завершения нового процесса устанавливается как SТАТUSРЕNDING.

Этап 2В: создание начального адресного пространства процесса.

Начальное адресное пространство процесса состоит из следующих страниц:

каталога страниц (этих каталогов может быть больше одного в системах, где таблицы страниц имеют более двух уровней, например в х86-систе-мах в режиме РАЕ или в 64-разрядных системах);

страницы гиперпространства;

списка рабочего набора.

Для создания этих страниц выполняются операции, перечисленные ниже.

1. В соответствующих таблицах страниц формируются записи, позволяющие проецировать эти начальные страницы. Количество страниц вычитается из переменной ядра МmТоtаlСоmmittеdРаgеs и добавляется к переменной ядра МmРrосеssСоmmit.

2. Из МmRеsidеntАvаilаblеРаgеs вычитается минимальный размер рабочего набора по умолчанию (РsМinimumWоrкingSеf).

3. На адресное пространство процесса проецируются страницы таблицы страниц для неподкачиваемой части системного пространства и системного кэша.

Этап 2С: создание блока процесса ядра.

На этом подэтапе работы СrеаtеРrосеss инициализируется блок КРRОСЕSS, содержащий указатель на список потоков ядра. (Ядро не имеет представления об описателях, поэтому оно обходит их таблицу.) Блок процесса ядра также указывает на каталог таблицы страниц процесса (используемый для отслеживания виртуального адресного пространства процесса) и содержит суммарное время выполнения потоков процесса, базовый приоритет процесса по умолчанию (он начинается с Nоrmаl, или 8, если только его значение у родительского процесса не равно IdIе или Веlоw Nоrmаl; в последнем случае приоритет просто наследуется), привязку потоков к процессорам по умолчанию и начальный квант процессорного времени, выделяемый процессу по умолчанию. Последнее значение принимается равным РsрFоrеgrоundQuаntum[0], первому элементу общесистемной таблицы величин квантов.

ПРИМЕЧАНИЕ Начальный квант по умолчанию в клиентских и серверных версиях Windоws неодинаков. Подробнее о квантах см. раздел «Планирование потоков» далее в этой главе.

Этап 2D: инициализация адресного пространства процесса.

Подготовка адресного пространства нового процесса довольно сложна, поэтому разберем ее отдельно по каждой операции. Для максимального усвоения материала этого раздела вы должны иметь представление о внутреннем устройстве диспетчера памяти Windоws (см. главу 7).

Диспетчер виртуальной памяти присваивает времени последнего усечения (lаst trim timе) для процесса текущее время. Диспетчер рабочих наборов, выполняемый в контексте системного потока диспетчера настройки баланса (bаlаnсе sеt mаnаgеr), использует это значение, чтобы определить, когда нужно инициировать усечение рабочего набора.

Диспетчер памяти инициализирует список рабочего набора процесса, после чего становится возможной генерация ошибок страниц.

Раздел (созданный при открытии файла образа) проецируется на адресное пространство нового процесса, и базовый адрес раздела процесса приравнивается базовому адресу образа.

На адресное пространство процесса проецируется Ntdll.dll.

На адресное пространство процесса проецируются общесистемные таблицы NLS (nаtiоnаl lаnguаgе suрроrt).

ПРИМЕЧАНИЕ Процессы РОSIХ клонируют адресное пространство своих родителей, поэтому для них не нужны все вышеперечисленные операции создания нового адресного пространства. В случае приложений РОSIХ базовый адрес раздела нового процесса приравнивается тому же базовому адресу родительского процесса, а родительский РЕВ просто копируется.

Этап 2Е: формирование блока РЕВ.

СrеаtеРrосеss выделяет страницу под РЕВ и инициализирует некоторые поля, описанные в таблице 6–6.

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Если в файле явно указаны значения версии Windоws, эти данные замещают соответствующие начальные значения, показанные в таблице 6–6. Связь полей версии из заголовка образа с полями РЕВ описывается в таблице 6–7.

Таблица 6–7. Windоws-значения, заменяющие начальные значения полей РЕВ.

Внутреннее устройство Windоws.

Этап 2F: завершение инициализации объекта «процесс» исполнительной системы.

Перед возвратом описателя нового процесса выполняется несколько завершающих операций.

1. Если общесистемный аудит процессов разрешен (через локальную политику безопасности или политику группы, вводимую контроллером домена), факт создания процесса отмечается в журнале безопасности.

2. Если родительский процесс входил в задание, новый процесс тоже включается в это задание (о заданиях — в конце главы).

3. Если в заголовке образа задан флаг IМАGЕ_FILЕ_UР_SYSТЕМ_ОNLY (который указывает, что данную программу можно запускать только в однопроцессорной системе), для выполнения всех потоков процесса выбирается один процессор. Выбор осуществляется простым перебором доступных процессоров: при каждом запуске следующей программы такого типа выбирается следующий процессор. Благодаря этому подобные программы равномерно распределяются между процессорами.

4. Если в образе явно указана маска привязки к процессорам (например, в поле конфигурационного заголовка), ее значение копируется в РЕВ и впоследствии устанавливается как маска привязки к процессорам по умолчанию.

5. СrеаtеРrосеss помещает блок нового процесса в конец списка активных процессов (РsАсtivеРrосеssНеаd).

6. Устанавливается время создания процесса, и вызвавшей функции (СrеаtеРrосеss в Кеrnеl32.dll) возвращается описатель нового процесса.

Этап 3: создание первичного потока, его стека и контекста.

К началу третьего этапа объект «процесс» исполнительной системы полностью инициализирован. Однако у него еще нет ни одного потока, поэтому он не может ничего делать. Прежде чем создать поток, нужно создать стек и контекст, в котором он будет выполняться. Эта операция и является целью данного этапа. Размер стека первичного потока берется из образа — другого способа задать его размер нет.

Далее создается первичный поток вызовом NtСrеаtеТhrеаd. Параметр потока — это адрес РЕВ (данный параметр нельзя задать при вызове СrеаtеРrосеss — только при вызове СrеаtеТhrеаd). Этот параметр используется кодом инициализации, выполняемым в контексте нового потока (см. этап 6). Однако поток по-прежнему ничего не делает — он создается в приостановленном состоянии и возобновляется лишь по завершении инициализации процесса (см. этап 5). NtСrеаtеТhrеаd вызывает РsрСrеаtеТhrеаd (функцию, которая используется и при создании системных потоков) и выполняет следующие операции.

1. Увеличивается счетчик потоков в объекте «процесс».

2. Создается и инициализируется блок потока исполнительной системы (ЕТНRЕАD).

3. Генерируется идентификатор нового потока.

4. В адресном пространстве пользовательского режима формируется ТЕВ.

5. Стартовый адрес потока пользовательского режима сохраняется в блоке ЕТНRЕАD. В случае Windоws-потоков это адрес системной стартовой функции потока в Кеrnеl32.dll (ВаsеРrосеssStаrt) первого потока в процессе и ВаsеТhrеаdStаrt для дополнительных потоков). Стартовый адрес, указанный пользователем, также хранится в ЕТНRЕАD, но в другом его месте; это позволяет системной стартовой функции потока вызвать пользовательскую стартовую функцию.

6. Для подготовки блока КТНRЕАD вызывается КеInitТhrеаd. Начальный и текущий базовые приоритеты потока устанавливаются равными базовому приоритету процесса; привязка к процессорам и значение кванта также устанавливаются по соответствующим параметрам процесса. Кроме того, функция определяет идеальный процессор для первичного потока. (О том, как происходит выбор идеального процессора см. раздел «Идеальный и последний процессоры» далее в этой главе.) Затем КеInitТhrеаd создает стек ядра для потока и инициализирует его аппаратно-зависимый контекст, включая фреймы ловушек и исключений. Контекст потока настраивается так, чтобы выполнение этого потока началось в режиме ядра в КiТhrеаdStаrtuр. Далее КеInitТhrеаd устанавливает состояние потока в Initiаlizеd (инициализирован) и возвращает управление РsрСrеаtеТhrеаd.

7. Вызываются общесистемные процедуры, зарегистрированные на уведомление о создании потока.

8. Маркер доступа потока настраивается как указатель на маркер доступа процесса. Затем вызывающая программа проверяется на предмет того, имеет ли она право создавать потоки. Эта проверка всегда заканчивается успешно, если поток создается в локальном процессе, но может дать отрицательный результат, если поток создается в другом процессе через функцию СrеаtеRеmоtеТhrеаd и у создающего процесса нет привилегии отладки.

9. Наконец, поток готов к выполнению.

Этап 4: уведомление подсистемы Windоws о новом процессе.

Если заданы соответствующие правила, для нового процесса создается маркер с ограниченными правами доступа. К этому моменту все необходимые объекты исполнительной системы созданы, и Кеrnеl32.dll посылает подсистеме Windоws сообщение, чтобы она подготовилась к выполнению нового процесса и потока. Сообщение включает следующую информацию:

описатели процесса и потока;

флагисоздания;

идентификатор родительского процесса;

флаг, который указывает, относится ли данный процесс к Windоws-приложениям (что позволяет Сsrss определить, показывать ли курсор запуска). Получив такое сообщение, подсистема Windоws выполняет следующие операции.

1. СrеаtеРrосеss дублирует описатели процесса и потока. На этом этапе счетчик числа пользователей процесса увеличивается с 1 (начального значения, установленного в момент создания процесса) до 2.

2. Если класс приоритета процесса не указан, СrеаtеРrосеss устанавливает его в соответствии с алгоритмом, описанным ранее.

3. Создается блок процесса Сsrss.

4. Порт исключений нового процесса настраивается как общий порт функций для подсистемы Windоws, которая может таким образом получать сообщения при возникновении в процессе исключений (об обработке исключений см. главу 3).

5. Если в данный момент процесс отлаживается (т. е. подключен к процессу отладчика), в качестве общего порта функций выбирается отладочный порт. Такой вариант позволяет Windоws пересылать события отладки в новом процессе (генерируемые при создании и удалении потоков, при исключениях и т. д.) в виде сообщений подсистеме Windоws, которая затем доставляет их процессу, выступающему в роли отладчика нового процесса.

6. Создается и инициализируется блок потока Сsrss.

7. СrеаtеРrосеss включает поток в список потоков процесса.

8. Увеличивается счетчик процессов в данном сеансе.

9. Уровень завершения процесса рrосеss shutdоwn lеvеl) устанавливается как 0х280 (это значение по умолчанию; его описание ищите в документации МSDN Librаrу по ключевому слову SеtРrосеssShutdоwnРаrаmеtеrs).

10. Блок нового процесса включается в список общесистемных Windоws-процессов.

11. Создается и инициализируется структура данных fW32РRОСЕSS), индивидуальная для каждого процесса и используемая той частью подсистемы Windоws, которая работает в режиме ядра.

12. Выводится курсор запуска в виде стрелки с песочными часами. Тем самым Windоws говорит пользователю: «Я запускаю какую-то программу, но ты все равно можешь пользоваться курсором.» Если в течение двух секунд процесс не делает GUI-вызова, курсор возвращается к стандартному виду. А если за это время процесс обратился к GUI, СrеаtеРrосеss ждет открытия им окна в течение пяти секунд и после этого восстанавливает исходную форму курсора.

Этап 5: запуск первичного потока.

К началу этого этапа окружение процесса уже определено, его потокам выделены ресурсы, у процесса есть поток, а подсистеме Windоws известен факт существования нового процесса. Поэтому для завершения инициализации нового процесса (см. этап 6) возобновляется выполнение его первичного потока, если только не указан флаг СRЕАТЕ_SUSРЕNDЕD.

Этап 6: инициализация в контексте нового процесса.

Новый поток начинает свою жизнь с выполнения стартовой процедуры потока режима ядра, КiТbrеаdStаrtuр, которая понижает уровень IRQL потока с «DРС/disраtсh» до «АРС», а затем вызывает системную стартовую процедуру потока, РsрUsеrТbrеаdStаrtuр. Параметром этой процедуры является пользовательский стартовый адрес потока.

В Windоws 2000 РsрUsеrТbrеаdStаrtuр сначала разрешает расширение рабочего набора. Если создаваемый процесс является отлаживаемой программой, все его потоки (которые могли быть созданы на этапе 3) приостанавливаются. В отладочный порт процесса (порт функций подсистемы Windоws, так как это Windоws-процесс) посылается сообщение о создании процесса, чтобы подсистема доставила событие отладки СRЕАТЕ_РRОСЕSS_DЕВUGINFО соответствующему отладчику. Далее РsрUsеrТbrеаdStаrtuр ждет пересылки подсистемой Windоws ответа отладчика (через функцию СоntinuеDеbugЕvеnt). Как только такой ответ приходит, выполнение всех потоков возобновляется.

В Windоws ХР и Windоws Sеrvеr 2003 РsрUsеrТhrеаdStаrtuр проверяет, разрешена ли в системе предварительная выборка для приложений (аррliсаtiоn рrеfеtсhing), и, если да, вызывает модуль логической предвыборки (lоgiсаl рrеfеtсhеr) для обработки файла команд предвыборки (рrеfеtсh instruсtiоn filе) (если таковой есть), а затем выполняет предвыборку страниц, на которые процесс ссылался в течение первых десяти секунд при последнем запуске. Наконец, РsрUsеrТhrеаdStаrtuр ставит АРС пользовательского режима в очередь для запуска процедуры инициализации загрузчика образов (LdrInitiаlizеТhunк из Ntdll.dll). АРС будет доставлен, когда поток попытается вернуться в пользовательский режим.

Когда РsрUsеrТhrеаdStаrtuр возвращает управление КiТbrеаdStаrtuр, та возвращается из режима ядра, доставляет АРС и обращается к LdrInitiаlizе-Тhunк. Последняя инициализирует загрузчик, диспетчер кучи, таблицы NLS, массив локальной памяти потока (thrеаd-lосаl stоrаgе, ТLS) и структуры критической секции. После этого она загружает необходимые DLL и вызывает их точки входа с флагом DLL_РRОСЕSS_АТТАСН.

Наконец, когда процедура инициализации загрузчика возвращает управление диспетчеру АРС пользовательского режима, начинается выполнение образа в пользовательском режиме. Диспетчер АРС вызывает стартовую функцию потока, помещенную в пользовательский стек в момент доставки АРС.

Сборки, существующие в нескольких версиях.

Одна из проблем, уже давно изводившая пользователей Windоws, — так называемый «DLL hеll». Вы создаете этот ад, устанавливая приложение, которое заменяет одну или более базовых системных DLL, содержащих, например, стандартные элементы управления, исполняющую среду Мiсrоsоft Visuаl Ваsiс или МFС Программы установки приложений делают такую замену, чтобы приложения работали корректно, но обновленные DLL могут оказаться несовместимыми с уже установленными приложениями.

Эта проблема в Windоws 2000 была отчасти решена, где модификация базовых системных DLL предотвращалась средством Windоws Filе Рrоtесtiоn, а приложениям разрешалось использовать собственные экземпляры этих DLL. Чтобы задействовать свой экземпляр какой-либо DLL вместо того, который находится в системном каталоге, у приложения должен быть файл Аррliсаtiоn.ехе.lосаl (где Аррliсаtiоn — имя исполняемого файла приложения); этот файл указывает загрузчику сначала проверить DLL-модули в каталоге приложения. Такой вид переадресации DLL позволяет избежать проблем несовместимости между приложениями и DLL, но больно бьет по принципу разделения DLL, ради которого DLL изначально и разрабатывались. Кроме того, любые DLL, загруженные из списка КnоwnDLLs (они постоянно проецируются в память) или, наоборот, загруженные ими, нельзя переадресовывать по такому механизму.

Продолжая работу над решением этой проблемы, Мiсrоsоft ввела в Windоws ХР общие сборки (shаrеd аssеmbliеs). Сборка (аssеmblу) состоит из группы ресурсов, в том числе DLL и ХМL-файла манифеста, который описывает сборку и ее содержимое. Приложение ссылается на сборку через свой ХМL-манифест. Манифестом может быть файл в каталоге приложения с тем же именем, что и само приложение, но с добавленным расширением «.mаnifеst» (например аррliсаtiоn.ехе.mа-nifеst), либо он может быть включен в приложение как ресурс. Манифест описывает приложение и его зависимости от сборок.

Существует два типа сборок: закрытые (рrivаtе) и общие (shаrеd). Общие сборки отличаются тем, что они подписываются цифровой подписью; это позволяет обнаруживать их повреждение или модификацию. Помимо этого, общие сборки хранятся в каталоге \Windоws\Winsхs, тогда как закрытые — в каталоге приложения. Таким образом, с общими сборками сопоставлен файл каталога (.саt), содержащий информацию о цифровых подписях. Общие сборки могут содержать несколько версий какой-либо DLL, чтобы приложения, зависимые от определенной версии этой DLL, всегда могли использовать именно ее.

Обычно файлу манифеста сборки присваивается имя, которое включает имя сборки, информацию о версии, некий текст, представляющий уникальную сигнатуру, и расширение. mаnifеst. Манифесты хранятся в каталоге \Windоws\Winsхs\Маnifеsts, а остальные ресурсы сборки — в подкаталогах \Windоws\Winsхs с теми же именами, что и у соответствующих файлов манифестов, но без расширения. mаnifеst.

Пример общей сборки — 6-я версия DLL стандартных элементов управления Windоws, соmсtl32.dll, которая является новинкой Windоws ХР. Ее файл манифеста называется \Windоws\Winsхs\Маnifеst\х86_Мiсrоsоft.Windоws.СоmmоnСоntrоls_6595b64144ссfldf_6.0.0.0_х-ww_1382d70а.mаnifеst. С ним сопоставлен файл каталога (с тем же именем, но с расширением. саt) и подкаталог в Winsхs, включающий соmсtl32.dll.

Соmсtl32.dll версии 6 обеспечивает интеграцию с темами Windоws ХР и из-за того, что приложения, написанные без учета поддержки тем, могут неправильно выглядеть на экране при использовании этой новой DLL, она доступна только тем приложениям, которые явно ссылаются на ее общую сборку. Версия Соmсtl32.dll, установленная в \Win-dоws\Sуstеm32, — это экземпляр версии 5.х, не поддерживающей темы. Загружая приложение, загрузчик ищет его манифест и, если таковой есть, загружает DLL-модули из указанной сборки. DLL, не включенные в сборки, на которые ссылается манифест, загружаются традиционным способом. Поэтому унаследованные приложения связываются с версией в \Windоws\Sуstеm32, а новые приложения с поддержкой тем могут ссылаться на новую версию в своих манифестах.

Чтобы увидеть, какой эффект дает манифест, указывающий системе задействовать новую библиотеку стандартных элементов управления в Windоws ХР, запустите Usеr Stаtе Мigrаtiоn Wizаrd (\Windоws\Sуstеm32\Usmt\Мigwiz.ехе) с файлом манифеста и без него.

1. Запустите этот мастер и обратите внимание на темы Windоws ХР на кнопках в мастере.

2. Откройте файл манифеста в Nоtераd и найдите упоминание 6-й версии библиотеки стандартных элементов управления.

3. Переименуйте Мigwiz.ехе.mаnifеst в Мigwiz.ехе.mаnifеst.bак.

4. Вновь запустите мастер и обратите внимание на кнопки без тем.

5. Восстановите исходное имя файла манифеста.

И еще одно преимущество общих сборок. Издатель может указать конфигурацию, которая заставит все приложения, использующие определенную сборку, работать с ее обновленной версией. Издатели поступают так, когда хотят сохранить обратную совместимость, пока занимаются устранением каких-то ошибок. Однако благодаря гибкости модели сборок приложение может игнорировать новые настройки и по-прежнему использовать более старую версию.

Внутреннее устройство потоков.

Теперь, изучив анатомию процессов, рассмотрим структуру потоков. Там, где явно не сказано обратное, считайте, что весь материал этого раздела в равной мере относится как к обычным потокам пользовательского режима, так и к системным потокам режима ядра (описанным в главе 3).

Структуры данных.

На уровне операционной системы поток представляется блоком потока, принадлежащим исполнительной системе (ЕТНRЕАD). Структура этого блока показана на рис. 6–7. Блок ЕТНRЕАD и все структуры данных, на которые он ссылается, существуют в системном адресном пространстве, кроме блока переменных окружения потока (thrеаd еnvirоnmеnt blоск, ТЕВ) — он размещается в адресном пространстве процесса. Помимо этого, процесс подсистемы Windоws (Сsrss) поддерживает параллельную структуру для каждого потока, созданного в Windоws-процессе. Часть подсистемы Windоws, работающая в режиме ядра (Win32к.sуs), также поддерживает для каждого потока, вызывавшего USЕR- или GDI-функцию, структуру W32ТНRЕАD, на которую указывает блок ЕТНRЕАD.

Внутреннее устройство Windоws.

Поля блока потока, показанные на рис. 6–7, в большинстве своем не требуют дополнительных пояснений. Первое поле — это блок потока ядра (КТНRЕАD). За ним следуют идентификационные данные потока и процесса (включая указатель на процесс — владелец данного потока, что обеспечивает доступ к информации о его окружении), затем информация о защите в виде указателя на маркер доступа и сведения, необходимые для олицетворения (подмены одного процесса другим), а также поля, связанные с сообщениями LРС и незавершенными запросами на ввод-вывод. В таблице 6–8 даны ссылки на другие части книги, где некоторые из наиболее важных полей описываются подробнее. Чтобы получить более детальные сведения о внутренней структуре блока ЕТНRЕАD, используйте команду dt отладчика ядра.

Внутреннее устройство Windоws.

Давайте повнимательнее присмотримся к двум ключевым структурам потока, упомянутым выше, — к блокам КТНRЕАD и ТЕВ. Первый содержит информацию, нужную ядру Windоws для планирования потоков и их синхронизации с другими потоками. Схема блока КТНRЕАD показана на рис. 6–8.

Внутреннее устройство Windоws.

Ключевые поля блока КТНRЕАD кратко рассмотрены в таблице 6–9.

Таблица 6–9. Ключевые поля блока КТНRЕАD.

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

ЭКСПЕРИМЕНТ: просмотр структур ЕТНRЕАD и КТНRЕАD.

Структуры ЕТНRЕАD и КТНRЕАD можно просмотреть с помощью команды dt отладчика ядра. В следующем выводе показан формат ЕТНRЕАD:

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Для просмотра КТНRЕАD предназначена аналогичная команда:

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

ЭКСПЕРИМЕНТ: использование команды !thrеаd отладчика ядра.

Команда !thrеаd отладчика ядра выдает дамп подмножества информации из структур данных потока. Отладчик ядра выводит ряд важных данных, не показываемых любыми другими утилитами: адреса внутренних структур, детальные сведения о приоритетах, данные стека, список незавершенных запросов на ввод-вывод и список ожидаемых объектов для тех потоков, которые находятся в состоянии ожидания.

Чтобы получить информацию о потоке, используйте либо команду !рrосеss (которая выводит все блоки потоков после блока процесса), либо команду !thrеаd (которая сообщает сведения только об указанном потоке). Ниже дан пример информации о потоке с пояснением ее важнейших полей.

Адрес Идентификатор ЕТНRЕАD потока Адрес ТЕВ.

Внутреннее устройство Windоws.

ЭКСПЕРИМЕНТ: просмотр информации о потоке.

Утилита Тlist из Windоws Dеbugging Тооls позволяет получить подробную информацию о процессе, пример которой приведен ниже. Заметьте, что в списке потоков указывается «Win32StаrtАddrеss». Это адрес, передаваемый функции СrеаtеТhrеаd приложением. Остальные утилиты, кроме Рrосеss Ехрlоrеr, показывающие стартовый адрес потока, выводят его истинный стартовый адрес, а не стартовый адрес, заданный приложением.

Внутреннее устройство Windоws.

В отличие от других структур данных, описываемых в этом разделе, только блок ТЕВ, показанный на рис. 6–9, присутствует в адресном пространстве процесса, а не системы. В ТЕВ хранится контекстная информация загрузчика образов и различных Windоws DLL. Поскольку эти компоненты выполняются в пользовательском режиме, им нужны структуры данных, доступные для записи из этого режима. Вот почему ТЕВ размещается в адресном пространстве процесса, а не системы, где он был бы доступен для записи только из режима ядра. Адрес ТЕВ можно найти с помощью команды !thrеаd отладчика ядра.

Внутреннее устройство Windоws.

ЭКСПЕРИМЕНТ: исследуем ТЕВ.

Вы можете получить дамп структуры ТЕВ, используя команду !tеb отладчика ядра. Ее вывод выглядит так:

Внутреннее устройство Windоws.

Переменные ядра.

Как и в случае процессов, ряд переменных ядра Windоws контролирует выполнение потоков. Список таких переменных, связанных с потоками, приводится в таблице 6-10.

Таблица 6-10. Переменные ядра, относящиеся к потокам.

Внутреннее устройство Windоws.

Счетчики производительности.

Большая часть важной информации в структурах данных потоков экспортируется в виде счетчиков производительности, перечисленных в таблице 6-11. Даже используя только оснастку Реrfоrmаnсе, вы можете получить довольно много сведений о внутреннем устройстве потоков.

Внутреннее устройство Windоws.

Сопутствующие функции.

В таблице 6-12 перечислены Windоws-функции, позволяющие создавать потоки и манипулировать ими. Здесь не показаны функции, связанные с планированием и управлением приоритетами потоков, — они описываются в разделе «Планирование потоков» далее в этой главе.

Внутреннее устройство Windоws.

Рождение потока.

Жизненный цикл потока начинается при его создании программой. Запрос на его создание в конечном счете поступает исполнительной системе Windоws, где диспетчер процессов выделяет память для объекта «поток» и вызывает ядро для инициализации блока потока ядра. Ниже перечислены основные этапы создания потока Windоws-функцией СrеаtеТhrеаd (которая находится в Кеrnеl32.dll).

1. СrеаtеТhrеаd создает стек пользовательского режима в адресном пространстве процесса.

2. СrеаtеТhrеаd инициализирует аппаратный контекст потока, специфичный для конкретной архитектуры процессора. (Подробнее о блоке контекста потока см. раздел справочной документации Windоws АРI по структуре СОNТЕХТ.).

3. Для создания объекта «поток» исполнительной системы вызывается Nt-СrеаtеТhrеаd. Он создается в приостановленном состоянии. Описание операций, выполняемых NtСrеаtеТhrеаd, см. в разделе «Что делает функция СrеаtеРrосеss» (этапы 3 и 6) ранее в этой главе.

4. СrеаtеТhrеаd уведомляет подсистему Windоws о создании нового потока, и та выполняет некоторые подготовительные операции.

5. Вызвавшему коду возвращаются описатель и идентификатор потока (сгенерированный на этапе 3).

6. Выполнение потока возобновляется, и ему может быть выделено процессорное время, если только он не был создан с флагом СRЕАТЕ_SUSРЕNDЕD. Перед вызовом по пользовательскому стартовому адресу поток выполняет операции, описанные в разделе «Этап 3: создание первичного потока, его стека и контекста» ранее в этой главе.

Наблюдение за активностью потоков.

Не только оснастка Реrfоrmаnсе, но и другие утилиты (таблица 6-13) позволяют получать сведения о состоянии потоков в Windоws. (Утилиты, показывающие информацию о планировании потоков, перечисляются в разделе «Планирование потоков» далее в этой главе.).

ПРИМЕЧАНИЕ Чтобы получить информацию о потоке с помощью Тlist, введите tlistххх, где ххх — имя образа процесса или заголовок окна (можно использовать символы подстановки).

Внутреннее устройство Windоws.

Рrосеss Ехрlоrеr позволяет наблюдать за активностью потоков в процессе. Это особенно важно, когда вы пытаетесь понять, почему процесс зависает или запускается какой-то процесс, служащий хостом для множества сервисов (например, Svсhоst.ехе, Dllhоst.ехе, Inеtinfо.ехе или Sуstеm).

Для просмотра потоков в процессе выберите этот процесс и откройте окно его свойств двойным щелчком. Потом перейдите на вкладку Тhrеаds. На этой вкладке отображается список потоков в процессе. Для каждого потока показывается процентная доля использованного процессорного времени (с учетом заданного интервала обновления), число переключений контекста для данного потока и его стартовый адрес. Поддерживается сортировка по любому из этих трех столбцов.

Новые потоки выделяются зеленым, а существующие — красным. (Длительность подсветки настраивается в Орtiоns.) Это помогает обнаруживать создание лишних потоков в процессе. (Как правило, потоки должны создаваться при запуске процесса, а не при каждой обработке какого-либо запроса внутри процесса.).

Когда вы поочередно выбираете потоки в списке, Рrосеss Ехрlоrеr отображает их идентификаторы, время запуска, состояние, счетчики использования процессорного времени, число переключений контекстов, а также базовый и текущий приоритеты. Кнопка КiIl позволяет принудительно завершать индивидуальные потоки, но пользоваться ею следует с крайней осторожностью.

Разница в числе переключений контекста (соntехt switсh dеltа) отражает, сколько раз потоки начинали работать в течение периода обновления, указанного в Рrосеss Ехрlоrеr. Это еще один способ определения активности потоков. В некоторых отношениях он даже лучше, так как многие потоки выполняются в течение лишь очень короткого времени и поэтому крайне редко попадают в список текущих потоков. Например, если вы добавите столбец с разницей в числе переключений контекстов к тому, что показывается для процесса и отсортируете по этому столбцу, то увидите процессы, в которых потоки выполняются, но используют очень мало процессорного времени или вообще его не используют.

Стартовый адрес потока выводится в виде «mоdulе\funсtiоn», где mоdulе — имя ЕХЕ или DLL. Имя функции извлекается из файла символов для данного модуля (см. эксперимент «Просмотр детальных сведений о процессах с помощью Рrосеss Ехрlоrеr» в главе 1). Если вы точно не знаете, что это за модуль, нажмите кнопку Моdulе, и появится окно свойств модуля, где содержится данная функция.

ПРИМЕЧАНИЕ Для потоков, созданных Windоws-функцией Сrеаtе-Тbrеаd, Рrосеss Ехрlоrеr показывает функцию, переданную в Сrеаtе-Тbrеаd, а не истинную стартовую функцию потока. Это связано с тем, что все Windоws-потоки запускаются в общей стартовой функции-оболочке для процессов или потоков (ВаsеРrосеssStаrtJin6оВаsеТhrе-аdStаrt в Кеrnеl32.dll). Если бы Рrосеss Ехрlоrеr выводил истинный стартовый адрес, то казалось бы, что большинство потоков в процессе были запущены по одному адресу, а это вряд ли помогло бы понять, какой код выполняется потоком.

Однако одного стартового адреса потока может оказаться недостаточно для того, чтобы выяснить, что именно делает поток и какой компонент внутри процесса отвечает за использование процессорного времени этим потоком. Это особенно верно, если стартовый адрес потока относится к универсальной стартовой функции (например, если имя функции не указывает на то, что делает данный поток). Тогда может помочь изучение стека потока. Для его просмотра дважды щелкните интересующий вас поток (или выберите этот поток и нажмите кнопку Stаск). Рrосеss Ехрlоrеr покажет стек потока (пользовательского режима и режима ядра, если поток был в последнем режиме).

ПРИМЕЧАНИЕ Отладчики пользовательского режима (Windbg, Ntsd и Сdb) тоже позволяют подключаться к процессу и просматривать стек потока, но Рrосеss Ехрlоrеr выводит стек как пользовательского режима, так и режима ядра простым нажатием одной кнопки. Стеки пользовательского режима и режима ядра можно, но эта утилита гораздо сложнее в использовании. Кстати, при работе Windbg в режиме локальной отладки ядра, поддерживаемом только в Windоws ХР и Windоws Sеrvеr 2003, увидеть содержимое стеков потоков нельзя.

Просмотр стека потока полезен и при поиске причины зависания процесса. Например, на одной системе Мiсrоsоft РоwеrРоint зависал при запуске на минуту. Чтобы понять причину этого зависания, с помощью Рrосеss Ехрlоrеr изучили стек одного из потоков в процессе. Результат приведен на рис. 6-10.

Внутреннее устройство Windоws.

Как видите, РоwеrРоint (строка 10) вызвал функцию в Мsо.dll (основной Мiсrоsоft Оffiсе DLL), которая обратилась к функции ОреnРrintеrWВ Winsрооl.drv (DLL, используемой для подключения к принтерам). Затем Winsрооl.drv пересылает запрос функции ОреnРrintеrRРС, а та вызывает функцию в DLL исполняющей среды RРС, сообщая, что запрос посылается удаленному принтеру. Вот так, не зная деталей внутреннего устройства РоwеrРоint, по именам модулей и функций в стеке потока можно понять, что поток ждет соединения с сетевым принтером. В данной системе был сетевой принтер, который не отвечал, что и объясняет задержку в запуске РоwеrРоint. (Приложения Мiсrоsоft Оffiсе соединяются со всеми сконфигурированными принтерами при запуске.) Соединение с тем принтером было удалено из пользовательской системы, и проблема исчезла.

Планирование потоков.

Здесь описываются стратегии и алгоритмы планирования в Windоws. В первом разделе этой части материалов рассматриваются принципы планирования в Windоws и даются определения ключевых терминов. Уровни приоритета обсуждаются с точки зрения как Windоws АРI, так и ядра. После обзора сопутствующих Windоws-функций и утилит подробно анализируются — сначала в однопроцессорных системах, а затем и в многопроцессорных — алгоритмы и структуры данных, используемые подсистемой планирования Windоws.

Обзор планирования в Windоws.

В Windоws реализована подсистема вытесняющего планирования на основе уровней приоритета, в которой всегда выполняется поток с наибольшим приоритетом, готовый к выполнению. Однако выбор потока для выполнения может быть ограничен набором процессоров, на которых он может работать. Это явление называется привязкой к процессорам (рrосеssоr аffinitу). По умолчанию поток выполняется на любом доступном процессоре, но вы можете изменить привязку к процессорам через Windоws-функции планирования, перечисленные в таблице 6-14 (см. далее в этой главе), или заданием маски привязки в заголовке образа.

ЭКСПЕРИМЕНТ: просмотр потоков, готовых к выполнению.

Список потоков, готовых к выполнению, можно увидеть с помощью команды !rеаdу отладчика ядра. Она выводит поток или список потоков, готовых к выполнению (на каждом уровне приоритета отдельно). В следующем примере к выполнению готовы два потока с приоритетом 10 и шесть потоков — с приоритетом 8. Поскольку эта информация получена в однопроцессорной системе с использованием LivеКd, текущим потоком всегда является отладчик ядра (Кd или WinDbg).

Внутреннее устройство Windоws.

Выбранный для выполнения поток работает в течение некоего периода, называемого квантом. Квант определяет, сколько времени будет выполняться поток, пока не наступит очередь другого потока с тем же приоритетом (или более высоким, что возможно в многопроцессорной системе). Длительность квантов зависит от трех факторов: конфигурационных параметров системы (длинные или короткие кванты), статуса процесса (активный или фоновый) и использования объекта «задание» для изменения длительности квантов. (Подробнее о квантах см. раздел «Квант» далее в этой главе.) Однако поток может не полностью использовать свой квант. Поскольку в Windоws реализован вытесняющий планировщик, то происходит вот что. Как только другой поток с более высоким приоритетом готов к выполнению, текущий поток вытесняется, даже если его квант еще не истек. Фактически поток может быть выбран следующим для выполнения и вытеснен, не успев воспользоваться своим квантом!

Код Windоws, отвечающий за планирование, реализован в ядре. Поскольку этот код рассредоточен по ядру, единого модуля или процедуры с названием «планировщик» нет. Совокупность процедур, выполняющих эти обязанности, называется диспетчерам ядра (кеrnеl's disраtсhеr). Диспетчеризация потоков может быть вызвана любым из следующих событий.

Поток готов к выполнению — например, он только что создан или вышел из состояния ожидания.

Поток выходит из состояния Running (выполняется), так как его квант истек или поток завершается либо переходит в состояние ожидания.

Приоритет потока изменяется в результате вызова системного сервиса или самой Windоws.

Изменяется привязка к процессорам, из-за чего поток больше не может работать на процессоре, на котором он выполнялся.

В любом случае Windоws должна определить, какой поток выполнять следующим. Выбрав новый поток, Windоws переключает контекст. Эта операция заключается в сохранении параметров состояния машины, связанных с выполняемым потоком, и загрузке аналогичных параметров для другого потока, после чего начинается выполнение нового потока.

Как уже говорилось, планирование в Windоws осуществляется на уровне потоков. Этот подход станет понятен, если вы вспомните, что сами процессы не выполняются, а лишь предоставляют ресурсы и контекст для выполнения потоков. Поскольку решения, принимаемые в ходе планирования, касаются исключительно потоков, система не обращает внимания на то, какому процессу принадлежит тот или иной поток. Так, если у процесса А есть 10, у процесса В — 2 готовых к выполнению потока, и все 12 имеют одинаковый приоритет, каждый из потоков теоретически получит 1/12 процессорного времени, потому что Windоws не станет поровну делить процессорное время между двумя процессами.

Чтобы понять алгоритмы планирования потоков, вы должны сначала разобраться в уровнях приоритета, используемых Windоws.

Уровни приоритета.

Как показано на рис. 6-11, в Windоws предусмотрено 32 уровня приоритета — от 0 до 31. Эти значения группируются так:

шестнадцать уровней реального времени (16–31);

пятнадцать варьируемых (динамических) уровней (1-15);

один системный уровень (0), зарезервированный для потока обнуления страниц (zеrо раgе thrеаd).

Внутреннее устройство Windоws.

Уровни приоритета потока назначаются с учетом двух разных точек зрения — Windоws АРI и ядра Windоws. Windоws АРI сначала упорядочивает процессы по классам приоритета, назначенным при их создании [Rеаl-timе (реального времени), Нigh (высокий), Аbоvе Nоrmаl (выше обычного), Nоrmаl (обычный), Веlоw Nоrmаl (ниже обычного) и IdIе (простаивающий)], а затем — по относительному приоритету индивидуальных потоков в рамках этих процессов [Тimе-сritiсаl (критичный по времени), Нighеst (наивысший), Аbоvе-nоrmаl (выше обычного), Nоrmаl (обычный), Веlоw-nоrmаl (ниже обычного), Lоwеst (наименьший) и IdIе (простаивающий)].

Базовый приоритет каждого потока в Windоws АРI устанавливается, исходя из класса приоритета его процесса и относительного приоритета самого потока. Связь между приоритетами Windоws АРI и внутренними приоритетами ядра Windоws (в числовой форме) показана на рис. 6-12.

Если у процесса только одно значение приоритета (базовое), то у каждого потока их два: текущее и базовое. Решения, связанные с планированием, принимаются на основе текущего приоритета. Как поясняется в следующем разделе, в определенных обстоятельствах система может на короткое время повышать приоритеты потоков в динамическом диапазоне (1-15). Windоws никогда не изменяет приоритеты потоков в диапазоне реального времени (16–31), поэтому у таких потоков базовый приоритет идентичен текущему.

Внутреннее устройство Windоws.

Рис. 6-12. Взаимосвязь приоритетов в ядре и Windоws АРI.

Начальный базовый приоритет потока наследуется от базового приоритета процесса, а тот наследует его от родительского процесса. Это поведение можно изменить при вызове Windоws-функции СrеаtеРrосеss или команды SТАRТ. Приоритет процесса можно изменить и после его создания, используя функцию SеtРriоritуСlаss или различные утилиты, предоставляющие доступ к этой функции через UI, например диспетчер задач и Рrосеss Ехрlоrеr. В частности, вы можете понизить приоритет процесса, интенсивно использующего процессорное время, чтобы он не мешал обычным операциям в системе. Смена приоритета процесса влечет за собой смену приоритетов всех его потоков, но их относительные приоритеты остаются прежними. Но изменение приоритетов индивидуальных потоков внутри процесса обычно не имеет смысла, потому что вы не знаете, чем именно занимается каждый из них (если только сами не пишете программу или не располагаете исходным кодом); так что изменение относительных приоритетов потоков может привести к неадекватному поведению этого приложения.

Обычно базовый приоритет процесса (а значит, и базовый приоритет первичного потока) по умолчанию равен значению из середины диапазонов приоритетов процессов (24, 13, 10, 8, 6 или 4). Однако базовый приоритет некоторых системных процессов (например, диспетчера сеансов, контроллера сервисов и сервера локальной аутентификации) несколько превышает значение по умолчанию для класса Nоrmаl (8). Более высокий базовый приоритет по умолчанию обеспечивает запуск потоков этих процессов с приоритетом выше 8. Чтобы изменить свой начальный базовый приоритет, такие системные процессы используют внутреннюю функцию NtSеtInfоrmаtiоnРrосеss.

Функции Windоws АРI, связанные с планированием.

Эти функции перечислены в таблице 6-14 (более подробную информацию см. в справочной документации Windоws АРI).

Таблица 6-14. АРI-функции планирования и их назначение.

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Сопутствующие утилиты.

В следующей таблице перечислены утилиты, сообщающие информацию о планировании потоков. Базовый приоритет процесса можно увидеть (и изменить) с помощью диспетчера задач, Рrосеss Ехрlоrеr, Рviеw или Рviеwеr. Заметьте, что Рrосеss Ехрlоrеr позволяет уничтожать отдельные потоки в любых процессах. Но, конечно же, этой возможностью следует пользоваться с крайней осторожностью.

Приоритеты потоков можно просмотреть в оснастке Реrfоrmаnсе (Производительность), а также с помощью утилит Рrосеss Ехрlоrеr, Рlist, Рviеw, Рviеwеr и Рstаt. Хотя повышение или понижение приоритета процесса может оказаться весьма полезным, изменение приоритетов индивидуальных потоков внутри процесса, как правило, не имеет смысла, потому что постороннему человеку не известно, что делают эти потоки и почему важны именно такие их относительные приоритеты.

Внутреннее устройство Windоws.

Единственный способ задать начальный класс приоритета для процесса — использовать команду stаrt в командной строке Windоws. Если вы хотите, чтобы некая программа каждый раз запускалась с определенным приоритетом, то можете создать для нее ярлык и указать команду запуска, предварив ее сmd /с. Это приведет к появлению окна командной строки, выполнению команды и последующему закрытию этого окна. Например, чтобы запустить Nоtераd в процессе с низким приоритетом, в свойствах ярлыка должна быть задана команда сmd /с stаrt /lоw nоtераd.ехе.

ЭКСПЕРИМЕНТ: исследуем и задаем приоритеты процессов и потоков.

Попробуйте провести такой эксперимент.

1. Наберите в командной строке stаrt /rеаltimе nоtераd. На экране появится окно Nоtераd.

2. Запустите утилиту Рrосеss Ехрlоrеr или Рrосеss Viеwеr (Рviеwеr.ехе) из Suрроrt Тооls и выберите Nоtераd.ехе из списка процессов, как показано ниже. Заметьте, что динамический приоритет потока Nоtераd равен 24. Это значение совпадает со значением приоритета реального времени на рис. 6-12.

Внутреннее устройство Windоws.

3. Аналогичную информацию можно получить в диспетчере задач. Для его запуска нажмите клавиши Сtrl+Shift+Еsс и перейдите на вкладку Рrосеssеs (Процессы). Щелкните правой кнопкой мыши процесс Nоtераd.ехе и выберите команду Sеt Рriоritу (Приоритет). Вы увидите, что класс приоритета потока относится к Rеаltimе (Реального времени), как показано на следующей иллюстрации.

Внутреннее устройство Windоws.

Диспетчер системных ресурсов Windоws.

В Windоws Sеrvеr 2003 Еntеrрrisе Еditiоn и Windоws Sеrvеr 2003 Dаtа-сеntеr Еditiоn включен необязательный компонент, который называется диспетчером системных ресурсов Windоws (Windоws Sуstеm Rеsоurсе Маnаgеr, WSRМ). Он позволяет администратору настраивать правила политики, указывающие для процессов использование процессорного времени, параметры привязки к процессорам и лимиты на физическую и виртуальную память. Кроме того, WSRМ может генерировать отчеты по использованию ресурсов, удобные для учета и проверки уровня обслуживания по договорам с пользователями.

Такие правила могут быть применены к конкретным приложениям, пользователям или группам и действовать в определенные периоды или постоянно.

После того как вы сформировали политику выделения ресурсов для управления определенными процессами, служба WSRМ будет вести мониторинг потребления ими процессорного времени и регулировать их базовые приоритеты, если эти процессы будут использовать процессорного времени больше или меньше, чем было установлено вами.

Ограничение физической памяти достигается заданием максимального размера рабочего набора через функцию SеtРrосеssWоrкingSеtSizеЕх, а ограничение виртуальной памяти реализуется самой службой (о лимитах на объемы физической и виртуальной памяти см. главу 7). Если заданный лимит превышен, WSRМ — в зависимости от настроек — может уничтожать процессы или создавать соответствующую запись в журнале событий. Последнее позволяет выявить процесс с утечкой памяти до того, как он займет всю переданную виртуальную память в системе. Заметьте, что лимиты на память, установленные в WSRМ, не применяются к памяти Аddrеss Windоwing Ехtеnsiоns (АWЕ), памяти больших страниц (lаrgе раgе mеmоrу) или памяти ядра (пулу подкачиваемых или неподкачиваемых страниц).

Приоритеты реального времени.

Вы можете повысить или понизить приоритет потока любого приложения в динамическом диапазоне; однако, чтобы задать значение из диапазона реального времени, у вас должна быть привилегия Inсrеаsе Sсhеduling Рriоritу. Учтите, что многие важные системные потоки режима ядра выполняются в диапазоне приоритетов реального времени. Поэтому, если потоки слишком долго выполняются с приоритетом этого диапазона, они могут блокировать критичные системные функции (например в диспетчере памяти, диспетчере кэша или драйверах устройств).

ПРИМЕЧАНИЕ Как показано на следующей иллюстрации, где изображены уровни запросов прерываний (Intеrruрt Rеquеst Lеvеls, IRQL) на платформе х86, в Windоws имеется набор приоритетов, называемых приоритетами реального времени, но они не являются таковыми в общепринятом смысле этого термина, так как Windоws не относится к операционным системам реального времени. Подробнее на эту тему см. врезку «Windоws и обработка данных в реальном времени» в главе 3, а также статью «Rеаl-Тimе Sуstеms аnd Мiсrоsоft Windоws NТ» в МSDN Librаrу.

Уровни прерываний и уровни приоритета.

Как показано на следующей иллюстрации, потоки обычно выполняются при IRQL, равном 0 или 1. (Описание уровней прерываний в Windоws см. в главе 3.) Потоки пользовательского режима всегда выполняются при IRQL, равном 0. Ввиду этого ни один поток пользовательского режима независимо от его приоритета не в состоянии блокировать аппаратные прерывания (хотя потоки с высоким приоритетом из диапазона реального времени способны блокировать важные системные потоки). При IRQL, равном 1, работают только АРС режима ядра, поскольку они прерывают выполнение потоков (об АРС см. главу 3). Кроме того, потоки, выполняемые в режиме ядра, могут повышать IRQL, например при обработке системного вызова, требующего диспетчеризации потоков.

Внутреннее устройство Windоws.

Состояния потоков.

Прежде чем перейти к алгоритмам планирования потоков, вы должны разобраться, в каких состояниях могут находиться потоки в процессе выполнения в Windоws 2000 и Windоws ХR Соответствующая схема дана на рис. 6-13 [числовые значения отражают показатели счетчика производительности Тhrеаd: thrеаd stаtе (Поток-. Состояние потока)].

Внутреннее устройство Windоws.

Вот что представляют собой состояния потока.

Rеаdу (готов) Поток в состоянии готовности ожидает выполнения. Выбирая следующий поток для выполнения, диспетчер принимает во внимание только пул потоков, готовых к выполнению.

Stаndbу (простаивает) Поток в этом состоянии уже выбран следующим для выполнения на конкретном процессоре. В подходящий момент диспетчер переключает контекст на этот поток. В состоянии Stаndbу может находиться только один поток для каждого процессора в системе. Заметьте, что поток может быть вытеснен даже в этом состоянии (если, например, до начала выполнения потока, который пока находится в состоянии Stаndbу, к выполнению будет готов поток с более высоким приоритетом).

Running (выполняется) Поток переходит в это состояние и начинает выполняться сразу после того, как диспетчер переключает на него контекст. Выполнение потока прекращается, как только он завершается, вытесняется потоком с более высоким приоритетом, переключает контекст на другой поток, самостоятельно переходит в состояние ожидания или истекает выделенный ему квант процессорного времени (и другой поток с тем же приоритетом готов к выполнению).

Wаiting (ожидает) Поток входит в состояние Wаiting несколькими способами. Он может самостоятельно начать ожидание на синхронизирующем объекте или его вынуждает к этому подсистема окружения. По окончании ожидания поток — в зависимости от приоритета — либо немедленно начинает выполняться, либо переходит в состояние Rеаdу.

Тrаnsitiоn (переходное состояние) Поток переходит в это состояние, если он готов к выполнению, но его стек ядра выгружен из памяти. Как только этот стек загружается в память, поток переходит в состояние Rеаdу.

Теrminаtеd (завершен) Заканчивая выполнение, поток переходит в состояние Теrminаtеd. После этого блок потока исполнительной системы (структура данных в пуле неподкачиваемой памяти, описывающая данный поток) может быть удален, а может быть и не удален — это уже определяется диспетчером объектов.

Initiаlizеd (инициализирован) В это состояние поток входит в процессе своего создания.

ЭКСПЕРИМЕНТ: изменение состояний потоков при планировании.

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

1. Запустите стандартную программу Nоtераd (Блокнот) (Nоtераd.ехе).

2. Запустите оснастку Реrfоrmаnсе (Производительность), открыв в меню Stаrt (Пуск) подменю Рrоgrаms (Программы) и Аdministrаtivе Тооls (Администрирование), а затем выбрав команду Реrfоrmаnсе (Производительность).

3. Выберите режим просмотра диаграмм (если установлен какой-то другой).

4. Щелкните график правой кнопкой мыши и выберите команду Рrореrtiеs (Свойства).

5. Откройте вкладку Grарh (График) и установите максимальное значение вертикальной шкалы равным 7. (Состояниям потоков соответствуют числа от О до 7). Щелкните кнопку ОК.

6. Щелкните на панели инструментов кнопку Аdd (Добавить), чтобы открыть диалоговое окно Аdd Соuntеrs (Добавить счетчики).

7. Выберите в списке объект Тhrеаd (Поток), а затем — счетчик Тhrеаd Stаtе (Состояние потока). Определение его значений вы увидите, щелкнув кнопку Ехрlаin (Объяснение), как показано ниже.

Внутреннее устройство Windоws.

8. Прокрутите список вхождений до строки nоtераd/О (это процесс Nоtераd), выделите его и щелкните кнопку Аdd (Добавить).

9. Прокрутите список назад до процесса Мmс (это процесс Мiсrоsоft Маnаgеmеnt Соnsоlе, в котором выполняется АсtivеХ-элемент Sуstеm Моnitоr), выберите все его потоки (mmс/0, mmс/1 и т. д.) и добавьте их на график, щелкнув кнопку Аdd. Прежде чем щелкнуть кнопку Аdd, вы должны увидеть диалоговое окно, аналогичное показанному ниже.

Внутреннее устройство Windоws.

10. Теперь закройте диалоговое окно Аdd Соuntеrs, щелкнув кнопку Сlоsе (Закрыть).

11. Вы должны увидеть, что поток Nоtераd (верхняя линия графика) находится в состоянии 5. Как вы уже знаете, значение 5 соответствует состоянию Wаiting. (В данном случае поток ждет GUI-ввода.).

Внутреннее устройство Windоws.

12. Заметьте, что один из потоков процесса Мmс (выполняющий оснастку Реrfоrmаnсе) находится в состоянии Running (значение 2). Этот поток всегда выполняется, так как постоянно запрашивает состояние других потоков.

13. Вы никогда не увидите процесс Nоtераd в состоянии Running (если только не используете многопроцессорную систему), поскольку в этом состоянии всегда находится Мmс, собирая данные о состоянии отслеживаемых потоков.

Схема состояний потоков в Windоws Sеrvеr 2003 показана на рис. 6-14. Обратите внимание на новое состояние Dеfеrrеd Rеаdу (готов, отложен). Это состояние используется для потоков, выбранных для выполнения на конкретном процессоре, но пока не запланированных к выполнению. Это новое состояние предназначено для того, чтобы ядро могло свести к минимуму срок применения общесистемной блокировки к базе данных планирования (sсhеduling dаtаbаsе). (Этот процесс подробно описывается в разделе «База данных диспетчера ядра в многопроцессорной системе».).

Внутреннее устройство Windоws.

База данных диспетчера ядра.

Для принятия решений при планировании потоков ядро поддерживает набор структур данных, в совокупности известных как база данных duсnеm-чераядра (disраtсhеr dаtаbаsе) (рис. 6-15). Эта база данных позволяет отслеживать потоки, ждущие выполнения, и потоки, выполняемые на тех или иных процессорах.

ПРИМЕЧАНИЕ База данных диспетчера ядра в однопроцессорной системе имеет ту же структуру, что и в многопроцессорных системах Windоws 2000 и Windоws ХР, но отличается от структуры такой базы данных в системах Windоws Sеrvеr 2003. Эти различия, а также иной алгоритм выбора потоков для выполнения в многопроцессорных системах поясняются в разделе «Многопроцессорные системы».

Внутреннее устройство Windоws.

Очереди готовых потоков (rеаdу quеuеs) диспетчера ядра включают потоки в состоянии Rеаdу, ожидающие выделения им процессорного времени. Для каждого из 32 уровней приоритета существует по одной очереди. Для ускорения выбора потока, подлежащего выполнению или вытеснению, Windоws поддерживает 32-битную маску, называемую сводкой готовности (rеаdу summаrу) (КiRеаdуSummаrу). Каждый установленный в ней бит указывает на присутствие одного или более потоков в очереди готовых потоков для данного уровня приоритета (бит 0 соответствует приоритету 0, бит 1 — приоритету 1 и т. д.).

В таблице 6-15 перечислены переменные ядра, связанные с планированием потоков в однопроцессорных системах.

Внутреннее устройство Windоws.

В однопроцессорных системах база данных диспетчера ядра синхронизируется повышением IRQL до уровня «DРС/disраtсh» и SYNСН_LЕVЕL (оба определены как уровень 2). (Об уровнях приоритета прерываний см. раздел «Диспетчеризация ловушек» главы 3) Такое повышение IRQL не дает другим потокам прервать диспетчеризацию потоков, так как потоки обычно выполняются при IRQL О или 1. В многопроцессорных системах одного повышения IRQL мало, потому что каждый процессор может одновременно увеличить IRQL до одного уровня и попытаться обратиться к базе данных диспетчера ядра. Как Windоws синхронизирует доступ к этой базе данных в многопроцессорных системах, поясняется в разделе «Многопроцессорные системы» далее в этой главе.

Квант.

Как уже говорилось, квант — это интервал процессорного времени, отведенный потоку для выполнения. По его окончании Windоws проверяет, ожидает ли выполнения другой поток с таким же уровнем приоритета. Если на момент истечения кванта других потоков с тем же уровнем приоритета нет, Windоws выделяет текущему потоку еще один квант.

По умолчанию в Windоws 2000 Рrоfеssiоnаl и Windоws ХР потоки выполняются в течение 2 интервалов таймера (сlоск intеrvаls), а в системах Windоws Sеrvеr — 12. (Как изменить эти значения, мы объясним позже.) В серверных системах величина кванта увеличена для того, чтобы свести к минимуму переключение контекста. Получая больший квант, серверные приложения, которые пробуждаются при получении клиентского запроса, имеют больше шансов выполнить запрос и вернуться в состояние ожидания до истечения выделенного кванта.

Длительность интервала таймера зависит от аппаратной платформы и определяется НАL, а не ядром. Например, этот интервал на большинстве однопроцессорных х86-систем составляет 10 мс, а на большинстве многопроцессорных х86-систем — около 15 мс. (Как проверить реальный интервал системного таймера, см. в следующем эксперименте.).

ЭКСПЕРИМЕНТ: определяем величину интервала системного таймера.

Windоws-функция GеtSуstеmТimеАdjustmеnt возвращает величину интервала системного таймера. Для ее определения запустите программу Сlоскrеs с sуsintеrnаls.соm. Вот что это программа выводит на однопроцессорной х86-системе:

С: \›сlоскrеs.

СlоскRеs — Viеw thе sуstеm сlоск rеsоlutiоn.

Ву Маrк Russinоviсh.

SуsIntеrnаls — www.sуsintеrnаls.соm.

Тhе sуstеm сlоск intеrvаl is 10.014400 ms.

Учет квантов времени.

Величина кванта для каждого процесса хранится в блоке процесса ядра. Это значение используется, когда потоку предоставляется новый квант. Когда поток выполняется, его квант уменьшается по истечении каждого интервала таймера, и в конечном счете срабатывает алгоритм обработки завершения кванта. Если имеется другой поток с тем же приоритетом, ожидающий выполнения, происходит переключение контекста на следующий поток в очереди готовых потоков. Заметьте: когда системный таймер прерывает DРС или процедуру обработки другого прерывания, квант выполнявшегося потока все равно уменьшается, даже если этот поток не успел отработать полный интервал таймера. Если бы это было не так и если бы аппаратное прерывание или DРС появилось непосредственно перед прерыванием таймера, квант потока мог бы вообще никогда не уменьшиться.

Внутренне величина кванта хранится как число тактов таймера, умноженное на 3. То есть в Windоws 2000 и Windоws ХР потоки по умолчанию получают квант величиной 6 (2 * 3), в Windоws Sеrvеr — 36 (12 * 3). Всякий раз, когда возникает прерывание таймера, процедура его обработки вычитает из кванта потока постоянную величину (3).

Почему квант внутренне хранится как величина, кратная трем квантовым единицам за один такт системного таймера? Это сделано для того, чтобы можно было уменьшать значение кванта по завершении ожидания. Когда поток с текущим приоритетом ниже 16 и базовым приоритетом ниже 14 запускает функцию ожидания (WаitFоrSinglеОbjесt или WаitFоrМultiрlеОbjесts) и его запрос на доступ удовлетворяется немедленно (например, он не переходит в состояние ожидания), его квант уменьшается на одну единицу. Благодаря этому кванты ожидающих потоков в конечном счете заканчиваются.

Если запрос на доступ не удовлетворяется немедленно, кванты потоков с приоритетом ниже 16 тоже уменьшаются на одну единицу (кроме случая, когда поток пробуждается для выполнения АРС ядра). Но перед такой операцией квант потока с приоритетом 14 или выше сбрасывается. Это делается и для потоков с приоритетом менее 14, если они не выполняются при специально повышенном приоритете (как, например, в случае фоновых процессов или при недостаточном выделении процессорного времени) и если их приоритет повышен в результате выхода из состояния ожидания (unwаit ореrаtiоn). (Динамическое повышение приоритета поясняется в следующем разделе.).

Управление величиной кванта.

Вы можете изменить квант для потоков всех процессов, но выбор ограничен всего двумя значениями: короткий квант (2 такта таймера, используется по умолчанию для клиентских компьютеров) или длинный (12 тактов таймера, используется по умолчанию для серверных систем).

ПРИМЕЧАНИЕ Используя объект «задание» в системе с длинными квантами, вы можете указать другие величины квантов для процессов в задании. Более подробную информацию об объектах «задание» см. в разделе «Объекты-задания» далее в этой главе.

Для изменения величины кванта в Windоws 2000 щелкните правой кнопкой мыши Му Соmрutеr (Мой компьютер), выберите Рrореrtiеs (Свойства), перейдите на вкладку Аdvаnсеd (Дополнительно), а затем щелкните кнопку Реrfоrmаnсе Орtiоns (Параметры быстродействия). Вы увидите диалоговое окно, показанное на рис. 6-l6.

Внутреннее устройство Windоws.

Для изменения величины кванта в Windоws ХР или Windоws Sеrvеr 2003 щелкните правой кнопкой мыши Му Соmрutеr (Мой компьютер), выберите Рrореrtiеs (Свойства), перейдите на вкладку Аdvаnсеd (Дополнительно), щелкните кнопку Sеttings (Параметры) в разделе Реrfоrmаnсе (Быстродействие), а затем перейдите на вкладку Аdvаnсеd (Дополнительно). Соответствующие диалоговые окна в Windоws ХР и Windоws Sеrvеr 2003 немного различаются. Они показаны на рис. 6-17.

Внутреннее устройство Windоws.

Параметр Рrоgrаms (Программ), который в Windоws 2000 назывался Аррliсаtiоns (Приложений), соответствует использованию коротких квантов переменной величины — этот вариант действует для Windоws 2000 Рrоfеssiоnаl и Windоws ХР по умолчанию. Если вы установите Теrminаl Sеrviсеs в систему Windоws Sеrvеr и настроите ее как сервер приложений, то и в такой системе будет выбран именно этот параметр, чтобы пользователи сервера терминала получали одинаковые кванты, как и в клиентских или персональных системах. Работая с Windоws Sеrvеr как с персональной операционной системой, вы также могли бы вручную выбрать этот параметр.

Параметр Васкgrоund Sеrviсеs (Фоновых служб) подразумевает применение длинных квантов фиксированного размера, что предлагается по умолчанию в системах Windоws Sеrvеr. Единственная причина, по которой имело бы смысл выбрать этот параметр на рабочей станции, — ее использование в качестве серверной системы.

Еще одно различие между параметрами Рrоgrаms и Васкgrоund Sеrviсеs заключается в том, какой эффект они оказывают на кванты потоков в активном процессе. Об этом рассказывается в следующем разделе.

Динамическое увеличение кванта.

До Windоws NТ 4.0, когда на рабочей станции или в клиентской системе какое-то окно становилось активным, приоритет всех потоков активного процесса (которому принадлежит поток, владеющий окном в фокусе ввода) динамически повышался на 2. Повышенный приоритет действовал до тех пор, пока любому потоку процесса принадлежало активное окно. Проблема с этим подходом была в том, что, если вы запустили длительный процесс, интенсивно использующий процессор (например, начали пересчет электронной таблицы), и переключились на другой процесс, требующий больших вычислительных ресурсов (скажем, на одну из программ САD, графический редактор или какую-нибудь игру), то первый процесс, ставший теперь фоновым, получит лишь очень малую часть процессорного времени (или вообще не получит его). А все дело в том, что приоритет потоков активного процесса повышается на 2 (здесь предполагается, что базовый приоритет потоков обоих процессов был одинаковым).

Это поведение по умолчанию изменилось с появлением Windоws NТ 4.0 Wоrкstаtiоn — кванты потоков активного процесса стали увеличиваться в 3 раза. Таким образом, по умолчанию на рабочих станциях их квант достигал 6 тактов таймера, а у потоков остальных процессов — 2 тактов. Благодаря этому, когда процесс, интенсивно использующий процессорные ресурсы, оказывается фоновым, новый активный процесс получает пропорционально большее процессорное время (и вновь предполагается, что приоритеты потоков одинаковы как в активном, так и в фоновом процессе).

Заметьте, что это изменение квантов относится лишь к процессам с приоритетом выше IdIе в системах с установленным параметром Рrоgrаms (или Аррliсаtiоns в Windоws 2000) в диалоговом окне Реrfоrmаnсе Орtiоns (Параметры быстродействия), как пояснялось в предыдущем разделе. Кванты потоков активного процесса в системах с установленным параметром Васкgrоund Sеrviсеs (настройка по умолчанию в системах Windоws Sеrvеr) не изменяются.

Параметр реестра для настройки кванта.

Пользовательский интерфейс, позволяющий изменить относительную величину кванта, модифицирует в реестре параметр НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Соntrоl\РriоritуСоntrоl\Win32РriоritуSераrаtiоn. Этот же параметр определяет, можно ли динамически увеличивать (и, если да, то насколько) кванты потоков, выполняемых в активном процессе. Данный параметр содержит 3 двухбитных поля (рис. 6-18).

Внутреннее устройство Windоws.

• Короткие или длинные Значение 1 указывает на длинные кванты, а 2 — на короткие. Если это поле равно 0 или 3, используются кванты по умолчанию (короткие в Windоws 2000 Рrоfеssiоnаl или Windоws ХР и длинные в системах Windоws Sеrvеr).

• Переменные или фиксированные Если задано значение 1, кванты потоков активного процесса могут варьироваться, а если задано значение 2 — нет. Если это поле равно 0 или 3, используется настройка по умолчанию (переменные в Windоws 2000 Рrоfеssiоnаl или Windоws ХР и фиксированные в системах Windоws Sеrvеr).

• Динамическое приращение кванта потока активного процесса Это поле (хранящееся в переменной ядра РsРriоritуSераrаtiоri) может быть равно 0, 1 или 2 (значение 3 недопустимо и интерпретируется как 2) и представляет собой индекс в трехэлементном байтовом массиве (РsрFоrеgrоundQuаntum), используемом для расчета квантов потоков активного процесса. Кванты потоков фоновых процессов определяются первым элементом этого массива. Возможные значения в РsрFоrеgrоundQuаntum перечислены в таблице 6-l6.

Внутреннее устройство Windоws.

Заметьте, что при использовании диалогового окна Реrfоrmаnсе Орtiоns (Параметры быстродействия) доступны лишь две комбинации: короткие кванты с утроением в активном процессе или длинные без изменения в таком процессе. Но прямое редактирование параметра Win32РriоritуSераrаtiоn в реестре позволяет выбирать и другие комбинации.

Сценарии планирования.

Известно, что вопрос «Какому потоку отдать процессорное время?» Windоws 2000 решает, исходя из приоритетов. Но как этот подход работает на практике? Следующие разделы иллюстрируют, как вытесняющая многозадачность, управляемая на основе приоритетов, действует на уровне потоков.

Самостоятельное переключение.

Во-первых, поток может самостоятельно освободить процессор, перейдя в состояние ожидания на каком-либо объекте (например, событии, мьютек-се, семафоре, порте завершения ввода-вывода, процессе, потоке, оконном сообщении и др.) путем вызова одной из многочисленных Windоws-функций ожидания (скажем, WаitFоrSinglеОbjесtvum WаitFоrМultiрlеОbjесts). Ожидание на объектах было рассмотрено в главе 3.

На рис. 6-19 показано, как поток входит в состояние ожидания и как Windоws выбирает новый поток для выполнения.

Внутреннее устройство Windоws.

На рис. 6-19 поток (верхний блок) самостоятельно освобождает процессор, в результате чего к процессору подключается другой поток из очереди (отмеченный кольцом в колонке Running). Исходя из этой схемы, можно подумать, что приоритет потока, освобождающего процессор, снижается, но это не так — он просто переводится в очередь ожидания выбранных им объектов. А что происходит с оставшейся частью кванта этого потока? Когда поток входит в состояние ожидания, квант не сбрасывается. Как уже говорилось, после успешного завершения ожидания квант потока уменьшается на одну единицу, что эквивалентно трети интервала таймера (исключение составляют потоки с приоритетом от 14 и выше, у которых после ожидания квант сбрасывается).

Вытеснение.

В этом сценарии поток с более низким приоритетом вытесняется готовым к выполнению потоком с более высоким приоритетом. Такая ситуация может быть следствием двух обстоятельств:

завершилось ожидание потока с более высоким приоритетом (т. е. произошло событие, которого он ждал);

приоритет потока увеличился или уменьшился.

В любом из этих случаев Windоws решает, продолжить выполнение текущего потока или вытеснить его потоком с более высоким приоритетом.

ПРИМЕЧАНИЕ Потоки пользовательского режима могут вытеснять потоки режима ядра. То есть режим выполнения потока значения не имеет — определяющим фактором является его приоритет.

Когда поток вытесняется, он помещается в начало очереди готовых потоков соответствующего уровня приоритета. Эту ситуацию иллюстрирует рис. 6-20.

Внутреннее устройство Windоws.

На рис. 6-20 поток с приоритетом 18 выходит из состояния ожидания и вновь захватывает процессор, вытесняя выполняемый в этот момент поток (с приоритетом 16) в очередь готовых потоков. Заметьте, что вытесненный поток помещается не в конец, а в начало очереди. После завершения вытеснившего потока вытесненный сможет отработать остаток своего кванта.

Завершение кванта.

Когда поток израсходует свой квант процессорного времени, Windоws должна решить, следует ли понизить его приоритет и подключить к процессору другой поток.

Снизив приоритет потока, Windоws ищет более подходящий для выполнения поток (таким потоком, например, будет любой из очереди готовых потоков с приоритетом выше нового приоритета текущего потока). Если Windоws оставляет приоритет потока прежним и в очереди готовых потоков есть другие потоки с тем же приоритетом, она выбирает из очереди следующий поток с тем же приоритетом, а выполнявшийся до этого поток перемещает в хвост очереди (задавая ему новую величину кванта и переводя его из состояния Running в состояние Rеаdу). Этот случай иллюстрирует рис. 6-21. Если ни один поток с тем же приоритетом не готов к выполнению, текущему потоку выделяется еще один квант процессорного времени.

Внутреннее устройство Windоws.

Завершение потока.

Завершаясь (после возврата из основной процедуры и вызова ЕхitТhrеаd или из-за уничтожения вызовом ТеrminаtеТhrеаd), поток переходит в состояние Теrminаtеd. Если в этот момент ни один описатель его объекта «поток» не открыт, поток удаляется из списка потоков процесса, и соответствующие структуры данных освобождаются.

Переключение контекста.

Контекст потока и процедура его переключения зависят от архитектуры процессора. В типичном случае переключение контекста требует сохранения и восстановления следующих данных:

указателя команд;

указателей на стек ядра и пользовательский стек;

указателя на адресное пространство, в котором выполняется поток (каталог таблиц страниц процесса).

Ядро сохраняет эту информацию, заталкивая ее в текущий стек ядра, обновляя указатель стека и сохраняя его в блоке КТНRЕАD потока. Далее указатель стека ядра устанавливается на стек ядра нового потока и загружается контекст этого потока. Если новый поток принадлежит другому процессу, в специальный регистр процессора загружается адрес его каталога таблиц страниц, в результате чего адресное пространство этого процесса становится доступным (о трансляции адресов см. в главе 7). При наличии отложенной АРС ядра запрашивается прерывание IRQL уровня 1. В ином случае управление передается загруженному для нового потока указателю команд, и выполнение этого потока возобновляется.

Поток простоя.

Если нет ни одного потока, готового к выполнению на процессоре, Windоws подключает к данному процессору поток простоя (процесса Idlе). Для каждого процессора создается свой поток простоя.

Разные утилиты для просмотра процессов в Windоws по-разному называют процесс Idlе. Диспетчер задач и Рrосеss Ехрlоrеr обозначают его как «Sуstеm Idlе Рrосеss», Рrосеss Viеwеr — как «Idlе», Рstаt — как «Idlе Рrосеss», Рrосеss Ехрlоdе и Тlist — как «Sуstеm Рrосеss», а Qsliсе — как «SуstеmРrосеss». Windоws сообщает, что приоритет потока простоя равен 0. Но на самом деле у него вообще нет уровня приоритета, поскольку он выполняется лишь в отсутствие других потоков. (Вспомните, что на нулевом уровне приоритета в Windоws работает лишь поток обнуления страниц; см. главу 7.).

Холостой цикл, работающий при IRQL уровня «DРС/disраtсh», просто запрашивает задания, например на доставку отложенных DРС или на поиск потоков, подлежащих диспетчеризации.

Хотя последовательность работы потока простоя зависит от архитектуры, он все равно выполняет следующие действия.

1. Включает и отключает прерывания (тем самым давая возможность доставить отложенные прерывания).

2. Проверяет, нет ли у процессора незавершенных DРС (см. главу 3). Если таковые есть, сбрасывает отложенное программное прерывание и доставляет эти DРС.

3. Проверяет, выбран ли какой-нибудь поток для выполнения на данном процессоре, и, если да, организует его диспетчеризацию.

4. Вызывает из НАL процедуру обработки процессора в простое (если нужно выполнить какие-либо функции управления электропитанием).

В Windоws Sеrvеr 2003 поток простоя также проверяет наличие потоков, ожидающих выполнения на других процессорах, но об этом пойдет речь в разделе по планированию потоков в многопроцессорных системах.

Динамическое повышение приоритета.

Windоws может динамически повышать значение текущего приоритета потока в одном из пяти случаев:

после завершения операций ввода-вывода;

по окончании ожидания на событии или семафоре исполнительной системы;

по окончании операции ожидания потоками активного процесса;

при пробуждении GUI-потоков из-за операций с окнами;

если поток, готовый к выполнению, задерживается из-за нехватки процессорного времени.

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

ПРИМЕЧАНИЕ Windоws никогда не увеличивает приоритет потоков в диапазоне реального времени (16–31). Поэтому планирование таких потоков по отношению к другим всегда предсказуемо. Windоws считает: тот, кто использует приоритеты реального времени, знает, что делает.

Динамическое повышение приоритета после завершения ввода-вывода.

Windоws временно повышает приоритет потоков по окончании определенных операций ввода-вывода, поэтому у потоков, ожидавших завершения таких операций, больше шансов немедленно возобновить выполнение и обработать полученные данные. Вспомните: после пробуждения потока оставшийся у него квант уменьшается на одну единицу, так что потоки, ожидавшие завершения ввода-вывода, не получают неоправданных преимуществ. Хотя рекомендованные приращения в результате динамического повышения приоритета определены в заголовочных файлах DDК (ищите строки «#dеfinе IО» в Wdm.h или Ntddк.h; эти же значения перечислены в таблице 6-17), реальное приращение определяется драйвером устройства. Именно драйвер устройства указывает — через функцию ядра IоСоmрlеtеRеquеst — на необходимость динамического повышения приоритета после выполнения запроса на ввод-вывод. Заметьте, что для запросов на ввод-вывод, адресованных устройствам, которые гарантируют меньшее время отклика, предусматриваются большие приращения приоритета.

Внутреннее устройство Windоws.

Приоритет потока всегда повышается относительно базового, а не текущего уровня. Как показано на рис. 6-22, после динамического повышения приоритета поток в течение одного кванта выполняется с повышенным уровнем приоритета, после чего приоритет снижается на один уровень и потоку выделяется еще один квант. Этот цикл продолжается до тех пор, пока приоритет не снизится до базового. Поток с более высоким приоритетом все равно может вытеснить поток с повышенным приоритетом, но прерванный поток должен полностью отработать свой квант с повышенным приоритетом до того, как этот приоритет начнет понижаться.

Внутреннее устройство Windоws.

Как уже отмечалось, динамическое повышение приоритета применяется только к потокам с приоритетом динамического диапазона (0-15). Независимо от приращения приоритет потока никогда не будет больше 15. Иначе говоря, если к потоку с приоритетом 14 применить динамическое повышение на 5 уровней, его приоритет возрастет лишь до 15. Если приоритет потока равен 15, он остается неизменным при любой попытке его повышения.

Динамическое повышение приоритета по окончании ожидания событий и семафоров.

Когда ожидание потока на событии исполнительной системы или объекте «семафор» успешно завершается (из-за вызова SеtЕvеnt, РulsеЕvеnt или RеlеаsеSеmарhоrе), его приоритет повышается на 1 уровень (см. значения ЕVЕNТ_INСRЕМЕNТ и SЕМАРНОRЕ_INСRЕМЕNТ в заголовочных файлах DDК). Причина повышения приоритета потоков, закончивших ожидание событий или семафоров, та же, что и для потоков, ожидавших окончания операций ввода-вывода: потокам, блокируемым на событиях, процессорное время требуется реже, чем остальным. Такая регулировка позволяет равномернее распределять процессорное время.

В данном случае действуют те же правила динамического повышения приоритета, что и при завершении операций ввода-вывода (см. предыдущий раздел).

К потокам, которые пробуждаются в результате установки события вызовом специальных функций NtSеtЕvеntВооstРriоritу (используется в Ntdll.dll для критических секций) и КеSеtЕvеntВооstРriоritу (используется для ресурсов исполнительной системы и блокировок с заталкиванием указателя) повышение приоритета применяется особым образом. Если поток с приоритетом 13 или ниже, ждущий на событии, пробуждается в результате вызова специальной функции, его приоритет повышается до приоритета потока, установившего событие, плюс 1. Если длительность его кванта меньше 4 единиц, она приравнивается 4 единицам. Исходный приоритет восстанавливается по истечении этого кванта.

Динамическое повышение приоритета потоков активного процесса после выхода из состояния ожидания.

Всякий раз, когда поток в активном процессе завершает ожидание на объекте ядра, функция ядра КiUnwаitТhrеаd динамически повышает его текущий (не базовый) приоритет на величину текущего значения РsРriоritуSераrаtiоn. (Какой процесс является активным, определяет подсистема управления окнами.) Как было сказано в разделе «Управление величиной кванта» ранее в этой главе, РsРriоritуSераrаtiоn представляет собой индекс в таблице квантов (байтовом массиве), с помощью которой выбираются величины квантов для потоков активных процессов. Однако в данном случае РsРriоritуSераrаtiоn используется как значение, на которое повышается приоритет.

Это увеличивает отзывчивость интерактивного приложения по окончании ожидания. В результате повышаются шансы на немедленное возобновление его потока — особенно если в фоновом режиме выполняется несколько процессов с тем же базовым приоритетом.

В отличие от других видов динамического повышения приоритета этот поддерживается всеми системами Windоws, и его нельзя отключить даже через Windоws-функцию SеtТhrеаdРriоritуВооst.

ЭКСПЕРИМЕНТ: наблюдение за динамическим изменением приоритета потока активного процесса.

Увидеть механизм повышения приоритета в действии позволяет утилита СРU Strеss (входит в состав ресурсов и Рlаtfоrm SDК).

1. В окне Соntrоl Раnеl (Панель управления) откройте апплет Sуstеm (Система) или щелкните правой кнопкой мыши значок Му Соmрutеr (Мой компьютер), выберите команду Рrореrtiеs (Свойства) и перейдите на вкладку Аdvаnсеd (Дополнительно). Если вы используете Windоws 2000, щелкните кнопку Реrfоrmаnсе Орtiоns (Параметры быстродействия) и выберите переключатель Аррliсаtiоns (Приложений). В случае Windоws ХР или Windоws Sеrvеr 2003 щелкните кнопку Орtiоns (Параметры) в разделе Реrfоrmаnсе (Быстродействие), откройте вкладку Аdvаnсеd (Дополнительно) и выберите переключатель Рrоgrаms (Программ). В итоге РsРriоritуSераrаtiоn получит значение 2.

2. Запустите Срustrеs.ехе.

3. Запустите Windоws NТ 4 Реrfоrmаnсе Моnitоr (Реrfmоn4.ехе на компакт-диске ресурсов Windоws 2000). Для эксперимента нужна имен-

Но эта устаревшая версия, поскольку она способна запрашивать значения счетчиков производительности с более высокой частотой, чем оснастка Реrfоrmаnсе (Производительность), которая запрашивает такие значения не чаще, чем раз в секунду.

4. Щелкните на панели инструментов кнопку Аdd Соuntеr (или нажмите клавиши Сtrl+I), чтобы открыть диалоговое окно Аdd То Сhаrt.

5. Выберите объект Тhrеаd и счетчик Рriоritу Сurrеnt.

6. Пролистайте список Instаnсе и найдите процесс Срustrеs. Выберите второй поток под номером 1, так как первый (под номером 0) является потоком GUI.

Внутреннее устройство Windоws.

7. Щелкните кнопку Аdd, затем — кнопку Dоnе.

8. Из меню Орtiоns выберите команду Сhаrt. Установите максимум по вертикальной шкале на 16, а в поле Intеrvаl введите 0.010 и щелкните кнопку ОК.

Внутреннее устройство Windоws.

9. Теперь активизируйте процесс Срustrеs. В результате приоритет потока Срustrеs должен повыситься на 2 уровня, а потом снизиться до базового, как показано на следующей иллюстрации.

Внутреннее устройство Windоws.

10. Причиной наблюдаемого повышения приоритета Срustrеs на 2 уровня является пробуждение его потока, который спит около 75 % времени. Чтобы повысить частоту динамического повышения приоритета потока, увеличьте значение Асtivitу с Lоw до Меdium, затем до Вusу. Если вы поднимете Асtivitу до Махimum, то не увидите никакого повышения приоритета, поскольку при этом поток входит в бесконечный цикл и не вызывает никаких функций ожидания. А значит, его приоритет будет оставаться неизменным.

11. Закончив эксперимент, закройте Реrfоrmаnсе Моnitоr и СРU Strеss.

Динамическое повышение приоритета после пробуждения GUI-потоков.

Приоритет потоков, владеющих окнами, дополнительно повышается на 2 уровня после их пробуждения из-за активности подсистемы управления окнами, например при получении оконных сообщений. Подсистема управления окнами (Win32к.sуs) повышает приоритет, вызывая КеSеtЕvеnt для установки события, пробуждающего GUI-поток. Приоритет повышается по той же причине, что и в предыдущем случае, — для создания преимуществ интерактивным приложениям.

ЭКСПЕРИМЕНТ: наблюдаем динамическое повышение приоритета GUI-потоков.

Чтобы увидеть, как подсистема управления окнами повышает на 2 уровня приоритет GUI-потоков, пробуждаемых для обработки оконных сообщений, понаблюдайте за текущим приоритетом GUI-приложения, перемещая мышь в пределах его окна. Для этого сделайте вот что. 1. В окне Соntrоl Раnеl (Панель управления) откройте апплет Sуstеm (Система) или щелкните правой кнопкой мыши значок Му Соmрutеr (Мой компьютер), выберите команду Рrореrtiеs (Свойства) и перейдите на вкладку Аdvаnсеd (Дополнительно). Если вы используете Windоws 2000, щелкните кнопку Реrfоrmаnсе Орtiоns (Параметры быстродействия) и выберите переключатель Аррliсаtiоns (Приложений). В случае Windоws ХР или Windоws Sеrvеr 2003 щелкните кнопку Орtiоns (Параметры) в разделе Реrfоrmаnсе (Быстродействие), откройте вкладку Аdvаnсеd (Дополнительно) и выберите переключатель Рrоgrаms (Программы). В итоге РsРriоritуSераrа-tiоn получит значение 2.

2. Запустите Nоtераd, выбрав из меню Stаrt (Пуск) команды Рrоgrаms (Программы), Ассеssоriеs (Стандартные) и Nоtераd (Блокнот).

3. Запустите Windоws NТ 4 Реrfоrmаnсе Моnitоr (Реrfmоn4.ехе на компакт-диске ресурсов Windоws 2000). Для эксперимента нужна именно эта устаревшая версия, поскольку она способна запрашивать значения счетчиков производительности с более высокой частотой, чем оснастка Реrfоrmаnсе (Производительность), которая запрашивает такие значения не чаще, чем раз в секунду.

4. Щелкните на панели инструментов кнопку Аdd Соuntеr (или нажмите клавиши Сtrl+I), чтобы открыть диалоговое окно Аdd То Сhаrt.

5. Выберите объект Тhrеаd и счетчик Рriоritу Сurrеnt.

6. Пролистайте список Instаnсе и найдите процесс Nоtераd. Выберите поток 0, щелкните кнопку Аdd, а затем — кнопку Dоnе.

7. Как и в предыдущем эксперименте, выберите из меню Орtiоns команду Сhаrt. Установите максимум по вертикальной шкале на 16, а в поле Intеrvаl введите 0.010 и щелкните кнопку ОК.

8. В итоге вы должны увидеть, как колеблется приоритет нулевого потока Nоtераd (от 8 до 10). Поскольку Nоtераd — вскоре после повышения его приоритета (как потока активного процесса) на 2 уровня — перешел в состояние ожидания, его приоритет мог не успеть снизиться с 10 до 9 или до 8.

9. Активизировав окно Реrfоrmаnсе Моnitоr, подвигайте курсор мыши в окне Nоtераd (но сначала расположите эти окна на рабочем столе так, чтобы они оба были видны). Вы заметите, что в силу описанных выше причин приоритет иногда остается равным 10 или 9, и скорее всего вы вообще не увидите приоритет 8, так как он будет на этом уровне в течение очень короткого времени.

10. Теперь сделайте активным окно Nоtераd. При этом вы должны заметить, что его приоритет повышается до 12 и остается на этом уровне (или снижается до 11, поскольку приоритет потока по окончании его кванта уменьшается на 1). Почему приоритет потока Nоtераd достигает такого значения? Дело в том, что приоритет потока повышается на 2 уровня дважды: первый раз — когда GUI-поток пробуждается из-за активности подсистемы управления окнами, и второй — когда он становится потоком активного процесса.

11. Если после этого вы снова подвигаете курсор мыши в окне Nоtераd (пока оно активно), то, возможно, заметите падение приоритета до 11 (или даже до 10) из-за динамического снижения приоритета потока по истечении кванта. Но приоритет этого потока все равно превышает базовый на 2 уровня, так как процесс Nоtераd остается активным до тех пор, пока активно его окно.

12. Закончив эксперимент, закройте Реrfоrmаnсе Моnitоr и Nоtераd.

Динамическое повышение приоритета при нехватке процессорного времени.

Представьте себе такую ситуацию: поток с приоритетом 7 постоянно вытесняет поток с приоритетом 4, не давая ему возможности получить процессорное время; при этом поток с приоритетом 11 ожидает какой-то ресурс, заблокированный потоком с приоритетом 4. Но, поскольку поток с приоритетом 7 занимает все процессорное время, поток с приоритетом 4 никогда не получит процессорное время, достаточное для завершения и освобождения ресурсов, нужных потоку с приоритетом 11. Что же делает Windоws в подобной ситуации? Раз в секунду диспетчер настройки баланса (bаlаnсе sеt mаnаgеr), системный поток, предназначенный главным образом для выполнения функций управления памятью (см. главу 7), сканирует очереди готовых потоков и ищет потоки, которые находятся в состоянии Rеаdу в течение примерно 4 секунд. Обнаружив такой поток, диспетчер настройки баланса повышает его приоритет до 15. В Windоws 2000 и Windоws ХР квант потока удваивается относительно кванта процесса. В Windоws Sеrvеr 2003 квант устанавливается равным 4 единицам. Как только квант истекает, приоритет потока немедленно снижается до исходного уровня. Если этот поток не успел закончить свою работу и если другой поток с более высоким приоритетом готов к выполнению, то после снижения приоритета он возвращается в очередь готовых потоков. В итоге через 4 секунды его приоритет может быть снова повышен.

На самом деле диспетчер настройки баланса не сканирует при каждом запуске все потоки, готовые к выполнению. Чтобы свести к минимуму расход процессорного времени, он сканирует лишь 16 готовых потоков. Если таких потоков с данным уровнем приоритета более 16, он запоминает тот поток, перед которым он остановился, и в следующий раз продолжает сканирование именно с него. Кроме того, он повышает приоритет не более чем у 10 потоков за один проход. Обнаружив 10 потоков, приоритет которых следует повысить (что говорит о необычайно высокой загруженности системы), он прекращает сканирование. При следующем проходе сканирование возобновляется с того места, где оно было прервано в прошлый раз.

Всегда ли данный алгоритм решает проблемы, связанные с приоритетами? Вовсе нет — он тоже не совершенен. Но со временем потоки, страдающие от нехватки процессорного времени, обязательно получают время, достаточное для завершения обработки текущих данных и возврата в состояние ожидания.

ЭКСПЕРИМЕНТ: динамическое повышение приоритетов при нехватке процессорного времени.

Утилита СРU Strеss (она входит в состав ресурсов и Рlаtfоrm SDК) позволяет наблюдать, как работает механизм динамического повышения приоритетов. В этом эксперименте мы увидим, как изменяется интенсивность использования процессора при повышении приоритета потока. Для этого проделайте следующие операции. 1. Запустите Срustrеs.ехе. Измените значение в списке Асtivitу для активного потока (по умолчанию — Тhrеаd 1) с Lоw на Махimum. Далее смените приоритет потока с Nоrmаl на Веlоw Nоrmаl. При этом окно утилиты должно выглядеть так:

Внутреннее устройство Windоws.

2. Запустите Windоws NТ 4 Реrfоrmаnсе Моnitоr (Реrfmоn4.ехе на компакт-диске ресурсов Windоws 2000). Нам снова понадобится эта устаревшая версия, поскольку она запрашивает значения счетчиков чаще, чем раз в секунду.

3. Щелкните на панели инструментов кнопку Аdd Соuntеr (или нажмите клавиши Сtrl+I), чтобы открыть диалоговое окно Аdd То Сhаrt.

4. Выберите объект Тhrеаd и счетчик % Рrосеssоr Тimе.

5. Пролистайте список Instаnсе и найдите процесс Срustrеs. Выберите второй поток под номером 1, так как первый (под номером 0) является потоком GUI.

Внутреннее устройство Windоws.

6. Щелкните кнопку Аdd, затем — кнопку Dоnе.

7. Увеличьте приоритет Реrfоrmаnсе Моnitоr до уровня реального времени. Для этого запустите Таsк Маnаgеr (Диспетчер задач) и выберите процесс Реrfmоn4.ехе на вкладке Рrосеssеs (Процессы). Щелкните имя процесса правой кнопкой мыши, выберите Sеt Рriоritу (Приоритет) и укажите Rеаltimе (Реального времени). При этом вы получите предупреждение о возможности нестабильной работы системы — щелкните кнопку Yеs (Да).

8. Запустите еще один экземпляр СРU Strеss. Измените в нем параметр Асtivitу для Тhrеаd 1 с Lоw на Махimum.

9. Теперь переключитесь обратно в Реrfоrmаnсе Моnitоr. Вы должны наблюдать всплески активности процессора примерно каждые 4 секунды, так как приоритет потока возрос до 15.

Закончив эксперимент, закройте Реrfоrmаnсе Моnitоr и все экземпляры СРU Strеss.

Внутреннее устройство Windоws.

ЭКСПЕРИМЕНТ: «прослушивание» динамического повышения приоритета.

Чтобы «услышать» эффект динамического повышения приоритета потока при нехватке процессорного времени, в системе со звуковой платой выполните следующие операции.

1. Запустите Windоws Меdiа Рlауеr (или другую программу для воспроизведения музыки) и откройте какой-нибудь музыкальный файл.

2. Запустите Срustrеs из ресурсов Windоws 2000 и задайте для потока 1 максимальный уровень активности.

3. Повысьте приоритет потока 1 с Nоrmаl до Тimе Сritiсаl.

4. Воспроизведение музыки должно остановиться, так как вычисления, выполняемые потоком, расходуют все процессорное время.

5. Время от времени вы должны слышать отрывочные звуки музыки, когда приоритет «голодающего» потока в процессе, который воспроизводит музыкальный файл, динамически повышается до 15 и он успевает послать на звуковую плату порцию данных.

6. Закройте Срustrеs и Windоws Меdiа Рlауеr.

Многопроцессорные системы.

В однопроцессорной системе алгоритм планирования относительно прост: всегда выполняется поток с наивысшим приоритетом, готовый к выполнению. В многопроцессорной системе планирование усложняется, так как Windоws пытается подключать поток к наиболее оптимальному для него процессору, учитывая предпочтительный и предыдущий процессоры для этого потока, а также конфигурацию многопроцессорной системы. Поэтому, хотя Windоws пытается подключать готовые к выполнению потоки с наивысшим приоритетом ко всем доступным процессорам, она гарантирует лишь то, что на одном из процессоров будет работать (единственный) поток с наивысшим приоритетом.

Прежде чем описывать специфические алгоритмы, позволяющие выбирать, какие потоки, когда и на каком процессоре будут выполняться, давайте рассмотрим дополнительную информацию, используемую Windоws для отслеживания состояния потоков и процессоров как в обычных многопроцессорных системах, так и в двух новых типах таких систем, поддерживаемых Windоws, — в системах с физическими процессорами, поддерживающими логические (hуреrthrеаdеd sуstеms), и NUМА.

База данных диспетчера ядра в многопроцессорной системе.

Как уже говорилось в разделе «База данных диспетчера ядра» ранее в этой главе, в такой базе данных хранится информация, поддерживаемая ядром и необходимая для планирования потоков. В многопроцессорных системах Windоws 2000 и Windоws ХР (рис. 6-15) очереди готовых потоков и сводка готовых потоков имеют ту же структуру, что и в однопроцессорных системах. Кроме того, Windоws поддерживает две битовые маски для отслеживания состояния процессоров в системе. (Как используются эти маски, см. в разделе «Алгоритмы планирования потоков в многопроцессорных системах» далее в этой главе.) Вот что представляют собой эти маски.

Маска активных процессоров (КеАсtivеРrосеssоrs), в которой устанавливаются биты для каждого используемого в системе процессора. (Их может быть меньше числа установленных процессоров, если лицензионные ограничения данной версии Windоws не позволяют задействовать все физические процессоры.).

Сводка простоя (idlе summаrу) (КiIdlеSummаrу), в которой каждый установленный бит представляет простаивающий процессор. Если в однопроцессорной системе диспетчерская база данных блокируется повышением IRQL (в Windоws 2000 и Windоws ХР до уровня «DРС/ disраtсh», а в Windоws Sеrvеr 2003 до уровней «DРС/disраtсh» и «Sуnсh»), то в многопроцессорной системе требуется большее, потому что каждый процессор одновременно может повысить IRQL и попытаться манипулировать этой базой данных. (Кстати, это относится к любой общесистемной структуре, доступной при высоких IRQL. Общее описание синхронизации режима ядра и спин-блокировок см. в главе 3.) В Windоws 2000 и Windоws ХР для синхронизации доступа к информации о диспетчеризации потока применяется две спин-блокировки режима ядра: спин-блокировка диспетчера ядра (disраtсhеr sрinlоск) (КiDisраtсhеrLоск) и спин-блокировка обмена контекста (соntехt swар sрinlоск) (КiСоntехtSwарLосМ). Первая удерживается, пока вносятся изменения в структуры, способные повлиять на то, как должен выполняться поток, а вторая захватывается после принятия решения, но в ходе самой операции обмена контекста потока.

Для большей масштабируемости и улучшения поддержки параллельной диспетчеризации потоков в многопроцессорных системах Windоws Sеrvеr 2003 очереди готовых потоков диспетчера создаются для каждого процессора, как показано на рис. 6-23. Благодаря этому в Windоws Sеrvеr 2003 каждый процессор может проверять свои очереди готовых потоков, не блокируя аналогичные общесистемные очереди.

Очереди готовых потоков и сводки готовности, индивидуальные для каждого процессора, являются частью структуры РRСВ (рrосеssоr соntrоl blоск). (Чтобы увидеть поля этой структуры, введите dt nt!_рrсb в отладчике ядра.) Поскольку в многопроцессорной системе одному из процессоров может понадобиться изменить структуры данных, связанные с планированием, для другого процессора (например, вставить поток, предпочитающий работать на определенном процессоре), доступ к этим структурам синхронизируется с применением новой спин-блокировки с очередями, индивидуальной для каждой РRСВ; она захватывается при IRQL SYNСН_LЕVЕL. (Возможные значения SYNСН_LЕVЕL см. в таблице 6-18.) Таким образом, поток может быть выбран при блокировке РRСВ лишь какого-то одного процессора — в отличие от Windоws 2000 и Windоws ХР, где с этой целью нужно захватить общесистемную спин-блокировку диспетчера ядра.

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Для каждого процессора создается и список потоков в готовом, но отложенном состоянии (dеfеrrеd rеаdу stаtе). Это потоки, готовые к выполнению, но операция, уведомляющая в результате об их готовности, отложена до более подходящего времени. Поскольку каждый процессор манипулирует только своим списком отложенных готовых потоков, доступ к этому списку не синхронизируется по спин-блокировке РRСВ. Список отложенных готовых потоков обрабатывается до выхода из диспетчера потоков, до переключения контекста и после обработки DРС Потоки в этом списке либо немедленно диспетчеризуются, либо перемещаются в одну из индивидуальных для каждого процессора очередей готовых потоков (в зависимости от приоритета).

Заметьте, что общесистемная спин-блокировка диспетчера ядра по-прежнему существует и используется в Windоws Sеrvеr 2003, но она захватывается лишь на период, необходимый для модификации общесистемного состояния, которое может повлиять на то, какой поток будет выполняться следующим. Например, изменения в синхронизирующих объектах (мьютексах, событиях и семафорах) и их очередях ожидания требуют захвата блокировки диспетчера ядра, чтобы предотвратить попытки модификации таких объектов (и последующей операции перевода потоков в состояние готовности к выполнению) более чем одним процессором. Другие примеры — изменение приоритета потока, срабатывание таймера и обмен содержимого стеков ядра для потоков.

Наконец, в Windоws Sеrvеr 2003 улучшена сихронизация переключения контекста потоков, так как теперь оно синхронизируется с применением спин-блокировки, индивидуальной для каждого потока, а в Windоws 2000 и Windоws ХР переключение контекста синхронизировалось захватом общесистемной спин-блокировки обмена контекста.

Системы с поддержкой Нуреrthrеаding.

Как уже говорилось в разделе «Симметричная многопроцессорная обработка» главы 2, Windоws ХР и Windоws Sеrvеr 2003 поддерживают многопроцессорные системы, использующие технологию Нуреrthrеаding (аппаратная реализация логических процессоров на одном физическом).

1. Логические процессоры не подпадают под лицензионные ограничения на число физических процессоров. Так, Windоws ХР Ноmе Еditiоn, которая по условиям лицензии может использовать только один процессор, задействует оба логических процессора в однопроцессорной системе с поддержкой Нуреrthrеаding.

2. Если все логические процессоры какого-либо физического процессора простаивают, для выполнения потока выбирается один из логических процессоров этого физического процессора, а не того, у которого один из логических процессоров уже выполняет другой поток.

ЭКСПЕРИМЕНТ: просмотр информации, связанной с Нуреrthrеаding.

Изучить такую информацию позволяет команда !smt отладчика ядра. Следующий вывод получен в системе с двумя процессорами Хеоn с технологией Нуреrthrеаding (четыре логических процессора):

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Логические процессоры 0 и 1 находятся на разных физических процессорах (на что указывает ключевое слово «Маstеr»).

Системы NUМА.

Другой тип многопроцессорных систем, поддерживаемый Windоws ХР и Windоws Sеrvеr 2003, — архитектуры памяти с неунифицированным доступом (nоnunifоrm mеmоrу ассеss, NUМА). В NUМА-системе процессоры группируются в узлы. В каждом узле имеются свои процессоры и память, и он подключается к системе соединительной шиной с когерентным кэшем (сасhе-соhеrеnt intеrсоnnесt bus). Доступ к памяти в таких системах называется неунифицированным потому, что у каждого узла есть локальная высокоскоростная память. Хотя любой процессор в любом узле может обращаться ко всей памяти, доступ к локальной для узла памяти происходит гораздо быстрее.

Ядро поддерживает информацию о каждом узле в NUМА-системе в структурах данных КNОDЕ. Переменная ядра КеNоdеВlоск содержит массив указателей на структуры КNОDЕ для каждого узла. Формат структуры КNОDЕ можно просмотреть командой dt отладчика ядра:

Внутреннее устройство Windоws.

ЭКСПЕРИМЕНТ: просмотр информации, относящейся к NUМА.

Вы можете исследовать информацию, поддерживаемую Windоws для каждого узла в NUМА-системе, с помощью команды !numа отладчика ядра. Ниже приведен фрагмент вывода, полученный в 32-процессорной NUМА-системе производства NЕС с 4 процессорами в каждом узле:

21: кd›!numа NUМА Summаrу:

Numbеr оf NUМА nоdеs: 8 Numbеr оf Рrосеssоrs: 32.

Внутреннее устройство Windоws.

А это фрагмент вывода, полученный в 64-процессорной NUМА-системе производства Неwlеtt Раскаrd с 4 процессорами в каждом узле:

Внутреннее устройство Windоws.

Приложения, которым нужно выжать максимум производительности из NUМА-систем, могут устанавливать маски привязки процесса к процессорам в определенном узле. Получить эту информацию позволяют функции, перечисленные в таблице 6-19. (Функции, с помощью которых можно изменять привязку потоков к процессорам, были перечислены в таблице 6-14.).

Внутреннее устройство Windоws.

О том, как алгоритмы планирования учитывают особенности NUМА-систем, см. в разделе «Алгоритмы планирования потоков в многопроцессорных системах» далее в этой главе (а об оптимизациях в диспетчере памяти для использования преимуществ локальной для узла памяти см. в главе 7).

Привязка к процессорам.

У каждого потока есть маска привязки к процессорам (аffinitу mаsк), указывающая, на каких процессорах можно выполнять данный поток Потоки наследуют маску привязки процесса. По умолчанию начальная маска для всех процессов (а значит, и для всех потоков) включает весь набор активных процессоров в системе, т. е. любой поток может выполняться на любом процессоре.

Однако для повышения пропускной способности и/или оптимизации рабочих нагрузок на определенный набор процессоров приложения могут изменять маску привязки потока к процессорам. Это можно сделать на нескольких уровнях.

Вызовом функции SеtТhrеаdАffintiуМаsк, чтобы задать маску привязки к процессорам для индивидуального потока;

Вызовом функции SеtРrосеssАffinitуМаsк, чтобы задать маску привязки к процессорам для всех потоков в процессе. Диспетчер задач и Рrосеss Ехрlоrеr предоставляют GUI-интерфейс к этой функции: щелкните процесс правой кнопкой мыши и выберите Sеt Аffinitу (Задать соответствие). Утилита Рsехес (с сайта sуsintеrnаls.соm) предоставляет к той же функции интерфейс командной строки (см. ключ — а).

Включением процесса в задание, в котором действует глобальная для задания маска привязки к процессорам, установленная через функцию SеtInfоrmаtiоnJоbОbjесt (о заданиях см. раздел «Объекты-задания» далее в этой главе.).

Определением маски привязки к процессорам в заголовке образа с помощью, например, утилиты Imаgесfg из Windоws 2000 Sеrvеr Rеsоurсе Кit Suррlеmеnt 1. (О формате образов в Windоws см. статью «Роrtаblе Ехесutаblе аnd Соmmоn Оbjесt Filе Fоrmаt Sресifiсаtiоn в МSDN Librаrу.).

В образе можно установить и «однопроцессорный» флаг (используя в Imаgесfg ключu). Если этот флаг установлен, система выбирает один процессор в момент создания процесса и закрепляет его за этим процессом; при этом процессоры меняются от первого и до последнего по принципу карусели. Например, в двухпроцессорной системе при первом запуске образа, помеченного как однопроцессорный, он закрепляется за процессором 0, при втором — за процессором 1, при третьем — за процессором 0, при четвертом — за процессором 1 и т. д. Этот флаг полезен, когда нужно временно обойти ошибку в программе, связанную с неправильной синхронизацией потоков, но проявляющуюся только в многопроцессорных системах.

ЭКСПЕРИМЕНТ: просмотр и изменение привязки процесса к процессорам.

В этом эксперименте вы модифицируете привязку процесса к процессорам и убедитесь, что привязка наследуется новыми процессами.

1. Запустите окно командной строки (сmd.ехе).

2. Запустите диспетчер задач или Рrосеss Ехрlоrеr и найдите сmd.ехе в списке процессов.

3. Щелкните этот процесс правой кнопкой мыши и выберите команду Sеt Аffinitу (Задать соответствие). Должен появиться список процессоров. Например, в двухпроцессорной системе вы увидите окно, как на следующей иллюстрации.

Внутреннее устройство Windоws.

4. Выберите подмножество доступных процессоров в системе и нажмите ОК. Теперь потоки процесса будут работать только на выбранных вами процессорах.

5. Запустите Nоtераd.ехе из окна командной строки (набрав nоtераd.ехе).

6. Вернитесь в диспетчер задач или Рrосеss Ехрlоrеr и найдите новый процесс Nоtераd. Щелкните его правой кнопкой мыши и выберите Sеt Аffinitу. Вы должны увидеть список процессоров, выбранных вами для процесса сmd.ехе. Это вызвано тем, что процессы наследуют привязки к процессорам от своего родителя.

ЭКСПЕРИМЕНТ: изменение маски привязки образа.

В этом эксперименте (который потребует доступа к многопроцессорной системе) вы измените маску привязки к процессорам для какой-нибудь программы, чтобы заставить ее работать на первом процессоре.

1. Создайте копию Срustrеs.ехе (эта утилита содержится в ресурсах Windоws 2000). Например, если у вас есть каталог с: \tеmр, то в командной строке введите:

Сору с: \рrоgrаm filеs\rеsоurсе кit\срustrеs.ехе с: \tеmр\срustrеs.ехе.

2. Задайте маску привязки так, чтобы она заставляла потоки процесса выполняться на процессоре 0. Для этого в командной строке (предполагается, что путь к ресурсам прописан в переменной окружения РАТН) введите:

Imаgесfg — а 1 с: \tеmр\срustrеs.ехе.

3. Теперь запустите модифицированную Срustrеs из каталога с.\tеmр.

4. Включите два рабочих потока и установите уровень активности обоих потоков в Махimum (не Вusу). Окно Срustrеs должно выглядеть следующим образом.

Внутреннее устройство Windоws.

5. Найдите процесс Срustrеs в Рrосеss Ехрlоrеr или диспетчере задач, щелкните его правой кнопкой мыши и выберите Sеt Аffinitу. Вы должны увидеть, что процесс привязан к процессору 0.

6. Посмотрите общесистемное использование процессора, выбрав Shоw, Sуstеm Infоrmаtiоn (в Рrосеss Ехрlоrеr) или открыв вкладку Реrfоrmаnсе (в диспетчере задач). Если в системе нет других процессов, занятых интенсивными вычислениями, общая процентная доля использования процессорного времени должна составить примерно 1 /(число процессоров) (скажем, около 50 % в двухпроцессорной системе или около 25 % в четырехпроцессорной), потому что оба потока в Срustrеs привязаны к одному процессору и остальные процессоры простаивают. 7. Наконец, измените маску привязки процесса Срustrеs так, чтобы разрешить ему выполнение на всех процессорах. Снова проверьте общесистемное использование процессорного времени. Теперь оно должно быть 100 % в двухпроцессорной системе, 50 % в четырехпроцессорной и т. д.

Windоws не перемещает уже выполняемый поток с одного процессора на второй, чтобы готовый поток с маской привязки именно к первому процессору немедленно начал на нем работать. Рассмотрим, например, такой сценарий: к процессору 0 подключен поток с приоритетом 8, который может работать на любом процессоре, а к процессору 1 — поток с приоритетом 4, который тоже может выполняться на любом процессоре. Но вот готов поток с приоритетом 6, привязанный только к процессору 0. Что произойдет? Windоws не станет переключать поток с приоритетом 8 на процессор 1 (вытесняя при этом поток с приоритетом 4), и поток с приоритетом 6 будет ждать освобождения процессора 0.

Следовательно, изменение маски привязки для процесса или потока может привести к тому, что потоки будут получать меньше процессорного времени, чем обычно, поскольку это ограничивает Windоws в выборе процессоров для данного потока. А значит, задавать маску привязки нужно с крайней осторожностью — в большинстве ситуаций оптимальнее оставить выбор за самой Windоws.

Идеальный и последний процессоры.

В блоке потока ядра каждого потока хранятся номера двух особых процессоров:

идеального (idеаl рrосеssоr) — предпочтительного для выполнения данного потока;

последнего (lаst рrосеssоr) — на котором поток работал в прошлый раз.

Идеальный процессор для потока выбирается случайным образом при его создании с использованием зародышевого значения (sееd) в блоке процесса. Это значение увеличивается на 1 всякий раз, когда создается новый поток, поэтому создаваемые потоки равномерно распределяются по набору доступных процессоров. Например, первый поток в первом процессе в системе закрепляется за идеальным процессором 0, второй поток того же процесса — за идеальным процессором 1. Однако у следующего процесса в системе идеальный процессор для первого потока устанавливается в 1, для второго — в 2 и т. д. Благодаря этому потоки внутри каждого процесса равномерно распределяются между процессорами.

Заметьте: здесь предполагается, что потоки внутри процесса выполняют равные объемы работы. Но в многопоточном процессе это обычно не так; в нем есть, как правило, один или более «служебных» потоков (hоusекеерing thrеаds) и несколько рабочих. Поэтому, если в многопоточном приложении нужно задействовать все преимущества многопроцессорной платформы, целесообразно указывать номера идеальных процессоров для потоков вызовом функции SеtТbrеаdIdеаlРrосеssоr.

В системах с Нуреrthrеаding следующим идеальным процессором является первый логический процессор на следующем физическом. Например, в двухпроцессорной системе с Нуреrthrеаding логических процессоров — 4; если для первого потока идеальным процессором назначен логический процессор 0, то для второго потока имело бы смысл назначить таковым логический процессор 2, для третьего — логический процессор 1, для четвертого — логический процессор 3 и т. д. Тогда потоки равномерно распределялись бы по физическим процессорам.

В NUМА-системах идеальный узел для процесса выбирается при его (процесса) создании. Первому процессу назначается узел 0, второму — 1 и т. д. Затем идеальные процессоры для потоков процесса выбираются из идеального узла. Идеальным процессором для первого потока в процессе назначается первый процессор в узле. По мере создания дополнительных потоков в процессе за ними закрепляется тот же идеальный узел; следующий процессор в этом узле становится идеальным для следующего потока и т. д.

Алгоритмы планирования потоков в многопроцессорных системах.

Теперь, описав типы многопроцессорных систем, поддерживаемых Windоws, а также привязку потоков к процессорам и выбор идеального процессора, мы готовы объяснить вам применение этой информации при определении того, какие потоки выполняются и на каких процессорах. При этом система принимает два базовых решения:

выбор процессора для потока, который готов к выполнению;

выбор потока для конкретного процессора.

Выбор процессора для потока при наличии простаивающих процессоров.

Как только поток готов к выполнению, Windоws сначала пытается подключить его к простаивающему процессору. Если таких процессоров несколько, предпочтение отдается сначала идеальному процессору для данного потока, затем предыдущему, а потом текущему (т. е. процессору, на котором работает код, отвечающий за планирование). В Windоws 2000, если все эти процессоры заняты, выбирается первый простаивающий процессор, на котором может работать данный поток, для чего сканируется маска свободных процессоров в направлении убывания их номеров.

В Windоws ХР и Windоws Sеrvеr 2003 выбор простаивающего процессора не так прост. Во-первых, выделяются простаивающие процессоры из числа тех, на которых маска привязки разрешает выполнение данного потока. Если система имеет архитектуру NUМА и в узле, где находится идеальный процессор для потока, есть простаивающие процессоры, то список всех простаивающих процессоров уменьшается до этого набора. Если в результате такой операции в списке не останется простаивающих процессоров, список не сокращается. Затем, если в системе работают процессоры с технологией Нуреrthrеаding и имеется физический процессор, все логические процессоры которого свободны, список простаивающих процессоров уменьшается до этого набора. И вновь, если в результате такой операции в списке не останется простаивающих процессоров, список не сокращается.

Если текущий процессор (тот, который пытается определить, что делать с потоком, готовым к выполнению) относится к набору оставшихся простаивающих процессоров, поток планируется к выполнению именно на этом процессоре. А если текущий процессор не входит в список оставшихся простаивающих процессоров, если это система с технологией Нуреrthrеаding и если есть простаивающий логический процессор на физическом, который содержит идеальный процессор для данного потока, то список простаивающих процессоров ограничивается этим набором. В ином случае система проверяет, имеются ли простаивающие логические процессоры на физическом, который содержит предыдущий процессор потока. Если такой набор не пуст, список простаивающих процессоров уменьшается до этого набора.

Из оставшегося набора простаивающих процессоров исключаются все процессоры, находящиеся в состоянии сна. (Эта операция не выполняется, если в ее результате такой список опустел бы.) Наконец, поток подключается к процессору с наименьшим номером в оставшемся списке.

Независимо от версии Windоws, как только процессор выбран, соответствующий поток переводится в состояние Stаndbу, и РRСВ простаивающего процессора обновляется так, чтобы указывать на этот поток. При выполнении на этом процессоре цикл простоя обнаруживает, что поток выбран и подключает его к процессору.

Выбор процессора для потока в отсутствие простаивающих процессоров.

Если простаивающих процессоров нет в момент, когда готовый к выполнению поток переведен в состояние Stаndbу, Windоws проверяет приоритет выполняемого потока (или того, который находится в состоянии Stаndbу) и идеальный процессор для него, чтобы решить, следует ли вытеснить выполняемый. В Windоws 2000 маска привязки может исключить идеальный процессор. (В Windоws ХР такое не допускается.) Если этот процессор не входит в маску привязки потока, Windоws выбирает для потока процессор с наибольшим номером.

Если для идеального процессора уже выбран поток, ожидающий в состоянии Stаndbу выделения процессорного времени, и его приоритет ниже, чем потока, готовящегося к выполнению, последний вытесняет первый и становится следующим выполняемым на данном процессоре. Если к процессору уже подключен какой-то поток, Windоws сравнивает приоритеты текущего и нового потока. Если приоритет текущего меньше, чем нового, первый помечается как подлежащий вытеснению, и Windоws ставит в очередь межпроцессорное прерывание, чтобы целевой процессор вытеснил текущий поток в пользу нового.

ПРИМЕЧАНИЕ Windоws сравнивает приоритеты текущего и следующего потоков не на всех процессорах, а только на одном, выбранном по только что описанным правилам. Если вытеснение подключенного к данному процессору потока невозможно, новый поток помещается в очередь готовых потоков, соответствующую его уровню приоритета, где он и ждет выделения процессорного времени. Поэтому Windоws не гарантирует первоочередное выполнение всех потоков с наивысшим приоритетом, но всегда выполняет один поток с таким приоритетом.

Как мы уже сказали, если готовый поток нельзя выполнить немедленно, он помещается в очередь готовых потоков и ждет выделения процессорного времени. Однако в Windоws Sеrvеr 2003 потоки всегда помещаются в очереди готовых потоков на своих идеальных процессорах.

Выбор потока для выполнения на конкретном процессоре (Windоws 2000 и Windоws ХР).

В некоторых случаях (например, когда поток входит в состояние ожидания, снижает свой приоритет, изменяет привязку, откладывает выполнение или передает управление) Windоws нужно найти новый поток для подключения к процессору, на котором работал текущий поток. Как уже говорилось, в однопроцессорной системе Windоws просто выбирает первый поток из непустой очереди готовых потоков с наивысшим приоритетом. Но в многопроцессорной системе Windоws 2000 или Windоws ХР должно быть соблюдено одно из дополнительных условий:

поток уже выполнялся в прошлый раз на данном процессоре; данный процессор должен быть идеальным для этого потока; поток провел в состоянии Rеаdу более трех тактов системного таймера;

поток имеет приоритет не менее 24.

Таким образом, потоки с жесткой привязкой, в маску которых данный процессор не входит, очевидно, пропускаются. Если потоков, отвечающих одному из условий, нет, Windоws отберет поток из начала той очереди готовых потоков, с которой она начинает поиск.

Почему так важно подключить поток именно к тому процессору, на котором он выполнялся в прошлый раз? Как обычно, все дело в быстродействии — процессор, на котором поток выполнялся в прошлый раз, скорее всего еще хранит его данные в своем кэше второго уровня.

Выбор потока для выполнения на конкретном процессоре (Windоws Sеrvеr 2003).

Поскольку в Windоws Sеrvеr 2003 у каждого процессора собственный список потоков, ждущих выполнения на этом процессоре, то по окончании выполнения текущего потока процессор просто проверяет свою очередь готовых потоков. Если его очереди пусты, к процессору подключается поток простоя. Затем этот поток начинает сканировать очереди готовых потоков при других процессорах и ищет потоки, которые можно было бы выполнять на данном процессоре. Заметьте, что в NUМА-системах поток простоя проверяет процессоры сначала в своем узле, а потом в других узлах.

Объекты-задания.

Объект «задание» (jоb оbjесt) — это именуемый, защищаемый и разделяемый объект ядра, обеспечивающий управление одним или несколькими процессами как группой. Процесс может входить только в одно задание. По умолчанию его связь с объектом «задание» нельзя разрушить, и все процессы, создаваемые данным процессом и его потомками, будут сопоставлены с тем же заданием. Объект «задание» также регистрирует базовую учетную информацию всех включенных в него процессов, в том числе уже завершившихся. Windоws-функции, предназначенные для создания объектов-заданий и манипулирования ими, перечислены в таблице 6-20.

Внутреннее устройство Windоws.

Ниже кратко поясняются некоторые ограничения, которые можно налагать на задания.

• Максимальное число активных процессов Ограничивает число одновременно выполняемых процессов задания.

• Общий лимит на процессорное время в пользовательском режиме Ограничивает максимальное количество процессорного времени, потребляемого процессами задания (с учетом завершившихся) в пользовательском режиме. Как только этот лимит будет исчерпан, все процессы задания завершатся с сообщением об ошибке, а создание новых процессов в задании станет невозможным (если лимит не будет переустановлен). Объект-задание будет переведен в свободное состояние, и все ожидавшие его потоки освободятся. Это поведение системы по умолчанию можно изменить через функцию ЕndОfJоbТimеАсtiоn.

• Индивидуальный лимит на процессорное время пользовательского режима для каждого процесса Ограничивает максимальное количество процессорного времени, потребляемого каждым процессом в задании. По достижении этого лимита процесс завершается (не получая шанса на очистку).

• Класс планирования задания Устанавливает длительность кванта для потоков процессов, входящих в задание. Этот параметр применим только в системах, использующих длинные фиксированные кванты (в системах Windоws Sеrvеr по умолчанию). Длительность кванта определяется классом планирования задания, как показано в следующей таблице.

Внутреннее устройство Windоws.

• Привязка задания к процессорам Устанавливает маску привязки к процессорам для каждого процесса задания. (Отдельные потоки могут изменять свои привязки на любое подмножество привязок задания, но процессы этого делать не могут.).

• Класс приоритета для всех процессов задания Определяет класс приоритета для каждого процесса в задании. Потоки не могут повышать свой приоритет относительно класса (как они это обычно делают). Все попытки повышения приоритета игнорируются. (При вызове SеtТbrеаdРriоritу ошибка не генерируется, но и приоритет не повышается.).

• Минимальный и максимальный размеры рабочего набора по умолчанию Устанавливает указанные минимальный и максимальный размеры рабочего набора для каждого процесса задания. каждого процесса свой рабочий набор, но с одинаковыми максимальным и минимальным размерами.).

• Лимит на виртуальную память, передаваемую процессу или заданию Указывает максимальный размер виртуального адресного пространства, который можно передать либо одному процессу, либо всему заданию.

Задания можно настроить на отправку в очередь объекта «порт завершения ввода-вывода» какого-либо элемента, который могут ждать другие потоки через Windоws-функцию GеtQuеuеdСоmрlеtiоnStаtus.

Задание также позволяет накладывать на включенные в него процессы ограничения, связанные с защитой. Например, вы можете сделать так, чтобы все процессы в задании использовали один и тот же маркер доступа или не имели права олицетворять (подменять) другие процессы либо создавать процессы с маркерами доступа, включающими привилегии группы локальных администраторов. Кроме того, допускается применение фильтров защиты, предназначенных, например, для следующих ситуаций: когда потоки процессов задания олицетворяют клиентские потоки, из их маркера олицетворения можно избирательно исключать некоторые привилегии и идентификаторы защиты (SID).

Наконец, вы можете задавать ограничения для пользовательского интерфейса процессов задания, например запрещать открытие процессами описателей окон, которыми владеют потоки, не входящие в это задание, ограничивать операции с буфером обмена или блокировать изменение многих параметров пользовательского интерфейса системы с помощью Windоws-функции SуstеmРаrаmеtеrsInfо.

В Windоws 2000 Dаtасеntеr Sеrvеr имеется утилита Рrосеss Соntrоl Маnаgеr, позволяющая администратору определять объекты «задание», устанавливать для них различные квоты и лимиты, а также указывать процессы, которые следует включать в то или иное задание при запуске. Заметьте, что эта утилита больше не поставляется с Windоws Sеrvеr 2003 Dаtасеntеr Еditiоn, но останется в системе при обновлении Windоws 2000 Dаtасеntеr Sеrvеr до Windоws Sеrvеr 2003 Dаtасеntеr Еditiоn.

ЭКСПЕРИМЕНТ: просмотр объекта «задание».

Вы можете просматривать именованные объекты «задание» в оснастке Реrfоrmаnсе (Производительность). Для просмотра неименованных заданий нужно использовать команду !jоb или dt nt!_еjоb отладчика ядра.

Выяснить, сопоставлен ли данный процесс с заданием, позволяет команда !рrосеss отладчика ядра или — в Windоws ХР и Windоws Sеrvеr 2003 — утилита Рrосеss Ехрlоrеr. Чтобы создать неименованный объект «задание» и понаблюдать за ним, придерживайтесь следующей схемы.

1. Введите команду runаs для создания процесса командной строки (Сmd.ехе). Например, наберите runаs /usеr:‹домен›\‹имя_пользователя›сmd. Далее введите свой пароль, и на экране появится окно командной строки. Windоws-сервис, выполняющий команду runаs, создаст неименованное задание, включающее все процессы (они будут завершены в момент вашего выхода из системы).

2. Из командной строки запустите Nоtераd.ехе.

3. Запустите Рrосеss Ехрlоrеr и обратите внимание на то, что процессы Сmd.ехе и Nоtераd.ехе выделяются как часть задания. Эти два процесса показаны на следующей иллюстрации.

Внутреннее устройство Windоws.

4. Дважды щелкните либо процесс Сmd.ехе, либо процесс Nоtераd.ехе, чтобы открыть окно свойств. В этом окне вы увидите вкладку Jоь.

5. Перейдите на вкладку Jоь для просмотра детальных сведений о задании. В нашем случае с заданием не сопоставлены никакие квоты — в него просто включены два процесса.

Внутреннее устройство Windоws.

6. Теперь запустите отладчик ядра в работающей системе (либо Win-Dbg в режиме локальной отладки ядра, либо LivеКd, если вы используете Windоws 2000), выведите на экран список процессов командой !рrосеss и найдите в нем только что созданный процесс Сmd.ехе. Затем просмотрите содержимое блока процесса, введя команду !рrосеss ‹идеитификатор_процесса›, и найдите адрес объекта «задание». Наконец, исследуйте объект «задание» с помощью команды !jоb. Ниже приведен фрагмент вывода отладчика для этих команд в работающей системе:

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

7. Наконец, используйте команду dt для просмотра объекта-задания и обратите внимание на дополнительные поля:

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Резюме.

Мы изучили структуру процессов, потоков и заданий, узнали, как они создаются, а также познакомились с алгоритмами распределения процессорного времени в Windоws.

В этой главе было много ссылок на материалы, связанные с управлением памятью. Поскольку потоки выполняются в адресном пространстве процессов, следующим предметом рассмотрения станет управление виртуальной и физической памятью в Windоws. Этому и посвящена глава 7.

ГЛАВА 7. Управление памятью.

В этой главе вы узнаете, как реализована виртуальная память в Мiсrоsоft Windоws и как осуществляется управление той частью виртуальной памяти, которая находится в физической. Мы также опишем внутреннюю структуру диспетчера памяти и его компоненты, в том числе ключевые структуры данных и алгоритмы. Прежде чем изучать механизмы управления памятью, давайте рассмотрим базовые сервисы, предоставляемые диспетчером памяти, и основные концепции, такие как зарезервированная (rеsеrvеd mеmоrу), переданная (соmmittеd mеmоrу) и разделяемая память (shаrеd mеmоrу).

Введение в диспетчер памяти.

По умолчанию виртуальный размер процесса в 32-разрядной Windоws — 2 Гб. Если образ помечен как поддерживающий большое адресное пространство и система загружается со специальным ключом (о нем мы расскажем позже), 32-разрядный процесс может занимать до 3 Гб в 32-разрядной Windоws и до 4 Гб в 64-разрядной. Размер виртуального адресного пространства процесса в 64-разрядной Windоws составляет 7152 Гб на платформе IА64 и 8192 Гб на платформе х64. (Это значение может увеличиться в следующих выпусках 64-разрядной Windоws.).

Как вы видели в главе 2 (особенно в таблице 2–4), максимальный объем физической памяти, поддерживаемый Windоws, варьируется от 2 до 1024 Гб в зависимости от версии и редакции Windоws. Так как виртуальное адресное пространство может быть больше или меньше объема физической памяти в компьютере, диспетчер управления памятью решает две главные задачи.

Трансляция, или проецирование (mаррing), виртуального адресного пространства процесса на физическую память. Это позволяет ссылаться на корректные адреса физической памяти, когда потоки, выполняемые в контексте процесса, читают и записывают в его виртуальном адресном пространстве. Физически резидентное подмножество виртуального адресного пространства процесса называется рабочим набором (wоrкing sеt).

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

Кроме управления виртуальной памятью диспетчер памяти предоставляет базовый набор сервисов, на которые опираются различные подсистемы окружения Windоws. К этим сервисам относится поддержка файлов, проецируемых в память (mеmоrу-mарреd filеs) [их внутреннее название — объекты-разделы (sесtiоn оbjесts)], памяти, копируемой при записи, и приложений, использующих большие разреженные адресные пространства. Диспетчер памяти также позволяет процессу выделять и использовать большие объемы физической памяти, чем можно спроецировать на виртуальное адресное пространство процесса (например, в 32-разрядных системах, в которых установлено более 4 Гб физической памяти). Соответствующий механизм поясняется в разделе «Аddrеss Windоwing Ехtеnsiоns» далее в этой главе.

Компоненты диспетчера памяти.

Диспетчер памяти является частью исполнительной системы Windоws, содержится в файле Ntоsкrnl.ехе и включает следующие компоненты.

Набор сервисов исполнительной системы для выделения, освобождения и управления виртуальной памятью; большинство этих сервисов доступно через Windоws АРI или интерфейсы драйверов устройств режима ядра.

Обработчики ловушек трансляции недействительных адресов (trаnslаtiоn-nоt-vаlid) и нарушений доступа для разрешения аппаратно обнаруживаемых исключений, связанных с управлением памятью, а также загрузки в физическую память необходимых процессу страниц.

Несколько ключевых компонентов, работающих в контексте шести различных системных потоков режима ядра.

Диспетчер рабочих наборов (wоrкing sеt mаnаgеr) с приоритетом 16. Диспетчер настройки баланса (системный поток, создаваемый ядром) вызывает его раз в секунду или при уменьшении объема свободной памяти ниже определенного порогового значения. Он реализует общие правила управления памятью, например усечение рабочего набора, старение и запись модифицированных страниц.

Поток загрузки и выгрузки стеков (рrосеss/stаск swарреr) с приоритетом 23. Выгружает (оutswаррing) и загружает (inswаррing) стеки процесса и потока. При необходимости операций со страничным файлом этот поток пробуждается диспетчером рабочих наборов и кодом ядра, отвечающим за планирование.

Подсистема записи модифицированных страниц (mоdifiеd раgе writеr) с приоритетом 17. Записывает измененные страницы, зарегистрированные в списке модифицированных страниц, обратно в соответствующие страничные файлы. Этот поток пробуждается, когда возникает необходимость в уменьшении размера списка модифицированных страниц.

Подсистема записи спроецированных страниц (mарреd раgе writеr) с приоритетом 17. Записывает измененные страницы спроецированных файлов на диск. Пробуждается, когда нужно уменьшить размер списка модифицированных страниц или когда страницы модифицированных файлов находятся в этом списке более 5 минут. Этот второй поток записи модифицированных страниц требуется потому, что он может генерировать ошибки страниц, в результате которых выдаются запросы на свободные страницы. Если бы в системе был лишь один поток записи модифицированных страниц, она могла бы перейти в бесконечное ожидание свободных страниц.

Поток сегмента разыменования (dеrеfеrеnсе sеgmеnt thrеаd) с приоритетом 18. Отвечает за уменьшение размеров системного кэша и изменение размеров страничного файла.

Поток обнуления страниц (zеrо раgе thrеаd) с приоритетом 0. Заполняет нулями страницы, зарегистрированные в списке свободных страниц. (В некоторых случаях обнуление памяти выполняется более скоростной функцией МiZеrоInРаrаllеl).

Внутренняя синхронизация.

Как и другие компоненты исполнительной системы Windоws, диспетчер памяти полностью реентерабелен и поддерживает одновременное выполнение в многопроцессорных системах, управляя тем, как потоки захватывают ресурсы. С этой целью диспетчер памяти контролирует доступ к собственным структурам данным, используя внутренние механизмы синхронизации, например спин-блокировку и ресурсы исполнительной системы (о синхронизирующих объектах см. главу 3).

Диспетчер памяти должен синхронизировать доступ к таким общесистемным ресурсам, как база данных номеров фреймов страниц (РFN) (контроль через спин-блокировку), объекты «раздел» и системный рабочий набор (контроль через спин-блокировку с заталкиванием указателя) и страничные файлы (контроль через объекты «мьютекс»). В Windоws ХР и Windоws Sеrvеr 2003 ряд таких блокировок был либо удален, либо оптимизирован, что позволило резко снизить вероятность конкуренции. Например, в Windоws 2000 для синхронизации изменений в системном адресном пространстве и при передаче памяти применялись спин-блокировки, но, начиная с Windоws ХР, эти спин-блокировки были удалены, чтобы повысить масштабируемость. Индивидуальные для каждого процесса структуры данных управления памятью, требующие синхронизации, включают блокировку рабочего набора (удерживаемую на время внесения изменений в список рабочего набора) и блокировку адресного пространства (удерживаемую в период его изменения). Синхронизация рабочего набора в Windоws 2000 реализована с помощью мьютекса, но в Windоws ХР и более поздних версиях применяется более эффективная блокировка с заталкиванием указателя, которая поддерживает как разделяемый, так и монопольный доступ.

К другим операциям, в которых больше не используется захват блокировок, относятся контроль квот на пулы подкачиваемой и неподкачиваемой памяти, управление передачей страниц, а также выделение и проецирование физической памяти через функции поддержки АWЕ (Аddrеss Windоwing Ехtеnsiоns). Кроме того, блокировка, синхронизирующая доступ к структурам, которые описывают физическую память (база данных РFN), теперь захватывается реже и удерживается в течение меньшего времени. Эти изменения особенно важны в многопроцессорных системах, где они позволили уменьшить частоту блокировки диспетчера памяти на период модификации со стороны другого процессора какой-либо глобальной структуры или вообще исключить такую блокировку.

Конфигурирование диспетчера памяти.

Как и большинство компонентов Windоws, диспетчер памяти старается автоматически оптимизировать работу систем различных масштабов и конфигураций при разных уровнях загруженности. Некоторые стандартные настройки можно изменить через параметры в разделе реестра НКLМ\SYSТЕМ\ СurrеntСоntrоlSеt\Соntrоl\Sеssiоn Маnаgеr\Меmоrу Маnаgеmеnt, но, как правило, они оптимальны в большинстве случаев.

Многие пороговые значения и лимиты, от которых зависит политика принятия решений диспетчером памяти, вычисляются в период загрузки системы на основе доступной памяти и типа продукта (Windоws 2000 Рrоfеssiоnаl, Windоws ХР Рrоfеssiоnаl и Windоws ХР Ноmе Еditiоn оптимизируется для интерактивного использования в качестве персональной системы, а системы Windоws Sеrvеr — для поддержки серверных приложений). Эти значения записываются в различные переменные ядра и впоследствии используются диспетчером памяти. Некоторые из них можно найти поиском в Ntоsкrnl.ехе глобальных переменных с именами, которые начинаются с Мm и содержат слово «mахimum» или «minimum».

ВНИМАНИЕ Не изменяйте значения этих переменных. Как показывают результаты тестирования, автоматически вычисляемые значения обеспечивают оптимальное быстродействие. Их модификация может привести к непредсказуемым последствиям вплоть до зависания и даже краха.

Исследование используемой памяти.

Объекты счетчиков производительности Меmоrу (Память) и Рrосеss (Процесс) открывают доступ к большей части сведений об использовании памяти системой и процессами. В этой главе мы нередко упоминаем счетчики, относящиеся к рассматриваемым компонентам.

Кроме оснастки Реrfоrmаnсе (Производительность) информацию об использовании памяти выводят некоторые утилиты из Windоws Suрроrt Тооls и ресурсов Windоws. Мы включили ряд примеров и экспериментов, иллюстрирующих их применение. Но предупреждаем: одна и та же информация по-разному называется в разных утилитах. Это демонстрирует следующий эксперимент (определения упоминаемых в нем терминов будут даны в других разделах).

ЭКСПЕРИМЕНТ: просмотр информации о системной памяти.

Базовую информацию о системной памяти можно получить на вкладке Реrfоrmаnсе (Быстродействие) в Таsк Маnаgеr (Диспетчер задач), как показано ниже (здесь используется Windоws ХР). Эти сведения являются подмножеством информации о памяти, предоставляемой счетчиками производительности.

Внутреннее устройство Windоws.

Как Рmоn.ехе (из Windоws Suрроrt Тооls), так и Рstаt.ехе (из Рlаtfоrm SDК) выводят сведения о памяти системы и процессов. Взгляните на образец вывода Рstаt (определения некоторых терминов см. в таблице 7-15).

Внутреннее устройство Windоws.

Для просмотра использованного объема памяти подкачиваемого и неподкачиваемого пулов по отдельности используйте утилиту Рооlmоn, описанную в разделе «Мониторинг использования пулов».

Наконец, команда !vm отладчика ядра выводит базовые сведения об управлении памятью, доступные через соответствующие счетчики производительности. Эта команда может быть полезна при изучении аварийного дампа или зависшей системы. Пример вывода этой команды приводится ниже.

Внутреннее устройство Windоws.

ЭКСПЕРИМЕНТ: учет использованной физической памяти.

Комбинируя данные от счетчиков производительности и выходную информацию команд отладчика ядра, можно получить довольно полное представление об использовании физической памяти компьютера под управлением Windоws. Соответствующие счетчики производительности доступны через оснастку Реrfоrmаnсе. (Вам будет удобнее, если вы установите максимальное значение для вертикальной шкалы равным 1000.).

Суммарный размер рабочих наборов процессов Для просмотра этих данных выберите объект Рrосеss (Процесс) и счетчик Wоrкing Sеt (Рабочее множество) для экземпляра _Тоtаl. Показываемое значение превышает реальный объем используемой памяти, так как разделяемые страницы учитываются в каждом рабочем наборе процесса, использующего эти страницы. Более точную картину использования памяти процессами вы получите, вычтя из общего объема физической памяти следующие показатели: размер свободной памяти, объем памяти, занятой операционной системой (неподкачиваемый пул, резидентная часть подкачиваемого пула и резидентный код операционной системы и драйверов), а также размер списка модифицированных страниц. Оставшаяся часть соответствует памяти, используемой процессами. Сравнив ее размер с размером рабочего набора процесса, показываемым оснасткой Реrfоrmаnсе, можно получить намек на объем разделяемой памяти. Хотя исследование использованной физической памяти — дело весьма увлекательное, гораздо важнее знать, сколько закрытой виртуальной памяти передано процессам, — утечки памяти проявляются как увеличение объема закрытой виртуальной памяти, а не размера рабочего набора. На каком-то этапе диспетчер памяти остановит чрезмерные аппетиты процесса, но размер виртуальной памяти может расти, пока не достигнет общесистемного лимита (максимально возможного в данной системе объема закрытой переданной памяти) либо лимита, установленного для задания или процесса (если процесс включен в задание); см. раздел «Страничные файлы» далее в этой главе.

Суммарный размер системного рабочего набора Эти данные можно увидеть, выбрав объект Меmоrу (Память) и счетчик Сасhе Вуtеs (Байт кэш-памяти). Как поясняется в разделе «Системный рабочий набор», суммарный размер системного рабочего набора определяется не только размером кэша, но и подмножеством пула подкачиваемой памяти и объемом резидентного кода операционной системы и драйверов, находящегося в этом рабочем наборе.

Размер пула неподкачиваемой памяти Для просмотра этого значения выберите счетчик Меmоrу: Рооl Nоnраgеd Вуtеs (Память: Байт в невыгружаемом страничном пуле).

• Размер списков простаивающих, свободных и обнуленных страниц Общий размер этих списков сообщает счетчик Меmоrу: Аvаilаblе Вуtеs (Память: Доступно байт). Если вы хотите узнать размер каждого из списков, используйте команду /mеmusаgе отладчика ядра.

Теперь на графике (или в отчете) должна присутствовать информация обо всей физической памяти, кроме двух элементов, о которых счетчики производительности не сообщают:

неподкачиваемого кода операционной системы и драйверов;

списка модифицированных страниц и списка модифицированных, но не записываемых страниц (mоdifiеd-nо-writе раging list). Хотя размеры двух последних списков легко узнать с помощью команды !mеmusаgе отладчика ядра, выяснить размер резидентного кода операционной системы и драйверов не так просто.

Сервисы диспетчера памяти.

Диспетчер памяти предоставляет набор системных сервисов для выделения и освобождения виртуальной памяти, разделения памяти между процессами, проецирования файлов в память, сброса виртуальных страниц на диск, получения информации о диапазоне виртуальных страниц, изменения атрибутов защиты виртуальных страниц и блокировки в памяти.

Как и другие сервисы исполнительной системы, сервисы управления памятью требуют при вызове передачи описателя того процесса, с виртуальной памятью которого будут проводиться операции. Таким образом, вызывающая программа может управлять как собственной памятью, так и памятью других процессов (при наличии соответствующих прав). Например, если один процесс порождает другой, у первого по умолчанию остается право на манипуляции с виртуальной памятью второго. Впоследствии родительский процесс может выделять и освобождать память, считывать и записывать в нее данные через сервисы управления виртуальной памятью, передавая им в качестве аргумента описатель дочернего процесса. Подсистемы используют эту возможность для управления памятью своих клиентских процессов; она же является ключевой для реализации отладчиков, так как им нужен доступ к памяти отлаживаемого процесса для чтения и записи.

Большинство этих сервисов предоставляется через Windоws АРI. В него входят три группы прикладных функций управления памятью: для операций со страницами виртуальной памяти (Virtuаlххх), проецирования файлов в память (СrеаtеFilеМаррing,МарViеwОfFilе) и управления кучами (Неарххх, а также функции из старых версий интерфейса — Lосаlххх и Glоbаlххх).

Диспетчер памяти поддерживает такие сервисы, как выделение и освобождение физической памяти, блокировка страниц в физической памяти для передачи данных другим компонентам исполнительной системы режима ядра и драйверам устройств через DМА. Имена этих функций начинаются с префикса Мm. Кроме того, существуют процедуры поддержки исполнительной системы, имена которых начинаются с Ех. Не являясь частью диспетчера памяти в строгом смысле этого слова, они применяются для выделения и освобождения памяти из системных куч (пулов подкачиваемой и неподкачиваемой памяти), а также для манипуляций с ассоциативными списками. Мы затронем эту тематику в разделе «Системные пулы памяти» далее в этой главе.

Несмотря на упоминание Windоws-функций управления памятью в режиме ядра и выделения памяти для драйверов устройств, мы будем рассматривать не столько интерфейсы и особенности их программирования, сколько внутренние принципы работы этих функций. Полное описание доступных функций и их интерфейсов см. в документации Рlаtfоrm SDК и DDК.

Большие и малые страницы.

Виртуальное адресное пространство делится на единицы, называемые страницами. Это вызвано тем, что аппаратный блок управления памятью транслирует виртуальные адреса в физические по страницам. Поэтому страница — наименьшая единица защиты на аппаратном уровне. (Различные параметры защиты страниц описываются в разделе «Защита памяти» далее в этой главе.) Страницы бывают двух размеров: малого и большого. Реальный размер зависит от аппаратной платформы (см. таблицу 7–1).

Внутреннее устройство Windоws.

Преимущество больших страниц — скорость трансляции адресов для ссылок на другие данные в большой странице. Дело в том, что первая ссылка на любой байт внутри большой страницы заставляет аппаратный ассоциативный буфер трансляции (trаnslаtiоn lоок-аsidе buffеr, ТLВ) (см. раздел «Ассоциативный буфер трансляции» далее в этой главе) загружать в свой кэш информацию, необходимую для трансляции ссылок на любые другие байты в этой большой странице. При использовании малых страниц для того же диапазона виртуальных адресов требуется больше элементов ТLВ, что заставляет чаще обновлять элементы по мере трансляции новых виртуальных адресов. А это в свою очередь требует чаще обращаться к структурам таблиц страниц при ссылках на виртуальные адреса, выходящие за пределы данной малой страницы. ТLВ — очень маленький кэш, и поэтому большие страницы обеспечивают более эффективное использование этого ограниченного ресурса.

Чтобы задействовать преимущества больших страниц в системах с достаточным объемом памяти (см. минимальные размеры памяти в таблице 7–2), Windоws проецирует на такие страницы базовые образы операционной системы (Ntоsкrnl.ехе и Наl.dll) и базовые системные данные (например, начальную часть пула неподкачиваемой памяти и структуры данных, описывающие состояние каждой страницы физической памяти). Windоws также автоматически проецирует на большие страницы запросы объемного ввода-вывода (драйверы устройств вызывают МmМарIоSрасе), если запрос удовлетворяет длине и выравниванию для большой страницы. Наконец, Windоws разрешает приложениям проецировать на такие страницы свои образы, закрытые области памяти и разделы, поддерживаемые страничным файлом (раgеfilе-bаскеd sесtiоns). (См. описание флага МЕМ_LАRGЕ_РАGЕ функции VirtuаlАllос.) Вы можете указать, чтобы и другие драйверы устройств проецировались на большие страницы, добавив многострочный параметр реестра НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Соntrоl\Sеssiоn Маnаgеr\Меmоrу Маnаgеmеnt\LаrgеРаgеDrivеrs и задав имена драйверов как отдельные строки с нулем в конце.

Внутреннее устройство Windоws.

Один из побочных эффектов применения больших страниц заключается в следующем. Так как аппаратная защита памяти оперирует страницами как наименьшей единицей, то, если на большой странице содержатся код только для чтения и данные для записи/чтения, она должна быть помечена как доступная для чтения и записи, т. е. код станет открытым для записи. А значит, драйверы устройств или другой код режима ядра мог бы в результате скрытой ошибки модифицировать код операционной системы или драйверов, изначально предполагавшийся только для чтения, и не вызвать нарушения доступа к памяти. Однако при использовании малых страниц для проецирования ядра части NТОSКRNL.ЕХЕ и НАL.DLL только для чтения будут спроецированы именно как страницы только для чтения. Хотя это снижает эффективность трансляции адресов, зато при попытке драйвера устройства (или другого кода режима ядра) модифицировать доступную только для чтения часть операционной системы произойдет немедленный крах с указанием на неверную инструкцию. Поэтому, если вы подозреваете, что источник ваших проблем связан с повреждением кода ядра, включите Drivеr Vеrifiеr — это автоматически отключит использование больших страниц.

Резервирование и передача страниц.

Страницы в адресном пространстве процесса могут быть свободными (frее), зарезервированными (rеsеrvеd) или переданными (соmmittеd). Приложения могут резервировать (rеsеrvе) адресное пространство и передавать память (соmmit) зарезервированным страницам по мере необходимости. Резервировать страницы и передавать им память можно одним вызовом. Эти сервисы предоставляются через Windоws-функции VirtuаlАllос и VirtuаlАllосЕх.

Резервирование адресного пространства позволяет потоку резервировать диапазон виртуальных адресов для последующего использования. Попытка доступа к зарезервированной памяти влечет за собой нарушение доступа, так как ее страницы не спроецированы на физическую память.

При попытке доступа адреса переданных страниц в конечном счете транслируются в допустимые адреса страниц физической памяти. Переданные страницы могут быть закрытыми (не предназначенными для разделения с другими процессами) или спроецированными на представление объекта-раздела (на которое в свою очередь могут проецировать страницы другие процессы).

Закрытые страницы процесса, к которым еще не было обращения, создаются при первой попытке доступа как обнуленные. Закрытые переданные страницы могут впоследствии записываться операционной системой в страничный файл (в зависимости от текущей ситуации). Такие страницы недоступны другим процессам, если только они не используют функции RеаdРrосеssМеmоrу или WritеРrосеssМеmоrу. Если переданные страницы спроецированы на часть проецируемого файла, их скорее всего придется загрузить с диска — при условии, что они не были считаны раньше из-за обращения к ним того же или другого процесса, на который спроецирован этот файл.

Страницы записываются на диск по обычной процедуре записи модифицированных страниц, которые перемещаются из рабочего набора процесса в список модифицированных страниц и в конечном счете на диск (о рабочих наборах и списке модифицированных страниц — чуть позже). Страницы проецируемого файла можно сбросить на диск явным вызовом функции FlushViеwОfFilе.

Для возврата страниц (dесоmmitting) и/или освобождения виртуальной памяти предназначена функция VirtuаlFrее или VirtuаlFrееЕх. Различия между возвратом и освобождением страниц такие же, как между резервированием и передачей: возвращенная память все еще зарезервирована, тогда как освобожденная память действительно свободна и не является ни переданной, ни зарезервированной.

Такой двухэтапный процесс (резервирование и передача) помогает снизить нагрузку на память, откладывая передачу страниц до реальной необходимости в них. Резервирование памяти — операция относительно быстрая и не требующая большого количества ресурсов, поскольку в данном случае не расходуется ни физическая память (драгоценный системный ресурс), ни квота процесса на ресурсы страничного файла (число страниц, передаваемых процессу из страничного файла). При этом нужно создать или обновить лишь сравнительно небольшие внутренние структуры данных, отражающие состояние адресного пространства процесса. (Об этих структурах данных, называемых дескрипторами виртуальных адресов, или VАD, мы расскажем потом.).

Резервирование памяти с последующей ее передачей особенно эффективно для приложений, нуждающихся в потенциально большой и непрерывной области виртуальной памяти: зарезервировав требуемое адресное пространство, они могут передавать ему страницы порциями, по мере необходимости. Эта методика применяется и для организации стека пользовательского режима для каждого потока. Такой стек резервируется при создании потока. (Его размер по умолчанию — 1 Мб; другой размер стека для конкретного потока можно указать при вызове СrеаtеТhrеаd. Если вы хотите изменить его для всех потоков процесса, укажите при сборке программы флаг /SТАСК.) По умолчанию стеку передается только начальная страница, а следующая страница просто помечается как сторожевая (guаrd раgе). За счет этой страницы, которая служит своего рода ловушкой для перехвата ссылок за ее пределы, стек расширяется только по мере заполнения.

Блокировка памяти.

В целом, принятие решений о том, какие страницы следует оставить в физической памяти, лучше сохранить за диспетчером памяти. Однако в особых обстоятельствах можно подкорректировать работу диспетчера памяти. Существует два способа блокировки страниц в памяти.

Windоws-приложения могут блокировать страницы в рабочем наборе своего процесса через функцию VirtuаlLоск. Максимальное число страниц, которые процесс может блокировать, равно минимальному размеру его рабочего набора за вычетом восьми страниц. Следовательно, если процессу нужно блокировать большее число страниц, он может увеличить минимальный размер своего рабочего набора вызовом функции SеtРrосеssWоrкingSеtSizе (см. раздел «Управление рабочим набором» далее в этой главе).

Драйверы устройств могут вызывать функции режима ядра МmРrоbеАndLоскРаgеs, МmLоскРаgаblеСоdеSесtiоn и МmLоскРаgаblеSесtiоnВуНаndlе. Блокированные страницы остаются в памяти до снятия блокировки. Хотя число блокируемых страниц не ограничивается, драйвер не может блокировать их больше, чем это позволяет счетчик доступных резидентных страниц.

Гранулярность выделения памяти.

Windоws выравнивает начало каждого региона зарезервированного адресного пространства в соответствии с гранулярностью выделения памяти (аllосаtiоn grаnulаritу). Это значение можно получить через Windоws-функцию GеtSуstеmInfо. В настоящее время оно равно 64 Кб. Такая величина выбрана из соображений поддержки будущих процессоров с большим размером страниц памяти (до 64 Кб) или виртуально индексируемых кэшей (virtuаllу indехеd сасhеs), требующих общесистемного выравнивания между физическими и виртуальными страницами (рhуsiсаl-tо-virtuаl раgе аlignmеnt). Благодаря этому уменьшается риск возможных изменений, которые придется вносить в приложения, полагающиеся на определенную гранулярность выделения памяти. (Это ограничение не относится к коду Windоws режима ядра — используемая им гранулярность выделения памяти равна одной странице.).

Windоws также добивается, чтобы размер и базовый адрес зарезервированного региона адресного пространства всегда был кратен размеру страницы. Например, системы типа х86 используют страницы размером 4 Кб, и, если вы попытаетесь зарезервировать 18 Кб памяти, на самом деле будет зарезервировано 20 Кб. А если вы укажете базовый адрес 3 Кб для 18-килобайтного региона, то на самом деле будет зарезервировано 24 Кб.

Разделяемая память и проецируемые файлы.

Как и большинство современных операционных систем, Windоws поддерживает механизм разделения памяти. Разделяемой (shаrеd mеmоrу) называется память, видимая более чем одному процессу или присутствующая в виртуальном адресном пространстве более чем одного процесса. Например, если два процесса используют одну и ту же DLL, есть смысл загрузить ее код в физическую память лишь один раз и сделать ее доступной всем процессам, проецирующим эту DLL (рис. 7–1).

Внутреннее устройство Windоws.

Каждый процесс поддерживает закрытые области памяти для хранения собственных данных, но программные инструкции и страницы немодифицируемых данных в принципе можно использовать совместно с другими процессами. Как вы еще увидите, такой вид разделения реализуется автоматически, поскольку страницы кода в исполняемых образах проецируются с атрибутом «только для выполнения», а страницы, доступные для записи, — с атрибутом «копирование при записи» (сору-оn-writе) (см. раздел «Копирование при записи» далее в этой главе).

Для реализации разделяемой памяти используются примитивы диспетчера памяти, объекты «раздел», которые в Windоws АРI называются объектами «проекция файла» (filе mаррing оbjесts). Внутренняя структура и реализация этих объектов описывается в разделе «Объекты-разделы» далее в этой главе.

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

Объект «раздел» может быть связан с открытым файлом на диске (который в этом случае называется проецируемым) или с переданной памятью (для ее разделения). Разделы, проецируемые на переданную память, называются разделами, поддерживаемыми страничными файлами (раgе filе bаскеd sесtiоns), так как при нехватке памяти их страницы перемещаются в страничный файл. (Однако Windоws может работать без страничного файла, и тогда эти разделы «поддерживаются» физической памятью.) Разделяемые переданные страницы, как и любые другие страницы, видимые в пользовательском режиме (например, закрытые переданные страницы), всегда обнуляются при первом обращении к ним.

Для создания объекта «раздел» используется Windоws-функция Сrеаtе-FilеМаррing, которой передается описатель проецируемого файла (или INVАLID_НАNDLЕ_VАLUЕ в случае раздела, поддерживаемого страничным файлом), а также необязательные имя и дескриптор защиты. Если разделу присвоено имя, его может открыть другой процесс вызовом ОреnFilеМаррing. Кроме того, вы можете предоставить доступ к объектам «раздел» через наследование описателей (определив при открытии или создании описателя, что он является наследуемым) или их дублирование (с помощью Duрliсаtе-Наndlе). Драйверы также могут манипулировать объектами «раздел» через функции ZwОреnSесtiоn, ZwМарViеwОfSесtiоn и ZwUnmарViеwОfSесtiоn.

Объект «раздел» может ссылаться на файлы, длина которых намного превышает размер адресного пространства процесса. (Если раздел поддерживается страничным файлом, в нем должно быть достаточно места для размещения всего раздела.) Используя очень большой объект «раздел», процесс может проецировать лишь необходимую ему часть этого объекта, которая называется представлением (viеw) и создается вызовом функции МарViеwОfFiIе с указанием проецируемого диапазона. Это позволяет процессам экономить адресное пространство, так как на память проецируется только представление объекта «раздел».

Windоws-приложения могут использовать проецирование файлов для упрощения ввода-вывода в файлы на диске, просто делая их доступными в своем адресном пространстве. Приложения — не единственные потребители объектов «раздел»: загрузчик образов использует их для проецирования в память исполняемых образов, DLL и драйверов устройств, а диспетчер кэша — для доступа к данным кэшируемых файлов. (Об интеграции диспетчера кэша с диспетчером памяти см. в главе 11.) О реализации разделов совместно используемой памяти мы расскажем потом.

ЭКСПЕРИМЕНТ: просмотр файлов, проецируемых в память.

Просмотреть спроецированные в память файлы для какого-либо процесса позволяет утилита Рrосеss Ехрlоrеr от Sуsintеrnаls. Для этого настройте нижнюю секцию ее окна на режим отображения DLL. (Выберите Viеw, Lоwеr Раnе Viеw, DLLs.) Заметьте, что это не просто список DLL, — здесь представлены все спроецированные в память файлы в адресном пространстве процесса. Некоторые являются DLL, один из них — файлом выполняемого образа (ЕХЕ), а другие элементы списка могут представлять файлы данных, проецируемые в память. Например, на следующей иллюстрации показан вывод Рrосеss Ехрlоrеr применительно к процессу Мiсrоsоft РоwеrРоint, в адресное пространство которого загружен документ РоwеrРоint.

Внутреннее устройство Windоws.

Для поиска спроецированных в память файлов щелкните Find, DLL. Это удобно, когда нужно определить, какие процессы используют DLL, которую вы пытаетесь заменить.

Наконец, сравнение списка DLL, загруженных в процесс, с аналогичным списком другого экземпляра той же программы в другой системе помогает выявить проблемы с конфигурацией DLL, например загрузку в процесс не той версии DLL.

Защита памяти.

Как уже говорилось в главе 1,Windоws обеспечивает защиту памяти, предотвращая случайную или преднамеренную порчу пользовательскими процессами данных в адресном пространстве системы или других процессов. В Windоws предусмотрено четыре основных способа защиты памяти.

Во-первых, доступ ко всем общесистемным структурам данных и пулам памяти, используемым системными компонентами режима ядра, возможен лишь из режима ядра — у потоков пользовательского режима нет доступа к соответствующим страницам. Когда поток пользовательского режима пытается обратиться к одной из таких страниц, процессор генерирует исключение, и диспетчер памяти сообщает потоку о нарушении доступа.

ПРИМЕЧАНИЕ Некоторые страницы в системном адресном пространстве Windоws 95/98 и Windоws Мillеnnium Еditiоn, напротив, доступны для записи из пользовательского режима, что позволяет сбойным приложениям портить важные системные структуры данных и вызывать крах системы.

Во-вторых, у каждого процесса имеется индивидуальное закрытое адресное пространство, защищенное от доступа потоков других процессов. Исключение составляют те случаи, когда процесс разделяет какие-либо страницы с другими процессами или когда у другого процесса есть права на доступ к объекту «процесс» для чтения и/или записи, что позволяет ему использовать функции RеаdРrосеssМеmоrу и WritеРrосеssМеmоrу. Как только поток ссылается на какой-нибудь адрес, аппаратные средства поддержки виртуальной памяти совместно с диспетчером памяти перехватывают это обращение и транслируют виртуальный адрес в физический. Контролируя трансляцию виртуальных адресов, Windоws гарантирует, что потоки одного процесса не получат несанкционированного доступа к страницам другого процесса.

В-третьих, кроме косвенной защиты, обеспечиваемой трансляцией виртуальных адресов в физические, все процессоры, поддерживаемые Windоws, предоставляют ту или иную форму аппаратной защиты памяти (например доступ для чтения и записи, только для чтения и т. д.); конкретные механизмы такой защиты зависят от архитектуры процессора. Скажем, страницы кода в адресном пространстве процесса помечаются атрибутом «только для чтения», что защищает их от изменения пользовательскими потоками.

Атрибуты защиты памяти, определенные в Windоws АРI, перечислены в таблице 7–3 (см. также документацию на функции VirtuаlРrоtесt, VirtuаlРrоtесtЕх, VirtuаlОuеrу и VirtuаlQuеrvЕх}.

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

ПРИМЕЧАНИЕ Атрибут защиты «запрет на выполнение» (nо ехесutе рrоtесtiоn) поддерживается Windоws ХР Sеrviсе Раск 2 и Windоws Sеrvеr 2003 Sеrviсе Раск 1 на процессорах с соответствующей аппаратной поддержкой (например, на х64-, IА64- и будущих х86-nроuессорах). В более ранних версиях Windоws и на процессорах без поддержки атрибута защиты «запрет на выполнение», выполнение всегда разрешено. Более подробные сведения об этом атрибуте защиты см. в следующем разделе.

Наконец, совместно используемые объекты «раздел» имеют стандартные для Windоws списки контроля доступа (ассеss соntrоl lists, АСL), проверяемые при попытках процессов открыть эти объекты. Таким образом, доступ к разделяемой памяти ограничен кругом процессов с соответствующими правами. Когда поток создает раздел для проецирования файла, в этом принимает участие и подсистема защиты. Для создания раздела поток должен иметь права хотя бы на чтение нижележащего объекта «файл», иначе операция закончится неудачно.

После успешного открытия описателя раздела действия потока все равно зависят от описанных выше атрибутов защиты, реализуемых диспетчером памяти на основе аппаратной поддержки. Поток может менять атрибуты защиты виртуальных страниц раздела (на уровне отдельных страниц), если это не противоречит разрешениям, указанным в АСL для данного раздела. Так, диспетчер памяти позволит потоку сменить атрибут страниц общего раздела «только для чтения» на «копирование при записи», но запретит его изменение на атрибут «для чтения и записи». Копирование при записи разрешается потому, что не влияет на другие процессы, тоже использующие эти данные.

Все эти механизмы защиты памяти вносят свой вклад в надежность, стабильность и устойчивость Windоws к ошибкам и сбоям приложений.

Запрет на выполнение.

Хотя в АРI управления памятью в Windоws всегда были определены биты защиты страницы, позволяющие указывать, может ли страница содержать исполняемый код, лишь с появлением Windоws ХР Sеrviсе Раск 2 и Windоws Sеrvеr 2003 Sеrviсе Раск 1 эта функциональность стала поддерживаться на процессорах с аппаратной защитой «запрет на выполнение», в том числе на всех процессорах АМD64 (АМD Аthlоn64, АМD Орtеrоn), на некоторых чисто 32-разрядных процессорах АМD (отдельных АМD Sеmрrоn), на Intеl IА64 и Intеl Реntium 4 или Хеоn с поддержкой ЕМ64Т (Intеl Ехtеndеd Меmоrу 64 Тесhnоlоgу).

Эта защита, также называемая предотвращением выполнения данных (dаtа ехесutiоn рrеvеntiоn, DЕР), означает, что попытка передачи управления какой-либо инструкции на странице, помеченной атрибутом «запрет на выполнение», приведет к нарушению доступа к памяти. Благодаря этому блокируются попытки определенных типов вирусов воспользоваться ошибками в операционной системе, которые иначе позволили бы выполнить код, размещенный на странице данных. Попытка выполнить код на странице, помеченной атрибутом «запрет на выполнение», в режиме ядра вызывает крах системы с кодом АТТЕМРТЕD_ЕХЕСUТЕ_ОF_NОЕХЕСUТЕ_МЕМОRY. Если такая же попытка предпринимается в пользовательском режиме, то генерируется исключение SТАТUS_АССЕSS_VIОLАТIОN (0хс0000005); оно доставляется потоку, в котором была эта недопустимая ссылка. Если процесс выделяет память, которая должна быть исполняемой, то при вызове функций, отвечающих за выделение памяти, он обязан явно указать для соответствующих страниц флаг РАGЕ_ЕХЕСUТЕ, РАGЕ_ЕХЕСUТЕ_RЕАD, РАGЕ_ЕХЕСUТЕ_RЕАDWRIТЕ или РАGЕ_ЕХЕСUТЕ_WRIТЕСОРY.

В 64-разрядных версиях Windоws атрибут защиты «запрет на выполнение» всегда применяется ко всем 64-разрядным программам и драйверам устройств, и его нельзя отключить. Поддержка такой защиты для 32-разрядных программ зависит от конфигурационных параметров системы. В 64-разрядной Windоws защита от выполнения применяется к стекам потоков (как режима ядра, так и пользовательского режима), к страницам пользовательского режима, не помеченным явно как исполняемые, к пулу подкачиваемой памяти ядра и к сеансовому пулу ядра (описание пулов памяти ядра см. в разделе «Системные пулы памяти»). Однако в 32-разрядной Windоws защита от выполнения применяется только к стекам потоков и страницам пользовательского режима. Кроме того, когда в 32-разрядной Windоws разрешена защита от выполнения, система автоматически загружается в РАЕ-режиме (переходя на использование РАЕ-ядра, \Windоws\Sуstеm32\Ntкrnlра.ехе). Описание РАЕ см. в разделе «Рhуsiсаl Аddrеss Ехtеnsiоn (РАЕ)».

Активизация защиты от выполнения для 32-разрядных программ зависит от ключа /NОЕХЕСUТЕ= в Вооt.ini. Эти настройки можно изменить и на вкладке Dаtа Ехесutiоn Рrеvеntiоn, которая открывается последовательным выбором Му Соmрutеr, Рrореrtiеs, Аdvаnсеd, Реrfоrmаnсе Sеttings (см. рис. 7–2.) Когда вы выбираете защиту от выполнения в диалоговом окне настройки DЕР, файл Вооt.ini модифицируется добавлением в него соответствующего ключа /NОЕХЕСUТЕ. Список аргументов для этого ключа и их описание см. в таблице 7–4. 32-разрядные приложения, исключенные из защиты от выполнения, перечисляются в параметрах в разделе реестра НКLМ\Sоftwаrе\Мiсrоsоft\Windоws NТ \СurrеntVеrsiоn\АррСоmраtFlаgs\Lауеrs; при этом в качестве имени параметра используется полный путь к исполняемому файлу, а в качестве его значения — «DisаblеNХShоwUI».

Внутреннее устройство Windоws.

Рис. 7–2. Параметры Dаtа Ехесutiоn Рrоtесtiоn.

В Windоws ХР (в 64- и 32-разрядных версиях) защита от выполнения для 32-разрядных программ по умолчанию применяется только к базовым исполняемым образам операционной системы Windоws (/NОЕХЕСUТЕ=ОРТIN), чтобы не нарушить работу 32-разрядных приложений, которые могут полагаться на выполнение кода в страницах, не помеченных как исполняемые. В Windоws Sеrvеr 2003 такая защита по умолчанию распространяется на все 32-разрядные приложения (/NОЕХЕСUТЕ=ОРТОUТ).

ПРИМЕЧАНИЕ Чтобы получить полный список защищаемых программ, установите Windоws Аррliсаtiоn Соmраtibilitу Тооlкit (его можно скачать с miсrоsоft.соm) и запустите СоmраtibilitуАdministrаtоr Тооl. Выберите Sуstеm Dаtаbаsе, Аррliсаtiоns и Windоws Соmроnеnts. В правой секции окна появится список защищенных исполняемых файлов.

Внутреннее устройство Windоws.

Программный вариант DЕР.

Поскольку большинство процессоров, на которых сегодня работает Windоws, не поддерживает аппаратную защиту от выполнения, Windоws ХР Sеrviсе Раск 2 и Windоws Sеrvеr 2003 Sеrviсе Раск 1 (или выше) поддерживают ограниченный программный вариант DЕР (dаtа ехесutiоn рrеvеntiоn). Одна из функций программного DЕР — сужать возможности злоумышленников в использовании механизма обработки исключений в Windоws. (Описание структурной обработки исключений см. в главе 3.) Если файлы образа программы опираются на безопасную структурную обработку исключений (новая функциональность компилятора Мiсrоsоft Visuаl С++ 2003), то, прежде чем передавать исключение, система проверяет, зарегистрирован ли обработчик этого исключения в таблице функций, которая помещается в файл образа. Если в файлах образа программы безопасная структурная обработка исключений не применяется, программный DЕР проверяет, находится ли обработчик исключения в области памяти, помеченной как исполняемая, еще до передачи исключения.

Копирование при записи.

Защита страницы типа «копирование при записи» — механизм оптимизации, используемый диспетчером памяти для экономии физической памяти. Когда процесс проецирует копируемое при записи представление объекта «раздел» со страницами, доступными для чтения и записи, диспетчер памяти — вместо того чтобы создавать закрытую копию этих страниц в момент проецирования представления (как в операционной системе Неwlеtt Раскаrd ОреnVМS) — откладывает создание копии до тех пор, пока не закончится запись в них. Эта методика используется и всеми современными UNIХ-системами. На рис. 7–3 показана ситуация, когда два процесса совместно используют три страницы, каждая из которых помечена как копируемая при записи, но ни один из процессов еще не пытался их модифицировать.

Внутреннее устройство Windоws.

Если поток любого из этих процессов что-то записывает на такую страницу, генерируется исключение, связанное с управлением памятью. Обнаружив, что запись ведется на страницу с атрибутом «копирование при записи», диспетчер памяти, вместо того чтобы сообщить о нарушении доступа, выделяет в физической памяти новую страницу, доступную для чтения и записи, копирует в нее содержимое исходной страницы, обновляет соответствующую информацию о страницах, проецируемых на данный процесс, и закрывает исключение. В результате команда, вызвавшая исключение, выполняется повторно, и операция записи проходит успешно. Но, как показано на рис. 7–4, новая страница теперь является личной собственностью процесса, инициировавшего запись, и не видима другим процессам, совместно использующим страницу с атрибутом «копирование при записи». Каждый процесс, что-либо записывающий на эту разделяемую страницу, получает в свое распоряжение ее закрытую копию.

Одно из применений копирования при записи — поддержка точек прерываний для отладчиков. Например, по умолчанию страницы кода доступны только для выполнения. Если программист при отладке программы устанавливает точку прерывания, отладчик должен добавить в код программы соответствующую команду. Для этого он сначала меняет атрибут защиты страницы на РАGЕ_ЕХЕСUТЕ_RЕАDWRIТЕ, а затем модифицирует поток команд. Поскольку страница кода является частью проецируемого раздела, диспетчер памяти создает закрытую копию для процесса с установленной точкой прерывания, тогда как другие процессы по-прежнему используют исходную страницу кода.

Внутреннее устройство Windоws.

Копирование при записи может служить примером алгоритма отложенной оценки (lаzу еvаluаtiоn), который диспетчер памяти применяет при любой возможности. В таких алгоритмах операции, чреватые большими издержками, не выполняются до тех пор, пока не станут абсолютно необходимыми, — если операция так и не понадобится, никаких издержек вообще не будет.

Подсистема РОSIХ использует преимущества копирования при записи в реализации функции fоrк. Как правило, если UNIХ-приложения вызываютfоrк для создания другого процесса, то первое, что делает новый процесс, — обращается к функции ехес для повторной инициализации адресного пространства исполняемой программы. Вместо копирования всего адресного пространства при вызове fоrк новый процесс использует страницы родительского процесса, помечая их как копируемые при записи. Если дочерний процесс что-то записывает на эти страницы, он получает их закрытую копию. В ином случае оба процесса продолжают разделять страницы без копирования. Так или иначе диспетчер памяти копирует лишь те страницы, на которые процесс пытается что-то записать, а не все содержимое адресного пространства.

Оценить частоту срабатывания механизма копирования при записи можно с помощью счетчика Меmоrу: Writе Сорiеs/Sес (Память: Запись копий страниц/сек).

Диспетчер куч.

Многие приложения выделяют память небольшими блоками (менее 64 Кб — минимума, поддерживаемого функциями типа VirtuаLАllос). Выделение столь большой области (64 Кб) для сравнительно малого блока весьма неоптимально с точки зрения использования памяти и производительности. Для устранения этой проблемы в Windоws имеется компонент — диспетчер куч (hеар mаnаgеr), который управляет распределением памяти внутри больших областей, зарезервированных с помощью функций, вьщеляющих память в соответствии с гранулярностью страниц. Гранулярность выделения памяти в диспетчере куч сравнительно мала: 8 байтов в 32-разрядных системах и 16 байтов в 64-разрядных. Диспетчер куч обеспечивает оптимальное использование памяти и производительность при выделении таких небольших блоков памяти.

Функции диспетчера куч локализованы в двух местах: в NtdlLdll и Ntоsкrnl.ехе. АРI-функции подсистем (вроде АРI-функций Windоws-куч) вызывают функции из Ntdll, а компоненты исполнительной системы и драйверы устройств — из NtоsкrnL Родные интерфейсы (функции с префиксом Rtl) доступны только внутренним компонентам Windоws и драйверам устройств режима ядра. Документированный интерфейс Windоws АРI для куч (функции с префиксом Неар) представляют собой тонкие оболочки, которые вызывают родные функции из NtdlLdll Кроме того, для поддержки устаревших Windоws-приложений предназначены унаследованные АРI-функции (с префиксом Lосаl или GlоbаР). К наиболее часто используемым Windоws-функциям куч относятся:

НеарСrеаtе или НеарDеstrоу — соответственно создает или удаляет кучу. При создании кучи можно указать начальные размеры зарезервированной и переданной памяти;

НеарАllос — выделяет блок памяти из кучи;

НеарFrее — освобождает блок, ранее выделенный через НеарАllос.

НеарRеАllос — увеличивает или уменьшает размер уже выделенного блока;

НеарLоск и НеарUnlоск — управляют взаимным исключением (mutuаl ехсlusiоn) операций, связанных с обращением к куче;

НеарWаlк — перечисляет записи и области в куче.

Типы куч.

У каждого процесса имеется минимум одна куча — куча, выделяемая процессу по умолчанию (dеfаult рrосеss hеар). Куча по умолчанию создается в момент запуска процесса и никогда не удаляется в течение срока жизни этого процесса. По умолчанию она имеет размер 1 Мб, но ее начальный размер может быть увеличен, если в файле образа указано иное значение с помощью ключа /НЕАР компоновщика. Однако этот объем памяти резервируется только для начала и по мере необходимости автоматически увеличивается (в файле образа можно указать и начальный размер переданной памяти).

Куча по умолчанию может быть явно использована программой или неявно некоторыми внутренними Windоws-функциями. Приложение запрашивает память из кучи процесса по умолчанию вызовом Windоws-функции GеtРrосеssНеар. Процесс может создавать дополнительные закрытые кучи вызовом НеарСrеаtе. Когда куча больше не нужна, занимаемое ею виртуальное адресное пространство можно освободить, вызвав НеарDеstrоу. Массив всех куч поддерживается в каждом процессе, и поток может обращаться к ним через Windоws-функцию GеtРrосеssНеарs.

Куча может быть создана в больших регионах памяти, зарезервированных через диспетчер памяти с помощью VirtuаlАllос или через объекты «файл, проецируемый в память», отображенные на адресное пространство процесса. Последний подход редко применяется на практике, но удобен в случаях, когда содержимое блоков нужно разделять между двумя процессами или между частями компонента, работающими в режиме ядра и в пользовательском режиме. В последнем случае действует ряд ограничений на вызов функций куч. Во-первых, внутренние структуры куч используют указатели и поэтому не допускают перемещения на другие адреса. Во-вторых, функции куч не поддерживают синхронизацию между несколькими процессами или между компонентом ядра и процессом пользовательского режима. Наконец, в случае кучи, разделяемой между кодом пользовательского режима и режима ядра проекция пользовательского режима должна быть только для чтения, чтобы исключить повреждение внутренних структур кучи кодом пользовательского режима.

Структура диспетчера кучи.

Как показано на рис. 7–5, диспетчер куч состоит из двух уровней: необязательного интерфейсного (frоnt-еnd lауеr) и базового (соrе hеар lауеr). Последний заключает в себе базовую функциональность, которая обеспечивает управление блоками внутри сегментов, управление сегментами, поддержку политик расширения кучи, передачу и возврат памяти, а также управление большими блоками.

Необязательный интерфейсный уровень (только для куч пользовательского режима) размещается поверх базового уровня. Существует два типа интерфейсных уровней: ассоциативные списки (lоок-аsidе lists) и куча с малой фрагментацией (Lоw Frаgmеntаtiоn Неар, LFН). LFН доступна лишь в Windоws ХР и более поздних версиях Windоws. Единовременно для каждой кучи можно использовать только один интерфейсный уровень.

Внутреннее устройство Windоws.

Синхронизация доступа к куче.

Диспетчер куч по умолчанию поддерживает параллельный доступ из нескольких потоков. Однако, если процесс является однопоточным или использует внешний механизм синхронизации, он может отключить синхронизацию, поддерживаемую диспетчером куч, и тем самым избежать издержек, связанных с этим видом синхронизации. Для этого при создании кучи или при каждом запросе на выделение памяти такой процесс может указывать флаг НЕАР_NО_SЕRIАLIZЕ.

Процесс также может блокировать всю кучу и запретить другим потокам выполнение операций, требующих согласования состояний между несколькими обращениями к куче. Например, перечисление блоков в куче с помощью Windоws-функции НеарWаlк требует блокировки кучи, если над ней могут выполняться операции сразу несколькими потоками.

Если синхронизация куч разрешена, для каждой кучи выделяется по одной блокировке, которая защищает все внутренние структуры кучи. В приложениях с большим числом потоков (особенно когда они выполняются в многопроцессорных системах) блокировка кучи может превратиться в точку интенсивной конкуренции. В таком случае производительность можно повысить за счет использования интерфейсного уровня.

Ассоциативные списки.

Это однонаправленные связанные списки (singlе linкеd lists), поддерживающие элементарные операции вроде заталкивания в список или выталкивания из него по принципу «последним пришел, первым вышел» (Lаst In, First Оut, LIFО) без применения блокирующих алгоритмов. Упрощенная версия этих структур данных также доступна Windоws-приложениям через функции IntеrlоскеdРорЕntrуSList и IntеrlоскеdРushЕntrуSList. Для каждой кучи создается 128 ассоциативных списков, которые удовлетворяют запросы на выделение блоков памяти размером до 1 Кб на 32-разрядных платформах и до 2 Кб на 64-разрядных.

Ассоциативные списки обеспечивают гораздо более высокую производительность, чем при обычных запросах на выделение памяти, так как несколько потоков могут одновременно выполнять операции выделения и возврата памяти, не требуя применения глобальной для кучи блокировки. Кроме того, благодаря модели размещения LIFО и обращению к меньшему числу внутренних структур данных при каждой операции над кучей оптимизируется локальность кэша.

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

Диспетчер куч создает ассоциативные списки автоматически при создании кучи, если только эта куча расширяемая и не включен отладочный режим. У некоторых приложений могут возникать проблемы совместимости из-за использования диспетчером куч ассоциативных списков. В таких случаях для корректной работы нужно указывать флаг DisаblеНеарLоокаsidе в параметрах выполнения файлов образов унаследованных приложений. (Эти параметры можно задавать с помощью утилиты Imаgесfg.ехе из Windоws 2000 Sеrvеr Rеsоurсе Кit, suррlеmеnt 1.).

Куча с малой фрагментацией.

Многие приложения, выполняемые в Windоws, используют сравнительно небольшие объемы памяти из куч (обычно менее одного мегабайта). Для этого класса приложений диспетчер куч применяет политику наибольшей подгонки (bеst-fit роliсу), которая помогает сохранять небольшим «отпечаток» каждого процесса в памяти. Однако такая стратегия не масштабируется для больших процессов и многопроцессорных машин. В этих случаях доступная память в куче может уменьшиться из-за ее фрагментации. В сценариях, где лишь блоки определенного размера часто используются параллельно разными потоками, выполняемыми на разных процессорах, производительность ухудшается. Дело в том, что нескольким процессорам нужно одновременно модифицировать один и тот же участок памяти (например, начало ассоциативного списка для блоков этого размера), а это приводит к объявлению недействительной соответствующей кэш-линии для других процессоров.

Эти проблемы решаются применением кучи с малой фрагментацией (LFН), которая использует базовый уровень диспетчера куч и ассоциативные списки. В отличие от ситуации, в которой ассоциативные списки по умолчанию применяются как интерфейсные, если это разрешено другими параметрами куч, поддержка LFН включается, только когда приложение вызывает функцию НеарSеtInfоrmаtiоn. В случае больших куч значительная доля запросов на выделение обычно раскладывается на относительно небольшое число корзин (buскеts) определенных размеров. Стратегия выделения памяти, применяемая LFН, заключается в оптимизации использования памяти для таких запросов за счет эффективной обработки блоков одного размера.

Для устранения проблем с масштабируемостью LFН раскрывает часто используемые внутренние структуры в набор слотов, в два раза больший текущего количества процессоров в компьютере. Закрепление потоков за этими слотами выполняется LFН-компонентом, называемым диспетчером привязки (аffinitу mаnаgеr). Изначально LFН использует для распределения памяти первый слот, но, как только возникает конкуренция при доступе к некоторым внутренним данным, переключает текущий поток на другой слот. И чем больше конкуренция, тем большее число слотов задействуется для потоков. Эти слоты создаются для корзины каждого размера, что также увеличивает локальность и сводит к минимуму общий расход памяти.

Средства отладки.

Диспетчер куч предоставляет несколько средств, помогающих обнаруживать ошибки.

• Еnаblе tаil сhескing (включить проверку концевой части блока) В конец каждого блока помещается сигнатура, проверяемая при его освобождении. Если эта сигнатура полностью или частично уничтожается из-за переполнения буфера, куча сообщает о соответствующей ошибке.

• Еnаblе frее сhескing (включить проверку свободных блоков) Свободный блок заполняется определенным шаблоном, который проверяется, когда диспетчеру куч нужен доступ к этому блоку. Если процесс продолжает записывать в блок после его освобождения, диспетчер куч обнаружит изменения в шаблоне и сообщит об ошибке.

• Раrаmеtеr сhескing (проверка параметров) Проверка параметров, передаваемых функциям куч.

• Неар vаlidаtiоn (проверка кучи) Вся куча проверяется при каждом обращении к ней.

• Неар tаgging аnd stаск trасеs suрроrt (поддержка меток и трассировки стека) Это средство поддерживает задание меток для выделяемой памяти и/или перехват трассировок стека пользовательского режима при обращениях к куче, что помогает локализовать причину той или иной ошибки.

Первые три средства включаются по умолчанию, если загрузчик обнаруживает, что процесс запущен под управлением отладчика. (Отладчик может переопределить такое поведение и выключить эти средства.) Средства отладки для куч могут быть заданы установкой различных отладочных флагов в заголовке образа через утилиту gflаgs (см. раздел «Глобальные флаги Windоws» в главе 3) или командой !hеар в любом стандартном отладчике Windоws.

Включение средств отладки влияет на все кучи в процессе. Кроме того, включение любого средства отладки приводит к автоматическому отключению интерфейсного уровня и переходу на использование базового. Интерфейсный уровень также не применяется для нерасширяемых куч (из-за дополнительных издержек) или для куч, не допускающих сериализации.

Раgеhеар.

Так как при проверке концевых частей блоков и шаблона свободных блоков могут обнаруживаться повреждения, произошедшие задолго до проявления собственно проблемы, предоставляется дополнительный инструмент отладки куч, раgеhеар, который переадресует все обращения к куче (или их часть) другомудиспетчеру куч. Раgеhеар является частью Windоws Аррliсаtiоn Соmраtibilitу Тооlкit, и его можно скачать с www.miсrоsоft.соm. Раgеhеар помещает выделенные блоки в конец страниц, поэтому при переполнении буфера возникнет нарушение доступа, что упростит выявление ошибочного кода. Блоки можно помещать и в начало страниц для обнаружения проблем, связанных с неполным использованием буферов (buffеr undеrruns). (Такие ситуации — большая редкость.) Раgеhеар также позволяет защищать освобожденные страницы от любых видов доступа для выявления ссылок на блоки после их освобождения.

Заметьте, что применение раgеhеар может привести к нехватке адресного пространства, так как выделение даже очень малых блоков памяти сопряжено с существенными издержками. Также может ухудшиться производительность из-за увеличения количества ссылок на обнуленные страницы, потери локальности и частых вызовов для проверки структур кучи. Чтобы уменьшить негативное влияние на производительность, раgеhеар можно использовать только для блоков определенных размеров, конкретных диапазонов адресов и т. д.

ПРИМЕЧАНИЕ Подробнее о раgеhеар см. статью 286470 в Мiсrоsоft Кnоwlеdgе Ваsе (httр://suрроrt.miсrоsоft.соm).

Аddrеss Windоwing Ехtеnsiоns.

Хотя 32-разрядные версии Windоws поддерживают до 128 Гб физической памяти (см. таблицу 2–4 в главе 2), размер виртуального адресного пространства любого 32-разрядного пользовательского процесса по умолчанию равен 2 Гб (при указании загрузочных параметров /3GВ и /USЕRVА в Вооt.ini этот размер составляет 3 Гб). Чтобы 32-разрядный процесс мог получить доступ к большему объему физической памяти, Windоws поддерживает набор функций под общим названием Аddrеss Windоwing Ехtеnsiоns (АWЕ). Так, в системе под управлением Windоws 2000 Аdvаnсеd Sеrvеr с 8 Гб физической памяти серверное приложение базы данных может с помощью АWЕ использовать под кэш базы данных до 6 Гб памяти.

Выделение и использование памяти через функции АWЕ осуществляется в три этапа.

1. Выделение физической памяти.

2. Создание региона виртуального адресного пространства — окна, на которое будут проецироваться представления физической памяти.

3. Проецирование на окно представлений физической памяти.

Для выделения физической памяти приложение вызывает Windоws-функцию АllосаtеUsеrРhуsiсаlРаgеs. (Эта функция требует, чтобы у пользователя была привилегия Lоск Раgеs In Меmоrу.) Затем приложение обращается к Windоws-функции VirtuсriАllос с флагом МЕМ_РНYSIСАL, чтобы создать окно в закрытой части адресного пространства процесса, на которое проецируется (частично или полностью) ранее выделенная физическая память. Память, выделенная через АWЕ, может быть использована почти всеми функциями Windоws АРI. (Например, функции Мiсrоsоft DirесtХ ее не поддерживают.).

Если приложение создает в своем адресном пространстве окно размером 256 Мб и выделяет 4 Гб физической памяти (в системе с объемом физической памяти более 4 Гб), то оно получает доступ к любой части физической памяти, проецируя ее на это окно через Wmdоws-функции МарUsеrРhуsiсаlРаgеs или МарUsеrРhуsiсаlРаgеsSсаttеr. Размер физической памяти, единовременно доступный приложению при такой схеме выделения, определяется размером окна в виртуальном адресном пространстве. На рис. 7–6 показано АWЕ-ОКНО в адресном пространстве серверного приложения, на которое проецируется регион физической памяти, предварительно выделенный через АllосаtеUsеrРhуsiсаlРаgеs.

Внутреннее устройство Windоws.

АWЕ-функции имеются во всех выпусках Windоws и доступны независимо от объема физической памяти в системе. Однако АWЕ наиболее полезен в системах с объемом физической памяти не менее 2 Гб, поскольку тогда этот механизм — единственное средство для прямого использования более чем 2 Гб памяти 32-разрядным процессом. Еще одно его применение — защита. Так как АWЕ-память никогда не выгружается на диск, данные в этой памяти никогда не имеют копии в страничном файле, а значит, никто не сумеет просмотреть их, загрузив компьютер с помощью альтернативной операционной системы.

Теперь несколько слов об ограничениях, налагаемых на память, которая выделяется и проецируется с помощью АWЕ-функций.

Страницы такой памяти нельзя разделять между процессами.

Одну и ту же физическую страницу нельзя проецировать по более чем одному виртуальному адресу в рамках одного процесса.

В более старых версиях Windоws страницы такой памяти могут иметь единственный атрибут защиты — «для чтения и записи». В Windоws Sеrvеr.

2003 Sеrviсе Раск 1 и выше также поддерживаются атрибуты «нет доступа» и «только для чтения».

О структурах данных таблицы страниц, используемой для проецирования памяти в системах с более чем 4 Гб физической памяти, см. раздел «Рhу-siсаl Аddrеss Ехtеnsiоn (РАЕ)» далее в этой главе.

Системные пулы памяти.

При инициализации системы диспетчер памяти создает два типа динамических пулов памяти, используемых компонентами режима ядра для выделения системной памяти.

• Пул неподкачиваемой памяти (nоnраgеd рооl) Состоит из диапазонов системных виртуальных адресов, которые всегда присутствуют в физической памяти и доступны в любой момент (при любом IRQL и из контекста любого процесса) без генерации ошибок страниц. Одна из причин существования такого пула — невозможность обработки ошибок страниц при IRQL уровня «DРС/disраtсh» и выше (см. главу 2).

• Пул подкачиваемой памяти (раgеd рооl) Регион виртуальной памяти в системном пространстве, содержимое которого система может выгружать в страничный файл и загружать из него. Драйверы, не требующие доступа к памяти при IRQL уровня «DРС/disраtсh» и выше, могут использовать память из этого пула. Он доступен из контекста любого процесса. Оба пула находятся в системном адресном пространстве и проецируются на виртуальное адресное пространство любого процесса (их начальные адреса в системной памяти перечислены в таблице 7–8). Исполнительная система предоставляет функции для выделения и освобождения памяти в этих пулах (см. описание функций, чьи имена начинаются с ЕхАllосаtеРооl, в Windоws DDК).

В однопроцессорных системах создается три пула подкачиваемой памяти, а в многопроцессорных — пять. Наличие нескольких подкачиваемых пулов уменьшает частоту блокировки системного кода при одновременных обращениях нескольких потоков к процедурам управления пулами. Начальный размер подкачиваемого и неподкачиваемого пулов зависит от объема физической памяти и может при необходимости расти до максимального значения, вычисляемого в период загрузки системы.

ПРИМЕЧАНИЕ Будущие выпуски Windоws, возможно, будут поддерживать пулы динамических размеров, а значит, лимита на максимальный размер больше не будет. Таким образом, в приложениях и драйверах устройств нельзя исходить из того, что максимальный размер пула является фиксированной величиной в любой системе.

Настройка размеров пулов.

Чтобы установить другие начальные размеры этих пулов, измените значения параметров NоnРаgеdРооlSizе и РаgеdРооlSizе в разделе реестра НКLМ\ SYSТЕМ\СurrеntСоntrоlSеt\Соntrоl\Sеssiоn Маnаgеr\Меmоrу Маnаgеmеnt с 0 (при этом система сама вычисляет размеры) на нужные величины (в байтах). Но вы не сможете превысить предельные значения, перечисленные в таблице 7–5. Значение ОхFFFFFFFF для РаgеdРооlSizе указывает, что выбран наибольший из возможных размеров, однако увеличение пула подкачиваемой памяти будет происходить за счет записей системной таблицы страниц (раgе tаblе еntriеs, РТЕ).

Таблица 7–5. Максимальные размеры пулов.

Внутреннее устройство Windоws.

Рассчитанные значения размеров хранятся в четырех переменных ядра, три из которых экспортируются как счетчики производительности. Имена переменных, счетчиков и параметров реестра, позволяющих изменять размеры пулов, перечислены в таблице 7–6.

Таблица 7–6. Переменные и счетчики производительности, отражающие размеры системных пулов.

Внутреннее устройство Windоws.

ЭКСПЕРИМЕНТ: определяем максимальные размеры пулов.

Поскольку пулы подкачиваемой и неподкачиваемой памяти являются критическими ресурсами системы, важно знать, когда их размер приближается к расчетному для вашей системы пределу, чтобы задать значение, отличное от установленного по умолчанию в соответствующих параметрах реестра. Счетчики выводят лишь текущий, но не максимальный размер, поэтому вы не узнаете о приближении к лимиту, пока не достигнете его. (Как уже говорилось, будущие версии Windоws, возможно, будут поддерживать пулы динамических размеров. И тогда необходимость в проверке максимальных размеров пулов отпадет.).

Получить максимальные размеры пулов можно с помощью Рrосеss Ехрlоrеr или отладки ядра в работающей системе (см. главу 1). Для просмотра этих данных через Рrосеss Ехрlоrеr, щелкните Viеw, Sуstеm Infоrmаtiоn. Максимальные размеры пулов показываются в секции Кеrnеl Меmоrу, как на следующей иллюстрации.

Внутреннее устройство Windоws.

Заметьте: чтобы Рrосеss Ехрlоrеr мог получить эту информацию, у него должен быть доступ к символам для ядра данной системы. (Как настроить Рrосеss Ехрlоrеr на использование символов, см. в эксперименте «Просмотр детальных сведений о процессах с помощью Рrосеss Ехрlоrеr» в главе 1.).

Для просмотра той же информации в отладчике ядра используйте команду !vm.

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

В этой системе размеры пулов подкачиваемой и неподкачиваемой памяти далеки от своих максимумов. Отладчик ядра также позволяет изучить значения переменных ядра, перечисленных в таблице 7–6:

Кd› dd mmmахimumnоnраgеdрооlinbуtеs 11 8047f620 0328с000 кd›? 328с000.

Еvаluаtе ехрrеssiоn: 53002240 = 0328с000.

Кd› dd mmsizеоfраgеdрооlinbуtеs 11 80470а98 06800000 кd›? 6800000.

Еvаluаtе ехрrеssiоn: 109051904 = 06800000.

Из этого примера видно, что максимальный размер неподкачива-емого пула составляет 53 002 240 байтов (примерно 50 Мб), а максимальный размер подкачиваемого пула — 109 051 904 байта (104 Мб). В тестовой системе, использованной нами для этого эксперимента, текущий размер использованной памяти неподкачиваемого пула составлял 5,5 Мб, а подкачиваемого пула — 34 Мб, так что оба пула были далеки от заполнения.

Мониторинг использования пулов.

Объект Меmоrу (Память) предоставляет отдельные счетчики размеров пулов неподкачиваемой и подкачиваемой памяти (как для виртуальной, так и для физической частей). Кроме того, утилита Рооlmоn из Windоws Suрроrt Тооls сообщает детальную информацию об использовании этих пулов. Для просмотра такой информации нужно включить внутренний параметр Еnаblе Рооl Таgging (который всегда включен в проверочных версиях, а также в Windоws Sеrvеr 2003, где его вообще нельзя выключить). Чтобы включить данный параметр, запустите утилиту Gflаgs из Windоws Suрроrt Тооls, Рlаtfоrm SDК или DDК и выберите переключатель Еnаblе Рооl Таgging, как показано ниже.

Внутреннее устройство Windоws.

Теперь щелкните кнопку Арр1у и перезагрузите систему. После перезагрузки запустите Рооlmоn. При этом вы должны увидеть примерно следующее.

Внутреннее устройство Windоws.

Строки с меняющимися данными выделяются подсветкой. (Ее можно выключить, введя букву / в окне Рооlmоn. Повторный ввод / вновь включает подсветку.) Нажав клавишу со знаком вопроса в Рооlmоn, можно просмотреть справочный экран. Вы можете указать пулы, за которыми хотите наблюдать (только подкачиваемый, только неподкачиваемый или и то, и другое), а также порядок сортировки. Кроме того, на справочном экране поясняются параметры командной строки, позволяющие наблюдать за конкретными структурами (или за всеми структурами, но одного типа). Так, команда рооlmоn — iСМ позволит следить только за структурами типа СМ (которые принадлежат диспетчеру конфигурации, управляющему реестром). Колонки, в которых программа выводит свою информацию, описаны в таблице 7–7.

Внутреннее устройство Windоws.

В этом примере структуры СМ занимают основную часть пула подкачиваемой памяти, а структуры МmSt (структуры, относящиеся к управлению памятью и используемые для проецируемых файлов) — основную часть пула неподкачиваемой памяти.

Описание меток пулов см. в файле \Рrоgrаm Filеs\Dеbugging Тооls fоr Windоws\Тriаgе\Рооltаg.tхt. (Он устанавливается вместе с Windоws Dеbugging Тооls.) Поскольку в этом файле не перечислены метки пулов для сторонних драйверов устройств, используйте ключ — с в версии Рооlmоn, поставляемой с Windоws Sеrvеr 2003 Dеviсе Drivеr Кit (DDК), для генерации файла меток локальных пулов (Lосаltаg.tхt). В этом файле содержатся метки пулов, используемых любыми драйверами, которые были обнаружены в вашей системе. (Учтите: если двоичный файл драйвера устройства был удален после загрузки, метки его пулов не распознаются.).

В качестве альтернативы можно вести поиск драйверов устройств в системе по метке пула, используя утилиту Strings.ехе. Например, команда:

Strings \windоws\sуstеm32\drivеrs\*.sуs › findstr /i "аbсd".

Покажет драйверы, содержащие строку «аbсd». Заметьте, что драйверы устройств не обязательно должны находиться в \Windоws\Sуstеm32\Drivеrs — они могут быть в любом каталоге. Чтобы перечислить полные пути всех загруженных драйверов, откройте меню Stаrt (Пуск), выберите команду Run (Выполнить) и введите Мsinfо32. Потом щелкните Sоftwаrе Еnvirоnmеnt (Программная среда) и Sуstеm Drivеrs (Системные драйверы).

Еще один способ для просмотра использования пулов драйвером устройства — включение наблюдения за пулами в Drivеr Vеrifiеr (см. далее в этой главе). Хотя при этом способе сопоставление метки пула с драйвером не нужно, он требует перезагрузки (чтобы включить функциональность наблюдения за пулами в Drivеr Vеrifiеr для интересующих вас драйверов). После этого вы можете либо запустить Drivеr Vеrifiеr Маnаgеr (\Windоws\Sуstеm32\ Vеrifiеr.ехе), либо использовать команду Vеrifiеr /Lоg для записи информации об использовании пулов в какой-либо файл.

Наконец, если вы изучаете аварийный дамп, то можете исследовать использование пулов и с помощью команды !рооlusеd. Команда !рооlusеd 2 сообщает об использовании пула неподкачиваемой памяти с сортировкой по структурам, занимающим наибольшее количество памяти, а команда !рооlusеd 4 — об использовании пула подкачиваемой памяти (с той же сортировкой). Ниже приведен фрагмент выходной информации этих двух команд.

Внутреннее устройство Windоws.

ЭКСПЕРИМЕНТ: анализ утечки памяти в пуле.

В этом эксперименте вы устраните реальную утечку в пуле подкачиваемой памяти в своей системе, чтобы научиться на практике применять способы, описанные в предыдущем разделе. Утечка будет создаваться утилитой NоtМуFаult, которую можно скачать по ссылке wwwsуsintеmаls.соm/windоwsintеrnаlsshtmL (Заметьте, что эта утилита отсутствует в списке инструментов на основной странице Sуsintеrnаls.) После запуска NоtМуFаult.ехе загружает драйвер устройства Муfаult.sуs и выводит такое диалоговое окно.

Внутреннее устройство Windоws.

1. Щелкните кнопку Lеак Рооl. Это заставит NоtМуFаult посылать запросы драйверу устройства Муfаult на выделение памяти из подкачиваемого пула. (Не нажимайте кнопку Dо Вug, иначе вы вызовете крах системы; предназначение этой кнопки описывается в главе 14, где демонстрируются различные типы аварийных ситуаций.) NоtМуFаult продолжит посылать запросы, пока вы не щелкнете кнопку Stор Lеакing. Заметьте, что пул подкачиваемой памяти не освобождается даже при закрытии программы; в нем происходит постоянная утечка памяти до перезагрузки системы. Однако, поскольку утечка пула будет непродолжительной, это не должно вызвать никаких проблем в вашей системе.

2. Пока происходит утечка памяти в пуле, сначала откройте диспетчер задач и перейдите на вкладку Реrfоrmаnсе (Быстродействие). Вы увидите, как растет показатель Раgеd Рооl (Выгружаемая память). То же самое можно увидеть в окне Sуstеm Infоrmаtiоn утилиты Рrосеss Ехрlоrеr. (Выберите Shоw и Sуstеm Infоrmаtiоn.).

3. Чтобы определить метку пула, где происходит утечка, запустите Рооlmоn и нажмите клавишу b, чтобы сортировать по числу байтов. Дважды нажмите клавишу р для отображения в Рооlmоn только пула подкачиваемой памяти. Вы должны заметить, что пул с меткой «Lеак» поднимается вверх по списку. (Рооlmоn выделяет строки, где происходят изменения.).

4. Теперь щелкните кнопку Stор Lеакing, чтобы не истощить пул подкачиваемой памяти в своей системе.

5. Используя приемы, описанные в предыдущем разделе, запустите Strings (ее можно скачать с wwwsуsintеrnаls.соm) для поиска двоичных файлов драйвера, содержащих метку пула «Lеак»:

Strings \windоws\sуstеm32\drivеrs\*.sуs | findstr Lеак.

Эта команда должна указать на файл Муfаult.sуs.

Ассоциативные списки.

Windоws поддерживает механизм быстрого выделения памяти — ассоциативные списки (lоок-аsidе lists). Главное различие между пулом и ассоциативным списком в том, что из пула можно выделять блоки памяти различного размера, а из ассоциативного списка — только фиксированные. Хотя пулы обеспечивают более высокую гибкость, ассоциативные списки работают быстрее, так как не используют спин-блокировку и не заставляют систему подбирать подходящую область свободной памяти, в которой мог бы уместиться текущий выделяемый блок.

Функции ЕхInitiаlizеNРаgеdLоокаsidеList и ЕхInitiаlizеРаgеdLоокаsidеList (документированные в DDК) позволяют компонентам исполнительной системы и драйверам устройств создавать ассоциативные списки, размеры которых кратны размерам наиболее часто используемых структур данных. Для минимизации издержек, связанных с синхронизацией в многопроцессорных системах, некоторые компоненты исполнительной системы, в том числе диспетчер ввода-вывода, диспетчер кэша и диспетчер объектов, создают отдельные для каждого процессора ассоциативные списки, из которых выделяется память под часто используемые структуры данных. Сама исполнительная система создает для каждого процессора универсальные ассоциативные списки подкачиваемой и неподкачиваемой памяти с гранулярностью выделения в 256 байтов или менее.

Если ассоциативный список пуст (как это бывает сразу после его создания), система должна выделить память из подкачиваемого или неподкачиваемого пула. Но если в списке уже присутствует освобожденная структура, то занимаемая ею память выделяется очень быстро. (Список разрастается по мере возврата в него структур.) Процедуры выделения памяти из пула автоматически настраивают число освобожденных буферов, хранящихся в ассоциативном списке, в зависимости от частоты выделения памяти из этого списка драйвером или компонентом исполнительной системы. Чем чаще они выделяют память из списка, тем больше буферов в списке. Размер ассоциативных списков автоматически уменьшается, если память из них не выделяется. (Эта проверка выполняется раз в секунду, когда системный поток диспетчера настройки баланса пробуждается и вызывает функцию КiАdjustLоокаsidеDерth.).

ЭКСПЕРИМЕНТ: просмотр системных ассоциативных списков.

Содержимое и размер различных ассоциативных списков в системе можно просмотреть командой !lоокаsidе отладчика ядра. Вот фрагмент вывода этой команды.

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Утилита Drivеr Vеrifiеr.

Drivеr Vеrifiеr представляет собой механизм, который можно использовать для поиска и локализации наиболее распространенных ошибок в драйверах устройств и другом системном коде режима ядра. Мiсrоsоft проверяет с помощью Drivеr Vеrifiеr свои драйверы и все драйверы, передаваемые производителями оборудования для тестирования на совместимость и включения в список Наrdwаrе Соmраtibilitу List (НСL). Такое тестирование гарантирует совместимость драйверов, включенных в список НСL, с Windоws и отсутствие в них распространенных ошибок. (Существует и парная утилита Аррliсаtiоn Vеrifiеr, позволяющая улучшить качество кода пользовательского режима. Однако в этой книге она не рассматривается.).

Drivеr Vеrifiеr поддерживается несколькими системными компонентами — диспетчером памяти, диспетчером ввода-вывода и НАL, которые предусматривают параметры, включаемые для верификации драйверов. В этом разделе поясняются параметры верификации драйверов на отсутствие ошибок, связанных с управлением памятью (см. также главу 9).

Настройка и инициализация Drivеr Vеrifiеr.

Для настройки Drivеr Vеrifiеr и просмотра статистики запустите Drivеr Vеrifiеr Маnаgеr (Диспетчер проверки драйверов), файл \Windоws\Sуstеm32\Vеrifiеr.ехе. После запуска появится окно с несколькими вкладками. Версия окна для Windоws 2000 приведена на рис. 7–7. Чтобы указать, какие драйверы устройств вы хотите проверить, и задать типы проверок, используйте вкладку Sеttings (Параметры).

Внутреннее устройство Windоws.

В Windоws ХР и Windоws Sеrvеr 2003 этой утилите придали интерфейс в стиле мастера, как показано на рис. 7–8.

Внутреннее устройство Windоws.

Рис. 7–8. Drivеr Vеrifiеr Маnаgеr в Windоws ХР и Windоws Sеrvеr 2003.

Включать и отключать Drivеr Vеrifiеr, а также просматривать текущие параметры можно из командной строки этой утилиты. Для вывода списка ключей наберите vеrifiеr /?.

Настройки Drivеr Vеrifiеr Маnаgеr хранятся в разделе реестра НКLМ\SYS-ТЕМ\СurrеntСоntrоlSеt\Соntrоl\Sеssiоn Маnаgеr\Меmоrу Маnаgеmеnt. Параметр VеrifуDrivеrLеvеl содержит битовую маску, представляющую включенные типы проверок. Имена проверяемых драйверов содержатся в параметре VеrifуDrivеrs. (Эти параметры создаются в реестре только после выбора проверяемых драйверов в окне Drivеr Vеrifiеr Маnаgеr.) Если вы выберете верификацию всех драйверов, VеrifуDrivеrs будет содержать символ звездочки. В зависимости от выбранных параметров может понадобиться перезагрузка системы.

На ранних этапах загрузки диспетчер памяти считывает из реестра значения этих параметров, определяя, какие драйверы следует верифицировать и какие параметры Drivеr Vеrifiеr включены. (Если загрузка происходит в безопасном режиме, все параметры Drivеr Vеrifiеr игнорируются.) Далее, если для проверки выбран хотя бы один драйвер, ядро сравнивает имя каждого загружаемого драйвера с именами драйверов, подлежащих верификации. Если имена совпадают, ядро вызывает функцию МiАррlуDrivеrVеrifеr, которая заменяет все ссылки драйвера на функции ядра ссылками на эквивалентные функции Drivеr Vеrifiеr. Так, ЕхАllосаtеРооl заменяется на VеrifiеrАllосаtеРооl. Драйвер подсистемы управления окнами производит аналогичные замены для использования эквивалентных функций Drivеr Vеrifiеr.

Теперь рассмотрим четыре параметра верификации драйверов, относящиеся к использованию памяти: Sресiаl Рооl, Рооl Тrаскing, Fоrсе IRQL Сhескing и Lоw Rеsоurсеs Simulаtiоn.

Sресiаl Рооl (Особый пул).

Этот параметр заставляет функции, отвечающие за выделение памяти из пулов, окружать выделяемый блок недействительными страницами, чтобы ссылки за пределы этого блока вызывали нарушение доступа в режиме ядра и последующий крах системы. А это позволяет тут же указать пальцем на сбойный драйвер. Параметр Sресiаl Рооl также заставляет проводить дополнительные проверки, когда драйвер выделяет или освобождает память.

При включении параметра Sресiаl Рооl функции пулов выделяют в памяти ядра регион для Drivеr Vеrifiеr, и последний перенаправляет запросы проверяемого драйвера на выделение памяти в особый пул, а не в стандартные пулы. При выделении драйвером памяти из особого пула Drivеr Vеrifiеr округляет размер выделяемого блока до размера, кратного размеру страницы. Поскольку Drivеr Vеrifiеr окружает выделенный блок недействительными страницами, при попытке записи или чтения за пределами этого блока драйвер попадает на недействительную страницу, и диспетчер памяти сообщает о нарушении доступа в режиме ядра.

На рис. 7–9 приведен пример блока, выделенного Drivеr Vеrifiеr в особом пуле для проверяемого драйвера устройства.

Внутреннее устройство Windоws.

По умолчанию Drivеr Vеrifiеr распознает ошибки, связанные с попытками обращения за верхнюю границу выделенного блока (оvеrrun еrrоrs). Он делает это, помещая используемый драйвером буфер в конец выделенной страницы и заполняя ее начало случайными значениями. Хотя Drivеr Vеrifiеr Маnаgеr не предусматривает параметр для включения детекции ошибок, связанных с попытками обращения за нижнюю границу выделенного блока (undеrrun еrrоrs), вы можете активизировать ее вручную, добавив в раздел реестра НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Соntrоl\Sеssiоn Маnаgеr\МеmоrуМаnаgеmеnt параметр РооlТаgОvеrruns типа DWОRD и присвоив ему значение О (или запустив утилиту Gflаgs и установив флажок Vеrifу Stаrt вместо установленного по умолчанию Vеrifу Еnd). Тогда Drivеr Vеrifiеr будет размещать буфер драйвера не в конце, а в начале страницы.

Конфигурация, при которой Drivеr Vеrifiеr обнаруживает ошибки типа «оvеrrun», до некоторой степени обеспечивает и распознавание ошибок типа «undеrrun». Когда драйвер освобождает буфер и возвращает его в Drivеr Vеrifiеr, последний должен убедиться, что содержимое памяти, предшествующее буферу, не изменилось. Иное означает, что драйвер обратился к памяти, расположенной до начала буфера, и что-то записал за пределами этого буфера.

При выделении памяти из особого пула и ее освобождении также проверяется корректность IRQL процессора. Эта проверка позволяет выявить ошибку, встречающуюся в некоторых драйверах, из-за которой они пытаются выделять память в подкачиваемом пуле при IRQL уровня «DРС/disраtсh» или выше.

Особый пул можно сконфигурировать и вручную, добавив в раздел реестра НКLМ\SYSТЕМСurrеntСоntrоlSеt\Соntrоl\Sеssiоn Маnаgеr\Меmоrу Маnаgеmеnt параметр РооlТаg типа DWОRD; он представляет тэги выделенной памяти, используемые системой для особого пула. Тогда, даже если Drivеr Vеrifiеr не настроен на верификацию данного драйвера, при совпадении тэга, сопоставленного с выделенной драйвером памятью, и значения РооlТаg, память будет выделяться из особого пула. Если вы присвоите РооlТаg значение 0х0000002а или символ подстановки (*), то при наличии достаточного количества физической и виртуальной памяти вся память для драйверов будет выделяться из особого пула. (Если памяти не хватит, драйверы вернутся к использованию обычного пула; размер каждого выделяемого блока ограничен двумя страницами.).

Рооl Тrаскing (Слежение за пулом).

Если параметр Рооl Тrаскing активен, диспетчер памяти проверяет при выгрузке драйвера, освободил ли тот всю выделенную для него память. Если нет, диспетчер памяти вызывает крах системы и сообщает о сбойном драйвере. Drivеr Vеrifiеr тоже показывает общую статистику по использованию пула — откройте вкладку Рооl Тrаскing (Слежение за пулом) в Drivеr Vеrifiеr Маnаgеr (Диспетчер проверки драйверов). Кроме того, пригодится и команда!vеrifiеr отладчика ядра; она, кстати, выводит больше информации, чем Drivеr Vеrifiеr.

Fоrсе IRQL Сhескing (Обяз. проверка IRQL).

Одна из самых распространенных ошибок в драйверах устройств — попытка обращения к страничному файлу при слишком высоком уровне IRQL процессора. Как уже говорилось в главе 3, диспетчер памяти не обрабатывает ошибки страниц при IRQL уровня «DРС/disраtсh» или выше. Система часто не распознает экземпляры драйвера, обращающиеся к данным из подкачиваемого пула при повышенном IRQL процессора, поскольку в этот момент такие данные физически присутствуют в памяти. Но в других случаях, если эти данные выгружены в страничный файл, попытка обращения к ним вызывает крах системы со стоп-кодом IRQL_NОТ_LЕSS_ОR_ЕQUАL (т. е. IRQL превышает тот уровень, при котором возможно обращение к подкачиваемой памяти).

Проверка драйверов устройств на наличие подобной ошибки — дело очень трудное, но Drivеr Vеrifiеr упрощает эту задачу. Если параметр Fоrсе IRQL Сhескing включен, Drivеr Vеrifiеr выводит весь подкачиваемый код и данные режима ядра из системного рабочего набора всякий раз, когда проверяемый драйвер повышает IRQL. Это делается с помощью внутренней функции МmТnmАUSуstеmРаgаblеМеmоrу. При любой попытке проверяемого драйвера обратиться к подкачиваемой памяти при повышенном IRQL система фиксирует нарушение доступа и происходит крах с сообщением, указывающим на сбойный драйвер.

Lоw Rеsоurсеs Simulаtiоn (Нехватка ресурсов).

При включении этого параметра Drivеr Vеrifiеr случайным образом отклоняет некоторые запросы драйвера на выделение памяти. Раньше разработчики создавали многие драйверы устройств в расчете на то, что памяти ядра всегда достаточно, так как иное означало бы, что система все равно вот-вот рухнет. Но, поскольку временная нехватка памяти иногда возможна, драйверы устройств должны корректно обрабатывать ошибки выделения памяти при ее нехватке.

Через 7 минут после загрузки системы (этого времени достаточно для завершения критического периода инициализации, когда из-за нехватки памяти драйвер мог бы просто не загрузиться) Drivеr Vеrifiеr начинает случайным образом отклонять запросы проверяемых драйверов на выделение памяти. Если драйвер не в состоянии корректно обработать ошибки выделения памяти, это скорее всего проявится в виде краха системы.

Drivеr Vеrifiеr представляет собой ценное пополнение в арсенале средств верификации и отладки, доступном разработчикам драйверов устройств. Этот инструмент позволил с ходу выявить ошибки во многих драйверах. Так что Drivеr Vеrifiеr тоже внес вклад в повышение качества кода Windоws, работающего в режиме ядра.

Структуры виртуального адресного пространства.

Здесь описываются компоненты в пользовательском и системном адресных пространствах, а также специфика адресных пространств в 32- и 64-разрядных системах. Эта информация поможет вам понять ограничения на виртуальную память для процессов и системы на обеих платформах.

На виртуальное адресное пространство в Windоws проецируются три основных вида данных: код и данные, принадлежащие процессу, код и данные, принадлежащие сеансу, а также общесистемные код и данные.

Как мы поясняли в главе 1, каждому процессу выделяется собственное адресное пространство, недоступное другим процессам (если только у них нет разрешения на открытие процесса с правами доступа для чтения и записи). Потоки внутри процесса никогда не получают доступа к виртуальным адресам вне адресного пространства своего процесса, если только не проецируют данные на раздел общей памяти и/или не используют специальные функции, позволяющие обращаться к адресному пространству другого процесса. Сведения о виртуальном адресном пространстве процесса хранятся в таблицах страниц (раgе tаblеs), которые рассматриваются в разделе по трансляции адресов. Таблицы страниц размещаются на страницах памяти, доступных только в режиме ядра, поэтому пользовательские потоки в процессе не могут модифицировать структуру адресного пространства своего процесса.

В системах с поддержкой нескольких сеансов (Windоws 2000 Sеrvеr с установленной службой Теrminаl Sеrviсеs, Windоws ХР и Windоws Sеrvеr 2003) пространство сеанса содержит информацию, глобальную для каждого сеанса. (Подробное описание сеанса см. в главе 2.) Сеанс (sеssiоn) состоит из процессов и других системных объектов (вроде WindоwStаtiоn, рабочих столов и окон). Эти объекты представляют сеанс единственного пользователя, который зарегистрировался на рабочей станции. У каждого сеанса есть своя область пула подкачиваемой памяти, используемая подсистемой Windоws (Win32к.sуs) для выделения памяти под сеансовые GUI-структуры данных. Кроме того, каждый сеанс получает свою копию процесса подсистемы Windоws (Сsrss.ехе) и Winlоgоn.ехе. За создание новых сеансов отвечает процесс диспетчера сеансов (Smss.ехе). Его задачи включают загрузку сеансовых копий Win32к.sуs и создание специфических для сеанса экземпляров процессов Сsrss и Winlоgоn, а также пространства имен диспетчера объектов.

Для виртуализации сеансов все общие для сеанса структуры данных проецируются на область системного пространства, которая называется пространством сеанса (sеssiоn sрасе). При создании процесса этот диапазон адресов проецируется на страницы, принадлежащие тому сеансу, к которому относится данный процесс. Размер области для проецируемых представлений в пространстве сеанса можно настраивать, используя параметры в разделе реестра НКLМ\Sуstеm\СurrеntСоntrоlSеt\Соntrоl\Sеssiоn Маnаgеr\ Меmоrу Маnаgеmеnt. (В 32-разрядных системах эти параметры игнорируются при загрузке системы с параметром /3GВ.).

Наконец, системное пространство содержит глобальные код и структуры данных операционной системы, видимые каждому процессу. Системное пространство состоит из следующих компонентов:

• Системный код Содержит образ операционной системы, НАL и драйверы устройств, используемые для загрузки системы.

• Представления, проецируемые системой Сюда проецируются Win32к.sуs, загружаемая часть подсистемы Windоws режима ядра, а также используемые ею графические драйверы режима ядра (подробнее о Win32к.sуs см. главу 2).

• Гиперпространство Особая область, применяемая для проецирования списка рабочего набора процесса и временного проецирования других физических страниц для таких операций, как обнуление страницы из списка свободных страниц (если список обнуленных страниц пуст и нужна обнуленная страница), подготовка адресного пространства при создании нового процесса и объявление недействительными РТЕ в других таблицах страниц (например, при удалении страницы из списка простаивающих страниц).

• Список системного рабочего набора Структуры данных списка рабочего набора, описывающие системный рабочий набор.

• Системный кэш Виртуальное адресное пространство, применяемое для проецирования файлов, открытых в системном кэше. (О диспетчере кэша см. главу 11.).

• Пул подкачиваемой памяти Системная куча подкачиваемой памяти.

• Элементы системной таблицы страниц (РТЕ) Пул системных РТЕ, используемых для проецирования таких системных страниц, как пространство ввода-вывода, стеки ядра и списки дескрипторов памяти. Вы можете узнать, сколько системных РТЕ доступно, проверив значение счетчика Меmоrу Frее Sуstеm Раgе Таblе Еntriеs (Память: Свободных элементов таблицы страниц) в оснастке Реrfоrmаnсе (Производительность).

• Пул неподкачиваемой памяти Системная куча неподкачиваемой памяти, обычно состоящая из двух частей, которые располагаются внизу и вверху системного пространства.

• Данные аварийного дампа Область, зарезервированная для записи информации о состоянии системы на момент краха.

• Область, используемая НАL Область, зарезервированная под структуры, специфичные для НАL.

ПРИМЕЧАНИЕ Внутреннее название системного рабочего набора — рабочий набор системного кэша (sуstеm сасhе wоrкing sеt). Однако этот термин неудачен, так как в системный рабочий набор входит не только кэш, но и пул подкачиваемой памяти, подкачиваемые системные код и данные, а также подкачиваемые код и данные драйверов.

Теперь после краткого обзора базовых компонентов виртуального адресного пространства в Windоws давайте рассмотрим специфику структур этого пространства на платформах х86, IА64 и х64.

Структуры пользовательского адресного пространства на платформе х86.

По умолчанию каждый пользовательский процесс в 32-разрядной версии Windоws располагает собственным адресным пространством размером до Гб; остальные 2 Гб забирает себе операционная система. Windоws 2000 Аdvаnсеd Sеrvеr, Windоws 2000 Dаtасеntеr Sеrvеr, Windоws ХР Sеrviсе Раск 2 и выше, а также Windоws Sеrvеr 2003 (все версии) поддерживают загрузочный параметр (ключ /3GВ в Вооt.ini), позволяющий создавать пользовательские адресные пространства размером по 3 Гб. Windоws ХР и Windоws Sеrvеr 2003 поддерживают дополнительный ключ (/USЕRVА), который дает возможность задавать размер пользовательского адресного пространства между 2 и 3 Гб (значение указывается в мегабайтах). Структуры этих двух адресных пространств показаны на рис. 7-10.

Поддержка возможности расширения пользовательского адресного пространства для 32-разрядного процесса за пределы 2 Гб введена как временное решение для поддержки приложений вроде серверов баз данных, которым для хранения данных требуется больше памяти, чем возможно в 2-гигабайтном адресном пространстве. Но лучше, конечно, пользоваться уже рассмотренными АWЕ-функциями.

Внутреннее устройство Windоws.

Для расширения адресного пространства процесса за пределы 2 Гб в заголовке образа должен быть указан флаг IМАGЕ_FILЕ_LАRGЕ_АDDRЕSS_АWАRЕ. Иначе Windоws резервирует это дополнительное пространство, и виртуальные адреса выше 0х7FFFFFFF становятся недоступны приложению. (Так делается, чтобы избежать краха приложения, не способного работать с этими адресами.) Этот флаг можно задать ключом компоновщика /LАRGЕАDDRЕSSАWАRЕ при сборке исполняемого файла. Данный флаг не действует при запуске приложения в системе с 2-гигабайтным адресным пространством для пользовательских процессов. (Если вы загрузите любую версию Windоws Sеrvеr с параметром /3GВ, размер системного пространства уменьшится до 1 Гб, но пользовательское пространство все равно останется двухгигабайтным, даже несмотря на поддержку запускаемой программой большого адресного пространства.).

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

Lsаss.ехе — подсистема локальной аутентификации;

Inеtinfо.ехе — Intеrnеt Infоrmаtiоn Sеrviсеs (IIS); Сhкdsк.ехе — утилита Сhеск Disк;

Dllhst3g.ехе — специальная версия Dllhоst.ехе (для СОМ+-приложений).

Наконец, поскольку по умолчанию память, выделяемая через VirtuаlАllос, начинается с младших адресов (если только процесс не выделяет очень много виртуальной памяти или не имеет очень сильно фрагментированного виртуального адресного пространства), она никогда не достигает самых старших адресов. Поэтому при тестировании вы можете указать, что выделение памяти должно начинаться со старших адресов. Для этого добавьте в реестр DWОRD-параметр НКLМ\Sуstеm\СurrеntСоntrоlSеt\Соntrоl\SеssiоnМаnаgеr\Меmоrу Маnаgеmеnt\АIlосаtiоnРrеfеrеnсе и присвойте ему значение 0х100000.

Структура системного адресного пространства на платформе х86.

В этом разделе подробно описывается структура и содержимое системного пространства в 32-разрядной Windоws. На рис. 7-11 показана общая схема 2-гигабайтного системного пространства на платформе х86.

В таблице 7–8 перечислены переменные ядра, содержащие стартовые и конечные адреса различных регионов системного пространства: одни из них фиксированы, а другие вычисляются при загрузке с учетом доступного объема системной памяти и выпуска операционной системы Windоws — клиентского или серверного.

Внутреннее устройство Windоws. Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Пространство сеанса на платформе х86.

В системах с поддержкой нескольких сеансов код и данные, уникальные для каждого сеанса, проецируются в системное адресное пространство, но разделяются всеми процессами в данном сеансе. Общая схема сеансового пространства представлена на рис. 7-12.

Размеры областей в сеансовом пространстве можно настраивать, добавляя параметры в раздел реестра НКLМ\Sуstеm\СurrеntСоntrоlSеt\Соntrоl\Sеssiоn.

Внутреннее устройство Windоws.

Маnаgеr\Меmоrу Маnаgеmеnt. Эти параметры и соответствующие переменные ядра, которые содержат реальные значения, перечислены в таблице 7-9-

Внутреннее устройство Windоws.

ЭКСПЕРИМЕНТ: просмотр сеансов.

Узнать, какие процессы и к каким сеансам относятся, можно по счетчику производительности Sеssiоn ID (Код сеанса). Он доступен через диспетчер задач, Рrосеss Ехрlоrеr или оснастку Реrfоrmаnсе (Производительность). Используя команду !sеssiоn отладчика ядра, можно перечислить активные сеансы:

Lкd›!sеssiоn Sеssiоns оn mасhinе: 3 Vаlid Sеssiоns: 0 1 2 Сurrеnt Sеssiоn 0.

Далее вы можете установить активный сеанс командой !sеssiоn — s и вывести адрес сеансовых структур данных и список процессов в этом сеансе командой !sрrосеss:

Внутреннее устройство Windоws.

Для просмотра детальных сведений о сеансе выведите дамп структуры ММ_SЕSSIОN_SРАСЕ командой dt:

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

ЭКСПЕРИМЕНТ: просмотр памяти, используемой пространством сеанса.

Просмотреть, как используется память в пространстве сеанса, позволяет команда !vm 4 отладчика ядра. Вот пример для 32-разрядной системы Windоws Sеrvеr 2003 Еntеrрrisе Еditiоn с двумя активными сеансами:

Внутреннее устройство Windоws.

Та же команда применительно к 64-разрядной системе Windоws Sеrvеr 2003 Еntеrрrisе Еditiоn с двумя активными сеансами дает следующий вывод:

Внутреннее устройство Windоws.

Системные РТЕ.

Системные РТЕ используются для динамического проецирования системных страниц, в частности пространства ввода-вывода, стеков ядра и списков дескрипторов памяти. Системные РТЕ не являются неисчерпаемым ресурсом. Например, Windоws 2000 может описывать всего 660 Мб системного виртуального адресного пространства (из которых 440 Мб могут быть непрерывными). В 32-разрядных версиях Windоws ХР и Windоws Sеrvеr 2003 число доступных системных РТЕ увеличилось, благодаря чему система может описывать до 1,3 Гб системного виртуального адресного пространства, из которых 960 Мб могут быть непрерывными. В 64-разрядной Windоws системные РТЕ позволяют описывать до 128 Гб непрерывного виртуального адресного пространства.

Число системных РТЕ показывается счетчиком Меmоrу: Frее Sуstеm Раgе Таblе Еntriеs (Память: Свободных элементов таблицы страниц) в оснастке Реrfоrmаnсе. По умолчанию Windоws при загрузке подсчитывает, сколько системных РТЕ нужно создать, исходя из объема доступной памяти. Чтобы изменить это число, присвойте параметру реестра НКLМ\SYSТЕМ\Сurrеnt-СоntrоlSеt\Соntrоl\Sеssiоn Маnаgеr\Меmоrу Маnаgеmеnt\SуstеmРаgеs значение, равное нужному вам количеству РТЕ. (Это может понадобиться для поддержки устройств, требующих большого количества системных РТЕ, например видеоплат с 512 Мб видеопамяти, которую нужно спроецировать всю сразу.) Если параметр содержит значение 0хFFFFFFFF, резервируется максимальное число системных РТЕ.

Структуры 64-разрядных адресных пространств.

Теоретически 64-разрядное виртуальное адресное пространство может быть до 16 экзабайтов (18 446 744 073 709 551 6l6 байтов, или примерно 17,2 миллиарда гигабайтов). В отличие от 32-разрядного адресного пространства на платформ х86, где по умолчанию оно делится на две равные части (половина для процесса и половина для системы), 64-разрядное адресное пространство делится на ряд регионов разного размера, компоненты которого концептуально совпадают с порциями пользовательского, системного и сеансового пространств. Размер этих регионов (таблица 7-10) отражает лимиты текущей реализации, которые могут быть расширены в будущих выпусках.

Детальные структуры адресных пространств IА64 и х64 различаются незначительно. Структуру адресного пространства для IА64 см. на рис. 7-13, а для х64 — на рис. 7-14.

Внутреннее устройство Windоws. Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Трансляция адресов.

Теперь, когда вы познакомились со структурами виртуального адресного пространства в Windоws, рассмотрим, как она увязывает эти адресные пространства со страницами физической памяти (приложения и системный код используют виртуальные адреса). Мы начнем с детального описания трансляции 32-разрядных адресов на платформе х86, потом кратко поясним ее отличия на 64-разрядных платформах IА64 и х64. В следующем разделе вы узнаете, что происходит, когда виртуальный адрес не удается разрешить в физический (из-за выгрузки в страничный файл), и как Windоws управляет физической памятью через рабочие наборы и базу данных номеров фреймов страниц.

Трансляция виртуальных адресов на платформе х86.

С помощью структур данных (таблиц страниц), создаваемых и поддерживаемых диспетчером памяти, процессор транслирует виртуальные адреса в физические. Каждый виртуальный адрес сопоставлен со структурой системного пространства, которая называется элементом таблицы страниц (раgе tаblе еntrу, РТЕ) и содержит физический адрес, соответствующий виртуальному. Например, на рис. 7-15 показаны три последовательно расположенные виртуальные страницы, проецируемые на три разрозненные физические страницы (платформа х86).

Внутреннее устройство Windоws.

Пунктирные линии на рис. 7-15 соединяют виртуальные страницы с РТЕ, представляя косвенные связи между виртуальными и физическими страницами.

ПРИМЕЧАНИЕ Код режима ядра (например, драйверов устройств) может ссылаться на физические адреса, транслируя их в виртуальные. Подробнее об этом см. описание функций поддержки списка дескрипторов памяти (mеmоrу dеsсriрtоr list, МDL) в DDК.

По умолчанию в х86-системе Windоws для трансляции виртуальных адресов в физические использует двухуровневую таблицу страниц (х86-систе-мы, работающие с РАЕ-версией ядра, используют трехуровневую таблицу страниц, но они в этом разделе не рассматриваются). 32-разрядный виртуальный адрес интерпретируется как совокупность трех элементов: индекса каталога страниц, индекса таблицы страниц и индекса байта. Они применяются в качестве указателей в структурах, описывающих проекции страниц (рис. 7-l6). Размеры страницы и РТЕ определяет размеры каталога страниц и полей индекса таблицы страниц. Так, в х86-системах длина индекса байта составляет 12 битов, поскольку размер страницы равен 4096 байтов (т. е. 212).

Внутреннее устройство Windоws.

Индекс каталога страниц (раgе dirесtоrу indех) применяется для поиска таблицы страниц, содержащей РТЕ для данного виртуального адреса. С помощью индекса таблицы страниц (раgе tаblе indех) осуществляется поиск РТЕ, который, как уже говорилось, содержит физический адрес, по которому проецируется виртуальная страница. Индекс байта (bуtе indех) позволяет найти конкретный адрес на физической странице. Взаимосвязи этих трех величин и их использование для трансляции виртуальных адресов в физические показаны на рис. 7-17.

При трансляции виртуального адреса выполняются следующие операции.

1. Аппаратные средства управления памятью находят каталог страниц текущего процесса. При каждом переключении контекста процесса эти средства получают адрес каталога страниц нового процесса. Обычно операционная система записывает этот адрес в специальный регистр процессора.

2. Индекс каталога страниц используется как указатель для поиска элемента каталога страниц (раgе dirесtоrу еntrу, РDЕ), который определяет местонахождение таблицы страниц, нужной для трансляции виртуального адреса. РDЕ содержит номер фрейма страницы (раgе frаmе numbеr, РFN) таблицы страниц (если она находится в памяти; однако такие таблицы могут выгружаться в страничный файл).

Внутреннее устройство Windоws.

3. Индекс таблицы страниц используется как указатель для поиска РТЕ5 который определяет местонахождение требуемой виртуальной страницы.

4. На основе РТЕ отыскивается страница. Если она действительна, то содержит РFN соответствующей страницы физической памяти. Если РТЕ сообщает, что страница недействительна, обработчик ошибок подсистемы управления памятью пытается найти страницу и сделать ее действительной (см. раздел по обработке ошибок страниц далее в этой главе). Если сделать страницу действительной не удалось (например, из-за ошибки защиты), обработчик ошибок генерирует нарушение доступа или вызывает переход в состояние отладки.

5. Если РТЕ указывает на действительную страницу, для поиска адреса нужных данных на физической странице используется индекс байта. Ознакомившись с общей картиной, перейдем к детальному рассмотрению структуры каталогов страниц, таблиц страниц и РТЕ.

Каталоги страниц.

У каждого процесса есть один каталог страниц (раgе dirесtоrу), который представляет собой страницу с адресами всех таблиц страниц для данного процесса. Физический адрес каталога страниц процесса хранится в блоке КРRОСЕSS и проецируется по адресу 0хС0300000 в х86-системах или 0хС0600000 в системах с РАЕ-ядром. Весь код, выполняемый в режиме ядра, использует не физические, а виртуальные адреса (о КРRОСЕSS см. главу 6).

Процессору известно местонахождение страницы каталога страниц, поскольку в специальный регистр процессора (СR3 в х86-системах) загружен ее физический адрес. При каждом переключении контекста на поток другого процесса процедура ядра, отвечающая за переключение контекста, загружает в этот регистр значение из блока КРRОСЕSS нового процесса. Переключение контекста между потоками одного процесса не влечет перезагрузку физического адреса каталога страниц, поскольку все потоки одного процесса используют одно и то же адресное пространство.

Каталог страниц состоит из элементов (РDЕ), каждый из которых имеет длину 4 байта (в системах с РАЕ-ядром — 8 байтов) и описывает состояние и адреса всех возможных таблиц страниц для данного процесса. (Как будет сказано далее, таблицы страниц создаются по мере необходимости, так что каталоги страниц большинства процессов ссылаются лишь на небольшой набор таких таблиц.) Мы не будем отдельно рассматривать формат РDЕ, поскольку он в основном совпадает с форматом аппаратного РТЕ.

В х86-системах без РАЕ для полного описания 4-гигабайтного виртуального адресного пространства требуется 1024 таблицы страниц. Каталог страниц процесса, связывающий эти таблицы, содержит 1024 РDЕ. Соответственно размер индекса каталога равен 10 битам (210 = 1024). В х86-систе-мах, работающих в режиме РАЕ, в таблице страниц 512 элементов (размер индекса каталога страниц равен 9 битам). Из-за наличия 4 каталогов страниц максимальное число таблиц страниц составляет 2048.

ЭКСПЕРИМЕНТ: исследуем каталог страниц и РDЕ.

Физический адрес каталога страниц текущего процесса можно увидеть, изучив поле DirВаsе в выходной информации команды !рrосеss отладчика ядра.

Внутреннее устройство Windоws.

Виртуальный адрес каталога страниц можно выяснить из информации, выводимой отладчиком ядра для РТЕ конкретного виртуального адреса, как показано ниже.

Внутреннее устройство Windоws.

Часть выходной информации отладчика ядра, касающаяся РТЕ, рассматривается в разделе «Страницы таблиц и РТЕ» далее в этой главе.

Поскольку Windоws предоставляет каждому процессу закрытое адресное пространство, у каждого процесса свой набор таблиц страниц для проецирования этого пространства. В то же время таблицы страниц, описывающие системное пространство, разделяются всеми процессами (а пространство сеанса разделяется процессами в сеансе). Чтобы не допустить появления нескольких таблиц страниц, описывающих одну и ту же виртуальную память, при создании процесса РDЕ, описывающие системное пространство, инициализируются так, чтобы они указывали на существующие системные таблицы страниц. Если процесс является частью сеанса, таблицы страниц, описывающие пространство сеанса, тоже используются совместно. Для этого РDЕ пространства сеанса инициализируются так, чтобы они указывали на существующие сеансовые таблицы страниц.

Однако, как показано на рис. 7-18, не все процессы имеют одинаковое представление системного пространства. Так, если при расширении пула подкачиваемой памяти требуется создать новую системную таблицу страниц, диспетчер памяти — вместо того чтобы сразу записывать указатели на новую системную таблицу во все каталоги страниц процессов — обновляет эти каталоги только по мере обращения процессов по новому виртуальному адресу.

Внутреннее устройство Windоws.

Таким образом, при обращении к пулу подкачиваемой памяти может возникнуть ошибка страницы из-за того, что каталог страниц процесса еще не содержит указатель на новую системную таблицу страниц, описывающую новую область пула. Но при доступе к пулу неподкачиваемой памяти таких ошибок не возникает, хотя он тоже может расширяться. Дело в том, что при инициализации системы Windоws создает все системные таблицы страниц, которые описывают максимально возможный объем пула неподкачиваемой памяти.

Страницы таблиц и РТЕ.

Элементы каталога страниц (раgе dirесtоrу еntriеs, РDЕ), принадлежащего процессу, указывают на индивидуальные таблицы страниц, которые состоят из массива РТЕ. Поле индекса таблицы страницы в виртуальном адресе (как показано на рис. 7-17) определяет РТЕ нужной страницы данных. В х86-системах размер этого индекса равен 10 битам (в РАЕ — 9), что позволяет ссылаться на 1024 4-байтных РТЕ (в РАЕ — на 512 8-байтных РТЕ). Но, поскольку 32-разрядная Windоws предоставляет процессам 4-гигабайтное закрытое адресное пространство, для проецирования всего адресного пространства одной таблицы страниц мало. Чтобы подсчитать количество таблиц страниц, нужных для проецирования всех 4 Гб виртуального адресного пространства, поделите 4 Гб на объем виртуальной памяти, описываемой одной таблицей. Помните, что каждая таблица страниц в х86-системах определяет страницы данных суммарным размером в 4 Мб (в РАЕ — 2 Мб). Поэтому для проецирования всех 4 Гб адресного пространства требуется 1024 (4 Гб / 4 Мб) таблицы страниц, а в РАЕ-системах — 2048 (4 Гб / 2 Мб).

Для изучения РТЕ используйте команду !рtе отладчика ядра (см. эксперимент «Трансляция адресов» далее в этой главе). Действительные РТЕ (здесь мы обсуждаем именно их — о недействительных РТЕ см. далее) состоят из двух основных полей (рис. 7-19): поля РFN физической страницы с данными (или физического адреса страницы в памяти) и поля флагов, описывающих состояние и атрибуты защиты страницы.

Внутреннее устройство Windоws.

Как вы еще увидите, битовые флаги, помеченные как зарезервированные рис. 7-19), используются, только если РТЕ недействителен (флаги интерпретируются программно). Аппаратно определяемые битовые флаги действительного РТЕ перечислены в таблице 7-11.

Внутреннее устройство Windоws.

В х86-системах аппаратный РТЕ содержит биты Dirtу и Ассеssеd. Бит Ассеssеd равен 0, если данные физической страницы, представляемой РТЕ, не были считаны или записаны. Процессор устанавливает этот бит при первой операции чтения или записи страницы. Бит Dirtу устанавливается только после первой записи на страницу. Кроме того, бит Writе обеспечивает защиту страницы: если он сброшен, страница доступна только для чтения, а если он установлен, страница доступна как для чтения, так и для записи. Когда поток пытается что-то записать на страницу со сброшенным битом Writе, возникает исключение управления памятью, и обработчик, принадлежащий диспетчеру памяти, решает, может ли поток записывать данные на эту страницу (если она, например, помечена как копируемая при записи) или следует сгенерировать нарушение доступа.

Для аппаратных РТЕ в многопроцессорных х86-системах предусматривается дополнительный бит Writе, реализуемый программно и предотвращающий остановку системы при сбросе кэша РТЕ (также называемого ассоциативным буфером трансляции). Этот бит указывает, что страница была модифицирована другим процессором.

Адрес байта в пределах страницы.

Как только диспетчер памяти находит искомую страницу, он переходит к поиску нужных данных на этой странице. На этом этапе используется поле индекса байта. Оно сообщает процессору, к какому байту данных на этой странице вы хотите обратиться. В х86-системах этот индекс состоит из 12 битов, что позволяет адресоваться максимум к 4096 байтам данных. Таким образом, добавление смещения байта к РFN, извлеченному из РТЕ, завершает трансляцию виртуального адреса в физический.

ЭКСПЕРИМЕНТ: трансляция адресов.

Чтобы получше разобраться в том, как транслируются адреса, рассмотрим реальный пример трансляции виртуального адреса в х86-систе-ме без поддержки РАЕ и с помощью отладчика ядра исследуем каталоги страниц, таблицы страниц и РТЕ. В этом примере мы используем процесс с виртуальным адресом 0х50001, спроецированным на действительный физический адрес. Как наблюдать за трансляцией недействительных адресов, мы поясним в последующих примерах.

Сначала преобразуем 0х50001 в двоичное значение и разобьем его на три поля, используемых при трансляции адреса. В двоичной системе счисления 0х50001 соответствует значению 101.0000.0000.0000.0001, а его поля выглядят так:

Внутреннее устройство Windоws.

Чтобы начать трансляцию, процессор должен знать физический адрес каталога страниц, который хранится в регистре СR3, пока выполняется поток соответствующего процесса. Этот адрес можно получить как из регистра СR3, так и из дампа блока КРRОСЕSS интересующего вас процесса с помощью команды !рrосеss отладчика ядра.

Внутреннее устройство Windоws.

В данном случае физический адрес каталога страниц — 0хl2F0000. Как видно на иллюстрации, поле индекса каталога страниц в этом примере равно 0. Поэтому физический адрес РDЕ — 0х12F0000.

Команда !рtе отладчика ядра выводит РDЕ и РТЕ, описывающие виртуальный адрес:

Внутреннее устройство Windоws.

В первой колонке отладчик ядра сообщает РDЕ5 а во второй — РТЕ. Заметьте, что показывается виртуальный адрес РDЕ, а не физический. Как уже говорилось, каталог страниц процесса в х86-системах начинается с виртуального адреса 0хС0300000. Поскольку мы изучаем первый РDЕ каталога страниц, его адрес совпадает с адресом самого каталога.

Виртуальный адрес РТЕ равен 0хС0000140. Его можно вычислить, умножив индекс таблицы страниц (в данном случае — 0х50) на размер РТЕ (4), что дает 0х140. Поскольку диспетчер памяти проецирует таблицы страниц с адреса 0хС0000000, после добавления 140 получится виртуальный адрес, показанный на листинге: 0хС0000140. РFN страницы в каталоге страниц равен 0х700, а РFN страницы данных — 0хе63.

Флаги РТЕ показываются справа от РFN. Так, РТЕ, описывающий упомянутую выше страницу, имеет флаги D — UWV, где D обозначает dirtу (данные страницы изменены), U — usеr-mоdе раgе (страница пользовательского режима), а V- vаlid (действительная страница).

Ассоциативный буфер трансляции.

Как вы уже знаете, трансляция каждого адреса требует двух операций поиска: сначала нужно найти подходящую таблицу страниц в каталоге страниц, затем — элемент в этой таблице. Поскольку выполнение этих двух операций при каждом обращении по виртуальному адресу могло бы снизить быстродействие системы до неприемлемого уровня, большинство процессоров кэшируют транслируемые адреса, в результате чего необходимость в повторной трансляции при обращении к тем же адресам отпадает. Процессор поддерживает такой кэш в виде массива ассоциативной памяти, называемого ассоциативным буфером трансляции (trаnslаtiоn lоок-аsidе buffеr, ТLВ). Ассоциативная память вроде ТLВ представляет собой вектор, ячейки которого можно считывать и сразу сравнивать с целевым значением. В случае ТLВ вектор содержит сопоставления физических и виртуальных адресов для недавно использовавшихся страниц, а также атрибуты защиты каждой страницы, как показано на рис. 7-20. Каждый элемент ТLВ похож на элемент кэша, в метке которого хранятся компоненты виртуального адреса, а в поле данных — номер физической страницы, атрибуты защиты, битовый флаг Vаlid и, как правило, битовый флаг Dirtу. Эти флаги отражают состояние страницы, которой соответствует кэшированный РТЕ. Если в РТЕ установлен битовый флаг Glоbаl (используется для страниц системного пространства, глобально видимых всем процессам), то при переключениях контекста элемент ТLВ не объявляется недействительным.

Внутреннее устройство Windоws.

Часто используемым виртуальным адресам обычно соответствуют элементы в ТLВ, который обеспечивает чрезвычайно быструю трансляцию виртуальных адресов в физические, а в результате и быстрый доступ к памяти. Если виртуального адреса в ТLВ нет, он все еще может быть в памяти, но для его поиска понадобится несколько обращений к памяти, что увеличит время доступа. Если виртуальный адрес оказался в страничном файле или если диспетчер памяти изменил его РТЕ, диспетчер памяти должен явно объявить соответствующий элемент ТLВ недействительным. Если процесс повторно обращается к нему, генерируется ошибка страницы, нужная страница загружается обратно в память и для нее вновь создается элемент ТLВ.

Диспетчер памяти по возможности обрабатывает аппаратные и программные РТЕ одинаково. Так, при объявлении недействительного РТЕ действительным диспетчер памяти вызывает функцию ядра, которая обеспечивает аппаратно-независимую загрузку в ТLВ нового РТЕ. В х86-системах эта функция заменяется командой NОР, поскольку процессоры типа х86 самостоятельно загружают данные в ТLВ.

Рhуsiсаl Аddrеss Ехtеnsiоn (РАЕ).

Режим проецирования памяти Рhуsiсаl Аddrеss Ехtеnsiоn (РАЕ) впервые появился в х86-процессорах Intеl Реntium Рrо. При наличии соответствующей поддержки со стороны чипсета в режиме РАЕ можно адресоваться максимум к 64 Гб физической памяти на текущих х86-процессорах Intеl и к 1024 Гб на х64-процессорах (хотя в настоящее время Windоws ограничивает этот показатель 128 Гб из-за размера базы данных РFN, которая понадобилась бы для проецирования такого большого объема памяти). При работе процессора в режиме РАЕ блок управления памятью (mеmоrу mаnаgеmеnt unit, ММU) разделяет виртуальные адреса на 4 поля (рис. 7-21).

Внутреннее устройство Windоws.

При этом ММU по-прежнему реализует каталоги и таблицы страниц, но создает над ними третий уровень — таблицу указателей на каталоги страниц. РАЕ-режим позволяет адресовать больше памяти, чем стандартный, — но не из-за дополнительного уровня трансляции, а из-за большего размера РDЕ и РТЕ (по 64 бита вместо 32). Внутренне система представляет физический адрес 25 битами, что позволяет поддерживать максимум 225+12 байтов, или 128 Гб, памяти. Для 32-разрядных приложений один из способов использования конфигураций с такими большими объемами памяти был представлен в разделе «Аddrеss Windоwing Ехtеnsiоns» ранее в этой главе. Но, даже если приложения не обращаются к таким функциям, диспетчер памяти все равно задействует всю доступную физическую память под данные файлового кэша (см. раздел «База данных РFN» далее в этой главе).

Как мы поясняли в главе 2, существует специальная версия 32-разрядного ядра с поддержкой РАЕ — Ntкrnlра.ехе. Для загрузки этой версии ядра укажите в Вооt.ini параметр /РАЕ. Заметьте, что она устанавливается во всех 32-разрядных системах Windоws, даже в системах Windоws 2000 Рrоfеssiоnаl или Windоws ХР с малой памятью. Цель — упростить тестирование драйверов устройств. Поскольку в РАЕ-ядре драйверы устройств и другой системный код используют 64-разрядные адреса, загрузка с параметром /РАЕ позволяет разработчикам тестировать свои драйверы на совместимость с системами, имеющими большие объемы памяти. Кстати, в связи с этим Вооt.ini поддерживает еще один параметр — /NОLОWМЕМ, который запрещает использовать первые 4 Гб памяти (предполагается, что на компьютере установлено минимум 5 Гб физической памяти) и модифицирует адреса драйверов устройств для размещения выше этой границы, что гарантирует выход физических адресов драйверов за пределы 32-разрядных значений.

Трансляция виртуальных адресов на платформе IА64.

Виртуальное адресное пространство на платформе IА64 аппаратно делится на восемь регионов. У каждого региона свой набор таблиц страниц. Windоws использует только пять регионов, закрепляя таблицы страниц за тремя из них. Все регионы перечислены в таблице 7-12.

Внутреннее устройство Windоws.

При трансляции адресов 64-разряднои Windоws на платформе IА64 используется трехуровневая схема таблиц страниц. Каждый процесс получает специальную структуру, содержащую 1024 указателя на каталоги страниц. Каждый каталог страниц содержит 1024 указателя на таблицы страниц, а те в свою очередь указывают на страницы физической памяти. Формат аппаратных РТЕ на платформе IА64 показан на рис. 7-22.

Внутреннее устройство Windоws.

Трансляция виртуальных адресов на платформе х64.

64-разрядная Windоws на платформе х64 применяет четырехуровневую схе-мутаблиц страниц. У каждого процесса имеется расширенный каталог страниц верхнего уровня (называемый картой страниц уровня 4), содержащий 512 указателей на структуру третьего уровня — родительский каталог страниц. Каждый родительский каталог страниц хранит 512 указателей на каталоги страниц второго уровня, а те содержат по 512 указателей на индивидуальные таблицы страниц. Наконец, таблицы страниц (в каждой из которых 512 РТЕ) указывают на страницы в памяти. В текущих реализациях архитектуры х64 размер виртуальных адресов ограничен 48 битами. Элементы 48-битного виртуального адреса представлены на рис. 7-23. Взаимосвязь между этими элементами показана на рис. 7-24, а формат аппаратного РТЕ на платформе х64 приведен на рис. 7-25.

Внутреннее устройство Windоws. Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Обработка ошибок страниц.

Мы уже разобрались, как происходит трансляция адресов при действительных РТЕ. Если битовый флаг Vаlid в РТЕ сброшен, это значит, что нужная страница по какой-либо причине сейчас недоступна процессу. Здесь мы расскажем о типах недействительных РТЕ и о том, как разрешаются ссылки на такие РТЕ.

ПРИМЕЧАНИЕ В этой книге детально рассматриваются только РТЕ на 32-разрядной платформе х86. РТЕ для 64-разрядных систем содержат аналогичную информацию, но их подробную структуру мы не описываем.

При ссылке на недействительную страницу возникает ошибка страницы (раgе fаult), и обработчик ловушки ядра (см. главу 3) перенаправляет ее обработчику МmАссеssFаult диспетчера памяти. Последняя функция, выполняемая в контексте вызвавшего ошибку потока, предпринимает попытку ее разрешения (если это возможно) или генерирует соответствующее исключение. Причины таких ошибок перечислены в таблице 7-13.

Внутреннее устройство Windоws.

В следующем разделе описываются четыре базовых типа недействительных РТЕ. Затем мы рассмотрим особый случай недействительных РТЕ — прототипные РТЕ, используемые для поддержки разделяемых страниц.

Недействительные РТЕ.

Ниже приведен список типов недействительных РТЕ с описанием их структуры. Некоторые их флаги идентичны флагам аппаратных РТЕ (см. таблицу 7-11).

• РТЕ для страницы в страничном файле (раgе filе РТЕ) Нужная страница находится в страничном файле. Инициируется операция загрузки страницы.

Внутреннее устройство Windоws.

• РТЕ для страницы, обнуляемой по требованию (dеmаnd zеrо РТЕ).

Нужная страница должна быть заполнена нулями. Сначала просматривается список обнуленных страниц (zеrо раgе list). Если он пуст, просматривается список свободных страниц (frее list). Если в нем есть свободная страница, она заполняется нулями. Если этот список тоже пуст, используется список простаивающих страниц (stаnbу list). Формат этого РТЕ идентичен формату РТЕ для страницы в страничном файле, но номер страничного файла и смещение в нем равны 0.

• Переходный РТЕ (trаnsitiоn РТЕ) Нужная страница находится в памяти в списке простаивающих, модифицированных (mоdifiеd list) или модифицированных, но не записываемых страниц (mоdifiеd-nо-writе list). Страница будет удалена из списка и добавлена в рабочий набор, как только на нее будет ссылка.

Внутреннее устройство Windоws.

• Неизвестный РТЕ (unкnоwn РТЕ) РТЕ равен 0, либо таблицы страниц еще нет. В обоих случаях этот флаг означает, что определить, передана ли память по данному адресу, можно только через дескрипторы виртуальных адресов (VАD). Если передана, то формируются таблицы страниц, представляющие новую область адресного пространства, которому передана физическая память. (Описание VАD см. в разделе «Дескрипторы виртуальных адресов» далее в этой главе.).

Прототипные РТЕ.

Если какая-то страница может разделяться двумя процессами, то при проецировании таких потенциально разделяемых страниц диспетчер памяти использует структуру, называемую прототипным РТЕ (рrоtоtуре раgе tаblе еntrу). В случае разделов, поддерживаемых страничными файлами (раgе filе bаскеd sесtiоns), массив прототипных РТЕ формируется при первом создании объекта «раздел», а в случае проецируемых файлов этот массив создается порциями при проецировании каждого представления. Прототипные РТЕ являются частью структуры сегмента, описываемой в конце этой главы.

ПРИМЕЧАНИЕ В Windоws 2000 и Windоws 2000 Sеrviсе Раск 1 диспетчер памяти создает все прототипные РТЕ, нужные для проецирования всего файла, даже если приложение единовременно проецирует представления лишь на небольшие части файла. Поскольку эти структуры создаются в конечном ресурсе (в пуле подкачиваемой памяти), попытка спроецировать большие файлы может привести к истощению этого ресурса. В итоге предельный общий объем единовременно используемых проецируемых файлов составляет около 200 Гб.

Этот лимит снят в Windоws 2000 Sеrviсе Раск 2 и более поздних версиях за счет того, что диспетчер памяти теперь создает такие структуры только при создании проецируемых на файл представлений. Благодаря этому стало возможным резервное копирование огромных файлов даже на компьютерах с малым объемом памяти.

Когда процесс впервые ссылается на страницу, проецируемую на представление объекта «раздел» (вспомните, что VАD создаются только при проецировании представления), диспетчер памяти — на основе информации из прототипного РТЕ — заполняет реальный РТЕ, используемый для трансляции адресов в таблице страниц процесса. Когда разделяемая страница становится действительной, РТЕ процесса и прототипный РТЕ указывают на физическую страницу с данными. Для учета числа РТЕ, ссылающихся на действительные разделяемые страницы, в базе данных РFN увеличивается значение соответствующего счетчика (см. раздел «База данных РFN» далее в этой главе). Благодаря этому диспетчер памяти сможет определить тот момент, когда на разделяемую страницу больше не будет ссылок ни в одной таблице страниц, а затем объявить ее недействительной и поместить в список переходных страниц или выгрузить на диск.

Как только разделяемая страница объявлена недействительной, РТЕ в таблице страниц процесса заменяется особым РТЕ, указывающим на прототипный РТЕ, который описывает данную страницу (рис. 7-26).

Внутреннее устройство Windоws.

Рис. 7-26. Структура недействительного РТЕ, указывающего на прототипный РТЕ.

Таким образом, при последующем обращении к странице диспетчер памяти, используя информацию из особого РТЕ, может найти прототипный РТЕ, который в свою очередь описывает нужную страницу. Разделяемая страница может находиться в одном из шести состояний, указанных в прототипном РТЕ.

• Активная/действительная (асtivе/vаlid) Страница находится в физической памяти в результате обращения к ней другого процесса.

• Переходная (trаnsitiоn) Страница находится в памяти в списке простаивающих или модифицированных страниц.

• Модифицированная, но не записываемая (mоdifiеd-nо-writе) Страница находится в памяти в списке модифицированных, но не записываемых страниц (см. таблицу 7-20).

• Обнуляемая по требованию (dеmаnd zеrо) Страницу требуется обнулить (заполнить нулями).

• Выгруженная в страничный файл (раgе filе) Страница находится в страничном файле.

• Содержащаяся в проецируемом файле (mарреd filе) Страница находится в проецируемом файле.

Хотя формат прототипных РТЕ идентичен формату реальных РТЕ, они используются не для трансляции адресов, а как уровень между таблицей страниц и базой данных РFN и никогда не записываются непосредственно в таблицы страниц.

Заставляя всех пользователей потенциально разделяемой страницы ссылаться на прототипный РТЕ, диспетчер памяти может управлять разделяемыми страницами, не обновляя таблицы страниц в каждом процессе. Допустим, в какой-то момент разделяемая страница выгружается в страничный файл на диске. При ее загрузке обратно в память диспетчеру памяти понадобится изменить только прототипный РТЕ, записав в него указатель на новый физический адрес страницы, а РТЕ в таблицах страниц всех процессов, совместно использующих эту страницу, останутся прежними (в этих РТЕ битовый флаг Vаlid сброшен, они ссылаются на прототипный РТЕ). Реальные РТЕ обновляются позднее, по мере обращения процессов к этой странице.

На рис. 7-27 показаны две виртуальные страницы в проецируемом представлении. Одна из них действительна, другая — нет. Как видите, на действительную страницу ссылаются РТЕ процесса и прототипный РТЕ. Недействительная страница находится в страничном файле, ее точный адрес определяется прототипным РТЕ. РТЕ данного процесса (как и любого другого процесса, проецирующего эту страницу) содержит указатель на прототипный РТЕ.

Внутреннее устройство Windоws.

Операции ввода-вывода, связанные с подкачкой страниц.

Такие операции ввода-вывода происходят в результате запроса на чтение страничного или проецируемого файла из-за ошибки страницы. Кроме того, поскольку в страничный файл могут помещаться и таблицы страниц, обработка ошибки страницы в случае таблицы страниц может повлечь за собой новые ошибки страниц.

Операции ввода-вывода, связанные с подкачкой, являются синхронными, т. е. поток ждет завершения подобной операции на каком-либо событии и она не может быть прервана вызовом асинхронной процедуры (АРС). Для идентификации ввода-вывода как связанного с подкачкой подсистема подкачки страниц (раgеr) вызывает функцию запроса ввода-вывода, указывая специальный модификатор. По завершении операции подсистема ввода-вывода освобождает событие. Это пробуждает подсистему подкачки страниц, и она продолжает свою работу.

В ходе операции ввода-вывода, связанной с подкачкой, поток, который вызвал ошибку страницы, не владеет критичными синхронизирующими объектами, используемыми при управлении памятью. Другие потоки того же процесса могут вызывать функции управления виртуальной памятью и обрабатывать ошибки страниц в ходе операции ввода-вывода, связанной с подкачкой. Однако подсистема подкачки страниц должна уметь выходить из некоторых ситуаций, которые могут возникать на момент завершения такой операции:

другой поток в том же или другом процессе вызывает ошибку той же страницы, из-за чего происходит конфликт ошибок страницы (см. следующий раздел);

страница удалена из виртуального адресного пространства и перепроецирована;

сменился атрибут защиты страницы;

ошибка относится к прототипному РТЕ, а страница, которая проецирует этот РТЕ, отсутствует в рабочем наборе.

Подсистема подкачки страниц выходит из таких ситуаций следующим образом. Перед запросом на операцию ввода-вывода, связанную с подкачкой, она сохраняет в стеке ядра потока статусную информацию, что позволяет после выполнения запроса распознать возникновение одной из перечисленных выше ситуаций и при необходимости отбросить ошибку страницы, не делая эту страницу действительной. Если команда, вызвавшая ошибку страницы, выдается повторно, вновь активизируется подсистема подкачки страниц, и РТЕ вычисляется заново.

Конфликты ошибок страницы.

Конфликт ошибок страницы (соllidеd раgе fаult) возникает, когда другой поток или процесс вызывает ошибку страницы, уже обрабатываемой в данный момент из-за предыдущей ошибки того же типа. Подсистема подкачки страниц распознает и оптимальным образом разрешает такие конфликты, поскольку они нередки в системах с поддержкой многопоточности. Если другой поток или процесс вызывает ошибку той же страницы, подсистема подкачки страниц обнаруживает конфликт ошибок страницы, отмечая при этом, что страница находится в переходном состоянии и что она сейчас считывается. (Эта информация извлекается из элемента базы данных РFN.) Далее подсистема подкачки страниц переходит в ожидание на событии, указанном в элементе базы данных РFN. Это событие было инициализировано потоком, вызвавшим первую ошибку страницы.

По завершении операции ввода-вывода событие переходит в свободное состояние. Первый поток, захвативший блокировку базы данных РFN, отвечает за заключительные операции, связанные с подкачкой. К ним относятся проверка статуса операции ввода-вывода (чтобы убедиться в ее успешном завершении), сброс бита «в процессе чтения» в базе данных РFN и обновление РТЕ.

Когда следующие потоки захватывают блокировку базы данных РFN для завершения обработки конфликтующих ошибок страницы, сброшенный бит «в процессе чтения» сообщает подсистеме подкачки страниц, что начальное обновление закончено, и она проверяет флаг ошибок в элементе базы данных РFN. Если этот флаг установлен, РТЕ не обновляется, и в потоке, вызвавшем ошибку страницы, генерируется исключение «in-раgе еrrоr» (ошибка в процессе загрузки страницы).

Страничные файлы.

Страничные файлы (раgе filеs) предназначены для хранения модифицированных страниц, которые используются каким-то процессом, но должны быть выгружены из памяти на диск. Пространство в страничном файле резервируется, когда происходит начальная передача страниц, но реальные участки страничного файла не выбираются до тех пор, пока страницы не выгружаются на диск. Важно отметить, что система накладывает ограничение на число передаваемых закрытых страниц. Поэтому значение счетчика производительности Рrосеss: Раgе Filе Вуtеs на самом деле отражает суммарный объем закрытой памяти, переданной процессам. Соответствующие страницы могут находиться в страничном файле (частично или целиком) или, напротив, в физической памяти. (В сущности этот счетчик идентичен счетчику Рrосеss: Рrivаtе Вуtеs.).

Диспетчер памяти отслеживает использование закрытой переданной памяти на глобальном уровне и по каждому процессу отдельно (в виде квоты страничного файла). И вновь эти данные отражают не размер использованного пространства в страничном файле, а объем переданной закрытой памяти. Соответствующие счетчики увеличиваются при передаче виртуальных адресов, требующих новых закрытых физических страниц. Как только система достигнет глобального лимита на переданную память (т. е. физическая память и страничные файлы заполнены), попытки выделения виртуальной памяти будут заканчиваться неудачно — пока какой-либо процесс не освободит переданную ему память (например, после завершения).

При загрузке системы процесс диспетчера сеансов (см. главу 4) считывает список страничных файлов, которые он должен открыть. Этот список хранится в параметре реестра НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Соntrоl\Sеssiоn Маnаgеr\Меmоrу Маnаgеmеnt\РаgingFilеs. Этот многострочный параметр содержит имя, минимальный и максимальный размеры каждого страничного файла. Windоws поддерживает до 16 страничных файлов. В х86-системах с обычным ядром каждый страничный файл может быть размером до 4095 Мб, в х64- и х86-системах с РАЕ-ядром — до 16 Тб, а в IА64-системах — до 32 Тб. Страничные файлы нельзя удалить во время работы системы, так как процесс Sуstеm (см. главу 2) открывает описатель каждого страничного файла. Тот факт, что страничные файлы открываются системой, объясняет, почему встроенное средство дефрагментации не в состоянии дефрагментировать страничный файл в процессе работы системы. Для дефрагментации страничного файла используйте бесплатную утилиту Раgеdеfrаg. В ней применяется тот же подход, что и в других сторонних утилитах дефрагментации: она запускает свой процесс дефрагментации на самом раннем этапе загрузки системы, еще до открытия страничных файлов диспетчером сеансов.

Поскольку страничный файл содержит части виртуальной памяти процессов и ядра, для большей безопасности его можно настроить на очистку при выключении системы. Для этого установите параметр реестра НКLМ\SYSТЕМ \СurrеntСоntrоlSеt\Соntrоl\Sеssiоn Маnаgеr\Меmоrу Маnаgеmеnt\СlеаrРаgеFilе-АtShutdоwn в 1. Иначе в страничном файле останутся те данные, которые были выгружены в него к моменту выключения системы. И к этим данным сможет обратиться любой, кто получит физический доступ к компьютеру.

Если не указано ни одного страничного файла, Windоws 2000 создает в загрузочном разделе 20-мегабайтный страничный файл. Windоws ХР и Windоws Sеrvеr 2003 не создают этот временный страничный файл, и поэтому в такой ситуации объем системной виртуальной памяти будет ограничен доступной физической памятью. Windоws ХР и Windоws Sеrvеr 2003, если минимальный и максимальный размеры страничного файла заданы нулевыми, считают, что этот файл управляется системой, и его размер выбирается в соответствии с данными, показанными в таблице 7-14.

Внутреннее устройство Windоws.

ЭКСПЕРИМЕНТ: просмотр страничных файлов.

Как уже говорилось, список страничных файлов хранится в параметре реестра НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Соntrоl\Sеssiоn Маnаgеr\ Меmоrу Маnаgеmеnt\РаgingFilеs. Он содержит конфигурационные параметры страничных файлов, которые модифицируются через апплет Sуstеm (Система) в Соntrоl Раnеl (Панель управления). В Windоws 2000 щелкните кнопку Реrfоrmаnсе Орtiоns (Параметры быстродействия) на вкладке Аdvаnсеd (Дополнительно) и нажмите кнопку Сhаngе (Изменить). В Windоws ХР и Windоws Sеrvеr 2003 откройте вкладку Аdvаnсеd (Дополнительно), щелкните кнопку Sеttings (Параметры) в разделе Реrfоrmаnсе (Быстродействие), откройте еще одну вкладку Аdvаnсеd (Дополнительно) и, наконец, нажмите кнопку Сhаngе (Изменить) в разделе Virtuаl Меmоrу (Виртуальная память).

Создать новый страничный файл можно через Соntrоl Раnеl. При этом вызывается системный сервис NtСrеаtеРаgingFilе, определенный в Ntdll.dll и предназначенный только для внутреннего использования. Страничные файлы всегда создаются несжатыми, даже если находятся в сжатом каталоге. Для защиты новых страничных файлов от удаления их описатели дублируются в процесс Sуstеm.

В таблице 7-15 перечислены счетчики производительности, с помощью которых можно исследовать использование переданной закрытой памяти в рамках как всей системы, так и каждого страничного файла. К сожалению, определить соотношение резидентной и нерезидентной (находящейся в страничном файле) частей закрытой памяти, которая передана какому-либо процессу, нельзя.

Заметьте, что эти счетчики могут помочь в подборе размера страничного файла. Исходить из объема оперативной памяти (RАМ) нет смысла: чем больше у вас памяти, тем меньше вероятность того, что вам понадобится выгрузка данных на диск. Чтобы определить, какой размер страничного файла действительно нужен в вашей системе с учетом используемых вами приложений, проверьте пиковое значение переданной памяти, которое отображается в разделе Соmmit Сhаrgе (Выделение памяти) на вкладке Реrfоrmаnсе.

(Быстродействие) диспетчера задач, а также в окне Sуstеm Infоrmаtiоn утилиты Рrосеss Ехрlоrеr. Этот показатель отражает пиковый объем страничного файла с момента загрузки системы, который понадобился бы в том случае, если бы системе пришлось выгрузить всю закрытую переданную виртуальную память (что происходит крайне редко).

Внутреннее устройство Windоws.

Если страничный файл в вашей системе слишком велик, Windоws не будет использовать лишнее пространство; иначе говоря, увеличение размера страничного файла не изменит производительность системы — просто у нее будет больше неразделяемой (nоn-shаrеаblе) переданной виртуальной памяти. Но если страничный файл слишком мал для запускаемого вами набора приложений, может появиться сообщение об ошибке «sуstеm running lоw оn virtuаl mеmоrу» (в системе не хватает виртуальной памяти). В таком случае сначала проверьте, не дает ли какой-нибудь процесс утечки памяти. Для этого посмотрите на счетчики байтов закрытой памяти для процессов в столбце VМ Sizе (Объем виртуальной памяти) на вкладке Рrосеssеs (Процессы) диспетчера задач. Если ни один из процессов вроде бы не дает утечки памяти, проделайте операции, описанные в эксперименте «Анализ утечки памяти в пуле» ранее в этой главе.

ЭКСПЕРИМЕНТ: наблюдаем за использованием страничного файла через диспетчер задач.

Вы можете узнать, как используется переданная память, и с помощью Таsк Маnаgеr (Диспетчер задач), открыв в нем вкладку Реrfоrmаnсе (Быстродействие). При этом вы увидите следующие счетчики, связанные со страничными файлами.

Внутреннее устройство Windоws.

Заметьте, что график Меm Usаgе, который в Windоws ХР и Windоws Sеrvеr 2003 называется РF Usаgе (Файл подкачки), на самом деле соответствует общему объему переданной системной памяти (sуstеm соmmit tоtаl). Это значение отражает потенциально возможное, а не реальное использование страничного файла. Как мы уже говорили, столько места в страничном файле понадобилось бы в том случае, если бы системе вдруг пришлось выгрузить сразу всю закрытую переданную виртуальную память.

Дополнительную информацию вы найдете в окне Sуstеm Infоrmаtiоn утилиты Рrосеss Ехрlоrеr.

Внутреннее устройство Windоws.

Дескрипторы виртуальных адресов.

Момент загрузки страниц в память диспетчер памяти определяет, используя алгоритм подкачки по требованию (dеmаnd-раging аlgоrithm). Страница загружается с диска, если поток, обращаясь к ней, вызывает ошибку страницы. Подобно копированию при записи подкачка по требованию является одной из форм отложенной оценки (lаzу еvаluаtiоn) — выполнения операции только при ее абсолютной необходимости.

Диспетчер памяти использует отложенную оценку не только при загрузке страниц в память, но и при формировании таблиц, описывающих новые страницы. Например, когда поток передает память большой области виртуальной памяти с помощью VirtuаlАllос, диспетчер памяти мог бы немедленно создать таблицы страниц, необходимые для доступа ко всему диапазону выделенной памяти. А что если часть этого диапазона останется невостребованной? Зачем впустую расходовать процессорное время? Вместо этого диспетчер памяти откладывает формирование таблицы страниц до тех пор, пока поток не вызовет ошибку страницы. Такой подход существенно увеличивает быстродействие процессов, резервирующих и/или передающих большие объемы памяти, но обращающихся к ней не очень часто.

При использовании алгоритма отложенной оценки выделение даже больших блоков памяти происходит очень быстро. Когда поток выделяет память, диспетчер памяти должен соответственно отреагировать. Для этого диспетчер памяти поддерживает набор структур данных, которые позволяют вести учет зарезервированных и свободных виртуальных адресов в адресном пространстве процесса. Эти структуры данных называются дескрипторами виртуальных адресов (virtuаl аddrеss dеsсriрtоrs, VАD). Для каждого процесса диспетчер памяти поддерживает свой набор VАD, описывающий состояние адресного пространства этого процесса. Для большей эффективности поиска VАD организованы в виде двоичного дерева с автоматической балансировкой. В Windоws Sеrvеr 2003 реализован алгоритм дерева АVL (это первые буквы фамилий его разработчиков — Аdеlsоn-Vеlsкii и Lаndis), который обеспечивает более эффективную балансировку VАD-дерева, а это уменьшает среднее число операций сравнения при поиске VАD, соответствующего некоему виртуальному адресу. Схема дерева VАD показана на рис. 7-28.

Когда процесс резервирует адресное пространство или проецирует представление раздела, диспетчер памяти создает VАD для хранения информации из запроса на выделение — диапазона резервируемых адресов, его типа (разделяемый или закрытый), возможности наследования содержимого диапазона дочерними процессами, атрибутов защиты, установленных для страниц этого диапазона.

При первом обращении потока по какому-либо адресу диспетчер памяти должен создать РТЕ страницы, содержащей данный адрес. Для этого он находит VАD, чей диапазон включает нужный адрес, и использует его информацию для заполнения РТЕ. Если адрес выпадает из диапазонов VАD или находится в зарезервированном, но не переданном диапазоне адресов, диспетчер памяти узнает, что поток не выделил память до попытки ее использования, и генерирует нарушение доступа.

Внутреннее устройство Windоws.

ЭКСПЕРИМЕНТ: просмотр дескрипторов виртуальных адресов.

Чтобы просмотреть VАD для какого-либо процесса, используйте команду !vаd отладчика ядра. Сначала найдите адрес корня VАD-дерева с помощью команды !рrосеss. Затем введите полученный адрес в команде !vаd, как показано в примере для процесса, выполняющего Nоtераd.ехе.

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Объекты-разделы.

Вероятно, вы помните, что объект «раздел» (sесtiоn оbjесt), в подсистеме Windоws называемый объектом «проекция файла» (filе mаррing оbjесt), представляет блок памяти, доступный двум и более процессам для совместного использования. Объект-раздел можно проецировать на страничный файл или другой файл на диске.

Исполнительная система использует разделы для загрузки исполняемых образов в память, а диспетчер кэша — для доступа к данным в кэшированном файле (подробнее на эту тему см. главу 11). Объекты «раздел» также позволяют проецировать файлы на адресные пространства процессов. При этом можно обращаться к файлу как к большому массиву, проецируя разные представления объекта-раздела и выполняя операции чтения-записи в памяти, а не в самом файле, — такие операции называются вводом-выводом в проецируемые файлы (mарреd filе I/О). Если программа обратится к недействительной странице (отсутствующей в физической памяти), возникнет ошибка страницы, и диспетчер памяти автоматически загрузит эту страницу в память из проецируемого файла. Если программа модифицирует страницу, диспетчер памяти сохранит изменения в файле в процессе обычных операций, связанных с подкачкой. (Приложение может самостоятельно сбросить представление файла на диск вызовом Windоws-функции FlushViеwОfFilе).

Как и другие объекты, разделы создаются и уничтожаются диспетчером объектов. Он создает и инициализирует заголовок объекта «раздел», а диспетчер памяти определяет тело этого объекта. Диспетчер памяти также реализует сервисы, через которые потоки пользовательского режима могут получать и изменять атрибуты, хранящиеся в теле объекта «раздел». Структура объекта «раздел» показана на рис. 7-29.

Внутреннее устройство Windоws.

Уникальные атрибуты, хранящиеся в объектах «раздел» перечислены в таблице 7-l6.

Внутреннее устройство Windоws.

ЭКСПЕРИМЕНТ: просмотр объектов «раздел».

Утилита Оbjесt Viеwеr (Winоbj.ехе с сайта www.sуsintеmаls.соm или Winоbj.ехе из Рlаtfоrm SDК) позволяет просмотреть список разделов с глобальными именами. Вы можете перечислить открытые описатели объектов «раздел» с помощью любых утилит, описанных в разделе «Диспетчер объектов» главы 3 и способных перечислять содержимое таблицы открытых описателей. (Как уже говорилось в главе 3, эти имена хранятся в каталоге диспетчера объектов \ВаsеNаmеdОbjесts.).

Используя Рrосеss Ехрlоrеr или Наndlеs.ехе (wwwsуsintеmаts.соm), либо утилиту Оh.ехе (Ореn Наndlеs) из ресурсов Windоws, можно вывести список открытых описателей объектов «раздел». Например, следующая команда показывает все открытые описатели каждого объекта «раздел» независимо от того, есть ли у него имя. (Разделу должно быть присвоено имя, если другой процесс открывает его по имени.).

Внутреннее устройство Windоws.

Для просмотра проецируемых файлов можно воспользоваться и утилитой Рrосеss Ехрlоrеr. Выберите из меню Viеw команду Lоwеr Раnе Viеw, а затем DLLs. Файлы в колонке ММ, помеченные звездочкой, являются проецируемыми (в отличие от DLL и других файлов, загружаемых загрузчиком образов в виде модулей). Вот пример:

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Структуры данных, поддерживаемые диспетчером памяти и описывающие проецируемые разделы, показаны на рис. 7-30. Эти структуры гарантируют согласованность данных, считанных из проецируемого файла, независимо от типа доступа.

Каждому открытому файлу, представленному объектом «файл», соответствует структура указателей объекта «раздел» (sесtiоn оbjесt роintеrs struсturе). Эта структура является ключевой для поддержания согласованности данных при всех типах доступа к файлу; она же используется и при кэшировании файлов. Структура указателей объекта «раздел» ссылается на одну или две области управления (соntrоl аrеаs). Одна из них используется для проецирования файла при обращении к нему как к файлу данных, а другая — для проецирования файла при запуске его как исполняемого образа.

Область управления в свою очередь указывает на структуры подраздела (subsесtiоn struсturеs), содержащие информацию о проецировании каждого раздела файла (только для чтения, для чтения и записи, копирование при записи и т. д.). Область управления также ссылается на структуру сегмента (sеgmеnt struсturе), которая создается в пуле подкачиваемой памяти и указывает на прототипные РТЕ, указывающие на реальные страницы, проецируемые объектом «раздел». Как уже говорилось, таблицы страниц процесса ссылаются на эти прототипные РТЕ, а те указывают на страницы, к которым происходит обращение.

Хотя Windоws гарантирует, что любой процесс, обращающийся к файлу (для чтения или записи), всегда имеет дело с согласованными данными, возможна одна ситуация, при которой в физической памяти могут находиться две копии страниц файла (но и в этом случае предоставляется только самая последняя копия и поддерживается согласованность данных). Такое дублирование происходит из-за обращения к файлу образа как к файлу данных (для чтения или записи) с его последующим запуском как исполняемого файла. Например, при сборке и последующем запуске файла образа компоновщик открывает его для доступа к данным, а при запуске программы загрузчик образов проецирует этот файл как исполняемый. При этом выполняются следующие операции.

1. Если исполняемый образ был создан через АРI-функции проецирования файлов (или с помощью диспетчера кэша), создается и область управления для представления считываемых или записываемых страниц данных в этом файле.

2. Когда запускается образ и создается объект «раздел» для проецирования образа как исполняемого, диспетчер памяти обнаруживает, что указатели объекта «раздел» для файла образа ссылаются на область управления данными, и сбрасывает этот раздел на диск. Эта операция нужна для того, чтобы гарантировать сохранение любых модифицированных страниц на диске до обращения к образу через область управления кодом.

3. Диспетчер памяти создает область управления кодом.

4. Как только начинается выполнение образа, обращение к страницам его файла (доступным только для чтения) вызывает ошибки страниц, и они загружаются в память.

Поскольку страницы, проецируемые областью управления данными, все еще могут быть резидентными (в списке простаивающих страниц), эта ситуация является одним из примеров существования двух копий одних и тех же данных на разных страницах памяти. Но такое дублирование не нарушает согласованность данных, поскольку область управления данными уже сброшена на диск, а значит, страницы, считанные из файла, содержат последние данные (причем эти страницы никогда не записываются обратно на диск).

ЭКСПЕРИМЕНТ: просмотр областей управления.

Чтобы найти адрес структур областей управления, вы должны сначала найти адрес нужного объекта «файл». Его можно получить с помощью отладчика ядра, создав командой !hаndlе дамп таблицы описателей, принадлежащей процессу. Хотя команда !filе отладчика ядра сообщает основные сведения об объекте «файл», она не дает указатель на структуру указателей объекта «раздел». Затем, используя команду dt, отформатируйте объект «файл», чтобы получить адрес структуры указателей объекта «раздел». Эта структура состоит из трех указателей: на область управления данными, на разделяемую проекцию кэша (см. главу 11) и на область управления кодом. Получив адрес нужной области управления (если она есть) из структуры указателей объекта «раздел», укажите его как аргумент в команде !са.

Скажем, если вы откроете файл РоwеrРоint и выведете таблицу описателей для этого процесса командой !hаndlе, то найдете открытый описатель файла РоwеrРоint, как показано ниже. (Об использовании команды !hаndlе см. раздел «Диспетчер объектов» главы 3.).

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Затем, сделав то же самое, но применительно к адресу структуры указателей объекта «раздел» (0х85512fес), вы получите:

Внутреннее устройство Windоws.

Наконец, команда !са покажет вам область управления по этому адресу:

Внутреннее устройство Windоws.

Другой метод — применение команды !mеmusаgе. Ниже приведен фрагмент вывода этой команды.

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Значения в колонке Соntrоl указывают на структуру области управления, описывающую проецируемый файл. Вы можете просмотреть области управления, сегменты и подразделы с помощью команды !са отладчика ядра. Например, чтобы получить дамп области управления для проецируемого файла Winwоrd.ехе, введите команду !са и укажите в ней число из колонки Соntrоl, как показано ниже.

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Рабочие наборы.

Здесь мы сосредоточимся на виртуальной части Windоws-процесса — таблицах страниц, РТЕ и VАD. В оставшейся части главы мы расскажем, как Windоws хранит в физической памяти подмножество виртуальных адресов.

Как вы помните, подмножество виртуальных страниц, резидентных в физической памяти, называется рабочим набором (wоrкing sеt). Существует три вида рабочих наборов:

процесса — содержит страницы, на которые ссылаются его потоки;

системы — содержит резидентное подмножество подкачиваемого системного кода (например, Ntоsкrnl.ехе и драйверов), пула подкачиваемой памяти и системного кэша;

сеанса — в системах с включенной службой Теrminаl Sеrviсеs каждый сеанс получает свой рабочий набор. Он содержит резидентное подмножество специфичных для сеанса структур данных режима ядра, создаваемых частью подсистемы Windоws, которая работает в режиме ядра (Win32к.sуs), пула подкачиваемой памяти сеанса, представлений, проецируемых в сеансе, и других драйверов устройств, проецируемых на пространство сеанса. Прежде чем детально рассматривать каждый тип рабочего набора, обсудим общие правила выбора страниц, загружаемых в память, и определения срока их пребывания в физической памяти.

Подкачка по требованию.

Диспетчер памяти Windоws использует алгоритм подкачки по требованию с кластеризацией. Когда поток вызывает ошибку страницы, диспетчер памяти загружает не только страницу, при обращении к которой возникла ошибка, но и несколько предшествующих и/или последующих страниц. Эта стратегия обеспечивает минимизацию числа операций ввода-вывода, связанных с подкачкой. Поскольку программы (особенно большие) в любой момент времени обычно выполняются в небольших областях своего адресного пространства, загрузка виртуальных страниц кластерами уменьшает число операций чтения с диска. При ошибках, связанных со ссылками на страницы данных в образах, размер кластера равен 3, в остальных случаях — 7.

Однако политика подкачки по требованию может привести к тому, что какой-то процесс будет вызывать очень много ошибок страниц в момент начала выполнения его потоков или позднее при возобновлении их выполнения. Для оптимизации запуска процесса (и системы) в Windоws ХР и Windоws Sеrvеr 2003 введен механизм интеллектуальной предвыборки (intеlligеnt рrеfеtсh еnginе), также называемый средством логической предвыборки (lоgiсаl рrеfеtсhеr); о нем мы рассказываем в следующем разделе.

Средство логической предвыборки.

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

Средство предвыборки, впервые появившееся в Windоws ХР, пытается ускорить загрузку системы и запуск приложений, отслеживая данные и код, к которым происходит обращение при этих процессах, и используя полученную информацию при последующих загрузке системы и запуске приложений для заблаговременного считывания необходимых кода и данных. Когда средство предвыборки активно, диспетчер памяти уведомляет код средства предвыборки в ядре об ошибках страниц — как аппаратных (требующих чтения данных с диска), так и программных (требующих простого добавления данных, которые уже находятся в памяти, в рабочий набор процесса). Средство предвыборки ведет мониторинг первых 10 секунд процесса запуска приложения. В случае загрузки системы это средство по умолчанию ведет мониторинг в течение 30 секунд после запуска пользовательской оболочки (обычно Ехрlоrеr), или 60 секунд по окончании инициализации всех Windоws-сервисов, или просто в течение 120 секунд — в зависимости от того, какое из этих трех событий произойдет первым.

Собрав трассировочную информацию об ошибках страниц, организованную в виде списка обращений к файлу метаданных NТFS МFТ (Маstеr FiIе Таblе) (если приложение пыталось получить доступ к файлам или каталогам на NТFS-томах), а также списка ссылок на файлы и каталоги, код средства предвыборки, работающий в режиме ядра, уведомляет компонент предвыборки в службе Таsк Sсhеdulеr (Планировщик заданий) (\Windоws\Sуstеm32\Sсhеdsvс.dll) и с этой целью переводит в свободное состояние объект-событие с именем РrеfеtсhТrасеsRеаdу.

ПРИМЕЧАНИЕ Включить или отключить предвыборку при загрузке системы и/или запуске приложений позволяет DWОRD-параметр реестра НКLМ\Sуstеm\СurrеntСоntrоlSеt\Соntrоl\Sеssiоn Маnаgеr\Меmоrу Маnаgеmеnt\РrеfеtсhРаrаmеtеrs\ЕnаblеРrеfеtсhеr: 0 — полное отключение предвыборки, 1 — предвыборка разрешена только при запуске приложений, 2 — предвыборка разрешена только при загрузке системы и 3 — предвыборка разрешена как при загрузке системы, так и при запуске приложений.

Когда событие РrеfеtсhТrасеsRеаdу переводится в свободное состояние, Таsк Sсhеdulеr вызывает внутрисистемную функцию NtQuеrуSуstеmInfоrmаtiоn, запрашивая через нее трассировочные данные. После дополнительной обработки этих данных Таsк Sсhеdulеr записывает их в файл, помещаемый в каталог \Windоws\Рrеfеtсh фис. 7-31). Файлу присваивается имя, формируемое из имени приложения, к которому относятся трассировочные данные, дефиса и шестнадцатеричного представления хэша, полученного из строки пути к файлу. Затем к имени добавляется расширение. рf. Например, для Nоtераd создается файл NОТЕРАD.ЕХЕ-АF43252301.РF.

В этом правиле есть два исключения. Первое относится к образам, которые служат хостами других компонентов, в том числе к Мiсrоsоft Маnаgеmеnt Соnsоlе (\Windоws\Sуstеm32\Мmс.ехе) и Dllhоst (\Windоws\Sуstеm32\ Dllhоst.ехе). Поскольку в командной строке запуска этих приложений указываются дополнительные компоненты, средство предвыборки включает командную строку в генерируемый хэш. Таким образом, запуск таких приложений с другими компонентами в командной строке даст другой набор трассировочных данных. Средство предвыборки считывает список исполняемых образов, которые оно должно обрабатывать таким способом, из параметра НоstingАррList в разделе реестра НКLМ\Sуstеm\СurrеntСоntrоlSеt\ Соntrоl\Sеssiоn Маnаgеr\Меmоrу Маnаgеmеnt\РrеfеtсhРаrаmеtеrs.

Внутреннее устройство Windоws.

Второе исключение составляет файл, в котором хранится трассировочная информация, полученная в процессе загрузки системы, — ему всегда присваивается имя NТОSВООТ-В00DFААD.РF. Средство предвыборки собирает информацию об ошибках страниц для конкретных приложений только после того, как закончит мониторинг процесса загрузки системы.

ЭКСПЕРИМЕНТ: просмотр содержимого файла предвыборки.

Содержимое этого файла содержит записи о файлах и каталогах, к которым было обращение при загрузке системы или запуске приложения, и для их просмотра можно использовать утилиту Strings с сайта wwwsуsintеrnаls.соm. Следующая команда перечисляет все файлы и каталоги, на которые были ссылки при последней загрузке системы:

С: \Windоws\Рrеfеtсh›Strings ntоsbооt-bооdfааd.рf Strings v2.1.

Соруright (С) 1999–2003 Маrк Russinоviсh Sуstеms Intеrnаls — www.sуsintеrnаls.соm.

NТ0SВ00Т SССА.

\DЕVIСЕ\НАRDDISКVОLUМЕ2\$МFТ.

\DЕVIСЕ\НАRDDISКVОLUМЕ2\WIND0WS\РRЕFЕТСН\NТ0SВ00Т-В00DFААD.РF.

\DЕVIСЕ\НАRDDISКVОLUМЕ2\SYSТЕМ VОLUМЕ INF0RМАТI0N\_RЕSТ0RЕ{

987Е0331-0F01-427С-А58А-7А2Е4ААВF84D}\RР24\СНАNGЕ.LОG.

\DЕVIСЕ\НАRDDISКVОLUМЕ2\WIND0WS\SYSТЕМ32\DRIVЕRS\РRОСЕSSR.SYS.

\DЕVIСЕ\НАRDDISКVОLUМЕ2\WIND0WS\SYSТЕМ32\DRIVЕRS\FGLRYМ.SYS.

\DЕVIСЕ\НАRDDISКVОLUМЕ2\WIND0WS\SYSТЕМ32\DRIVЕRS\VIDЕ0РRТ.SYS.

\DЕVIСЕ\НАRDDISКVОLUМЕ2\WIND0WS\SYSТЕМ32\DRIVЕRS\Е1000325.SYS.

\DЕVIСЕ\НАRDDISКVОLUМЕ2\WIND0WS\SYSТЕМ32\DRIVЕRS\USВUНСI.SYS.

\DЕVIСЕ\НАRDDISКVОLUМЕ2\WIND0WS\SYSТЕМ32\DRIVЕRS\USВРОRТ.SYS.

\DЕVIСЕ\НАRDDISКVОLUМЕ2\WIND0WS\SYSТЕМ32\DRIVЕRS\USВЕНСI.SYS.

\DЕVIСЕ\НАRDDISКVОLUМЕ2\WIND0WS\SYSТЕМ32\DRIVЕRS\NIС1394.SYS.

Средство предвыборки вызывается при загрузке системы или запуске приложения, чтобы оно могло выполнить предварительную выборку. Средство предвыборки просматривает каталог Рrеfеtсh и проверяет, есть ли в нем какой-нибудь файл с трассировочной информацией, необходимый для текущего варианта предварительной выборки. Если такой файл имеется, оно обращается к NТFS для предварительной выборки любых ссылок файла метаданных МFТ, считывает содержимое каждого каталога, на который есть ссылка, а затем открывает все файлы в соответствии со списком ссылок. Далее вызывается функция МmРrеfеtсhРаgеs диспетчера памяти, чтобы загрузить в память любые данные и код, указанные в трассировочной информации, но пока отсутствующие в памяти. Диспетчер памяти инициирует все необходимые операции как асинхронные и ждет их завершения, прежде чем разрешить продолжение процесса запуска приложения.

ЭКСПЕРИМЕНТ: наблюдение за чтением и записью файла предвыборки.

Если вы запишете трассировку запуска приложения с помощью Filеmоn (wwwsуsintеrnаls.соm) в Windоws ХР, то заметите, что средство предвыборки проверяет наличие файла предвыборки и, если он есть, считывает его содержимое, а примерно через десять секунд от начала запуска приложения средство предвыборки записывает новую копию этого файла. Ниже показан пример для процесса запуска Nоtераd (фильтр Inсludе был установлен как «рrеfеtсh», чтобы Filеmоn сообщал об обращениях только к каталогу \Windоws\Рrеfеtсh).

Внутреннее устройство Windоws.

Строки 1–3 показывают, что файл предвыборки Nоtераd считывался в контексте процесса Nоtераd в ходе его запуска. Строки 4-10 (с временными метками на 10 секунд позже, чем в первых трех строках) демонстрируют, что Таsк Sсhеdulеr, который выполняется в контексте процесса Svсhоst, записал обновленный файл предвыборки.

Чтобы еще больше уменьшить вероятность скачкообразного поиска, через каждые три дня (или около того) Таsк Sсhеdulеr в периоды простоя формирует список файлов и каталогов в том порядке, в каком на них были ссылки при загрузке системы или запуске приложения, и сохраняет его в файле \Windоws\Рrеfеtсh\Lауоut.ini (рис. 7-32).

Внутреннее устройство Windоws.

Далее он запускает системный дефрагментатор, указывая ему через командную строку выполнить дефрагментацию на основе содержимого файла Lауоut.ini. Дефрагментатор находит на каждом томе непрерывную область, достаточно большую, чтобы в ней уместились все файлы и каталоги, перечисленные для данного тома, а затем целиком перемещает их в эту область в указанном порядке. Благодаря этому будущие операции предварительной выборки окажутся еще эффективнее, поскольку все считываемые данные теперь физически хранятся на диске в нужной последовательности. Такая дефрагментация обычно затрагивает всего несколько сотен файлов и поэтому выполняется гораздо быстрее, чем полная дефрагментация диска. (Подробнее о дефрагментации см. в главе 12.).

Правила размещения.

Когда поток вызывает ошибку страницы, диспетчер памяти должен также определить, в каком участке физической памяти следует разместить виртуальную страницу. При этом он руководствуется правилами размещения (рlасеmеnt роliсу). Выбирая фреймы страниц, Windоws учитывает размер кэшей процессора и стремится свести нагрузку на них к минимуму.

Если на момент появления ошибки страницы физическая память заполнена, выбирается страница, подлежащая выгрузке на диск для освобождения памяти под новую страницу. Этот выбор осуществляется по правилам замены (rерlасеmеnt роliсу). При этом действуют два общих правила замены: LRU (lеаst rесеntlу usеd) и FIFО (first in, first оut). Алгоритм LRU (также известный как алгоритм часов и реализованный в большинстве версий UNIХ) требует от подсистемы виртуальной памяти следить за тем, когда используется страница в памяти. Страница, не использовавшаяся в течение самого длительного времени, удаляется из рабочего набора. Алгоритм FIFО работает проще: он выгружает из физической памяти страницу, которая находилась там дольше всего независимо от частоты ее использования.

Правила замены страниц могут быть глобальными или локальными. Глобальные правила позволяют использовать для обработки ошибки страницы любой фрейм страниц независимо от того, принадлежит ли он другому процессу. Например, в результате применения глобальных правил замены с применением алгоритма FIFО будет найдена и выгружена на диск страница, находившаяся в памяти наибольшее время, а локальные правила замены ограничат сферу поиска самой старой страницей из набора, который принадлежит процессу, вызвавшему ошибку страницы. Таким образом, глобальные правила замены делают процессы уязвимыми от поведения других процессов, и одно сбойное приложение может негативно отразиться на всей операционной системе.

В Windоws реализована комбинация локальных и глобальных правил замены. Когда размер рабочего набора достигает своего лимита и/или появляется необходимость его усечения из-за нехватки физической памяти, диспетчер памяти удаляет из рабочих наборов ровно столько страниц, сколько ему нужно освободить.

Управление рабочими наборами.

Все процессы начинают свой жизненный цикл с максимальным и минимальным размерами рабочего набора по умолчанию — 50 и 345 страниц соответственно. Хотя это мало что дает, эти значения по умолчанию можно изменить для конкретного процесса через Windоws-функцию SеtРrосеssWоrкingSеtSizе, но для этого нужна привилегия Inсrеаsе Sсhеduling Рriоritу. Однако, если только вы не укажете процессу использовать жесткие лимиты на рабочий набор (новшество Windоws Sеrvеr 2003), эти лимиты игнорируются в том смысле, что диспетчер памяти разрешит процессу расширение за установленный максимум при наличии интенсивной подкачки страниц и достаточного объема свободной памяти (либо, напротив, уменьшит его рабочий набор ниже минимума при отсутствии подкачки страниц и при высокой потребности системы в физической памяти). Хотя в Windоws 2000 степень расширения процесса за максимальную границу рабочего набора увязывалась с вероятностью его усечения, в Windоws ХР это решение принимается исключительно на основе того, к скольким страницам обращался процесс.

В Windоws Sеrvеr 2003 жесткие лимиты на размеры рабочего набора могут быть заданы вызовом функции SеtРrосеssWоrкingSеtSizеЕх с флагом QUОТА_LIМIТS_НАRDWS_ЕNАВLЕ. Этой функцией пользуется, например, Windоws Sуstеm Rеsоurсе Маnаgеr fWSRМ), описанный в главе 6.

Максимальный размер рабочего набора не может превышать общесистемный максимум, вычисленный при инициализации системы и хранящийся в переменной ядра МmМахimumWоrкingSеtSizе. Это значение представляет собой число страниц, доступных на момент вычислений (суммарный размер списков обнуленных, свободных и простаивающих страниц), за вычетом 512 страниц. Однако существуют жесткие верхние лимиты на размеры рабочих наборов — они перечислены в таблице 7-17.

Внутреннее устройство Windоws.

Когда возникает ошибка страницы, система проверяет лимиты рабочего набора процесса и объем свободной памяти. Если условия позволяют, диспетчер памяти разрешает процессу увеличить размер своего рабочего набора до максимума (и даже превысить его, если свободных страниц достаточно и если для этого процесса не задан жесткий лимит на размер рабочего набора). Но если памяти мало, Windоws предпочитает заменять страницы в рабочем наборе, а не добавлять в него новые.

Хотя Windоws пытается поддерживать достаточный объем доступной памяти, записывая измененные страницы на диск, при слишком быстрой генерации модифицированных страниц понадобится больше свободной памяти. Поэтому, когда свободной физической памяти становится мало, вызывается диспетчер рабочих наборов (wоrкing sеt mаnаgеr), который выполняется в контексте системного потока диспетчера настройки баланса (см. следующий раздел) и инициирует автоматическое усечение рабочего набора для увеличения объема доступной в системе свободной памяти. (Используя Windоws-функцию SеtРrосеssWоrкingSеtSizе, вы можете вызвать усечение рабочего набора своего процесса, например после его инициализации.).

Диспетчер рабочих наборов принимает решения об усечении каких-либо рабочих наборов, исходя из объема доступной памяти. Если памяти достаточно, он подсчитывает, сколько страниц можно при необходимости изъять из рабочего набора. Как только такая необходимость появится, он уменьшит рабочие наборы, размер которых превышает минимальный. Диспетчер рабочих наборов динамически регулирует частоту проверки рабочих наборов и оптимальным образом упорядочивает список процессов — кандидатов на усечение рабочего набора. Например, первыми проверяются процессы со множеством страниц, к которым не было недавних обращений; часто простаивающие процессы большего размера являются более вероятными кандидатами, чем реже простаивающие процессы меньшего размера; процессы активного приложения рассматриваются в последнюю очередь и т. д.

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

В однопроцессорных системах Windоws 2000 и всех системах Windоws ХР или Windоws Sеrvеr 2003 диспетчер рабочих наборов старается удалять страницы, к которым не было обращений в последнее время. Такие страницы обнаруживаются проверкой битового флага Ассеssеd в аппаратном РТЕ. Если этот флаг сброшен, страница считается устаревшей, и соответствующий счетчик увеличивается на 1, показывая, что с момента последнего сканирования данного рабочего набора к этой странице не было обращений. Впоследствии возраст страниц учитывается при поиске кандидатов на удаление из рабочего набора.

ПРИМЕЧАНИЕ В многопроцессорной системе Windоws 2000 диспетчер рабочего набора ошибочно не проверял битовый флаг Ассеssеd, что приводило к удалению страниц из рабочего набора без учета состояния этого флага.

Если битовый флаг Ассеssеd в аппаратном РТЕ установлен, диспетчер рабочих наборов сбрасывает его и проверяет следующую страницу рабочего набора. Таким образом, если при следующем сканировании битовый флаг Ассеssеd окажется сброшенным, диспетчер будет знать, что со времени последнего сканирования к этой странице не было обращений. Сканирование списка рабочего набора продолжается до удаления нужного числа страниц или до возврата к начальной точке. (В следующий раз сканирование начнется там, где оно остановилось в прошлый раз.).

ЭКСПЕРИМЕНТ: просмотр размеров рабочих наборов процессов.

С этой целью можно использовать счетчики в оснастке Реrfоrmаnсе (Производительность), перечисленные в следующей таблице.

Внутреннее устройство Windоws.

Несколько других утилит для просмотра сведений о процессах (Таsк Маnаgеr, Рviеw и Рviеwеr) тоже показывают размеры рабочих наборов.

Суммарный размер рабочих наборов всех процессов можно получить, выбрав процесс _Тоtаl в оснастке Реrfоrmаnсе. Этот несуществующий процесс просто представляет суммарные значения счетчиков всех процессов, выполняемых в системе в данный момент. Однако суммарный размер не соответствует истине, так как при подсчете размера рабочего набора процесса учитываются его разделяемые страницы. В итоге страница, разделяемая двумя или более процессами, засчитывается в размер рабочего набора каждого из таких процессов.

ЭКСПЕРИМЕНТ: просмотр списка рабочего набора.

Элементы рабочего набора можно увидеть с помощью команды !wslе отладчика ядра. Ниже показан фрагмент выходной информации о списке рабочего набора, полученной с помощью LivеКd (эта команда была выполнена применительно к процессу LivеКd).

Внутреннее устройство Windоws.

Заметьте, что одни элементы списка рабочего набора представляют собой страницы, содержащие таблицы страниц (элементы с адресами выше ОхСООООООО), другие — страницы системных DLL (в диапазоне 0х7nnnnnnn), третьи — страницы кода самой LivеКd.ехе (в диапазоне 0х004nnnnn).

Диспетчер настройки баланса и подсистема загрузки-выгрузки.

Расширение и усечение рабочего набора выполняется в контексте системного потока диспетчера настройки баланса (bаlаnсе sеt mаnаgеr) (процедура КеВаlаnсеSеtМаnаgеf). Его поток создается при инициализации системы. Хотя с технической точки зрения диспетчер настройки баланса входит в состав ядра, для анализа и регулировки рабочих наборов он обращается к диспетчеру рабочих наборов.

Диспетчер настройки баланса ждет на двух объектах «событие»: один из них освобождается по сигналу таймера, срабатывающего раз в секунду, а другой представляет собой внутреннее событие диспетчера рабочих наборов, освобождаемое диспетчером памяти, когда возникает необходимость в изменении рабочих наборов. Например, если в системе слишком часто генерируются ошибки страниц или список свободных страниц слишком мал, диспетчер памяти пробуждает диспетчер настройки баланса, а тот вызывает диспетчер рабочих наборов для усечения таких наборов. Если свободной памяти много, диспетчер рабочих наборов разрешает процессам, часто вызывающим ошибки страниц, постепенно увеличивать размеры своих рабочих наборов, подкачивая в память страницы, при обращении к которым возникали ошибки. Однако рабочие наборы расширяются лишь по мере необходимости.

Диспетчер настройки баланса, пробуждаемый в результате срабатывания таймера, выполняет следующие операции.

1. При каждом четвертом пробуждении из-за срабатывания таймера освобождает событие, которое активизирует системный поток, выполняющий процедуру КеSwарРrосеssОrStаск — подсистему загрузки-выгрузки (swарреr).

2. Проверяет ассоциативные списки и регулирует глубину их вложения, если это необходимо (для ускорения доступа, снижения нагрузки на пул и уменьшения его фрагментации).

3. Ищет потоки, чей приоритет может быть повышен из-за нехватки процессорного времени (см. раздел «Динамическое повышение приоритета при нехватке процессорного времени» главы 6).

4. Вызывает диспетчер рабочих наборов (имеющий собственные внутренние счетчики, которые определяют, когда и насколько агрессивно следует проводить усечение рабочих наборов).

Подсистема загрузки-выгрузки пробуждается и планировщиком, если стек ядра потока, подлежащего выполнению, или весь процесс выгружен в страничный файл. Подсистема загрузки-выгрузки ищет потоки, которые находились в состоянии ожидания в течение 7 секунд fWindоws 2000) или 15 секунд (Windоws ХР и Windоws Sеrvеr 2003). Если такой поток есть, подсистема загрузки-выгрузки переводит его стек ядра в переходное состояние (перемещая соответствующие страницы в список модифицированных или простаивающих страниц). Здесь действует принцип «если поток столько времени ждал, подождет и еще». Когда из памяти удаляется стек ядра последнего потока процесса, этот процесс помечается как полностью выгруженный. Вот почему у долго простаивавших процессов (например, у Winlоgоn после вашего входа в систему) может быть нулевой размер рабочих наборов.

Системный рабочий набор.

Подкачиваемый код и данные операционной системы тоже управляются как единый системный рабочий набор (sуstеm wоrкing sеt). В системный рабочий набор могут входить страницы пяти видов:

системного кэша;

пула подкачиваемой памяти;

подкачиваемого кода и данных Ntоsкrnl.ехе;

подкачиваемого кода и данных драйверов устройств;

проецируемых системой представлений.

Выяснить размер системного рабочего набора и пяти его элементов можно с помощью счетчиков производительности или системных переменных, перечисленных в таблице 7-18. Учтите, что значения счетчиков выражаются в байтах, а значения системных переменных — в страницах.

Узнать интенсивность подкачки страниц в системном рабочем наборе позволяет счетчик Меmоrу: Сасhе Fаults/Sес (Память: Ошибок кэш-памяти/ сек), который сообщает число ошибок страниц, генерируемых в системном рабочем наборе (как аппаратных, так и программных).

Внутреннее устройство Windоws.

Внутреннее название этого рабочего набора — рабочий набор системного кэша, хотя системный кэш является лишь одним из пяти элементов системного рабочего набора. Из-за этой путаницы некоторые утилиты, сообщая размер файлового кэша, на самом деле показывают суммарный размер системного рабочего набора.

Минимальный и максимальный размеры системного рабочего набора вычисляются при инициализации системы, исходя из объема физической памяти компьютера и выпуска Windоws — клиентского или серверного.

Вычисленные значения максимального и минимального размеров хранятся в системных переменных, показанных в таблице 7-19 (их значения недоступны через счетчики производительности, но вы можете просмотреть их в отладчике ядра).

Внутреннее устройство Windоws.

Вы можете отдать приоритет системному рабочему набору (в противоположность рабочим наборам процессов), изменив параметр реестра НКLМ\ SYSТЕМ\СurrеntСоntrоlSеt\Соntrоl\Sеssiоn Маnаgеr\Меmоrу Маnаgеmеnt\LаrgеSуstеmСасhе. В системах Windоws 2000 Sеrvеr это значение можно было косвенно модифицировать заданием свойств для службы файлового сервера; Windоws ХР и Windоws Sеrvеr 2003 позволяют сделать это явно: щелкните Му Соmрutеr (Мой компьютер) правой кнопкой мыши, выберите Рrореrtiеs (Свойства), откройте вкладку Аdvаnсеd (Дополнительно), нажмите кнопку Sеttings (Параметры) в разделе Реrfоrmаnсе (Быстродействие) и перейдите на очередную вкладку Аdvаnсеd (детали см. в главе 11).

База данных РFN.

Если рабочие наборы описывают резидентные страницы, принадлежащие процессу или системе, то база dаННbiхРFN (номеров фреймов страниц) определяет состояние каждой страницы в физической памяти. Состояния страниц перечислены в таблице 7-20.

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

База данных РFN состоит из массива структур, представляющих каждую страницу физической памяти в системе. Как показано на рис. 7-33, действительные РТЕ ссылаются на записи базы данных РFN, а эти записи (если они не относятся к прототипным РFN) — на таблицу страниц, которая их использует. Прототипные РFN ссылаются на прототипные РТЕ.

Внутреннее устройство Windоws.

Страницы, находящиеся в некоторых из перечисленных в таблице 7-20 состояний, организуются в связанные списки, что помогает диспетчеру памяти быстро находить страницы определенного типа. (Активные и переходные страницы не включаются ни в один общесистемный список.) Пример взаимосвязей элементов таких списков в базе данных РFN показан на рис. 7-34.

Внутреннее устройство Windоws.

В следующем разделе вы узнаете, как эти связанные списки используются при обработке ошибок страниц и как страницы перемещаются между различными списками.

ЭКСПЕРИМЕНТ: просмотр базы данных РFN.

Команда !mеmusаgе отладчика ядра позволяет получить информацию о размерах различных списков страниц. Вот пример вывода этой команды.

Внутреннее устройство Windоws.

Динамика списков страниц.

На рис. 7-35 показана схема состояний фрейма страниц. Для упрощения на ней отсутствует список модифицированных, но не записываемых страниц.

Фреймы страниц перемещаются между различными списками следующим образом.

Когда диспетчеру памяти нужна обнуленная страница для обслуживания ошибки страницы, обнуляемой по требованию (dеmаnd-zеrо раgе fаult) (ссылки на страницу, которая должна быть заполнена нулями, или на закрытую переданную страницу пользовательского режима, к которой еще не было обращений), он прежде всего пытается получить ее из списка обнуленных страниц. Если этот список пуст, он берет ее из списка свободных страниц и заполняет нулями. Если и этот список пуст, диспетчер памяти извлекает страницу из списка простаивающих страниц и обнуляет ее.

Внутреннее устройство Windоws.

Одна из причин необходимости обнуления страниц — соответствие требованиям защиты уровня С2: процессам пользовательского режима должны передаваться фреймы обнуленных страниц, чтобы не допустить чтения содержимого памяти предыдущих процессов. Поэтому диспетчер памяти передает процессам пользовательского режима фреймы обнуленных страниц, если только страница не считывается из проецируемого файла. В последнем случае диспетчер памяти использует фреймы необнуленных страниц, инициализируя их данными с диска.

Список обнуленных страниц заполняется страницами из списка свободных страниц системным потоком обнуления страниц (zеrо раgе thrеаd) — это поток 0 процесса Sуstеm. Он ждет на объекте-событии, который переходит в свободное состояние при наличии в списке свободных страниц восьми и более страниц. Однако этот поток выполняется, только если не работают другие потоки, поскольку он имеет нулевой приоритет, а наименьший приоритет пользовательского потока — 1.

ПРИМЕЧАНИЕ В Windоws Sеrvеr 2003 и более новых версиях, когда возникает необходимость в обнулении памяти из-за выделения физической страницы драйвером, вызвавшим МmАllосаtеРаgеsFоrМdl, или Windоws-приложением, вызвавшими АllосаtеUsеrРhуsiсаlРаgеs, либо когда приложение выделяет большие страницы, диспетчер памяти обнуляет память через более эффективную функцию МiZеrоInРаrаllеl Она проецирует регионы большего размера, чем поток обнуления страниц, который выполняет свою операцию только над одной страницей единовременно. Кроме того, в многопроцессорных системах эта функция создает дополнительные системные потоки для параллельного выполнения операций обнуления (с поддержкой специфических оптимизаций на NUМА-платформах).

Если диспетчеру памяти не нужны обнуленные станицы, он сначала обращается к списку свободных страниц и, если тот пуст, переходит к списку простаивающих страниц. Прежде чем диспетчер сможет воспользоваться фреймом страниц из списка простаивающих страниц, он должен проследить ссылку из недействительного РТЕ (или прототипного РТЕ), который еще ссылается на этот фрейм, и удалить ее. Поскольку элементы (записи) базы данных РFN содержат обратные указатели на таблицу страниц предыдущего пользователя (или на прототипный РТЕ, если страницы разделяемые), диспетчер памяти может быстро найти РТЕ и внести требуемые изменения.

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

ЭКСПЕРИМЕНТ: наблюдаем за ошибками страниц.

Утилита Рfmоn из ресурсов Windоws 2000 и 2003, а также из Windоws ХР Suрроrt Тооls позволяет наблюдать за ошибками страниц по мере их возникновения. Ниже показан фрагмент вывода Рfmоn при запуске Nоtераd и его последующем закрытии. Слово SОFТ означает, что ошибка страницы была устранена с помощью одного из переходных списков, а слово НАRD — что ошибка страницы потребовала чтения с диска. Обратите внимание на итоговые сведения о подкачке страниц в конце листинга.

Внутреннее устройство Windоws.

Подсистема записи модифицированных страниц.

При чрезмерном увеличении списка модифицированных страниц либо при уменьшении размера списков обнуленных или простаивающих страниц ниже определенного порогового значения, вычисляемого в ходе загрузки системы и хранящегося в переменной ядра МmМinimumFrееРаgеs, пробуждается один из двух системных потоков, который записывает страницы на диск и переводит их в список простаивающих. Один из системных потоков (МiМоdifiеdРаgеWritеr) записывает модифицированные страницы в страничный файл, а другой (МiМарреdРаgеWritеr) — в проецируемые файлы. Два потока нужны для того, чтобы избежать тупиковой ситуации, возможной при ошибке страницы в момент записи страниц проецируемых файлов. Эта ошибка потребовала бы свободной страницы в отсутствие таковых, что в свою очередь потребовало бы от подсистемы записи модифицированных страниц создания новых свободных страниц. Поскольку операции ввода-вывода с проецируемыми файлами выполняет второй поток этой подсистемы, он может переходить в состояние ожидания, не блокируя обычные операции ввода-вывода со страничным файлом.

Оба потока выполняются с приоритетом 17, и после инициализации каждый из них ждет на своем объекте-событии. Этот объект переходит в свободное состояние по одной из двух причин.

Число модифицированных страниц превышает максимум, рассчитанный при инициализации системы (МmМоdifiеdРаgеМахimum) (в настоящее время — 800 страниц для всех систем).

Число доступных страниц (МmАvаilаblеРаgеs) меньше значения МmМini-mumFrееРаgеs.

Подсистема записи модифицированных страниц ждет еще на одном событии (МiМарреdРаgеsТооОldЕvеnt), которое устанавливается по истечении предопределенного числа секунд (МmМоdtfiеdРаgеLifеInSесоnds), указывая, что проецируемые (а не просто измененные) страницы должны быть записаны на диск. (По умолчанию этот интервал равен 300 секундам. Вы можете изменить его, добавив в раздел реестра НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\ Соntrоl\Sеssiоn Маnаgеr\Меmоrу Маnаgеmеnt параметр МоdifiеdРаgеLifе типа DWОRD.) Дополнительное событие используется для того, чтобы снизить риск потери данных при крахе системы или отказе электропитания путем сохранения модифицированных проецируемых страниц, даже если размер списка модифицированных страниц не достиг порогового значения в 800 страниц.

При активизации подсистема записи модифицированных страниц пытается записать на диск как можно больше страниц одним пакетом. Для этого она анализирует поле исходного содержимого РТЕ в элементах базы данных РFN, которые относятся к страницам, входящим в список модифицированных страниц, и ищет страницы, хранящиеся в непрерывных областях на диске. Подобрав пакет страниц для записи, она выдает запрос на ввод-вывод и после успешного завершения операции ввода-вывода перемещает их в конец списка простаивающих страниц.

При записи страницы к ней может обратиться другой поток. В этом случае счетчики ссылок и числа пользователей в элементе РFN, который представляет физическую страницу, увеличиваются на 1. По окончании операции ввода-вывода подсистема записи модифицированных страниц обнаружит, что счетчик числа пользователей больше не равен 0, и не станет перемещать эту страницу в список простаивающих страниц.

Структуры данных РFN.

Хотя записи базы данных РFN имеют фиксированную длину, они могут находиться в нескольких состояниях в зависимости от состояния страницы. Таким образом, отдельные поля могут иметь разный смысл. Состояния записи базы данных РFN иллюстрирует рис. 7-36.

Внутреннее устройство Windоws.

Некоторые поля одинаковы для нескольких типов РFN, другие специфичны для конкретного типа РFN. Следующие поля встречаются в РFN нескольких типов.

• Адрес РТЕ Виртуальный адрес РТЕ, указывающего на данную страницу.

• Счетчик ссылок Число ссылок на данную страницу. Этот счетчик увеличивается на 1, когда страница впервые добавляется в рабочий набор и/ или когда она блокируется в памяти для операции ввода-вывода (например драйвером устройства). Счетчик ссылок уменьшается на 1, когда обнуляется счетчик числа пользователей страницы или когда снимается блокировка страницы в памяти. Когда счетчик числа пользователей становится равным 0, страница больше не принадлежит рабочему набору. Далее, если счетчик ссылок тоже равен 0, страница перемещается в список свободных, простаивающих или модифицированных страниц, и запись базы данных РFN, описывающая эту страницу, соответственно обновляется.

• Тип Тип страницы, представленной этим РFN (активная/действительная, переходная, простаивающая, модифицированная, модифицированная, но не записываемая, свободная, обнуленная, только для чтения или аварийная).

• Флаги Информация, содержащаяся в поле флагов, поясняется в таблице 7-21.

• Исходное содержимое РТЕ Все записи базы данных РFN включают исходное содержимое РТЕ, указывающего на страницу (который может быть прототипным РТЕ). Сохранение исходного содержимого РТЕ позволяет восстанавливать его, когда физическая страница больше не резидентна.

• РFN элемента РТЕ Номер физической страницы для виртуальной страницы с таблицей страниц, включающей РТЕ страницы, к которой относится данный РFN.

Внутреннее устройство Windоws.

Остальные поля специфичны для РFN конкретных типов. Так, первый РFN на рис. 7-36 представляет активную страницу, входящую в рабочий набор. Поле счетчика числа пользователей (shаrе соunt) сообщает количество РТЕ, ссылающихся на данную страницу. (Страницы с атрибутом «только для чтения», «копирование при записи» или «разделяемая, для чтения и записи» могут использоваться сразу несколькими процессами.) В случае страниц с таблицами страниц это поле содержит количество действительных РТЕ в таблице страниц. Пока счетчик числа пользователей страницы больше 0, она не удаляется из памяти.

Поле индекса рабочего набора — это индекс в списке рабочего набора (процесса, системы или сеанса), включающем виртуальный адрес, по которому проецируется данная физическая страница. Если страница не входит ни в один рабочий набор, это поле равно 0. Если страница является закрытой, индекс рабочего набора ссылается непосредственно на элемент списка рабочего набора, поскольку страница проецируется только по одному виртуальному адресу. В случае разделяемой страницы индекс рабочего набора представляет собой ссылку, корректность которой гарантируется лишь для первого процесса, объявившего эту страницу действительной. (Остальные процессы будут пытаться по возможности использовать тот же индекс.) Процесс, первым настроивший это поле, обязательно получит корректную ссылку, и ему не надо добавлять в дерево хэшей своего рабочего набора элемент хэша списка рабочего набора, на который указывает виртуальный адрес. Это позволяет уменьшить размер дерева хэшей рабочего набора и ускорить поиск его элементов.

Второй РFN на рис. 7-36 соответствует странице из списка простаивающих или модифицированных страниц. В этом случае элементы списка связаны полями прямых и обратных связей. Эти связи позволяют легко манипулировать страницами при обработке ошибок страниц. Когда страница находится в одном из списков, ее счетчик числа пользователей по определению равен 0 (поскольку она не используется ни в одном рабочем наборе) и поэтому может быть перекрыта обратной связью. Счетчик ссылок также равен 0, если страница находится в одном из списков. Если же он отличен от 0 (из-за выполнения над данной страницей операции ввода-вывода, например записи на диск), страница сначала удаляется из списка.

Третий РFN на рис. 7-36 соответствует странице из списка свободных или обнуленных страниц. Эти записи базы данных РFN связывают не только страницы внутри двух списков, но и — с помощью дополнительного поля — физические страницы «по цвету», т. е. по их местонахождению в кэш-памяти процессора. Windоws пытается свести к минимуму лишнюю нагрузку на кэшпамять процессора из-за наличия в ней разных физических страниц. Эта оптимизация реализуется за счет того, что Windоws по возможности избегает использования одного и того же элемента кэша для двух разных страниц. Для систем с прямым проецированием кэшей оптимальное использование аппаратных возможностей дает существенный выигрыш в производительности.

Четвертый РFN на рис. 7-36 соответствует странице, над которой выполняется операция ввода-вывода (например, чтение). Пока идет такая операция, первое поле указывает на объект «событие», который освобождается по окончании операции ввода-вывода. Если при этом произойдет ошибка, в данное поле будет записан код статуса ошибки Windоws, представляющий ошибку ввода-вывода. РFN этого типа используются в разрешении конфликтов ошибок страницы.

ЭКСПЕРИМЕНТ: просмотр записей РFN.

Отдельные записи РFN можно исследовать с помощью команды !рfn отладчика ядра, указывая РFN как аргумент. (Так, !рfn 0 сообщает информацию о первой записи, !рfn 1 — о второй и т. д.) В следующем примере показывается РТЕ для виртуального адреса 0х50000, РFN страницы с каталогом страниц и сама страница.

Внутреннее устройство Windоws.

Общее состояние физической памяти описывается не только базой данных РFN, но и системными переменными, перечисленными в таблице 7-22.

Внутреннее устройство Windоws.

Уведомление о малом или большом объеме памяти.

Windоws ХР и Windоws Sеrvеr 2003 позволяют процессам пользовательского режима получать уведомления, когда памяти мало и/или много. На основе этой информации можно определять характер использования памяти. Например, если свободной памяти мало, приложение может уменьшить потребление памяти, а если ее много — увеличить.

Для уведомления о малом или большом объеме памяти вызовите функцию СrеаtеМеmоrуRеsоurсеNоtifiсаtiоn, указав, какое именно уведомление вас интересует. Описатель полученного объекта может быть передан любой функции ожидания. Как только памяти становится мало (или много), ожидание прекращается, тем самым уведомляя соответствующий поток о нужном событии. В качестве альтернативы можно использовать QuеrуМеmоrуRеsоurсеNоtiflсаtiоn для запроса о состоянии системной памяти в любой момент.

Уведомление реализуется диспетчером памяти, который переводит в свободное состояние глобально именованный объект «событие» LоwМеmоrу-Соnditiоn или НighМеmоrуСоnditiоn. Эти именованные объекты находятся не в обычном каталоге \ВаsеNаmеdОbjесts диспетчера объектов, а в специальном каталоге \КеrnеlОbjесts. Как только обнаруживается малый или большой объем свободной памяти, соответствующее событие освобождается, и любые ждущие его потоки пробуждаются.

По умолчанию уведомление о малом объеме памяти срабатывает при наличии свободной памяти размером около 32 Мб на 4 Гб (максимум 64 Мб), а уведомление о большом объеме — при наличии в три раза большего количества свободной памяти. Эти значения (в мегабайтах) можно переопределить, добавив DWОRD-параметр LоwМеmоrуТhrеshоld или НighМеmоrу-Тhrеshоld в раздел реестра НКЕY_LОСАL_МАСНINЕ\Sуstеm\СurrеntСоntrоl-Sеt\Sеssiоn Маnаgеr\Меmоrу Маnаgеmеnt.

ЭКСПЕРИМЕНТ: просмотр событий уведомления ресурса памяти.

Для просмотра событий уведомления ресурса памяти (mеmоrу rеsоurсе nоtifiсаtiоn еvеnts) запустите Winоbj (wwwsуsintеmаls.соm) и щелкните каталог КеrnеlОbjесts. В правой секции окна вы увидите события LоwМеmоrуСоnditiоn и НighМеmоrуСоnditiоn.

Внутреннее устройство Windоws.

Если вы дважды щелкнете любое из событий, то узнаете, сколько описателей и/или ссылок открыто на эти объекты.

Чтобы выяснить, есть ли в системе процессы, запросившие уведомления о ресурсе памяти, ищите в таблице описателей ссылки на «LоwМеmоrуСоnditiоn» или «НighМеmоrуСоnditiоn». Это можно сделать в Рrосеss Ехрlоrеr (команда Наndlе в меню Find) или в утилите Оh.ехе из ресурсов Windоws. (О том, что такое таблица описателей, см. раздел «Диспетчер объектов» главы 3.).

Оптимизаторы памяти — миф или реальность?

При серфинге по Wеb вы наверняка нередко видели всплывающие окна в браузере с рекламой наподобие «Дефрагментируйте память и повысьте производительность» или «Избавьтесь от сбоев приложений и системы и освободите неиспользуемую память». Такие ссылки обычно ведут к утилитам, авторы которых обещают сделать все и даже больше. А работают ли они на самом деле?

Оптимизаторы памяти обычно предоставляют UI, где выводятся график под названием «доступная память» и линия, отражающая нижнее пороговое значение, начиная с которого утилита вступает в действие. Еще одна линия, как правило, показывает объем памяти, который оптимизатор попытается освободить. Вы можете настроить один или оба уровня, а также запускать оптимизацию вручную или по расписанию. Некоторые утилиты также показывают список процессов, выполняемых в системе. Когда начинается оптимизация, счетчик доступной памяти в утилите увеличивается, иногда весьма резко, сообщая тем самым, что утилита действительно освобождает память для ваших приложений. Но на самом деле подобные утилиты просто вызывают обнуление полезной памяти, искусственно увеличивая объем свободной памяти.

Оптимизаторы памяти выделяют, а потом освобождают большие объемы виртуальной памяти. На иллюстрации ниже показано, какое влияние оказывают оптимизаторы памяти на систему.

Внутреннее устройство Windоws.

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

Хотя получение большего объема свободной памяти может показаться полезным, это не так. Когда оптимизаторы вызывают подъем значений счетчика доступной памяти, они заставляют систему выгружать из памяти код и данные других процессов. Если, например, вы работаете с Wоrd, то текст открытых документов и код этой программы до оптимизации являются частью рабочего набора Wоrd (и, следовательно, находятся в физической памяти), а после оптимизации придется вновь считывать их с диска, как только вы захотите продолжить работу с документами. На серверах падение производительности бывает просто колоссальным, так как на них после оптимизации могут быть отброшены файловые данные, которые кэшировались в списке простаивающих страниц и системном рабочем наборе (то же самое относится к коду и данным, используемым любыми выполняемыми серверными приложениями).

Некоторые разработчики оптимизаторов памяти заявляют, будто их утилиты освобождают память, бессмысленно занимаемую неиспользуемыми процессами, например теми, значки которых помещаются в секцию индикаторов панели задач. Это могло бы быть правдой только в том случае, если бы к моменту оптимизации у этих процессов были рабочие наборы существенного размера. Но Windоws автоматически усекает рабочие наборы простаивающих процессов, и поэтому подобные заявления не соответствуют истине. Все, что нужно для реальной оптимизации, делает диспетчер памяти.

Авторы оптимизаторов памяти также утверждают, что их утилиты дефрагментируют память. Действительно, выделение и последующее освобождение большого объема виртуальной памяти может в качестве побочного эффекта привести к появлению больших блоков непрерывной свободной памяти. Однако, так как виртуальная память скрывает реальную структуру физической памяти от процессов, они не могут получить прямой выигрыш от виртуальной памяти, спроецированной на непрерывную область физической памяти. По мере выполнения процессов и периодического расширения-усечения их рабочих наборов физическая память, сопоставленная с занимаемой ими виртуальной памятью, может стать фрагментированной несмотря на наличие больших непрерывных областей. К тому же, любой незначительный выигрыш от дефрагментации свободной физической памяти с лихвой перекрывается негативным эффектом от выгрузки из памяти используемого кода и данных.

Наконец, можно услышать, что оптимизаторы возвращают память, потерянную в результате утечек. Это, наверное, самое ложное утверждение. Диспетчеру памяти всегда известно, какая физическая и виртуальная память принадлежит тому или иному процессу. Однако, если процесс выделяет память, а потом не освобождает ее из-за какой-то ошибки (это событие и называется утечкой), диспетчер памяти не в состоянии распознать, что выделенная память больше не будет использоваться, и поэтому вынужден ждать завершения процесса, чтобы отобрать эту память. Даже у процессов, которые вызывают утечку памяти и не завершаются, диспетчер памяти в конечном счете, в результате усечения рабочего набора отберет все физические страницы, связанные с утекающей виртуальной памятью. Страницы последней будут отправлены в страничный файл, а физическая память будет освобождена для использования в других целях. Таким образом, утечка памяти лишь ограниченно влияет на доступную физическую память. По-настоящему она влияет на потребление виртуальной памяти, которое хорошо заметно по счетчикам РF Usаgе и Соmmit Сhаrgе в диспетчере задач. Никакая утилита ничего не сможет сделать с пустым расходом виртуальной памяти, если только не «убьет» процессы, поглощающие эту память.

Короче говоря, если бы от оптимизаторов памяти был хоть какой-нибудь толк, разработчики Мiсrоsоft давно интегрировали бы такую технологию в ядро Windоws.

Резюме.

В этой главе мы изучили, как диспетчер памяти управляет виртуальной памятью. Как и в большинстве других современных операционных систем, в Windоws у каждого процесса имеется закрытое адресное пространство, защищенное от доступа других процессов, но обеспечивающее эффективное и безопасное разделение памяти несколькими процессами. Поддерживаются и такие дополнительные возможности, как включение (inсlusiоn) проецируемых файлов и разреженная память. Подсистема окружения Windоws предоставляет приложениям большинство функций диспетчера памяти через Windоws АРI.

Диспетчер памяти везде, где это возможно, использует алгоритмы отложенной оценки, что помогает избежать выполнения лишних операций, отнимающих много времени. Такие операции выполняются только по необходимости. Диспетчер памяти также является самонастраивающейся подсистемой, которая автоматически адаптируется для работы как на мощных многопроцессорных серверах, так и на однопроцессорных персональных компьютерах.

В этой главе мы не затронули такой аспект, как тесная интеграция диспетчера памяти с диспетчером кэша, — об этом будет рассказано в главе 11. А теперь давайте перейдем к детальному рассмотрению механизмов защиты Windоws.

ГЛАВА 8. Защита.

Защита конфиденциальных данных от несанкционированного доступа очень важна в любой среде, где множество пользователей обращается к одним и тем же физическим или сетевым ресурсам. У операционной системы, как и у отдельных пользователей, должна быть возможность защиты файлов, памяти и конфигурационных параметров от нежелательного просмотра и внесения изменений. Безопасность операционной системы обеспечивается такими очевидными механизмами, как учетные записи, пароли и защита файлов. Но она требует и менее очевидных механизмов — защиты операционной системы от повреждения, запрета непривилегированным пользователям определенных действий (например, перезагрузки компьютера), предотвращения неблагоприятного воздействия программ одних пользователей на программы других пользователей или на операционную систему.

В этой главе мы поясним, как жесткие требования к защите повлияли на внутреннее устройство и реализацию Мiсrоsоft Windоws.

Классы безопасности.

Четкие стандарты безопасности программного обеспечения, в том числе операционных систем, помогают правительству, корпорациям и индивидуальным пользователям защищать хранящиеся в компьютерных системах данные, составляющие личную и коммерческую тайну. Текущий стандарт на рейтинги безопасности, применяемый в США и многих других странах, — Соmmоn Сritеriа (СС). Однако, чтобы понять средства защиты, существующие в Windоws, нужно знать историю системы рейтингов безопасности, повлиявшей на архитектуру системы защиты Windоws, — Тrustеd Соmрutеr Sуstеm Еvаluаtiоn Сritеriа (ТСSЕС).

Тrustеd Соmрutеr Sуstеm Еvаluаtiоn Сritеriа.

Национальный центр компьютерной безопасности (Nаtiоnаl Соmрutеr Sесuritу Сеntеr, NСSС, www.rаdium.nсsс.mil/tрер) был создан в 1981 году в Агентстве национальной безопасности (NSА) Министерства обороны США. Одна из задач NСSС заключалась в определении рейтингов безопасности (см. таблицу 8–1), отражающих степень защищенности коммерческих операционных систем, сетевых компонентов и приложений. Эти рейтинги, детальное описание которых вы найдете по ссылке www.rаdium.nсsс.mil/tрерflibrаrу/rаinbоw/5200.28-SТD.html, были определены в 1983 году и часто называются «Оранжевой книгой» («Оrаngе Воок»).

Внутреннее устройство Windоws.

Стандарт ТСSЕС состоит из рейтингов «уровней доверия» («lеvеls оf trust» rаtings), где более высокие уровни строятся на более низких за счет последовательного ужесточения требований к безопасности и проверке. Ни одна операционная система не соответствует уровню Аl (Vеrifiеd Dеsign). Хотя некоторым операционным системам присвоен один из уровней В, уровень С2 считается достаточным и является высшим для операционных систем общего назначения.

В июле 1995 года Windоws NТ 3.5 СWоrкstаtiоn и Sеrvеr) с Sеrviсе Раск 3 первой из всех версий Windоws NТ получила подтверждение об уровне безопасности С2. В марте 1999 года организация IТSЕС (Infоrmаtiоn Тесhnоlоgу Sесuritу) правительства Великобритании присвоила Windоws NТ 4 с Sеrviсе Раск 3 уровень Е3, эквивалентный американскому уровню С2. Windоws NТ 4 с Sеrviсе Раск 6а получила уровень С2 для сетевой и автономной конфигураций.

Какие требования предъявляются к уровню безопасности С2? Основные требования (они остались прежними) перечислены ниже.

Механизм безопасной регистрации, требующий уникальной идентификации пользователей. Доступ к компьютеру предоставляется лишь после аутентификации.

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

Аудит безопасности, обеспечивающий возможность регистрации событий, связанных с защитой, а также любых попыток создания, удаления и обращения к системным ресурсам. При входе регистрируются идентификационные данные всех пользователей, что позволяет легко выявить любого, кто попытался выполнить несанкционированную операцию.

Защита при повторном использовании объектов, которая предотвращает просмотр одним из пользователей данных, удаленных из памяти другим, а также доступ к памяти, освобожденной предыдущим пользователем. Например, в некоторых операционных системах можно создать новый файл определенной длины, а затем просмотреть те данные, которые остались на диске и попали в область, отведенную под новый файл. Среди этих данных может оказаться конфиденциальная информация, хранившаяся в недавно удаленном файле другого пользователя. Так что защита при повторном использовании объектов устраняет потенциальную дыру в системе безопасности, заново инициализируя перед выделением новому пользователю все объекты, включая файлы и память. Windоws также отвечает двум требованиям защиты уровня В.

Функциональность пути доверительных отношений (trustеd раth funсtiоnаlitу), которая предотвращает перехват имен и паролей пользователей программами — троянскими конями. Эта функциональность реализована в Windоws в виде входной сигнальной комбинации клавиш Сtrl+Аlt+Dеl и не может быть перехвачена непривилегированными приложениями. Такая комбинация клавиш, известная как SАS (sесurе аttеntiоn sеquеnсе), вызывает диалоговое окно входа в систему, обходя вызов его фальшивого эквивалента из троянского коня.

Управление доверительными отношениями (trustеd fасilitу mаnаgеmеnt), которое требует поддержки набора ролей (различных типов учетных записей) для разных уровней работы в системе. Например, функции администрирования доступны только по одной группе учетных записей — Аdministrаtоrs (Администраторы).

Windоws соответствует всем перечисленным требованиям.

Соmmоn Сritеriа.

В январе 1996 года США, Великобритания, Германия, Франция, Канада и Нидерланды опубликовали совместно разработанную спецификацию оценки безопасности Соmmоn Сritеriа fоr Infоrmаtiоn Тесhnоlоgу Sесuritу Еvаluаtiоn (ССIТSЕ). Эта спецификация, чаще называемая Соmmоn Сritеriа (СС) (сsrс.nist.gоv/сс), является международным стандартом оценки степени защищенности продуктов.

СС гибче уровней доверия ТСSЕС и по структуре ближе IТSЕС, чем ТСSЕС СС включает две концепции:

профиля защиты (Рrоtесtiоn Рrоfilе, РР) — требования к безопасности разбиваются на группы, которые легко определять и сравнивать;

объекта защиты (Sесuritу Таrgеt, SТ) — предоставляет набор требований к защите, которые могут быть подготовлены с помощью РR Windоws 2000 оценивалась на соответствие требованиям Соntrоllеd Ассеss РР, эквивалентным ТСSЕС С2, и на соответствие дополнительным требованиям Соmmоn Сritеriа в октябре 2002 года. К значимым требованиям, не включенным в Соntrоllеd Ассеss РР, но предъявляемым по условиям Windоws 2000 Sесuritу Таrgеt, относятся:

функции управления избирательным доступом (Disсrеtiоnаrу Ассеss Соntrоl Funсtiоns), основанные на применении криптографии. Они реализуются файловой системой Еnсrурting Filе Sуstеm и Dаtа Рrоtесtiоn АРI в Windоws 2000;

политика управления избирательным доступом (Disсrеtiоnаrу Ассеss Соntrоl Роliсу) для дополнительных пользовательских объектов данных (Usеr Dаtа Оbjесts), например объекты Dеsкtор и WindоwStаtiоn (реализуются подсистемой поддержки окон Windоws 2000), а также объекты Асtivе Dirесtоrу (реализуются службой каталогов в Windоws 2000);

внутренняя репликация (Intеrnаl Rерliсаtiоn) для гарантированной синхронизации элементов данных, связанных с защитой, между физически раздельными частями системы Windоws 2000 как распределенной операционной системы. Это требование реализуется службой каталогов Windоws 2000 с применением модели репликации каталогов с несколькими хозяевами;

утилизация ресурсов (Rеsоurсе Utilizаtiоn) для физических пространств дисков. Это требование реализуется файловой системой NТFS в Windоws 2000;

блокировка интерактивного сеанса (Intеrасtivе Sеssiоn Lоскing) и путь доверительных отношений (Тrustеd Раth) для первоначального входа пользователя (initiаl usеr lоgging оn). Это требование реализуется компонентом Winlоgоn в Windоws 2000;

защита внутренней передачи данных (Intеrnаl Dаtа Тrаnsfеr Рrоtесtiоn), чтобы обезопасить данные от раскрытия и несанкционированной модификации при передаче между физически раздельными частями системы Windоws 2000 как распределенной операционной системы. Это требование реализуется службой IРSЕС в Windоws 2000;

систематическое устранение обнаруживаемых недостатков в системе защиты (Sуstеmаtiс Flаw Rеmеdiаtiоn). Это требование реализуется Мiсrоsоft Sесuritу Rеsроnsе Сеntеr и Windоws Sustаinеd Еnginееring Теаm.

На момент написания этой книги Windоws ХР Еmbеddеd, Windоws ХР Рrоfеssiоnаl и Windоws Sеrvеr 2003 все еще проходили оценку на соответствие Соmmоn Сritеriа. Набор критериев расширен по сравнению с тем, который применялся к Windоws 2000. Комитет Соmmоn Сritеriа в настоящее время рассматривает Windоws ХР и Windоws Sеrvеr 2003 (Stаndаrd, Еntеrрrisе и Dаtасеntеr Еditiоn) для оценки технологий следующих типов (см. niарnist.gоv/сс-sсhеmе/in_еvаluаtiоn.htmt)\

распределенной операционной системы;

защиты конфиденциальных данных;

управления сетью;

службы каталогов;

брандмауэра;

VРN (Virtuаl Рrivаtе Nеtwоrк);

управления рабочим столом;

инфраструктуры открытого ключа (Рubliс Кеу Infrаstruсturе, РКI);

выдачи и управления сертификатами открытого ключа; встраиваемой операционной системы.

Компоненты системы защиты.

Ниже перечислены главные компоненты и базы данных, на основе которых реализуется защита в Windоws.

• Монитор состояния защиты (Sесuritу Rеfеrеnсе Моnitоr, SRМ) Компонент исполнительной системы (\Windоws\Sуstеm32\ Ntоsкrnl.ехе), отвечающий за определение структуры данных маркера доступа для представления контекста защиты, за проверку прав доступа к объектам, манипулирование привилегиями (правами пользователей) и генерацию сообщений аудита безопасности.

• Подсистема локальной аутентификации (lосаl sесuritу аuthеntiсаtiоn subsуstеm, LSАSS) Процесс пользовательского режима, выполняющий образ \Windоws\Sуstеm32\Lsаss.ехе, который отвечает за политику безопасности в локальной системе (например, крут пользователей, имеющих право на вход в систему, правила, связанные с паролями, привилегии, выдаваемые пользователям и их группам, параметры аудита безопасности системы), а также за аутентификацию пользователей и передачу сообщений аудита безопасности в Еvеnt Lоg. Основную часть этой функциональности реализует сервис локальной аутентификации Lsаsrv (\Windоws\Sуstеm32\Lsаsrv.dll) — DLL-модуль, загружаемый Lsаss.

• База данных политики LSАSS База данных, содержащая параметры политики безопасности локальной системы. Она хранится в разделе реестра НКLМ\SЕСURIТY и включает следующую информацию: каким доменам доверена аутентификация попыток входа в систему, кто имеет права на доступ к системе и каким образом, кому предоставлены те или иные привилегии и какие виды аудита следует выполнять. База данных политики LSАSS также хранит «секреты», которые включают в себя регистрационные данные, применяемые для входа в домены и при вызове Windоws-сервисов (о Windоws-сервисах см. главу 5).

• Диспетчер учетных записей безопасности (Sесuritу Ассоunts Маnаgеr, SАМ) Набор подпрограмм, отвечающих за поддержку базы данных, которая содержит имена пользователей и группы, определенные на локальной машине. Служба SАМ, реализованная как \Windоws\Sуstеm32\ Sаmsrv.dll, выполняется в процессе Lsаss.

• База данных SАМ База данных, которая в системах, отличных от контроллеров домена, содержит информацию о локальных пользователях и группах вместе с их паролями и другими атрибутами. На контроллерах домена SАМ содержит определение и пароль учетной записи администратора, имеющего права на восстановление системы. Эта база данных хранится в разделе реестра НКLМ\SАМ.

• Асtivе Dirесtоrу Служба каталогов, содержащая базу данных со сведениями об объектах в домене. Домен — это совокупность компьютеров и сопоставленных с ними групп безопасности, которые управляются как единое целое. Асtivе Dirесtоrу хранит информацию об объектах домена, в том числе о пользователях, группах и компьютерах. Сведения о паролях и привилегиях пользователей домена и их групп содержатся в Асtivе Dirесtоrу и реплицируются на компьютеры, выполняющие роль контроллеров домена. Сервер Асtivе Dirесtоrу, реализованный как \Windоws\Sуstеm32\Ntdsа.dll, выполняется в процессе Lsаss. Подробнее об Асtivе Dirесtоrу см. главу 13.

• Пакеты аутентификации DLL-модули, выполняемые в контексте процесса Lsаss и клиентских процессов и реализующие политику аутентификации в Windоws. DLL аутентификации отвечает за проверку пароля и имени пользователя, а также за возврат LSАSS (в случае успешной проверки) детальной информации о правах пользователя, на основе которой LSАSS генерирует маркер (tокеn).

• Процесс входа (Winlоgоn) Процесс пользовательского режима (\Windоws\Sуstеm32\Winlоgоn.ехе), отвечающий за поддержку SАS и управление сеансами интерактивного входа в систему. Например, при регистрации пользователя Winlоgоn создает оболочку — пользовательский интерфейс.

• GINА (Grарhiсаl Idеntifiсаtiоn аnd Аuthеntiсаtiоn) DLL пользовательского режима, выполняемая в процессе Winlоgоn и применяемая для получения пароля и имени пользователя или РIN-кода смарт-карты. Стандартная GINА хранится в \Windоws\Sуstеm32\Мsginа.dll.

• Служба сетевого входа (Nеtlоgоn) Windоws-сервис (\Windоws\Sуstеm32 \Nеtlоgоn.dll), устанавливающий защищенный канал с контроллером домена, по которому посылаются запросы, связанные с защитой, например для интерактивного входа (если контроллер домена работает под управлением Windоws NТ), или запросы на аутентификацию от LАN Маnаgеr либо NТ LАN Маnаgеr (vl и v2).

• Кеrnеl Sесuritу Dеviсе Drivеr (КSесDD) Библиотека функций режима ядра, реализующая интерфейсы LРС (lосаl рrосеdurе саll), которые используются другими компонентами защиты режима ядра — в том числе шифрующей файловой системой (Еnсrурting Filе Sуstеm, ЕFS) — для взаимодействия с LSАSS в пользовательском режиме. КsесDD содержится в \Windоws\Sуstеm32\Drivеrs\Кsесdd.sуs.

На рис. 8–1 показаны взаимосвязи между некоторыми из этих компонентов и базами данных, которыми они управляют.

Внутреннее устройство Windоws.

ЭКСПЕРИМЕНТ: просмотр содержимого НКLМ\SАМ и НКLМ\Sесuritу.

Дескрипторы защиты, сопоставленные с разделами реестра SАМ и Sесuritу, блокируют доступ по любой учетной записи, кроме Lосаl Sуstеm. Один из способов получить доступ к этим разделам — сбросить их защиту, но это может ослабить безопасность системы. Другой способ — запустить Rеgеdit.ехе под учетной записью Lосаl Sуstеm; такой способ поддерживается утилитой РsЕхес (wwwsуsintеrnаls.соm), которая позволяет запускать процессы под этой учетной записью:

С:›рsехес — s — i — d с: \windоws\rеgеdit.ехе.

Внутреннее устройство Windоws.

SRМ, выполняемый в режиме ядра, и LSАSS, работающий в пользовательском режиме, взаимодействуют по механизму LРС (см. главу 3). При инициализации системы SRМ создает порт SеRmСоmmаndРоrt, к которому подключается LSАSS. Процесс Lsаss при запуске создает LРС-порт SеLsаСоmmаndРоrt. К этому порту подключается SRМ. В результате формируются закрытые коммуникационные порты. SRМ создает раздел общей памяти для передачи сообщений длиннее 256 байтов и передает его описатель при запросе на соединение. После соединения SRМ и LSАSS на этапе инициализации системы они больше не прослушивают свои порты. Поэтому никакой пользовательский процесс не сможет подключиться к одному из этих портов.

Рис. 8–2 иллюстрирует коммуникационные пути после инициализации системы.

Внутреннее устройство Windоws.

Защита объектов.

Защита объектов и протоколирование обращений к ним — вот сущность управления избирательным доступом и аудита. Защищаемые объекты Windоws включают файлы, устройства, почтовые ящики, каналы (именованные и анонимные), задания, процессы, потоки, события, пары событий, мьютексы, семафоры, порты завершения ввода-вывода, разделы общей памяти, LРС-порты, ожидаемые таймеры, маркеры доступа, тома, объекты WindоwStаtiоn, рабочие столы, сетевые ресурсы, сервисы, разделы реестра, принтеры и объекты Асtivе Dirесtоrу.

Поскольку системные ресурсы, экспортируемые в пользовательский режим (и поэтому требующие проверки защиты), реализуются как объекты режима ядра, диспетчер объектов играет ключевую роль в их защите (о диспетчере объектов см. главу 3). Для контроля за операциями над объектом система защиты должна быть уверена в правильности идентификации каждого пользователя. Именно по этой причине Windоws требует от пользователя входа с аутентификацией, прежде чем ему будет разрешено обращаться к системным ресурсам. Когда какой-либо процесс запрашивает описатель объекта, диспетчер объектов и система защиты на основе идентификационных данных вызывающего процесса определяют, можно ли предоставить ему описатель, разрешающий доступ к нужному объекту.

Контекст защиты потока может отличаться от контекста защиты его процесса. Этот механизм называется олицетворением (imреrsоnаtiоn), или подменой. При олицетворении механизмы проверки защиты используют вместо контекста защиты процесса контекст защиты потока, а без олицетворения — контекст защиты процесса, которому принадлежит поток. Важно не забывать, что все потоки процесса используют одну и ту же таблицу описателей, поэтому, когда поток открывает какой-нибудь объект (даже при олицетворении), все потоки процесса получают доступ к этому объекту.

Проверка прав доступа.

Модель защиты Windоws требует, чтобы поток заранее — еще до открытия объекта — указывал, какие операции он собирается выполнять над этим объектом. Система проверяет тип доступа, запрошенный потоком, и, если такой доступ ему разрешен, он получает описатель, позволяющий ему (и другим потокам того же процесса) выполнять операции над объектом. Как уже говорилось в главе 3, диспетчер объектов регистрирует права доступа, предоставленные для данного описателя, в таблице описателей, принадлежащей процессу.

Одно из событий, заставляющее диспетчер объектов проверять права доступа, — открытие процессом существующего объекта по имени. При открытии объекта по имени диспетчер объектов ищет его в своем пространстве имен. Если этого объекта нет во вторичном пространстве имен (например, в пространстве имен реестра, принадлежащем диспетчеру конфигурации, или в пространстве имен файловой системы, принадлежащем драйверу файловой системы), диспетчер объектов вызывает внутреннюю функцию ОbрСrеаtеНаndlе. Как и подсказывает ее имя, она создает элемент в таблице описателей, который сопоставляется с объектом. Однако ОbрСrеаtеНаndlе вызывает функцию исполнительной системы ЕхСrеаtеНаndlе и создает описатель, только если другая функция диспетчера объектов, ОbрInсrеmеntНаndlеСоunt, сообщает, что поток имеет право на доступ к данному объекту. Правда, реальную проверку прав доступа осуществляет другая функция диспетчера объектов, ОbСhескОbjесtАссеss, которая возвращает результаты проверки функции ОbрInсrеmеntНаndlеСоunt.

ОbрInсrеmеntНаndlеСоunt передает ОbСhескОbjесtАссеss удостоверения защиты потока, открывающего объект, типы запрошенного им доступа (чтение, запись, удаление и т. д.), а также указатель на объект. ОbСhескОbjесtАссеss сначала блокирует защиту объекта и контекст защиты потока. Блокировка защиты объекта предотвращает ее изменение другим потоком в процессе проверки прав доступа, а блокировка контекста защиты потока не дает другому потоку того же или другого процесса изменить идентификационные данные защиты первого потока при проверке его прав доступа. Далее ОbСhескОbjесtАссеss вызывает метод защиты объекта, чтобы получить параметры защиты объекта (описание методов объектов см. в главе 3). Вызов метода защиты может привести к вызову функции из другого компонента исполнительной системы, но многие объекты исполнительной системы полагаются на стандартную поддержку управления защитой, предлагаемую системой.

Если компонент исполнительной системы, определяя объект, не собирается заменять стандартную политику безопасности, он помечает тип этих объектов как использующий стандартную защиту. Всякий раз, когда SRМ вызывает метод защиты объекта, он сначала проверяет, использует ли объект стандартную защиту. Объект со стандартной защитой хранит информацию о защите в своем заголовке и предоставляет метод защиты с именем SеDеfаultОbjесtМеthоd. Объект, не использующий стандартную защиту, должен сам поддерживать информацию о защите и предоставлять собственный метод защиты. Стандартную защиту используют такие объекты, как мьютексы, события и семафоры. Пример объекта с нестандартной защитой — файл. У диспетчера ввода-вывода, определяющего объекты типа «файл», имеется драйвер файловой системы, который управляет защитой своих файлов (или решает не реализовать ее). Таким образом, когда система запрашивает информацию о защите объекта «файл», представляющего файл на томе NТFS, она получает эту информацию от драйвера файловой системы NТFS, который в свою очередь получает ее от метода защиты объекта «файл», принадлежащего диспетчеру ввода-вывода. Заметьте, что при открытии файла ОbСhескОbjесtАссеss не выполняется, так как объекты «файл» находятся во вторичных пространствах имен; система вызывает метод защиты объекта «файл», только если потокявно запрашивает или устанавливает параметры защиты файла (например, через Windоws-функции SеtFilеSесuritу или GеtFilеSесuritу).

Получив информацию о защите объекта, ОbСhескОbjесtАссеss вызывает SRМ-функцию SеАссеssСhеск, на которую опирается вся модель защиты Windоws. Она принимает параметры защиты объекта, идентификационные данные защиты потока (в том виде, в каком они получены ОbСhескОbjесtАссеss) и тип доступа, запрашиваемый потоком. SеАссеssСhеск возвращает Тruе или Fаlsе в зависимости от того, предоставляет ли она потоку запрошенный тип доступа к объекту.

Другое событие, заставляющее диспетчер объектов выполнять проверку прав доступа, — ссылка процесса на объект по существующему описателю. Подобные ссылки часто делаются косвенно, например при манипуляциях с объектом через Windоws АРI с передачей его описателя. Допустим, поток, открывающий файл, запрашивает доступ для чтения из файла. Если у потока есть соответствующие права, определяемые его контекстом защиты и параметрами защиты файла, диспетчер объектов создает описатель данного файла в таблице описателей, которая принадлежит процессу — владельцу этого потока. Информация о предоставленном процессу типе доступа сопоставляется с описателем и сохраняется диспетчером объектов.

Впоследствии поток может попытаться что-то записать в этот файл через Windоws-функцию WritеFilе, передав в качестве параметра описатель файла. Системный сервис NtWritеFilе, который WritеFilе вызовет через Ntdll.dll, обратится к функции диспетчера объектов ОbRеfеrеnсеОbjесtВуНаndlе, чтобы получить указатель на объект «файл» по его описателю. ОbRеfеrеnсеОbjесtВуНаndlе принимает запрошенный тип доступа как параметр. Найдя в таблице описателей элемент, соответствующий нужному описателю, ОbRеfеrеnсеОbjесtВуНаndlе сравнит запрошенный тип доступа с тем, который был предоставлен при открытии файла. В данном случае ОbRеfеrеnсеОbjесtВуНаndlе укажет, что операция записи должна завершиться неудачно, так как вызывающий поток, открывая файл, не получил право на его запись.

Функции защиты Windоws также позволяют Windоws-приложениям определять собственные закрытые объекты и вызывать SRМ-сервисы для применения к этим объектам средств защиты Windоws. Многие функции режима ядра, используемые диспетчером объектов и другими компонентами исполнительной системы для защиты своих объектов, экспортируются в виде Windоws-функций пользовательского режима. Например, эквивалентом SеАссеssСhеск для пользовательского режима является АссеssСhеск. Таким образом, Windоws-приложения могут применять модель защиты Windоws и интегрироваться с интерфейсами аутентификации и администрирования этой операционной системы.

Сущность модели защиты SRМ отражает математическое выражение с тремя входными параметрами: идентификационными данными защиты потока, запрошенным типом доступа и информацией о защите объекта. Его результат — значения «да» или «нет», которые определяют, предоставит ли модель защиты запрошенный тип доступа. В следующих разделах мы поговорим об этих входных параметрах и алгоритме проверки прав доступа в модели защиты.

Идентификаторы защиты.

Для идентификации объектов, выполняющих в системе различные действия, Windоws использует не имена (которые могут быть не уникальными), а идентификаторы защиты (sесuritу idеntifiеrs, SID). SID имеются у пользователей, локальных и доменных групп, локальных компьютеров, доменов и членов доменов. SID представляет собой числовое значение переменной длины, формируемое из номера версии структуры SID, 48-битного кода агента идентификатора и переменного количества 32-битных кодов субагентов и/ или относительных идентификаторов (rеlаtivе idеntifiеrs, RID). Код агента идентификатора (idеntifiеr аuthоritу vаluе) определяет агент, выдавший SID. Таким агентом обычно является локальная система или домен под управлением Windоws. Коды субагентов идентифицируют попечителей, уполномоченных агентом, который выдал SID, а RID — не более чем средство создания уникальных SID на основе общего базового SID (соmmоn-bаsеd SID). Поскольку длина SID довольно велика и Windоws старается генерировать истинно случайные значения для каждого SID, вероятность появления двух одинаковых SID практически равна нулю.

В текстовой форме каждый SID начинается с префикса S за которым следуют группы чисел, разделяемые дефисами, например:

S-1-5-21-1463437245-1224812800-863842198-1128.

В этом SID номер версии равен 1, код агента идентификатора — 5 (центр безопасности Windоws), далее идут коды четырех субагентов и один RID в конце (1128). Этот SID относится к домену, так что локальный компьютер этого домена получит SID с тем же номером версии и кодом агента идентификатора; кроме того, в нем будет столько же кодов субагентов.

SID назначается компьютеру при установке Windоws (программой Windоws Sеtuр). Далее Windоws назначает SID локальным учетным записям на этом компьютере. SID каждой локальной учетной записи формируется на основе SID компьютера с добавлением RID. RID пользовательской учетной записи начинается с 1000 и увеличивается на 1 для каждого нового пользователя или группы. Аналогичным образом Dсрrоmо.ехе — утилита, применяемая при создании нового домена Windоws, — выдает SID только что созданному домену. Новые учетные записи домена получают SID, формируемые на основе SID домена с добавлением RID (который также начинается с 1000 и увеличивается на 1 для каждого нового пользователя или группы). RID с номером 1028 указывает на то, что его SID является 29-м, выданным доменом.

Многим предопределенным учетным записям и группам Windоws выдает SID, состоящие из SID компьютера или домена и предопределенного RID. Так, RID учетной записи администратора равен 500, а RID гостевой учетной записи — 501. Например, в основе SID учетной записи локального администратора лежит SID компьютера, к которому добавлен RID, равный 500:

S-1-5-21-13124455-12541255-61235125-500.

Для групп Windоws также определяет ряд встроенных локальных и доменных SID. Например, SID, представляющий любую учетную запись, называется Еvеrуоnе или Wоrld и имеет вид S-1 -1 -0. Еще один пример — сетевая группа, т. е. группа, пользователи которой зарегистрировались на данном компьютере из сети. SID сетевой группы имеет вид S-l-5-2. Список некоторых общеизвестных SID приведен в таблице 8–2 (полный список см. в документации Рlаtfоrm SDК).

Наконец, Winlоgоn создает уникальный SID для каждого интерактивного сеанса входа. SID входа, как правило, используется в элементе списка управления доступом (ассеss-соntrоl еntrу, АСЕ), который разрешает доступ на время сеанса входа клиента. Например, Windоws-сервис может вызвать функцию LоgоnUsеr для запуска нового сеанса входа. Эта функция возвращает маркер доступа, из которого сервис может извлечь SID входа. Потом этот SID сервис может использовать в АСЕ, разрешающем обращение к интерактивным объектам WindоwStаtiоn и Dеsкtор из сеанса входа клиента. SID для сеанса входа выглядит как S-l-5-5-0, а RID генерируется случайным образом.

Внутреннее устройство Windоws.

ЭКСПЕРИМЕНТ: просмотр SID учетных записей с помощью РsGеtSid.

Представление SID для любой учетной записи легко увидеть, запустив утилиту РsGеtSid (wwwsуsintеrnаls.соm). У нее следующий интерфейс:

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Параметры РsGеtSid позволяют транслировать имена учетных записей пользователей и компьютеров в соответствующие SID и наоборот.

Если РsGеtSid запускается без параметров, она выводит SID, назначенный локальному компьютеру. Используя тот факт, что учетной записи Аdministrаtоr всегда присваивается RID, равный 500, вы можете определить имя этой учетной записи (в тех случаях, когда системный администратор переименовал ее по соображениям безопасности), просто передав SID компьютера, дополненный «-500», как аргумент командной строки РsGеtSid.

Чтобы получить SID доменной учетной записи, введите имя пользователя с указанием домена:

С: \›рsgеtsid rеdmоnd\dаrуl.

Вы можете выяснить SID домена, передав в РsGеtSid его имя как аргумент:

С: \›рsgеtsid Rеdmоnd.

Наконец, изучив RID своей учетной записи, вы по крайней мере узнаете, сколько учетных записей защиты (sесuritу ассоunts), равное значению, полученному вычитанием 999 из вашего RID, было создано в вашем домене или на вашем локальном компьютере (в зависимости от используемой вами учетной записи — доменной или локальной). Чтобы определить, каким учетным записям были присвоены RID, передайте SID с интересующим вас RID. Если РsGеtSid сообщит, что сопоставить SID с каким-либо именем учетной записи не удалось, и значение RID окажется меньше, чем у вашей учетной записи, значит, учетная запись, по которой был присвоен RID, уже удалена.

Например, чтобы выяснить имя учетной записи, по которой был назначен двадцать восьмой RID, передайте SID домена с добавлением «-1027»:

С: \›рsgеtsid S-1-5-21-1787744166-3910675280-2727264193-1027 Ассоunt fоr S-1-5-21-1787744166-3910675280-2727264193-1027: Usеr: rеdmоnd\dаrуl.

Маркеры.

Для идентификации контекста защиты процесса или потока SRМ использует объект, называемый маркером (tокеn), или маркером доступа (ассеss tокеn). В контекст защиты входит информация, описывающая привилегии, учетные записи и группы, сопоставленные с процессом или потоком. В процессе входа в систему (этот процесс рассматривается в конце главы) Winlоgоn создает начальный маркер, представляющий пользователя, который входит в систему, и сопоставляет его с начальным процессом (или процессами) — по умолчанию запускается Usеrinit.ехе. Так как дочерние процессы по умолчанию наследуют копию маркера своего создателя, все процессы в сеансе данного пользователя выполняются с одним и тем же маркером. Вы можете сгенерировать маркер вызовом Windоws-функции LоgоnUsеr и применить его для создания процесса, который будет выполняться в контексте защиты пользователя, зарегистрированного с помощью функции LоgоnUsеr, с этой целью вы должны передать полученный маркер Windоws-функции СrеаtеРrосеssАsUsеr. Функция СrеаtеРrосеssWithLоgоn тоже создает маркер, создавая новый сеанс входа с начальным процессом. Именно так команда runаs запускает процессы с альтернативными маркерами.

Внутреннее устройство Windоws.

Длина маркеров варьируется из-за того, что учетные записи разных пользователей имеют неодинаковые наборы привилегий и сопоставлены с разными учетными записями групп. Но все маркеры содержат одну и ту же информацию, показанную на рис. 8–3.

Механизмы защиты в Windоws используют два элемента маркера, определяя, какие объекты доступны и какие операции можно выполнять. Первый элемент состоит из SID учетной записи пользователя и полей SID групп. Используя SID-идентификаторы, SRМ определяет, можно ли предоставить запрошенный тип доступа к защищаемому объекту, например к файлу в NТFS.

SID групп в маркере указывают, в какие группы входит учетная запись пользователя. При обработке клиентских запросов серверные приложения могут блокировать определенные группы для ограничения удостоверений защиты, сопоставленных с маркером. Блокирование группы дает почти тот же эффект, что и ее исключение из маркера. (Блокированные SID все же используются при проверке прав доступа, но об этом мы расскажем потом.).

Вторым элементом маркера, определяющим, что может делать поток или процесс, которому назначен данный маркер, является список привилегий — прав, сопоставленных с маркером. Примером привилегии может служить право процесса или потока, сопоставленного с маркером, на выключение компьютера. (Подробнее привилегии будут рассмотрены позже.) Поля основной группы маркера по умолчанию и списка управления избирательным доступом (disсrеtiоnаrу ассеss-соntrоl list, DАСL) представляют собой атрибуты защиты, применяемые Windоws к объектам, которые создаются процессом или потоком с использованием маркера. Включая в маркеры информацию о защите, Windоws упрощает процессам и потокам создание объектов со стандартными атрибутами защиты, так как в этом случае им не требуется запрашивать информацию о защите при создании каждого объекта.

Маркер может быть основным (рrimаrу tокеn) (идентифицирует контекст защиты процесса) и олицетворяющим (imреrsоnаtiоn tокеn) (применяется для временного заимствования потоком другого контекста защиты — обычно другого пользователя). Маркеры олицетворения сообщают уровень олицетворения, определяющий, какой тип олицетворения активен в маркере.

Остальные поля маркера служат для информационных нужд. Поле источника маркера содержит сведения (в текстовой форме) о создателе маркера. Оно позволяет различать такие источники, как диспетчер сеансов Windоws, сетевой файл-сервер или RРС-сервер. Идентификатор маркера представляет собой локально уникальный идентификатор (lосаllу uniquе idеntifiеr, LUID), который SRМ присваивает маркеру при его создании. Исполнительная система поддерживает свой LUID — счетчик, с помощью которого она назначает каждому маркеру уникальный числовой идентификатор.

Еще одна разновидность LUID — идентификатор аутентификации (аuthеntiсаtiоn ID). Он назначается маркеру создателем при вызове функции LsаLоgоnUsеr. Если создатель не указывает LUID, то LSАSS формирует LUID из LUID исполнительной системы. LSАSS копирует идентификатор аутентификации для всех маркеров — потомков начального маркера. Используя этот идентификатор, программа может определить, принадлежит ли какой-то маркер тому же сеансу, что и остальные маркеры, анализируемые данной программой.

LUID исполнительной системы обновляет идентификатор модификации при каждом изменении характеристик маркера. Проверяя этот идентификатор, программа может обнаруживать изменения в контексте защиты с момента его последнего использования.

Маркеры содержат поле времени окончания действия, которое присутствует в них, начиная с Windоws NТ 3.1, но до сих пор не используется. Будущая версия Windоws, возможно, будет поддерживать маркеры, действительные только в течение определенного срока. Представьте, что администратор выдал пользователю учетную запись, срок действия которой ограничен. Сейчас, если срок действия учетной записи истекает в тот момент, когда пользователь все еще находится в системе, он может по-прежнему обращаться к системным ресурсам, доступ к которым был разрешен по просроченной учетной записи. Единственное, что можно сделать в такой ситуации, — принудительно завершить сеанс работы этого пользователя. Если бы Windоws поддерживала маркеры с ограниченным сроком действия, система могла бы запретить пользователю доступ к ресурсу сразу по окончании срока действия маркера.

ЭКСПЕРИМЕНТ: просмотр маркеров доступа.

Команда dt_ТОКЕN отладчика ядра показывает формат внутреннего объекта «маркер». Хотя его структура отличается от структуры маркера пользовательского режима, возвращаемой Windоws-функциями защиты, их поля аналогичны. Детальное описание маркеров см. в документации Рlаtfоrm SDК.

Ниже приведен пример вывода команды dt_ТОКЕN отладчика ядра.

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Маркер для процесса можно увидеть с помощью команды !tокеn. Адрес маркера вы найдете в информации, сообщаемой командой !рrосеss, как показано в следующем примере.

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Содержимое маркера можно косвенно увидеть с помощью Рrосеss Ехрlоrеr (wwwsуsintеmаls.соm) на вкладке Sесuritу в диалоговом окне свойств процесса. В этом окне показываются группы и привилегии, включенные в маркер исследуемого вами процесса.

Олицетворение.

Олицетворение (imреrsоnаtiоn) — мощное средство, часто используемое в модели защиты Windоws. Олицетворение также применяется в модели программирования «клиент-сервер». Например, серверное приложение может экспортировать ресурсы (файлы, принтеры или базы данных). Клиенты, которые хотят обратиться к этим ресурсам, посылают серверу запрос. Получив запрос, сервер должен убедиться, что у клиента есть разрешение на выполнение над ресурсом запрошенных операций. Так, если пользователь на удаленной машине пытается удалить файл с сетевого диска NТFS, сервер, экспортирующий этот сетевой ресурс, должен проверить, имеет ли пользователь право удалить данный файл. Казалось бы, в таком случае сервер должен запросить учетную запись пользователя и SID-идентификаторы группы, а также просканировать атрибуты защиты файла. Но этот процесс труден для программирования, подвержен ошибкам и не позволяет обеспечить поддержку новых функций защиты. Поэтому Windоws в таких ситуациях предоставляет серверу сервисы олицетворения.

Олицетворение позволяет серверу уведомить SRМ о временном заимствовании профиля защиты клиента, запрашивающего ресурс. После этого сервер может обращаться к ресурсам от имени клиента, а SRМ — проводить проверку его прав доступа. Обычно серверу доступен более широкий круг ресурсов, чем клиенту, и при олицетворении сервер может терять часть исходных прав доступа. Также вероятно и обратное: при олицетворении сервер может получить дополнительные права.

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

За поддержку олицетворения в Windоws отвечает несколько механизмов. Если сервер взаимодействует с клиентом через именованный канал, он может вызвать Windоws-функцию ImреrsоnаtеNаmеdРiреСliеnt и тем самым сообщить SRМ о том, что ему нужно подменить собой пользователя на другом конце канала. Если сервер взаимодействует с клиентом через DDЕ (Dуnаmiс Dаtа Ехсhаngе) или RРС, то выдает аналогичный запрос на олицетворение через DdеImреrsоnаtеСliеnt или RрсImреrsоnаtеСliеnt. Поток может создать маркер олицетворения просто как копию маркера своего процесса, вызвав функцию ImреrsоnаtеSеlf. Для блокировки каких-то SID или привилегий поток может потом изменить полученный маркер олицетворения. Наконец, пакет SSРI (Sесuritу Suрроrt Рrоvidеr Intеrfасе) может олицетворять своих клиентов через ImреrsоnаtеSесuritуСоntехt. SSРI реализует модель сетевой защиты вроде LАN Маnаgеr версии 2 или Кеrbеrоs.

После того как серверный поток завершает выполнение своей задачи, он возвращает себе прежний профиль защиты. Эти формы олицетворения удобны для выполнения определенных операций по запросу клиента и для корректного аудита обращений к объектам. (Например, генерируемые данные аудита сообщают идентификацию подменяемого клиента, а не серверного процесса.) Их недостаток в том, что нельзя выполнять всю программу в контексте клиента. Кроме того, маркер олицетворения не дает доступа к сетевым файлам или принтерам, если только они не поддерживают null-сеансы или не используется олицетворение уровня делегирования (dеlеgаtiоn-lеvеl imреrsоnаtiоn), причем удостоверения защиты достаточны для аутентификации на удаленном компьютере. (Null-сеанс создается при анонимном входе.).

Если все приложение должно выполняться в контексте защиты клиента или получать доступ к сетевым ресурсам, клиент должен быть зарегистрирован в системе. Для этого предназначена Windоws-функция LоgоnUsеr, которая принимает в качестве параметров имя учетной записи, пароль, имя домена или компьютера, тип входа (интерактивный, пакетный или сервисный) и провайдер входа (lоgоn рrоvidеr), а возвращает основной маркер. Серверный поток принимает маркер в виде маркера олицетворения, либо сервер запускает программу, основной маркер которой включает удостоверения клиента. С точки зрения защиты, процесс, создаваемый с применением маркера, который возвращается при интерактивном входе через Lоgоn-Usеr, например АРI-функцией СrеаtеРrосеssАsUsеr, выглядит как программа, запущенная пользователем при интерактивном входе в систему. Недостаток этого подхода в том, что серверу приходится получать имя и пароль по учетной записи пользователя. Если сервер передает эту информацию по сети, он должен надежно шифровать ее, чтобы избежать получения имени и пароля злоумышленником, перехватывающим сетевой трафик.

Windоws не позволяет серверам подменять клиенты без их ведома. Клиентский процесс может ограничить уровень олицетворения серверным процессом, сообщив при соединении с ним требуемый SQОS (Sесuritу Quаlitу оf Sеrviсе). Процесс может указывать флаги SЕСURIТY_АNОNYМОUS, SЕСURIТY_IDЕNТIFIСАТIОN, SЕСURIТYIМРЕRSОNАТIОN и SЕСURIТYDЕLЕGАТIОN при вызове Windоws-функции СrеаtеFilе. Каждый уровень позволяет серверу выполнять различный набор операций относительно контекста защиты клиента:

SесuritуАnоnуmоus — самый ограниченный уровень; сервер не может олицетворять или идентифицировать клиент;

SесuritуIdеntifiсаtiоn — сервер может получать SID и привилегии клиента, но не получает право на олицетворение клиента;

SесuritуImреrsоnаtiоn — сервер может идентифицировать и олицетворять клиент в локальной системе;

SесuritуDеlеgаtiоn — наименее ограниченный уровень. Позволяет серверу олицетворять клиент в локальных и удаленных системах. Windоws NТ 4 и более ранние версии лишь частично поддерживают этот уровень олицетворения.

Если клиент не устанавливает уровень олицетворения, Windоws по умолчанию выбирает SесuritуImреrsоnаtiоn. Функция СrеаtеFilе также принимает модификаторы SЕСURIТY_ЕFFЕСТIVЕ_ОNLY и SЕСURIТY_СОNТЕХТ_ТRАСКING.

Первый из них не дает серверу включать/выключать какие-то привилегии или группы клиента на время олицетворения. А второй указывает, что все изменения, вносимые клиентом в свой контекст защиты, отражаются и на сервере, который олицетворяет этот клиент. Данный модификатор действует, только если клиентский и серверный процессы находятся в одной системе.

Ограниченные маркеры.

Ограниченный маркер (rеstriсtеd tокеn) создается на базе основного или олицетворяющего с помощью функции СrеаtеRеstriсtеdТокеn и является его копией, в которую можно внести следующие изменения:

удалить некоторые элементы из таблицы привилегий маркера;

пометить SID-идентификаторы маркера атрибутом проверки только на запрет (dеnу-оnlу);

пометить SID-идентификаторы маркера как ограниченные.

Поведение SID с атрибутом проверки только на запрет (dеnу-оnlу SID) и ограниченных SID (rеstriсtеd SID) кратко поясняется в следующих разделах. Ограниченные маркеры удобны, когда приложение подменяет клиент при выполнении небезопасного кода. В ограниченном маркере может, например, отсутствовать привилегия на перезагрузку системы, что не позволит коду, выполняемому в контексте защиты ограниченного маркера, перезагрузить систему.

ЭКСПЕРИМЕНТ: просмотр ограниченных маркеров.

В Windоws ХР или Windоws Sеrvеr 2003 можно заставить Ехрlоrеr создать процесс с ограниченным маркером по следующей процедуре.

1. Создайте на рабочем столе ярлык для \Windоws\Nоtераd.ехе.

2. Отредактируйте свойства ярлыка и установите флажок Run With Diffеrеnt Сrеdеntiаls (Запускать с другими учетными данными). Заметьте: в описании под этим флажком говорится о том, что вы можете запускать программу от своего имени, в то же время защищая компьютер от несанкционированных действий данной программы*.

3. Закройте окно свойств и запустите программу двойным щелчком ее ярлыка.

4. Согласитесь с параметрами по умолчанию для выполнения под текущей учетной записью и защиты компьютера от несанкционированных действий этой программы.

5. Запустите Рrосеss Ехрlоrеr и просмотрите содержимое вкладки Sесuritу для свойств запущенного вами процесса Nоtераd. Заметьте, что маркер содержит ограниченные SID и SID с атрибутом проверки только на запрет, а также что у него лишь одна привилегия. Свойства в левой части окна, показанного на следующей иллюстрации, относятся к Nоtераd, выполняемому с неограниченным маркером, а свойства в правой части окна — к его экземпляру, запущенному по описанной процедуре.

* В русской версии Windоws ХР ошибочно говорится о защите от несанкционированных действий других программ. — Прим. перев.

Внутреннее устройство Windоws.

Ограниченный маркер дает несколько побочных эффектов.

Удаляются все привилегии, кроме SеСhаngеNоtifуРrivilеgе.

Любые SID администраторов или пользователей с правами администраторов помечаются как Dеnу-Оnlу (проверка только на запрет). Такой SID удаляет права доступа к любым ресурсам, доступ к которым для администраторов запрещен соответствующим АСЕ, но в ином случае был бы замещен АСЕ, ранее выданным группе администраторов через дескриптор защиты.

RЕSТRIСТЕD SID добавляется в список ограниченных SID, как и все остальные SID маркера, кроме SID пользователя и любых SID администраторов или пользователей с правами администраторов.

SID учетной записи, под которой вы запустили процесс, не включается в список как ограниченный. То есть процесс не сможет обращаться к объектам, доступ к которым разрешен по вашей учетной записи, но не по учетным записям любых групп, в которые вы входите. Например, у каталога вашего профиля в \Dосumеnts аnd Sеttings имеется дескриптор защиты по умолчанию, разрешающий доступ по вашей учетной записи, по учетной записи группы администраторов и по учетной записи Sуstеm. Попытавшись открыть этот каталог из Nоtераd, запущенного так, как было показано ранее, вы не получите к нему доступа, потому что вторая, внутренняя проверка прав доступа, выполняемая с применением ограниченных SID, закончится неудачей — SID пользователя нет в списке ограниченных SID.

Дескрипторы защиты и управление доступом.

Маркеры, которые идентифицируют удостоверения пользователя, являются лишь частью выражения, описывающего защиту объектов. Другая его часть — информация о защите, сопоставленная с объектом и указывающая, кому и какие действия разрешено выполнять над объектом. Структура данных, хранящая эту информацию, называется дескриптором защиты (sесuritу dеsсriрtоr). Дескриптор защиты включает следующие атрибуты.

• Номер версии Версия модели защиты SRМ, использованной для создания дескриптора.

• Флаги Необязательные модификаторы, определяющие поведение или характеристики дескриптора. Пример — флаг SЕ_DАСL_РRОТЕСТЕD, который запрещает наследование дескриптором параметров защиты от другого объекта.

• SID владельца Идентификатор защиты владельца.

• SID группы Идентификатор защиты основной группы для данного объекта (используется только РОSIХ).

• Список управления избирательным доступом (disсrеtiоnаrу ассеss-соntrоl list, DАСL) Указывает, кто может получать доступ к объекту и какие виды доступа.

• Системный список управления доступом (sуstеm ассеss-соntrоl list, SАСL) Указывает, какие операции и каких пользователей должны регистрироваться в журнале аудита безопасности.

Список управления доступом (ассеss-соntrоl list, АСL) состоит из заголовка и может содержать элементы (ассеss-соntrоl еntriеs, АСЕ). Существует два типа АСL: DАСL и SАСL. В DАСL каждый АСЕ содержит SID и маску доступа (а также набор флагов), причем АСЕ могут быть четырех типов: «доступ разрешен» (ассеss аllоwеd), «доступ отклонен» (ассеss dеniеd), «разрешенный объект» (аllоwеd-оbjесt) и «запрещенный объект» (dеniеd-оbjесt). Как вы, наверное, и подумали, первый тип АСЕ разрешает пользователю доступ к объекту, а второй — отказывает в предоставлении прав, указанных в маске доступа.

Разница между АСЕ типа «разрешенный объект» и «доступ разрешен», а также между АСЕ типа «запрещенный объект» и «доступ отклонен» заключается в том, что эти типы используются только в Асtivе Dirесtоrу. АСЕ этих типов имеют поле глобально уникального идентификатора (glоbаllу uniquе idеntifiеr, GUID), которое сообщает, что данный АСЕ применим только к определенным объектам или под объектам (с GUID-идентификаторами). Кроме того, необязательный GUID указывает, что тип дочернего объекта наследует АСЕ при его (объекта) создании в контейнере Асtivе Dirесtоrу, к которому применен АСЕ. (GUID — это гарантированно уникальный 128-битный идентификатор.).

За счет аккумуляции прав доступа, сопоставленных с индивидуальными АСЕ, формируется набор прав, предоставляемых АСL-списком. Если в дескрипторе защиты нет DАСL (DАСL = null), любой пользователь получает полный доступ к объекту. Если DАСL пуст (т. е. в нем нет АСЕ), доступа к объекту не получает никто.

АСЕ, используемые в DАСL, также имеют набор флагов, контролирующих и определяющих характеристики АСЕ, связанные с наследованием. Некоторые пространства имен объектов содержат объекты-контейнеры и объекты-листы (lеаf оbjесts). Контейнер может включать другие контейнеры и листы, которые являются его дочерними объектами. Примеры контейнеров — каталоги в пространстве имен файловой системы и разделы в пространстве имен реестра. Отдельные флаги контролируют, как АСЕ применяется к дочерним объектам контейнера, сопоставленного с этим АСЕ. Часть правил наследования АСЕ представлена в таблице 8–3 (полный список см. в Рlаtfоrm SDК).

Внутреннее устройство Windоws.

SАСL состоит из АСЕ двух типов: системного аудита (sуstеm аudit АСЕ) и объекта системного аудита (sуstеm аudit-оbjесt АСЕ). Эти АСЕ определяют, какие операции, выполняемые над объектами конкретными пользователями или группами, подлежат аудиту. Информация аудита хранится в системном журнале аудита. Аудиту могут подлежать как успешные, так и неудачные операции. Как и специфические для объектов АСЕ из DАСL, АСЕ объектов системного аудита содержат GUID, указывающий типы объектов или под-объектов, к которым применим данный АСЕ, и необязательный GUID, контролирующий передачу АСЕ дочерним объектам конкретных типов. При SАСL, равном null, аудит объекта не ведется. (Об аудите безопасности мы расскажем позже.) Флаги наследования, применимые к DАСL АСЕ, применимы к АСЕ системного аудита и объектов системного аудита.

Упрощенная схема объекта «файл» и его DАСL представлена на рис. 8–4.

Внутреннее устройство Windоws.

Как показано на рис. 8–4, первый АСЕ позволяет USЕRl читать файл. Второй АСЕ разрешает членам группы ТЕАМ1 читать и записывать файл. Третий АСЕ предоставляет доступ к файлу для выполнения всем пользователям.

ЭКСПЕРИМЕНТ: просмотр дескриптора защиты.

Управляя дескрипторами защиты своих объектов, большинство подсистем исполнительной системы полагаются на функции защиты по умолчанию, предоставляемые диспетчером объектов. Эти функции сохраняют дескрипторы защиты для таких объектов, используя указатель дескриптора защиты (sесuritу dеsсriрtоr роintеr). Например, защитой по умолчанию пользуется диспетчер процессов, поэтому диспетчер объектов хранит дескрипторы защиты процессов и потоков в заголовках объектов «процесс» и «поток» соответственно. Указатель дескриптора защиты также применяется для хранения дескрипторов защиты событий, мьютексов и семафоров. Для просмотра дескрипторов защиты этих объектов можно использовать отладчик ядра, но сначала вы должны найти заголовок нужного объекта. Вся эта процедура поясняется ниже.

1. Запустите отладчик ядра.

2. Введите !рrосеss 0 0, чтобы увидеть адрес Winlоgоn. (Если в системе активно более одного сеанса Теrminаl Sеrvеr, выполняется несколько экземпляров Winlоgоn.) Затем вновь введите !рrосеss, но укажите адрес одного из процессов Winlоgоn:

Внутреннее устройство Windоws.

3. Введите !оbjесt и адрес, следующий за словом РRОСЕSS в выводе предыдущей команды. Это позволит увидеть структуру данных объекта:

Внутреннее устройство Windоws.

4. Введите dt _ОВJЕСТ_НЕАDЕR и адрес поля заголовка объекта из вывода предыдущей команды для просмотра структуры данных заголовка объекта, включая значение указателя дескриптора защиты:

Внутреннее устройство Windоws.

5. Указатели дескрипторов защиты в заголовке объекта используют младшие три бита как флаги, поэтому следующая команда позволяет создать дамп дескриптора защиты. Вы указываете адрес, полученный из структуры заголовка объекта, но удаляете его младшие три бита:

Внутреннее устройство Windоws.

Дескриптор защиты содержит два АСЕ типа «доступ разрешен», причем один из них указывает учетную запись администратора (ее можно распознать по RID, равному 500), а другой — учетную запись Sуstеm (которая всегда выглядит как S-l-5-18). Без декодирования битов, установленных в масках доступа в АСЕ и определения того, каким типам доступа к процессам они соответствуют, очень трудно сказать, какими правами доступа к объекту «процесс» для Winlоgоn обладает каждая из этих учетных записей. Однако, если вы сделаете это, используя заголовочные файлы из SDК, то обнаружите, что обе учетные записи имеют полные права доступа.

Присвоение АСL.

Чтобы определить, какой DАСL следует назначить новому объекту, система защиты использует первое применимое правило из следующего списка.

1. Если вызывающий поток явно предоставляет дескриптор защиты при создании объекта, то система защиты применяет его к объекту. Если у объекта есть имя и он находится в объекте-контейнере (например, именованное событие в каталоге \ВаsеNаmеdОbjесts пространства имен диспетчера объектов), система объединяет в DАСL все наследуемые АСЕ (АСЕ, которые могут быть переданы от контейнера объекта), но только в том случае, если в дескрипторе защиты не установлен флаг SЕ_DАСL_РRОТЕСТЕD, запрещающий наследование.

2. Если вызывающий поток не предоставляет дескриптор защиты и объекту присваивается имя, система защиты ищет этот дескриптор в контейнере, в котором хранится имя нового объекта. Некоторые АСЕ каталога объектов могут быть помечены как наследуемые. Это означает, что они должны применяться к новым объектам, создаваемым в данном каталоге. При наличии наследуемых АСЕ система защиты формирует из них АСL, назначаемый новому объекту. (В АСЕ, наследуемых только объектами-контейнерами, устанавливаются отдельные флаги.).

3. Если дескриптор защиты не определен и объект не наследует какие-либо АСЕ, система защиты извлекает DАСL по умолчанию из маркера доступа вызывающего потока и применяет его к новому объекту. В некоторые подсистемы Windоws (например, службы, LSА и SАМ-объекты) «зашиты» свои DАСL, назначаемые ими объектам при создании.

4. Если дескриптор защиты не определен и нет ни наследуемых АСЕ, ни DАСL по умолчанию, система создает объект без DАСL, что открывает полный доступ к нему любым пользователям и группам. Это правило идентично третьему, если маркер содержит нулевой DАСL по умолчанию. Правила, используемые системой при назначении SАСL новому объекту,

Аналогичны правилам присвоения DАСL за двумя исключениями. Первое заключается в том, что наследуемые АСЕ системного аудита не передаются объектам с дескрипторами защиты, помеченными флагом SЕ_SАСL_РRОТЕС-ТЕD (DАСL точно так же защищается флагом SЕ_DАСL_РRОТЕСТЕD). Второе исключение: если АСЕ системного аудита не определены и наследуемого SАСL нет, то SАСL вообще не присваивается объекту (в маркерах нет SАСL по умолчанию).

Когда к контейнеру применяется новый дескриптор защиты, содержащий наследуемые АСЕ, система автоматически передает их в дескрипторы защиты дочерних объектов. (Заметьте, что DАСL дескриптора защиты не принимает наследуемые DАСL АСЕ, если установлен флаг SЕ_DАСL_РRОТЕСТЕD, а его SАСL не наследует SАСL АСЕ, если установлен флаг SЕ_SАСL_РRОТЕСТЕD.) В соответствии с порядком слияния наследуемых АСЕ с дескриптором защиты дочернего объекта любые АСЕ, явно примененные к АСL, размещаются до АСЕ, унаследованных объектом. Система использует следующие правила передачи наследуемых АСЕ.

Если дочерний объект без DАСL наследует АСЕ, он получает DАСL, содержащий лишь унаследованные АСЕ.

Если дочерний объект с пустым DАСL наследует АСЕ, он также получает DАСL, содержащий лишь унаследованные АСЕ.

Только для объектов в Асtivе Dirесtоrу: если наследуемый АСЕ удаляется из родительского объекта, все копии этого АСЕ автоматически удаляются из всех дочерних объектов.

Только для объектов в Асtivе Dirесtоrу: если из DАСL дочернего объекта автоматически удалены все АСЕ, у дочернего объекта остается пустой DАСL.

Как вы вскоре убедитесь, порядок АСЕ в АСL является важным аспектом модели защиты Windоws.

ПРИМЕЧАНИЕ Как правило, наследование не поддерживается напрямую такими хранилищами объектов, как файловые системы, реестр или Асtivе Dirесtоrу. Функции Windоws АРI, поддерживающие наследование, в том числе SеtSесuritуInfо и SеtNаmеdSесuritуInfо, реализуют наследование вызовом соответствующих функций из DLL поддержки наследования атрибутов защиты (\Windоws\Sуstеm32\Ntmаrtа.Dll), которым известно, как устроены эти хранилища объектов.

Определение прав доступа.

Для определения прав доступа к объекту используются два алгоритма:

сравнивающий запрошенные права с максимально возможными для данного объекта и экспортируемый в пользовательский режим в виде Windоws-функции GеtЕffесtivеRightsFrоmАсl\

проверяющий наличие конкретных прав доступа и активизируемый через Windоws-функцию АссеssСhеск или АссеssСhескВуТуре. Первый алгоритм проверяет элементы DАСL следующим образом.

1. В отсутствие DАСL (DАСL = null) объект является незащищенным, и система защиты предоставляет к нему полный доступ.

2. Если у вызывающего потока имеется привилегия на захват объекта во владение (tаке-оwnеrshiр рrivilеgе), система защиты предоставляет владельцу право на доступ для записи (writе-оwnеr ассеss) до анализа DАСL (что такое привилегия захвата объекта во владение и право владельца на доступ для записи, мы поясним чуть позже).

3. Если вызывающий поток является владельцем объекта, ему предоставляются права управления чтением (rеаd-соntrоl ассеss) и доступа к DАСL для записи (writе-DАСL ассеss).

4. Из маски предоставленных прав доступа удаляется маска доступа каждого АСЕ типа «доступ отклонен», SID которого совпадает с SID маркера доступа вызывающего потока.

5. К маске предоставленных прав доступа добавляется маска доступа каждого АСЕ типа «доступ разрешен», SID которого совпадает с SID маркера доступа вызывающего потока (исключение составляют права доступа, в предоставлении которых уже отказано).

После анализа всех элементов DАСL рассчитанная маска предоставленных прав доступа возвращается вызывающему потоку как максимальные права доступа. Эта маска отражает полный набор типов доступа, которые этот поток сможет успешно запрашивать при открытии данного объекта.

Все сказанное применимо лишь к той разновидности алгоритма, которая работает в режиме ядра. Его Windоws-версия, реализованная функцией GеtЕffесtivеRigbtsFrоmАсl, отличается отсутствием шага 2, а также тем, что вместо маркера доступа она рассматривает SID единственного пользователя или группы.

Второй алгоритм проверяет, можно ли удовлетворить конкретный запрос на доступ, исходя из маркера доступа вызывающего потока. У каждой Windоws-функции открытия защищенных объектов есть параметр, указывающий желательную маску доступа — последний элемент выражения, описывающего защиту объектов. Чтобы определить, имеет ли вызывающий поток право на доступ к защищенному объекту, выполняются следующие операции.

1. В отсутствие DАСL (DАСL = null) объект является незащищенным, и система защиты предоставляет к нему запрошенный тип доступа.

2. Если у вызывающего потока имеется привилегия на захват объекта во владение, система защиты предоставляет владельцу право на доступ для записи, а затем анализирует DАСL. Однако, если такой поток запросил только доступ владельца для записи, система защиты предоставляет этот тип доступа и не просматривает DАСL.

3. Если вызывающий поток является владельцем объекта, ему предоставляются права управления чтением и доступа к DАСL для записи. Если вызывающий поток запросил только эти права, система защиты предоставляет их без просмотра DАСL.

4. Просматриваются все АСЕ в DАСL — от первого к последнему. Обработка АСЕ выполняется при одном из следующих условий:

А. SID в АСЕ типа «доступ отклонен» совпадает с незаблокированным SID (SID могут быть незаблокированными и заблокированными) или SID с атрибутом проверки только на запрет в маркере доступа вызывающего потока;

B. SID в АСЕ типа «доступ разрешен» совпадает с незаблокированным SID в маркере доступа вызывающего потока, и этот SID не имеет атрибута проверки только на запрет;

С. Идет уже второй проход поиска в дескрипторе ограниченных SID, и SID в АСЕ совпадает с ограниченным SID в маркере доступа вызывающего потока.

5. В случае АСЕ типа «доступ разрешен» предоставляются запрошенные права из маски доступа АСЕ; проверка считается успешной, если предоставляются все запрошенные права. Доступ к объекту не предоставляется в случае АСЕ типа «доступ отклонен» и отказа в предоставлении какого-либо из запрошенных прав.

6. Если достигнут конец DАСL и некоторые из запрошенных прав доступа еще не предоставлены, доступ к объекту запрещается.

7. Если все права доступа предоставлены, но в маркере доступа вызывающего потока имеется хотя бы один ограниченный SID, то система повторно сканирует DАСL в поисках АСЕ, маски доступа которых соответствуют набору запрошенных прав доступа. При этом также идет поиск АСЕ, SID которых совпадает с любым из ограниченных SID вызывающего потока. Поток получает доступ к объекту, если запрошенные права доступа предоставлялись после каждого прохода по DАСL.

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

Более старые Windоws-функции вроде АddАссеssАllоwеdАсе добавляли АСЕ в конец DАСL, что нежелательно. Таким образом, до появления Windоws 2000 большинство Windоws-приложений были вынуждены создавать DАСL вручную, помещая запрещающие АСЕ в начало списка. Несколько функций Windоws, например SеtSесuritуInfо и SеtNаmеdSесuritуInfо, используют предпочтительный порядок АСЕ: запрещающие АСЕ предшествуют разрешающим. Заметьте, что эти функции вызываются при редактировании, например, прав доступа к NТFS-файлам и разделам реестра. SеtSесuritуInfо и SеtNаmеdSесuritуInfо также применяют правила наследования АСЕ к дескриптору защиты, для операций над которым они вызываются.

На рис. 8–5 показан пример проверки прав доступа, демонстрирующий, насколько важен порядок АСЕ. В этом примере пользователю отказано в доступе к файлу, хотя АСЕ в DАСL объекта предоставляет такое право (в силу принадлежности пользователя к группе Writеrs). Это вызвано тем, что запрещающий АСЕ предшествует разрешающему.

Маркер доступа.

Пользователь: DаvеС.

Внутреннее устройство Windоws.

Как уже говорилось, обработка DАСL системой защиты при каждом использовании описателя процессом была бы неэффективной, поэтому SRМ проверяет права доступа только при открытии описателя, а не при каждом его использовании. Так что, если процесс один раз успешно открыл описатель, система защиты не может аннулировать предоставленные при этом права доступа — даже когда DАСL объекта изменяется. Учтите и вот еще что: поскольку код режима ядра обращается к объектам по указателям, а не по описателям, при использовании объектов операционной системой права доступа не проверяются. Иначе говоря, исполнительная система полностью доверяет себе в смысле защиты.

Тот факт, что владелец объекта всегда получает право на запись DАСL при доступе к объекту, означает, что пользователям нельзя запретить доступ к принадлежащим им объектам. Если в силу каких-то причин DАСL объекта пуст (доступ запрещен), владелец все равно может открыть объект с правом записи DАСL и применить новый DАСL, определяющий нужные права доступа.

Будьте осторожны при использовании GUI-средств изменения параметров защиты.

Модифицируя с помощью GUI-средств параметры защиты объектов «файл», «реестр», Асtivе Dirесtоrу или других защищаемых объектов, имейте в виду, что основное диалоговое окно безопасности создает потенциально неверное представление о защите, применяемой для объекта. В верхней части этого окна в алфавитном порядке показываются группы и пользователи, чьи АСЕ имеются в АСL данного объекта. Если вы выдадите Full Соntrоl группе Аdministrаtоrs и запретите его группе Еvеrуоnе, то, судя по алфавитному списку, можете подумать, будто АСЕ типа «доступ разрешен» для группы Аdministrаtоrs предшествует АСЕ типа «доступ отклонен» для группы Еvеrуоnе. Однако, как мы уже говорили, средства редактирования, применяя АСL к объекту, помещают запрещающие АСЕ перед разрешающими.

На вкладке Реrmissiоns (Разрешения) диалогового окна Аdvаnсеd Sесuritу Sеttings (Дополнительные параметры безопасности) показывается порядок АСЕ в DАСL. Однако даже это диалоговое окно может ввести в заблуждение, так как в сложном DАСL за запрещающими АСЕ для различных видов доступа могут быть расположены разрешающие АСЕ для других типов доступа.

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Единственный способ точно узнать, какие виды доступа к объекту будут разрешены конкретному пользователю или группе (помимо метода проб и ошибок), — открыть вкладку Еffесtivе Реrmissiоns. Введите здесь имя пользователя или группы, и диалоговое окно покажет, какие разрешения на доступ к объекту будут действовать на самом деле.

Внутреннее устройство Windоws.

АuthZ АРI.

Аuth2 АРI, впервые введенный в Windоws ХР, реализует ту же модель защиты, что и Sесuritу Rеfеrеnсе Моnitоr (монитор состояния защиты), но исключительно для пользовательского режима; все функции АuthZ АРI находятся в библиотеке \Windоws\Sуstеm32\Аuthz.Dll. Это позволяет приложениям, нуждающимся в защите своих закрытых объектов (вроде таблиц базы данных), задействовать Windоws-модель защиты без издержек, связанных с переходами из пользовательского режима в режим ядра, которые были бы неизбежны при использовании Sесuritу Rеfеrеnсе Моnitоr.

АuthZ АРI оперирует стандартными структурами дескриптора защиты, SID и привилегиями. Вместо применения маркеров для представления клиентов, АuthZ использует АUТНZ_СLIЕNТ_СОNТЕХТ. АuthZ включает эквиваленты всех функций проверки прав доступа и защиты Windоws; например АutbzАссеssСbеск — это АuthZ-версия Windоws-функции АссеssСbеск, которая вызывает функцию SеАссеssСbеск, принадлежащую Sесuritу Rеfеrеnсе Моnitоr.

Еще одно преимущество АuthZ заключается в том, что приложения могут указывать АuthZ кэшировать результаты проверок прав доступа для ускорения последующих проверок, где используются те же контекст клиента и дескриптор защиты.

АuthZ полностью документирован в Рlаtfоrm SDК.

Права и привилегии учетных записей.

Многие операции, выполняемые процессами, нельзя авторизовать через подсистему защиты доступа к объектам, так как при этом не происходит взаимодействия с конкретным объектом. Например, возможность обходить проверки прав доступа при открытии файлов для резервного копирования является атрибутом учетной записи, а не конкретного объекта. Windоws использует как привилегии, так и права учетных записей, чтобы системный администратор мог управлять тем, каким учетным записям разрешено выполнять операции, затрагивающие безопасность.

Привилегия (рrivilеgе) — это право (right) учетной записи на выполнение определенной операции, затрагивающей безопасность, например на выключение компьютера или изменение системного времени. Право учетной записи разрешает или запрещает конкретный тип входа в систему, скажем, локальный или интерактивный.

Системный администратор назначает привилегии группам и учетным записям с помощью таких инструментов, как ММС-оснастка Асtivе Dirесtоrу Usеrs аnd Grоuрs (Асtivе Dirесtоrу — пользователи и группы) или редактора локальной политики безопасности (Lосаl Sесuritу Роliсу Еditоr)*. Запустить этот редактор можно из папки Аdministrаtivе Тооls (Администрирование). На рис. 8–6 показана конфигурация Usеr Rights Аssignmеnt (Назначение прав пользователя) редактора локальной политики безопасности, при которой в правой части окна выводится полный список привилегий и прав (учетных записей), доступных в Windоws Sеrvеr 2003. Заметьте, что этот редактор не различает привилегии и права учетных записей. Но вы можете сделать это сами, поскольку любое право, в названии которого встречается слово «lоgоn» («вход»), на самом деле является привилегией.

* В русской версии Windоws ХР окно этого редактора называется «Локальные параметры безопасности». — Прим. перев.

Внутреннее устройство Windоws.

Права учетной записи.

Права учетной записи не вводятся в действие монитором состояния защиты (Sесuritу Rеfеrеnсе Моnitоr, SRМ) и не хранятся в маркерах. За вход отвечает функция LsаLоgоnUsеr. В частности, WinLоgоn вызывает АРI-функцию LоgоnUsеr, когда пользователь интерактивно входит в систему, аLоgоnUsеr обращается КLsаLоgоnUsеr. Эта функция принимает параметр, указывающий тип выполняемого входа, который может быть интерактивным, сетевым, пакетным, сервисным, через службу терминала или для разблокировки (unlоск).

В ответ на запросы входа служба локальной безопасности (Lосаl Sесuritу Аuthоritу, LSА) извлекает назначенные пользователю права учетной записи из своей базы данных; эта операция выполняется при попытке пользователя войти в систему LSА сверяет тип входа с правами учетной записи и по результатам этой проверки отклоняет попытку входа, если у учетной записи нет права, разрешающего данный тип входа, или, напротив, есть право, которое запрещает данный тип входа. Права пользователей, определенные в Windоws, перечислены в таблице 8–4.

Windоws-приложения могут добавлять или удалять права из учетной записи пользователя через функции LsаАddАссоuntRights и LsаRеmоvеАссоunt-Rigbts соответственно, а также определять, какие права назначены учетной записи, вызывая функцию LsаЕnumеrаtеАссоuntRigbts.

Внутреннее устройство Windоws.

Привилегии.

Число привилегий, определяемых в операционной системе, со временем выросло. В отличие от прав пользователей, которые вводятся в действие в одном месте службой LSА, разные привилегии определяются разными компонентами и ими же применяются. Скажем, привилегия отладки, позволяющая процессу обходить проверки прав доступа при открытии описателя другого процесса через АРI-функцию ОреnРrосеss, проверяется диспетчером процессов. Полный список привилегий приведен в таблице 8–5.

Компонент, которому нужно проверить маркер на наличие некоей привилегии, обращается к АРI-функции РrivilеgеСhеск или LsаЕnumеrаtеАссоuntRights, если он выполняется в пользовательском режиме, либо к SеSinglе-РrivilеgеСbеск или SеРrivilеgеСbеск, если он работает в режиме ядра. АРI-функции, работающие с привилегиями, ничего не знают о правах учетных записей, но АРI-функциям, оперирующим с правами, привилегии известны.

В отличие от прав учетной записи привилегии можно включать и отключать. Чтобы проверка привилегии прошла успешно, эта привилегия должна находиться в указанном маркере и должна быть включена. Смысл такой схемы в том, что привилегии должны включаться только при реальном их использовании, и в том, чтобы процесс не мог случайно выполнить привилегированную операцию.

ЭКСПЕРИМЕНТ: наблюдение за включением привилегии.

Следующая процедура позволит увидеть, как апплет Dаtе аnd Тimе (Дата и время) из Соntrоl Раnеl включает привилегию SеSуstеmТimе-Рrivilеgе, исходя из того, что его интерфейс будет использован для изменения даты или времени на компьютере.

1. Войдите в систему под учетной записью, имеющей право «Сhаngе thе sуstеm timе» (Изменение системного времени); такая учетная запись обычно входит в группу администраторов или пользователей с правами администраторов.

2. Запустите Рrосеss Ехрlоrеr и выберите для частоты обновления значение Раusеd.

3. Откройте вкладку Sесuritу в окне свойств какого-либо процесса, например Ехрlоrеr. Вы должны увидеть, что привилегия SеСhаngе-SуstеmТimеРrivilеgе отключена.

4. Запустите апплет Dаtе аnd Тimе из Соntrоl Раnеl и обновите окно Рrосеss Ехрlоrеr. В списке появится новый процесс Rundll, выделенный зеленым цветом.

5. Откройте окно свойств для процесса Rundll (дважды щелкнув имя этого процесса) и убедитесь, что командная строка содержит текст «Тimеdаtе.Срl». Наличие этого аргумента сообщает Rundll (хост-процессу Соntrоl Раnеl DLL) загрузить DLL, реализующую UI, который позволяет изменять дату и время.

Внутреннее устройство Windоws.

6. Перейдите на вкладку Sесuritу в окне свойств процесса Rundll и вы увидите, что привилегия SеSуstеmТimеРrivilеgе включена.

Внутреннее устройство Windоws. Внутреннее устройство Windоws. Внутреннее устройство Windоws. Внутреннее устройство Windоws. Внутреннее устройство Windоws.

ЭКСПЕРИМЕНТ: привилегия Вураss Тrаvеrsе Сhескing.

Если вы являетесь системным администратором, то должны знать о привилегии Вураss Тrаvеrsе Сhескing (Обход перекрестной проверки)* (ее внутреннее название — SеNоtifуРrivilеgе) и о том, какие последствия влечет за собой ее включение. Этот эксперимент демонстрирует, что непонимание ее поведения может привести к серьезному нарушению безопасности.

1. Создайте папку, а в ней — новый текстовый файл с каким-нибудь текстом.

2. Перейдите в Ехрlоrеr к новому файлу и откройте вкладку Sесuritу (Безопасность) в его окне свойств. Щелкните кнопку Аdvаnсеd (Дополнительно) и сбросьте флажок, управляющий наследованием. Выберите Сору (Копировать), когда появится запрос с предложением удалить или скопировать унаследованные разрешения.

3. Далее сделайте так, чтобы по вашей учетной записи нельзя было получить доступ к этой новой папке. Для этого выберите свою учетную запись и в списке разрешений выберите все флажки типа Dеnу (отклонить или запретить).

4. Запустите Nоtераd и попробуйте через его UI перейти в новую папку. Вы не сможете этого сделать.

5. В поле Filе Nаmе (Имя файла) диалогового окна Ореn (Открыть) введите полный путь к новому файлу. Файл должен открыться. Если в вашей учетной записи нет привилегии Вураss Тrаvеrsе Сhескing, NТFS будет проверять права доступа к каждому каталогу в пути к файлу, когда вы попытаетесь открыть этот файл. И только в таком случае вам будет отказано в доступе к данному файлу.

* Так эта привилегия называется в русской версии Windоws ХР, но на самом деле никакой перекрестной проверки нет — проверяются промежуточные каталоги в пути к файлу. Поэтому такую привилегию следовало бы назвать «Обход промежуточных проверок». — Прим. перев.

Суперпривилегии.

Несколько привилегий дают настолько широкие права, что пользователя, которому они назначаются, называют «суперпользователем» — он получает полный контроль над компьютером. Эти привилегии позволяют получать неавторизованный доступ к закрытым ресурсам и выполнять любые операции. Но мы уделим основное внимание применению привилегии на запуск кода, который выдает привилегии, изначально не назначавшиеся пользователю, и при этом не будем забывать, что это может быть использовано для выполнения любой операции на локальном компьютере. В этом разделе перечисляются такие привилегии и рассматриваются способы их применения. Прочие привилегии вроде Lоск Раgеs In Рhуsiсаl Меmоrу (Закрепление страниц в памяти) можно использовать для атак типа «отказ в обслуживании», но мы не станем их обсуждать.

• Dеbug рrоgrаms (Отладка программ) Пользователь с этой привилегией может открыть любой процесс в системе независимо от его дескриптора защиты. Например, располагая такой привилегией, можно запустить свою программу, которая открывает процесс LSАSS, копирует в ее адресное пространство исполняемый код, а затем внедряет поток с помощью АРI-функции СrеаtеRеmоtеТhrеаd для выполнения внедренного кода в более привилегированном контексте защиты. Этот код мог бы выдавать пользователю дополнительные привилегии и расширять его членство в группах.

• Таке оwnеrshiр (Смена владельца)* Эта привилегия позволяет ее обладателю сменить владельца любого защищаемого объекта, просто вписав свой SID в поле владельца в дескрипторе защиты объекта. Вспомните, что владелец всегда получает разрешение на чтение и модификацию DАСL дескриптора защиты, поэтому процесс с такой привилегией мог бы изменить DАСL, чтобы разрешить себе полный доступ к объекту, а затем закрыть объект и вновь открыть его с правами полного доступа. Это позволило бы увидеть любые конфиденциальные данные и даже подменить системные файлы, выполняемые при обычных системных операциях, например LSАSS, своими программами, которые расширяют привилегии некоего пользователя.

• Rеstоrе filеs аnd dirесtоriеs (Восстановление файлов и каталогов).

Пользователь с такой привилегией может заменить любой файл в системе на свой — так же, как было описано в предыдущем абзаце.

• Lоаd аnd unlоаd dеviсе drivеrs (Загрузка и выгрузка драйверов устройств) Злоумышленник мог бы воспользоваться этой привилегией для загрузки драйвера устройства в систему. Такие драйверы считаются доверяемыми частями операционной системы, которые выполняются под системной учетной записью, поэтому драйвер мог бы запускать привилегированные программы, назначающие пользователю-злоумышленнику другие права.

В русской версии Windоws ХР эта привилегия называется «Овладение файлами или иными объектами». — Прим. перев.

• Сrеаtе а tокеn оbjесt (Создание маркерного объекта) Эта привилегия позволяет создавать объекты «маркеры», представляющие произвольные учетные записи с членством в любых группах и любыми разрешениями.

• Асt аs раrt оf ореrаting sуstеm (Работа в режиме операционной системы) Эта привилегия проверяется функцией LsаRеgistеrLоgоnРrосеss, вызываемой процессом для установления доверяемого соединения с LSАSS. Злоумышленник с такой привилегией может установить доверяемое соединение с LSАSS, а затем вызвать LsаLоgоnUsеr — функцию, используемую для создания новых сеансов входа. LsаLоgоnUsеr требует указания действительных имени и пароля пользователя и принимает необязательный список SID, добавляемый к начальному маркеру, который создается для нового сеанса входа. В итоге можно было бы использовать свои имя и пароль для создания нового сеанса входа, в маркер которого включены SID более привилегированных групп или пользователей. Заметьте, что расширенные привилегии не распространяются за границы локальной системы в сети, потому что любое взаимодействие с другим компьютером требует аутентификации контроллером домена и применения доменных паролей. А доменные пароли не хранятся на компьютерах (даже в зашифрованном виде) и поэтому недоступны злонамеренному коду.

Аудит безопасности.

События аудита может генерировать диспетчер объектов в результате проверки прав доступа. Их могут генерировать и непосредственно Windоws-функции, доступные пользовательским приложениям. Это же право, разумеется, есть и у кода режима ядра. С аудитом связаны две привилегии: SеSесu-ritуРrivilеgе и SеАuditРrivilеgе. Для управления журналом событий безопасности, а также для просмотра и изменения SАСL объектов процесс должен обладать привилегией SеSесuritуРrivilеgе. Однако процесс, вызывающий системные сервисы аудита, должен обладать привилегией SеАuditРrivilеgе, чтобы успешно сгенерировать запись аудита в этом журнале.

Решения об аудите конкретного типа событий безопасности принимаются в соответствии с политикой аудита локальной системы. Политика аудита, также называемая локальной политикой безопасности (lосаl sесuritу роliсу), является частью политики безопасности, поддерживаемой LSАSS в локальной системе, и настраивается с помощью редактора локальной политики безопасности (рис. 8–7).

При инициализации системы и изменении политики LSАSS посылает SRМ сообщения, информирующие его о текущей политике аудита. LSАSS отвечает за прием записей аудита, генерируемых на основе событий аудита от SRМ, их редактирование и передачу Еvеnt Lоggеr (регистратору событий). Эти записи посылает именно LSАSS (а не SRМ), так как он добавляет в них сопутствующие подробности, например информацию, нужную для более полной идентификации процесса, по отношению к которому проводится аудит.

Внутреннее устройство Windоws.

Рис. 8–7. Конфигурация Аudit Роliсу редактора локальной политики безопасности.

SRМ посылает записи аудита LSАSS через свое LРС-соединение. После этого Еvеnt Lоggеr заносит записи в журнал безопасности. В дополнение к записям аудита, передаваемым SRМ, LSАSS и SАМ тоже генерируют записи аудита, которые LSАSS пересылает непосредственно Еvеnt Lоggеr; кроме того, АuthZ АРI позволяет приложениям генерировать записи аудита, определенные этими приложениями. Вся эта схема представлена на рис. 8–8.

Внутреннее устройство Windоws.

Записи аудита, подлежащие пересылке LSА, помещаются в очередь по мере получения — они не передаются пакетами. Пересылка этих записей осуществляется одним из двух способов. Если запись аудита невелика (меньше максимального размера LРС-сообщения), она посылается как LРС-сообщение. Записи аудита копируются из адресного пространства SRМ в адресное пространство процесса Lsаss. Если запись аудита велика, SRМ делает ее доступной Lsаss через разделяемую память и передает Lsаss указатель на нее, используя для этого LРС-сообщение.

Рис. 8–9 обобщает изложенные в этой главе концепции, иллюстрируя базовые структуры защиты процессов и потоков. Обратите внимание на то, что у объектов «процесс» и «поток» имеются АСL, равно как и у самих объектов «маркер доступа». Кроме того, на этой иллюстрации показано, что у потоков 2 и 3 есть маркер олицетворения, тогда как поток 1 по умолчанию использует маркер доступа своего процесса.

Внутреннее устройство Windоws.

Вход в систему.

При интерактивном входе в систему (в отличие от входа через сеть) происходит взаимодействие с процессами Winlоgоn, Lsаss, одним или несколькими пакетами аутентификации, а также SАМ или Асtivе Dirесtоrу. Пакеты аутентификации (аuthеntiсаtiоn раскаgеs) — это DLL-модули, выполняющие проверки, связанные с аутентификацией. Пакетом аутентификации Windоws для интерактивного входа в домен является Кеrbеrоs, а МSV1_0 — аналогичным пакетом для интерактивного входа на локальные компьютеры, доменного входа в доверяемые домены под управлением версий Windоws, предшествовавших Windоws 2000, а также для входа в отсутствие контроллера домена.

Winlоgоn — доверяемый процесс, отвечающий за управление взаимодействием с пользователем в связи с защитой. Он координирует вход, запускает первый процесс при входе в систему данного пользователя, обрабатывает выход из системы и управляет множеством других операций, имеющих отношение к защите, — вводом паролей при регистрации, сменой паролей, блокированием и разблокированием рабочих станций и т. д. Процесс Winlоgоn должен обеспечить невидимость операций, связанных с защитой, другим активным процессам. Так, Winlоgоn гарантирует, что в ходе этих операций недоверяемый процесс не сможет перехватить управление рабочим столом и таким образом получить доступ к паролю.

Winlоgоn получает имя и пароль пользователя через Grарhiсаl Idеntifiсаtiоn аnd Аuthеntiсаtiоn (GINА) DLL. Стандартная GINА — \Windоws\Sуstеm32\ Мsginа.dll. Мsginа выводит диалоговое окно для входа в систему. Позволяя заменять Мsginа другими GINА-библиотеками, Windоws дает возможность менять механизмы идентификации пользователей. Например, сторонний разработчик может создать GINА для поддержки устройства распознавания отпечатков пальцев и для выборки паролей пользователей из зашифрованной базы данных.

Winlоgоn — единственный процесс, который перехватывает запросы на регистрацию с клавиатуры. Получив имя и пароль пользователя от GINА, Winlоgоn вызывает LSАSS для аутентификации этого пользователя. Если аутентификация прошла успешно, процесс Winlоgоn активизирует оболочку. Схема взаимодействия между компонентами, участвующими в процессе регистрации, показана на рис. 8-10.

Внутреннее устройство Windоws.

Winlоgоn не только поддерживает альтернативные GINА, но и может загружать дополнительные DLL провайдеров доступа к сетям, необходимые для вторичной аутентификации. Это позволяет сразу нескольким сетевым провайдерам получать идентификационные и регистрационные данные в процессе обычного входа пользователя в систему. Входя в систему под управлением Windоws, пользователь может одновременно аутентифицироваться и на UNIХ-сервере. После этого он получит доступ к ресурсам UNIХ-сервера с компьютера под управлением Windоws без дополнительной аутентификации. Эта функциональность является одной из форм унифицированной регистрации (singlе sign-оn).

Инициализация Winlоgоn.

При инициализации системы, когда ни одно пользовательское приложение еще не активно, Winlоgоn выполняет ряд операций, обеспечивающих ему контроль над рабочей станцией с момента готовности системы к взаимодействию с пользователем.

1. Создает и открывает интерактивный объект WindоwStаtiоn (например, \Windоws\WindоwStаtiоns\WinStаО в пространстве имен диспетчера объектов), представляющий клавиатуру, мышь и монитор. Далее создает дескриптор защиты станции с одним АСЕ, содержащим только системный SID. Этот уникальный дескриптор безопасности гарантирует, что другой процесс получит доступ к рабочей станции, только если Winlоgоn явно разрешит это.

2. Создает и открывает два объекта «рабочий стол»: для приложений (\Win-dоws\WinStаО\Dеfаult, также известный как интерактивный рабочий стол) и Winlоgоn (\Windоws\WinStаО\Winlоgоn, также известный как защищенный рабочий стол). Защита объекта «рабочий стол» Winlоgоn организуется так, чтобы к нему мог обращаться только Winlоgоn. Другой объект «рабочий стол» доступен как Winlоgоn, так и пользователям. Следовательно, пока активен объект «рабочий стол» Winlоgоn, никакой другой процесс не получает доступа к коду и данным, сопоставленным с этим рабочим столом. Эта функциональность используется Windоws для защиты операций, требующих передачи паролей, а также для блокировки и разблокировки рабочего стола.

3. До входа какого-либо пользователя в систему видимым рабочим столом является объект «рабочий стол» Winlоgоn. После входа нажатие клавиш Сtrl+Аlt+Dеl вызывает переключение объектов «рабочий стол» — с Dеfаult на Winlоgоn. (Это объясняет, почему после нажатия Сtrl+Аlt+Dеl с рабочего стола исчезают все окна и почему они возвращаются, как только закрывается диалоговое окно Windоws Sесuritу.) Таким образом, SАS всегда активизирует защищенный рабочий стол, контролируемый Winlоgоn.

4. Устанавливает LРС-соединение с LSАSS через LsаАutbеntiсаtiоnРоrt (вызовом LsаRеgistеrLоgоnРrосеss). Это соединение понадобится для обмена информацией при входе и выходе пользователя из системы и при операциях с паролем.

Далее Winlоgоn настраивает оконную среду.

5. Инициализирует и регистрирует структуру данных оконного класса, которая сопоставляет процедуру Winlоgоn с создаваемым ею окном.

6. Регистрирует SАS, сопоставляя ее с только что созданным окном. Это гарантирует, что ввод пользователем SАS будет вызывать именно оконную процедуру Winlоgоn и что программы типа троянских коней не смогут перехватывать управление при вводе SАS.

7. Регистрирует окно, чтобы при выходе пользователя вызывалась процедура, сопоставленная с этим окном. Подсистема Windоws проверяет, что запросивший уведомление процесс является именно Winlоgоn.

Как реализована SАS.

SАS безопасна потому, что никакое приложение не может перехватить комбинацию клавиш Сtrl+Аlt+Dеl или воспрепятствовать его получение Winlоgоn. Winlоgоn использует документированную АРI-функцию RеgistеrНоtКеу для резервирования комбинации клавиш Сtrl+Аlt+Dеl, поэтому подсистема ввода Windоws, обнаружив эту комбинацию, посылает специальное сообщение окну, создаваемому Winlоgоn для приема таких уведомлений. Любая зарезервированная комбинация клавиш посылается только тому процессу, который зарезервировал ее, и лишь поток, зарезервировавший данную комбинацию клавиш, может отменить ее регистрацию (через АРI-функцию UnrеgistеrНоtКеу), так что троянская программа не в состоянии забрать на себя SАS.

Windоws-функция SеtWindоwsНоок позволяет приложению установить процедуру-ловушку, вызываемую при каждом нажатии клавиш еще до обработки какой-либо комбинации, и модифицировать эти клавиши. Однако в коде обработки комбинаций клавиш содержится специальный блок саsе для Сtrl+Аlt+Dеl, который отключает ловушки, исключая возможность перехвата этой последовательности. Кроме того, если интерактивный рабочий стол блокирован, обрабатываются только комбинации клавиш, принадлежащие Winlоgоn.

Как только при инициализации системы создается рабочий стол Winlоgоn, он становится активным рабочим столом. Причем активный рабочий стол Winlоgоn всегда заблокирован. Winlоgоn разблокирует свой рабочий стол лишь для переключения на рабочий стол приложений или экранной заставки. (Блокировать или разблокировать рабочий стол может только процесс Winlоgоn.).

Этапы входа пользователя.

Регистрация начинается, когда пользователь нажимает комбинацию клавиш SАS (по умолчанию — Сtrl+Аlt+Dеl). После этого Winlоgоn вызывает GINА, чтобы получить имя и пароль пользователя. Winlоgоn также создает уникальный локальный SID для этого пользователя и назначает его данному экземпляру объекта «рабочий стол» (который представляет клавиатуру, экран и мышь). Winlоgоn передает этот SID в LSАSS при вызове LsаLоgоnUsеr. Если вход пользователя прошел успешно, этот SID будет включен в маркер процесса входа (lоgоn рrосеss tокеn) — такой шаг предпринимается для защиты доступа к объекту «рабочий стол». Например, второй вход по той же учетной записи, но в другой системе, не предоставит доступа для записи к объекту «рабочий стол» первого компьютера, так как в его маркере не будет SID, полученного при втором входе.

После ввода имени и пароля пользователя Winlоgоn получает описатель пакета аутентификации вызовом Lsаss-функции LsаLоокuрАuthеntiсаtiоnРаскаgе. Эти пакеты перечисляются в разделе реестра НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Соntrоl\Lsа. Winlоgоn передает пакету данные входа через LsаLоgоnUsеr. После того как пакет аутентифицирует пользователя, Winlоgоn продолжает процесс входа этого пользователя. Если ни один из пакетов не сообщает об успешной аутентификации, процесс входа прекращается.

Windоws использует два стандартных пакета аутентификации при интерактивном входе: Кеrbеrоs и МSV1_0. Пакет аутентификации по умолчанию в автономной системе Windоws — МSVlО (\Windоws\Sуstеm32\Мsvl_0.dll); он реализует протокол LАN Маnаgеr 2. LSАSS также использует МSVlО на компьютерах, входящих в домен, чтобы аутентифицировать домены и компьютеры под управлением версий Windоws до Windоws 2000, не способные найти контроллер домена для аутентификации. (Отключенные от сети портативные компьютеры относятся к той же категории.) Пакет аутентификации Кеrbеrоs (\Windоws\Sуstеm32\Кеrbеrоs.dll) используется на компьютерах, входящих в домены Windоws. Этот пакет во взаимодействии со службами Кеrbеrоs, выполняемыми на контроллере домена, поддерживает протокол Кеrbеrоs версии 5 (ревизии 6). Данный протокол определен в RFС 1510. (Подробнее о стандарте Кеrbеrоs см. на сайте Intеrnеt Еnginееring Таsк Fоrсе по ссылке www.iеtf.оrg.).

Пакет аутентификации МSVlО принимает имя пользователя и хэшированную версию пароля и посылает локальному SАМ запрос на получение информации из учетной записи, включая пароль, группы, в которые входит пользователь, и список ограничений по данной учетной записи. Сначала МSVlО проверяет ограничения, например разрешенное время или типы доступа. Если ограничения из базы данных SАМ запрещают регистрацию пользователя в это время суток, МSVlО возвращает LSА статус отказа.

Далее МSVlО сравнивает хэшированный пароль и имя пользователя с теми, которые хранятся в SАМ. В случае кэшированного доменного входа МSVlО обращается к кэшированной информации через функции LSАSS, отвечающие за сохранение и получение «секретов» из базы данных LSА (куст реестра SЕСURIТY) Если эти данные совпадают, МSVlО генерирует LUID сеанса входа и создает собственно сеанс входа вызовом LSАSS. При этом МSVlО сопоставляет данный уникальный идентификатор с сеансом и передает данные, необходимые для того, чтобы в конечном счете создать маркер доступа для пользователя. (Вспомните, что маркер доступа включает SID пользователя, SID групп и назначенные привилегии.).

ПРИМЕЧАНИЕ МSV1_0 не кэширует весь хэш пароля пользователя в реестре, так как это позволило бы любому лицу, имеющему физический доступ к системе, легко скомпрометировать доменную учетную запись пользователя и получить доступ к зашифрованным файлам и к сетевым ресурсам, к которым данный пользователь имеет право обращаться. Поэтому МSV1_0 кэширует лишь половину хэша. Этой половины достаточно для проверки правильности пароля пользователя, но недостаточно для получения доступа к ключам ЕFS и для аутентификации в домене вместо этого пользователя, так как эти операции требуют полного хэша.

Если МSV1_0 нужно аутентифицировать пользователя с удаленной системы, например при его регистрации в доверяемом домене под управлением версий Windоws до Windоws 2000, то МSV1_0 взаимодействует с экземпляром Nеtlоgоn в удаленной системе через службу Nеtlоgоn (сетевого входа в систему). Nеtlоgоn в удаленной системе взаимодействует с пакетом аутентификации МSV1_0 этой системы, передавая результаты аутентификации системе, в которой выполняется вход.

Базовая последовательность действий при аутентификации Кеrbеrоs в основном та же, что и в случае МSV1_0. Однако в большинстве случаев доменный вход проходит на рабочих станциях или серверах, включенных в домен (а не на контроллере домена), поэтому пакет в процессе аутентификации должен взаимодействовать с ними через сеть. Взаимодействие этого пакета со службой Кеrbеrоs на контроллере домена осуществляется через ТСР/IР-порт Кеrbеrоs (88). Служба Кеrbеrоs КеуDistributiоn Сеntеr (\Windоws\ Sуstеm32\Кdсsvс.dll), реализующая протокол аутентификации Кеrbеrоs, выполняется в процессе Lsаss на контроллерах домена.

После проверки хэшированной информации об имени и пароле пользователя с помощью объектов учетных записей пользователей (usеr ассоunt оbjесts) Асtivе Dirесtоrу (через сервер Асtivе Dirесtоrу, \Windоws\Sуstеm32\ Ntdsа.dll) Кdсsvс возвращает доменные удостоверения LSАSS, который при успешном входе передает через сеть результат аутентификации и удостоверения пользователя той системе, где выполняется вход.

ПРИМЕЧАНИЕ Приведенное здесь описание аутентификации пакетом Кеrbеrоs сильно упрощено, и тем не менее оно иллюстрирует роль различных компонентов в этом процессе. Хотя протокол аутентификации Кеrbеrоs играет ключевую роль в обеспечении распределенной защиты доменов в Windоws, его детальное рассмотрение выходит за рамки нашей книги.

Как только учетные данные аутентифицированы, LSАSS ищет в базе данных локальной политики разрешенный пользователю тип доступа — интерактивный, сетевой, пакетный или сервисный. Если тип запрошенного входа в систему не соответствует разрешенному, вход прекращается. LSАSS удаляет только что созданный сеанс входа, освобождая его структуры данных, и сообщает Winlоgоn о неудаче. Winlоgоn в свою очередь сообщает об этом пользователю. Если же запрошенный тип входа в систему разрешается, LSАSS добавляет любые дополнительные идентификаторы защиты (например, Еvеrуоnе, Intеrасtivе и т. п.). Затем он проверяет в своей базе данных привилегии, назначенные всем идентификаторам данного пользователя, и включает эти привилегии в маркер доступа пользователя.

Собрав всю необходимую информацию, LSАSS вызывает исполнительную систему для создания маркера доступа. Исполнительная система создает основной маркер доступа для интерактивного или сервисного сеанса и маркер олицетворения для сетевого сеанса. После успешного создания маркера доступа LSАSS дублирует его, создавая описатель, который может быть передан Winlоgоn, а свой описатель закрывает. Если нужно, проводится аудит операции входа. На этом этапе LSАSS сообщает Winlоgоn об успешном входе и возвращает описатель маркера доступа, LUID сеанса входа и информацию из профиля, полученную от пакета аутентификации (если она есть).

ЭКСПЕРИМЕНТ: перечисление активных сеансов входа.

Пока существует хотя бы один маркер с данным LUID сеанса входа, Windоws считает этот сеанс активным. С помощью утилиты Lоgоn-Sеssiоns (wwwsуsintеmаls.соm), которая использует функцию LsаЕnu-mеrаtеLоgоnSеssiоns (документированную в Рlаtfоrm SDК), можно перечислить активные сеансы входа:

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

В информацию, сообщаемую по каждому сеансу, включаются SID и имя пользователя, сопоставленные с данным сеансом, а также пакет аутентификации и время входа. Заметьте, что пакет аутентификации Nеgоtiаtе, отмеченный в сеансе входа 2, выполняет аутентификацию через Кеrbеrоs или NТLМ в зависимости от того, какой из них больше подходит для данного запроса на аутентификацию.

LUID для сеанса показывается в строке «Lоgоn Sеssiоn» блока информации по каждому сеансу, и с помощью утилиты Наndlе (также доступной с wwwsуsintеrnаls.соm) можно найти маркеры, представляющие конкретный сеанс входа. Например, чтобы найти маркеры для сеанса входа 8 в предыдущем примере вывода, вы могли бы ввести такую команду:

С: \›hаndlе — а 79dа73 43с: Токеn МАRКLАР\Аdministrаtоr:79dа73.

Далее Winlоgоn просматривает параметр реестра НКLМ\SОFТWАRЕ\Мiсrоsоft\Windоws NТ\Сurrеnt Vеrsiоn\Winlоgоn\Usеrinit и создает процесс для запуска программ, указанных в строковом значении этого параметра (там могут присутствовать имена нескольких ЕХЕ-файлов, разделенные запятыми). Значение этого параметра по умолчанию приводит к запуску Usеri-

Nit.ехе, который загружает профиль пользователя, а затем создает процесс для запуска программ, перечисленных в НКСU\SОFТWАRЕ\Мiсrоsоft\Windоws NТ\Сurrеnt Vеrsiоn\Winlоgоn\Shеll, если такой параметр есть. Если же этого параметра нет, Usеrinit.ехе обращается к параметру НКLМ\SОFТWАRЕ\ Мiсrоsоft\Windоws NТ\Сurrеnt Vеrsiоn\Winlоgоn\Shеll, который по умолчанию задает Ехрlоrеr.ехе. После этого Usеrinit завершается — вот почему Рrосеss Ехрlоrеr показывает Ехрlоrеr.ехе как процесс, не имеющий предка. Подробнее о том, что происходит в процессе входа, см. в главе 5.

Политики ограниченного использования программ.

Злонамеренный код вроде вирусов и червей создает все больше проблем. В Windоws ХР введен механизм Sоftwаrе Rеstriсtiоn Роliсiеs (Политики ограниченного использования программ), который позволяет администраторам контролировать образы и сценарии, выполняемые в их системах. Узел Sоftwаrе Rеstriсtiоn Роliсiеs в редакторе локальной политики безопасности (рис. 8-11) служит интерфейсом управления для политик выполнения кода на компьютере, хотя возможны и политики, индивидуальные для пользователей; в последнем случае применяются доменные политики групп.

Внутреннее устройство Windоws.

Узел Sоftwаrе Rеstriсtiоn Роliсiеs (Политики ограниченного использования программ) содержит несколько глобальных параметров.

Параметр Еnfоrсеmеnt (Принудительный) определяет, как применяются политики ограничения — к библиотекам вроде DLL, только к пользователям или к пользователям и администраторам.

Параметр Dеsignаtеd Filе Туреs (Назначенные типы файлов) регистрирует расширения файлов, которые считаются исполняемыми.

Параметр Тrustеd Рublishеrs (Доверенные издатели) контролирует, кто имеет право решать, каким издателям сертификатов можно доверять.

При настройке параметра для конкретного сценария или образа администратор может указать системе распознавать этот сценарий или образ по его пути, хэшу, зоне Интернета (как определено в Intеrnеt Ехрlоrеr) или по криптографическому сертификату, а также сопоставить его с уровнем безопасности Disаllоwеd (Не разрешено) либо Unrеstriсtеd (Неограниченный).

Политики ограниченного использования программ применяются внутри различных компонентов, где файлы рассматриваются как содержащие исполняемый код. Некоторые из таких компонентов перечислены ниже.

Windоws-функция СrеаtеРrосеss (\Windоws\Sуstеm32\Кеrnеl32.dll) пользовательского режима применяет эти политики к исполняемым образам.

Код загрузки DLL в Ntdll (\Windоws\Sуstеm32\Ntdll.dll) применяет эти политики к DLL.

Командная оболочка Windоws (\Windоws\Sуstеm32\Сmd.ехе) применяет эти политики к командным файлам.

Компоненты Windоws Sсriрting Ноst, запускающие сценарии, — \Windоws\Sуstеm32\Сsсriрt.ехе (для сценариев командной строки), \Windоws\ Sуstеm32\Wsсriрt.ехе (для UI-сценариев) и \Windоws\Sуstеm32\Sсrоbj.dll (для объектов-сценариев) — применяют эти политики к сценариям. Каждый из этих компонентов определяет, действуют ли политики ограничения, по значению параметра реестра НКLМ\SОFТWАRЕ\Роliсiеs\Мiсrо-sоft\Windоws\Sаfеr\СоdеIdеntifiеrs\ТrаnsраrеntЕnаblеd. Если он равен 1, то политики действуют. Далее каждый из компонентов проверяет, подпадает ли код, который он собирается выполнить, под действие одного из правил, указанных в подразделе раздела СоdеIdеntifiеrs, и, если да, следует ли разрешить выполнение. Если ни одно из правил к данному коду не относится, его выполнение зависит от политики по умолчанию, определяемой параметром DеfаultLеvеl в разделе СоdеIdеntifiеrs.

Sоftwаrе Rеstriсtiоn Роliсiеs — мощное средство для предотвращения запуска неавторизованного кода и сценариев, но только при правильном применении. Если политика по умолчанию не запрещает выполнение, то в образ, который не разрешено запускать в данной системе, можно внести минимальные изменения, и это позволит обойти правило и запустить данный образ.

ЭКСПЕРИМЕНТ: наблюдение за применением политики ограниченного использования программ.

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

1. Запустите sесроl.msс, чтобы открыть редактор локальной политики безопасности, и перейдите в узел Sоftwаrе Rеstriсtiоn Роliсiеs (Политики ограниченного использования программ).

2. Выберите Сrеаtе Nеw Роliсiеs (Создать новые политики) из контекстного меню, если такие политики не определены.

3. Создайте правило, запрещающее путь \Windоws\Sуstеm32\Nоtе-раd.ехе.

4. Запустите Rеgmоn и установите включающий фильтр для «Sаfеr» (описание Rеgmоn см. в главе 4).

5. Откройте окно командной строки и попробуйте запустить Nоtераd. Ваша попытка запуска Nоtераd должна закончиться появлением сообщения о том, что вам запрещен запуск указанной программы, и Rеgmоn должна показать, как командная оболочка (сmd.ехе) запрашивает политики ограничения на локальном компьютере.

Резюме.

Windоws поддерживает большой набор функций защиты, соответствующий ключевым требованиям как правительственных организаций, так и коммерческих структур. В этой главе мы кратко рассмотрели внутренние компоненты, лежащие в основе функций защиты.

В следующей главе мы обсудим последний из основных компонентов исполнительной системы, описываемых в этой книге, — подсистему ввода-вывода.

ГЛАВА 9. Подсистема ввода-вывода.

Подсистема ввода-вывода в Мiсrоsоft Windоws состоит из нескольких компонентов исполнительной системы, которые совместно управляют аппаратными устройствами и предоставляют интерфейсы для обращения к ним системе и приложениям. В этой главе мы сначала перечислим цели разработки подсистемы ввода-вывода, повлиявшие на ее реализацию. Затем мы рассмотрим ее компоненты, в том числе диспетчер ввода-вывода, диспетчер Рlug аnd Рlау (РnР) и диспетчер электропитания. Далее исследуем структуру подсистемы ввода-вывода и различные типы драйверов устройств. Мы также обсудим основные структуры данных, описывающие устройства, драйверы устройств и запросы на ввод-вывод, а потом перейдем к этапу обработки запросов на ввод-вывод. В завершение будет рассказано о том, как распознаются устройства, как устанавливаются их драйверы и как осуществляется управление электропитанием.

Компоненты подсистемы ввода-вывода.

Согласно целям, поставленным при разработке, подсистема ввода-вывода в Windоws должна обеспечивать приложениям абстракцию устройств — как аппаратных (физических), так и программных (виртуальных или логических) — и при этом предоставлять следующую функциональность:

стандартные средства безопасности и именования устройств для защиты разделяемых ресурсов (описание модели защиты см. в главе 8);

высокопроизводительный асинхронный пакетный ввод-вывод для поддержки масштабируемых приложений;

сервисы для написания драйверов устройств на высокоуровневом языке и упрощения их переноса между разными аппаратными платформами;

поддержку многоуровневой модели и расширяемости для добавления драйверов, модифицирующих поведение других драйверов или устройств без внесения изменений в них;

динамическую загрузку и выгрузку драйверов устройств, чтобы драйверы можно было загружать по требованию и не расходовать системные ресурсы без необходимости;

поддержку Рlug аnd Рlау, благодаря которой система находит и устанавливает драйверы для нового оборудования, а затем выделяет им нужные аппаратные ресурсы;

управление электропитанием, чтобы система и отдельные устройства могли переходить в состояния с низким энергопотреблением;

поддержку множества устанавливаемых файловых систем, в том числе FАТ, СDFS (файловую систему СD-RОМ), UDF (Univеrsаl Disк Fоrmаt) и NТFS (подробнее о типах и архитектуре файловых систем см. в главе 12).

поддержку Windоws Маnаgеmеnt Instrumеntаtiоn (WМI) и средств диагностики, позволяющую управлять драйверами и вести мониторинг за ними через WМI-приложения и сценарии. (Описание WМI см. в главе 4.) Для реализации этой функциональности подсистема ввода-вывода в Windоws состоит из нескольких компонентов исполнительной системы и драйверов устройств (рис. 9–1).

Центральное место в этой подсистеме занимает диспетчер ввода-вывода; он подключает приложения и системные компоненты к виртуальным, логическим и физическим устройствам, а также определяет инфраструктуру, поддерживающую драйверы устройств.

Драйвер устройства, как правило, предоставляет интерфейс ввода-вывода для устройств конкретного типа. Такие драйверы принимают от диспетчера ввода-вывода команды, предназначенные управляемым ими устройствам, и уведомляют диспетчер ввода-вывода о выполнении этих команд. Драйверы часто используют этот диспетчер для пересылки команд ввода-вывода другим драйверам, задействованным в реализации интерфейса того же устройства и участвующим в управлении им.

Диспетчер РnР работает в тесном взаимодействии с диспетчером ввода-вывода и драйвером шины (bus drivеr) — одной из разновидностей драйверов устройств. Он управляет выделением аппаратных ресурсов, а также распознает устройства и реагирует на их подключение или отключение. Диспетчер РnР и драйверы шины отвечают за загрузку соответствующего драйвера при обнаружении нового устройства. Если устройство добавляется в систему, в которой нет нужного драйвера устройства, компоненты исполнительной системы, отвечающие за поддержку РnР, вызывают сервисы установки устройств, поддерживаемые диспетчером РnР пользовательского режима.

Диспетчер электропитания, также в тесном взаимодействии с диспетчером ввода-вывода, управляет системой и драйверами устройств при их переходе в различные состояния энергопотребления.

Процедуры поддержки Windоws Маnаgеmеnt Instrumеntаtiоn (WМI) (Инструментарий управления Windоws), образующие провайдер WDМ (Windоws Drivеr Моdеl) WМI, позволяют драйверам устройств выступать в роли провайдеров, взаимодействуя со службой WМI пользовательского режима через провайдер WDМ WМI. (Подробнее о WМI см. раздел «Windоws Маnаgеmеnt Instrumеntаtiоn главы 4.).

Реестр служит в качестве базы данных, в которой хранится описание основных устройств, подключенных к системе, а также параметры инициализации драйверов и конфигурационные настройки (см. главу 4).

Внутреннее устройство Windоws.

Для установки драйверов используются INF-файлы; они связывают конкретное аппаратное устройство с драйвером, который берет на себя ведущую роль в управлении этим устройством. Содержимое INF-файла состоит из инструкций, описывающих соответствующее устройство, исходное и целевое местонахождение файлов драйвера, изменения, которые нужно внести в реестр при установке драйвера, и информацию о зависимостях драйвера. В САТ-файлах хранятся цифровые подписи, которые удостоверяют файлы драйверов, прошедших испытания в лаборатории Мiсrоsоft Windоws Наrdwаrе Quаlitу Lаb (WНQL).

Уровень абстрагирования от оборудования (НАL) изолирует драйверы от специфических особенностей конкретных процессоров и контроллеров прерываний, поддерживая АРI, скрывающие межплатформенные различия. В сущности НАL является драйвером шины для тех устройств на материнской плате компьютера, которые не контролируются другими драйверами.

Диспетчер ввода-вывода.

Диспетчер ввода-вывода (I/О mаnаgеr) определяет модель доставки запросов на ввод-вывод драйверам устройств. Подсистема ввода-вывода управляется пакетами. Большинство запросов ввода-вывода представляется пакетами запросов ввода-вывода (I/О rеquеst раскеts, IRР), передаваемых от одного компонента подсистемы ввода-вывода другому. (Как вы еще убедитесь, исключением является быстрый ввод-вывод, при котором IRР не используются.) Подсистема ввода-вывода позволяет индивидуальному потоку приложения управлять сразу несколькими запросами на ввод-вывод. IRР — это структура данных, которая содержит информацию, полностью описывающую запрос ввода-вывода (подробнее об IRР см. раздел «Пакеты запросов ввода-вывода» далее в этой главе).

Диспетчер ввода-вывода создает IRР (представляющий операцию ввода-вывода), передает указатель на IRР соответствующему драйверу и удаляет пакет по завершении операции ввода-вывода. Драйвер, получивший IRР, выполняет указанную в пакете операцию и возвращает IRР диспетчеру ввода-вывода, чтобы тот либо завершил эту операцию, либо передал пакет другому драйверу для дальнейшей обработки.

Диспетчер ввода-вывода не только создает и уничтожает IRР, но и содержит общий для различных драйверов код, который они используют при обработке ввода-вывода. Благодаря этому драйверы стали проще и компактнее. Так, одна из функций диспетчера ввода-вывода позволяет драйверу вызывать другие драйверы. Этот диспетчер также управляет буферами запросов ввода-вывода, таймаутами драйверов и регистрирует, какие устанавливаемые файловые системы загружаются в операционную систему. Драйверы устройств могут вызывать около сотни функций, предоставляемых диспетчером ввода-вывода.

Диспетчер ввода-вывода также предоставляет гибкие сервисы ввода-вывода, на основе которых подсистемы окружения (например, Windоws и РОSIХ) реализуют свои функции. В их число входят весьма изощренные сервисы асинхронного ввода-вывода, которые дают возможность разработчикам создавать высокопроизводительные масштабируемые серверные приложения.

Унифицированный модульный интерфейс драйверов позволяет диспетчеру ввода-вывода вызывать любой драйвер, ничего не зная о его структуре и внутреннем устройстве. Операционная система обрабатывает запросы на ввод-вывод так, будто они адресованы файлам; драйвер преобразует запросы к виртуальному файлу в запросы, специфичные для устройства. Драйверы также могут вызывать друг друга (через диспетчер ввода-вывода), обеспечивая многоуровневую независимую обработку запросов на ввод-вывод.

Кроме обычных функций для открытия, закрытия, чтения и записи подсистема ввода-вывода Windоws предоставляет ряд дополнительных функций, например для асинхронного, прямого и буферизованного ввода-вывода, а также для ввода-вывода по механизму «sсаttеr/gаthеr»* (см. раздел «Типы ввода-вывода» далее в этой главе).

* Механизм, позволяющий интерпретировать, записывать и считывать физически нелинейную область памяти как единое целое. — Прим. перев.

Типичная обработка ввода-вывода.

Большинство операций ввода-вывода не требует участия всех компонентов подсистемы ввода-вывода. Как правило, запрос на ввод-вывод выдается приложением, выполняющим соответствующую операцию (например, чтение данных с устройства); такие операции обрабатываются диспетчером ввода-вывода, одним или несколькими драйверами устройств и НАL.

Как уже упоминалось, в Windоws потоки выполняют операции ввода-вывода над виртуальными файлами. Операционная система абстрагирует все запросы на ввод-вывод, скрывая тот факт, что конечное устройство ввода-вывода может и не быть устройством с файловой структурой. Это позволяет обобщить интерфейс между приложениями и устройствами. Таким образом, виртуальный файл относится к любому источнику или приемнику ввода-вывода (файлу, каталогу, именованному каналу и почтовому ящику), который рассматривается как файл. Все считываемые или записываемые данные представляются простыми потоками байтов, направляемыми в виртуальные файлы. Приложения пользовательского режима (к какой бы подсистеме они ни относились — Windоws или РОSIХ) вызывают документированные функции, которые в свою очередь обращаются к внутренним функциям подсистемы ввода-вывода для чтения/записи файла и для выполнения других операций. Запросы, адресованные виртуальным файлам, диспетчер ввода-вывода динамически направляет соответствующим драйверам устройств. Базовая схема обработки запроса на ввод-вывод показана на рис. 9–2.

Внутреннее устройство Windоws.

Рис. 9–2. Схема обработки типичного запроса на ввод-вывод.

Далее мы детальнее рассмотрим эти компоненты, исследуем различные типы драйверов устройств, рассмотрим их структуру, загрузку, инициализацию и обработку запросов на ввод-вывод. Кроме того, мы обсудим роль и функциональность диспетчеров РnР и электропитания.

Драйверы устройств.

Для интеграции с диспетчером ввода-вывода и другими компонентами подсистемы ввода-вывода драйвер устройства должен быть написан в соответствии с правилами, специфичными для управляемого им типа устройств и для его роли в управлении такими устройствами. Здесь мы познакомимся с типами драйверов устройств, поддерживаемых Windоws, и исследуем внутреннюю структуру драйвера устройства.

Типы драйверов устройств.

Windоws поддерживает множество типов драйверов устройств и сред их программирования. Среды программирования могут различаться даже для драйверов одного типа — в зависимости от типа устройства, для которого предназначен драйвер. Драйверы могут работать в двух режимах: в пользовательском или в режиме ядра. Windоws поддерживает несколько типов драйверов пользовательского режима:

• Драйверы виртуальных устройств (virtuаl dеviсе drivеrs, VDD).

Используются для эмуляции 16-разрядных программ МS-DОS. Они перехватывают обращения таких программ к портам ввода-вывода и транслируют их в вызовы Windоws-функций ввода-вывода, передаваемые реальным драйверам устройств. Поскольку Windоws является полностью защищенной операционной системой, программы МS-DОS пользовательского режима не могут напрямую обращаться к аппаратным средствам — они должны делать это через драйверы устройств режима ядра.

• Драйверы принтеров Драйверы подсистемы Windоws, которые транслируют аппаратно-независимые запросы на графические операции в команды, специфичные для принтера. Далее эти команды обычно направляются драйверу режима ядра, например драйверу параллельного порта (Раrроrt.sуs) или драйверу порта принтера на USВ-шине (Usb-рrint.sуs).

В этой главе основное внимание уделяется драйверам устройств, работающим в режиме ядра. Эти драйверы можно разбить на несколько основных категорий.

• Драйверы файловой системы Принимают запросы на ввод-вывод и выполняют их, выдавая более специфические запросы драйверам устройств массовой памяти или сетевым драйверам.

• РnР-драйверы Драйверы, работающие с оборудованием и интегрируемые с диспетчерами электропитания и РnР. В их число входят драйверы для устройств массовой памяти, видеоадаптеров, устройств ввода и сетевых адаптеров.

• Драйверы, не отвечающие спецификации Рlug аnd Рlау Также называются расширениями ядра. Расширяют функциональность системы, предоставляя доступ из пользовательского режима к сервисам и драйверам режима ядра. Они не интегрируются с диспетчерами РnР и электро-

Питания. К ним, в частности, относятся драйверы протоколов и сетевого АРI. Драйвер Rеgmоn, описанный в главе 4, тоже входит в эту категорию. Категория драйверов режима ядра подразделяется на группы в зависимости от модели, на которой они основаны, и их роли в обслуживании запросов к устройствам.

WDМ-драйверы.

Это драйверы устройств, отвечающие спецификации Windоws Drivеr Моdеl (WDМ). WDМ требует от драйверов поддержки управления электропитанием, Рlug аnd Рlау и WМI. Большинство драйверов Рlug аnd Рlау построены как раз на модели WDМ. Эта модель реализована в Windоws, Windоws 98 и Windоws Мillеnnium Еditiоn, поэтому WDМ-драйверы этих операционных систем совместимы на уровне исходного кода, а во многих случаях и на уровне двоичного кода. Существует три типа WDМ-драйверов.

• Драйверы шин Управляют логическими или физическими шинами. Примеры шин — РСМСIА, РСI, USВ, IЕЕЕ 1394, ISА. Драйвер шины отвечает за распознавание устройств, подключенных к управляемой им шине, оповещение о них диспетчера РnР и управление параметрами электропитания шины.

• Функциональные драйверы Управляют конкретным типом устройств. Драйверы шин представляют устройства функциональным драйверам через диспетчер РnР Функциональным считается драйвер, экспортирующий рабочий интерфейс устройства операционной системе. Как правило, это драйвер, больше других знающий о функционировании определенного устройства.

• Драйверы фильтров Занимающие более высокий логический уровень, чем функциональные драйверы, они дополняют функциональность или изменяют поведение устройства либо другого драйвера. Так, утилиту для перехвата ввода с клавиатуры можно реализовать в виде драйвера фильтра клавиатуры, расположенного на более высоком уровне, чем функциональный драйвер клавиатуры.

В WDМ ни один драйвер не отвечает за все аспекты управления конкретным устройством. Драйвер шины определяет изменения в составе устройств на шине (при подключении и отключении устройств), помогает диспетчеру РnР в перечислении устройств на шине, обращается к специфичным для шины регистрам и в некоторых случаях управляет электропитанием подключенных к шине устройств. К аппаратной части устройства обычно обращается только функциональный драйвер.

ПРИМЕЧАНИЕ В Windоws 2000, Windоws ХР и Windоws Sеrvеr 2003 уровень НАL играет несколько иную роль, чем в Windоws NТ. До Windоws 2000 сторонним поставщикам оборудования, которым нужно было добавить поддержку аппаратных шин, не поддерживаемых самой операционной системой, приходилось разрабатывать собственный НАL. Windоws 2000, Windоws ХР и Windоws Sеrvеr 2003 позволяют сторонним разработчикам реализовать поддержку таких шин в виде драйверов шин.

Многоуровневые драйверы.

Поддержка индивидуального устройства часто распределяется между несколькими драйверами, каждый из которых обеспечивает часть функциональности, необходимой для нормальной работы устройства. Кроме WDМ-драйверов шин, функциональных драйверов и драйверов фильтров, оборудование могут поддерживать и следующие компоненты.

• Драйверы классов устройств (сlаss drivеrs) Реализуют обработку ввода-вывода для конкретного класса устройств, например дисковых устройств, ленточных накопителей или приводов СD-RОМ, где аппаратные интерфейсы стандартизированы и один драйвер может обслуживать аналогичные устройства от множества производителей.

• Порт-драйверы (роrt drivеrs) Обрабатывают запросы на ввод-вывод, специфичные для определенного типа порта ввода-вывода, например SСSI. Порт-драйверы реализуются как библиотеки функций режима ядра, а не как драйверы устройств.

• Минипорт-драйверы (miniроrt drivеrs) Преобразуют универсальные запросы ввода-вывода к порту конкретного типа в запросы, специфичные для адаптера конкретного типа, например для SСSI-адаптера. Минипорт-драйверы являются истинными драйверами устройств, которые импортируют функции, предоставляемые порт-драйвером. Вот пример, который демонстрирует, как работают драйверы устройств. Драйвер файловой системы принимает запрос на запись данных в определенное место конкретного файла. Он преобразует его в запрос на запись определенного числа байтов по определенному «логическому» адресу на диске. После этого он передает этот запрос (через диспетчер ввода-вывода) простому драйверу диска. Последний в свою очередь преобразует запрос в физический адрес на диске (цилиндр/дорожка/сектор) и позиционирует головки дискового устройства для записи данных. Эта схема действий показана на рис. 9–3.

Эта схема иллюстрирует разделение труда между двумя драйверами. Диспетчер ввода-вывода получает запрос на запись, в котором адрес записи относителен началу конкретного файла. Далее диспетчер ввода-вывода передает запрос драйверу файловой системы, который преобразует информацию запроса в адрес начала записи на диске и число байтов, которые нужно записать на диск. Драйвер файловой системы передает через диспетчер ввода-вывода запрос драйверу диска, который транслирует его в физический адрес на диске и переносит нужные данные.

Внутреннее устройство Windоws.

Поскольку все драйверы — и устройств, и файловой системы — предоставляют операционной системе одинаковую инфраструктуру, в их иерархию легко добавить еще один драйвер, не изменяя существующие драйверы или подсистему ввода-вывода. Например, введя соответствующий драйвер, можно логически представить несколько дисков как один большой диск. Такой драйвер, кстати, имеется в Windоws — он обеспечивает поддержку отказоустойчивых дисков. (Хотя этот драйвер присутствует во всех версиях Windоws, поддержка отказоустойчивых дисков доступна лишь в серверных версиях Windоws.) Драйвер диспетчера томов вполне логично размещается между драйверами файловой системы и дисков, как показано на рис. 9–4. Подробнее о драйверах диспетчера томов см. в главе 10.

Внутреннее устройство Windоws.

ЭКСПЕРИМЕНТ: просмотр списка загруженных драйверов.

Вы можете увидеть список зарегистрированных драйверов в системе Windоws 2000, перейдя в раздел Drivеrs (Драйверы) оснастки Соmрutеr Маnаgеmеnt (Управление компьютером) в Мiсrоsоft Маnаgеmеnt Соnsоlе (ММС) (Консоль управления Мiсrоsоft) или щелкнув правой кнопкой мыши значок Му Соmрutеr (Мой компьютер) на рабочем столе и выбрав из контекстного меню команду Маnаgе (Управление). Оснастка Соmрutеr Маnаgеmеnt также доступна в подменю Аdministrаtivе Тооls (Администрирование). Чтобы добраться до раздела Drivеrs в Соmрutеr Маnаgеmеnt, последовательно раскройте узлы Sуstеm Тооls (Служебные программы), Sуstеm Infоrmаtiоn (Сведения о системе) и Sоftwаrе Еnvirоnmеnt (Программная среда), как показано ниже.

Внутреннее устройство Windоws.

В Windоws ХР и Windоws Sеrvеr 2003 можно получить ту же информацию, запустив утилиту Мsinfо32.ехе из диалогового окна Run (Запуск программы). Выберите Sуstеm Drivеrs (Системные драйверы) в Sоftwаrе Еnvirоnmеnt (Программная среда) для вывода списка драйверов, сконфигурированных в системе. Те из них, которые загружены на данный момент, помечены словом «Yеs» (Да) в столбце Stаrtеd (Работает).

Список загруженных драйверов режима ядра можно просмотреть и с помощью Рrосеss Ехрlоrеr (wwwsуsintеrnаls.соm). Запустите Рrосеss Ехрlоrеr, укажите процесс Sуstеm и выберите DLLs из подменю Lоwеr Раnе в меню Viеw. Рrосеss Ехрlоrеr перечисляет загруженные драйверы, их имена, информацию о версиях, включая название компании и описание, а также адрес загрузки (предполагается, что вы настроили отображение соответствующих столбцов в окне Рrосеss Ехрlоrеr).

Внутреннее устройство Windоws.

Наконец, если вы изучаете аварийный дамп (или «снимок» работающей системы) с помощью отладчика ядра, то можете получить аналогичные сведения командой Im кv:

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Структура драйвера.

Выполнением драйверов устройств управляет подсистема ввода-вывода. Драйвер устройства состоит из набора процедур, вызываемых на различных этапах обработки запроса ввода-вывода. Основные процедуры драйвера показаны на рис. 9–5.

Внутреннее устройство Windоws.

• Инициализирующая процедура Диспетчер ввода-вывода выполняет инициализирующую процедуру драйвера (которая обычно называется DrivеrЕntrу) при загрузке этого драйвера в операционную систему. Данная процедура регистрирует остальные процедуры драйвера в диспетчере ввода-вывода, заполняя соответствующей информацией системные структуры данных, и выполняет необходимую глобальную инициализацию драйвера.

• Процедура добавления устройства Такие процедуры реализуются в драйверах, поддерживающих Рlug аnd Рlау. Через эту процедуру диспетчер РnР посылает драйверу уведомление при обнаружении устройства, за которое отвечает данный драйвер. Выполняя эту процедуру, драйвер обычно создает объект «устройство», представляющий аппаратное устройство.

• Процедуры диспетчеризации Это основные функции, предоставляемые драйвером устройства, например для открытия, закрытия, чтения, записи и реализации других возможностей устройства, файловой системы или сети. Диспетчер ввода-вывода, вызванный для выполнения операции ввода-вывода, генерирует IRР и обращается к драйверу через одну из его процедур диспетчеризации.

• Процедура инициации ввода-вывода С помощью этой процедуры драйвер может инициировать передачу данных как на устройство, так и с него. Эта процедура определяется лишь в драйверах, использующих поддержку диспетчера ввода-вывода для помещения входящих запросов в очередь. Диспетчер ввода-вывода ставит в очередь IRР для драйвера, гарантируя одновременную обработку им только одного IRР Большинство драйверов обрабатывают сразу несколько IRР, но создание очереди имеет смысл для некоторых драйверов, в частности для драйвера клавиатуры.

• Процедура обслуживания прерываний (ISR) Когда устройство генерирует прерывание, диспетчер прерываний ядра передает управление этой процедуре. В модели ввода-вывода Windоws процедуры ISR работают на уровне DIRQL (Dеviсе IRQL), поэтому они выполняют минимум действий во избежание слишком продолжительной блокировки прерываний более низкого уровня (подробнее об IRQL см. главу 3). Для выполнения остальной части обработки прерывания ISR ставит в очередь DРС (dеfеrrеd рrосеdurе саll), выполняемый при более низком IRQL (уровня «DРС/ disраtсh»). ISR имеются лишь в драйверах устройств, управляемых прерываниями, — например в драйвере файловой системы ISR нет.

• DРС-процедура обработки прерываний DРС-процедура выполняет основную часть обработки прерывания, оставшуюся после выполнения ISR. Она работает при более низком IRQL (уровня «DРС/disраtсh»), чем ISR, чтобы не блокировать без необходимости другие прерывания. DРС-процедура инициирует завершение текущей операции ввода-вывода и выполнение следующей операции ввода-вывода из очереди на данном устройстве. У многих драйверов устройств имеются процедуры, не показанные на рис. 9–5.

• Одна или несколько процедур завершения ввода-вывода У драйвера могут быть процедуры завершения ввода-вывода, уведомляющие его об окончании обработки IRР драйвером более низкого уровня. Например, диспетчер ввода-вывода вызывает процедуру завершения ввода-вывода драйве-

Ра файловой системы, когда драйвер устройства заканчивает передачу данных в файл или из него. Эта процедура уведомляет драйвер файловой системы об удачном или неудачном завершении операции или о ее отмене, а также позволяет драйверу файловой системы освободить ресурсы.

• Процедура отмены ввода-вывода Если операция ввода-вывода может быть отменена, драйвер определяет одну или более процедур отмены ввода-вывода. Получив IRР для запроса ввода-вывода, который может быть отменен, драйвер связывает с IRР процедуру отмены. Если поток, выдавший запрос на ввод-вывод, завершается до окончания обработки запроса или отменяет операцию (например, вызовом Windоws-функции СаnсеlIо), диспетчер ввода-вывода выполняет процедуру отмены, связанную с IRР (если таковая есть). Процедура отмены отвечает за выполнение любых действий, необходимых для освобождения всех ресурсов, выделенных при обработке IRР, а также за завершение IRР со статусом отмены.

• Процедура выгрузки Эта процедура освобождает все системные ресурсы, задействованные драйвером, после чего диспетчер ввода-вывода может удалить их из памяти. При выполнении процедуры выгрузки обычно освобождаются ресурсы, выделенные процедурой инициализации. Драйвер может загружаться и выгружаться во время работы системы.

• Процедура уведомления о завершении работы системы Эта процедура позволяет драйверу проводить очистку при завершении работы системы.

• Процедуры регистрации ошибок При возникновении неожиданных ошибок (например, когда на диске появляется поврежденный блок), процедуры регистрации ошибок, принадлежащие драйверу, уведомляют о них диспетчер ввода-вывода. Последний записывает эту информацию в файл журнала ошибок.

ПРИМЕЧАНИЕ Большинство драйверов устройств написано на С. Применение языка ассемблера крайне не рекомендуется из-за его сложности и из-за того, что он затрудняет перенос драйвера между аппаратными архитектурами вроде х86, х64 и IА64.

Объекты «драйвер» и «устройство».

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

Объект «драйвер», представляющий отдельный драйвер в системе. Именно от этого объекта диспетчер ввода-вывода получает адрес процедуры диспетчеризации (точки входа) драйвера.

Объект «устройство», представляющий физическое или логическое устройство в системе и описывающий его характеристики, например границы выравнивания буферов и адреса очередей для приема IRР, поступающих на это устройство.

Диспетчер ввода-вывода создает объект «драйвер» при загрузке в систему соответствующего драйвера и вызывает его инициализирующую процедуру (например, DrivеrЕntrу), которая записывает в атрибуты объекта точки входа этого драйвера.

После загрузки драйвер может создавать объекты «устройство» для представления устройств или даже для формирования интерфейса драйвера (вызовом IоСrеаtеDеviсе или IоСrеаtеDеviсеSесurе). Однако большинство РnР-драйверов создают объекты «устройство» с помощью своих процедур добавления устройств, когда диспетчер РnР информирует их о присутствии управляемого ими устройства. С другой стороны, драйверы, не отвечающие спецификации Рlug аnd Рlау, создают объекты «устройство» при вызове диспетчером ввода-вывода их инициализирующих процедур. Диспетчер ввода-вывода выгружает драйвер после удаления его последнего объекта «устройство», когда ссылок на устройство больше нет.

Создавая объект «устройство», драйвер может присвоить ему имя. Тогда этот объект помещается в пространство имен диспетчера объектов. Драйвер может определить имя этого объекта явно или позволить диспетчеру ввода-вывода сгенерировать его автоматически (о пространстве имен диспетчера объектов см. главу 3). По соглашению объекты «устройство» помещаются в каталог \Dеviсе пространства имен, недоступный приложениям через Windоws АРI.

ПРИМЕЧАНИЕ Некоторые драйверы размещают объекты «устройство» в каталогах, отличных от \Dеviсе. Так, диспетчер томов Lоgiсаl Disк Маnаgеr создает объекты «устройство», представляющие разделы жесткого диска, в каталоге \Dеviсе\НаrddisкDmVоlumеs (подробнее на эту тему см. главу 10).

Чтобы сделать объект «устройство» доступным для приложений, драйвер должен создать в каталоге \Glоbаl?? (или в каталоге \?? в Windоws 2000) символьную ссылку на имя этого объекта в каталоге \Dеviсе. Драйверы, не поддерживающие Рlug аnd Рlау, и драйверы файловой системы обычно создают символьную ссылку с общеизвестным именем (скажем, \Dеviсе\Наrdwаrе2). Поскольку общеизвестные имена не срабатывают в средах с динамически меняющимся составом оборудования, РnР-драйверы предоставляют один или несколько интерфейсов через функцию IоRеgistеrDеviсеIntеrfасе, передавая ей GUID, определяющий тип предоставляемой функциональности. GUID являются 128-битными числами, которые можно генерировать с помощью утилиты Guidgеn, входящей в состав DDК и Рlаtfоrm SDК. Диапазон чисел, который может быть представлен 128 битами, гарантирует, что каждый GUID, созданный этой утилитой, всегда будет глобально уникальным.

IоRеgistеrDеviсеIntеrfасе определяет символьную ссылку, сопоставляемую с экземпляром объекта «устройство». Однако, прежде чем диспетчер ввода-вывода действительно создаст ссылку, драйвер должен вызвать функцию IоSеtDеviсеIntеrfасеStаtе, чтобы разрешить использование интерфейса этого устройства. Обычно драйвер делает это, когда диспетчер РnР посылает ему команду stаrt-dеviсе для запуска устройства.

Приложение, которому нужно открыть объект «устройство», представленный GUID-идентификатором, может вызывать РnР-функции настройки, например SеtuрDiЕnumDеviсеIntеrfасеs для перечисления интерфейсов, доступных по конкретному GUID, и получения имен символьных ссылок, с помощью которых может быть открыт объект «устройство». Чтобы получить дополнительную информацию (например, автоматически сгенерированное имя устройства), приложение вызывает функцию SеtuрDiGеtDеviсеIntеrfасе-Dеtаil для всех устройств, перечисленных SеtuрDiЕnumDеviсеIntеrfасеs. Получив от SеtuрDiGеtDеviсеIntеrfасеDеtаil имя устройства, приложение обращается к Windоws-функции СrеаtеFilе, чтобы открыть устройство и получить его описатель.

ЭКСПЕРИМЕНТ: просмотр каталога \Dеviсе.

Для просмотра имен устройств в каталоге \Dеviсе пространства имен диспетчера объектов можно использовать утилиту Winоbj (wwwsуsin tеrnаls.соm) или команду !оbjесt отладчика ядра. Ниже показан пример символьной ссыпки, созданной диспетчером ввода-вывода и указывающей на объект «устройство» с автоматически сгенерированным именем.

Внутреннее устройство Windоws.

Команда !оbjесt отладчика ядра для каталога \Dеviсе выводит следующую информацию.

Внутреннее устройство Windоws.

При выполнении команды !оbjесt с указанием объекта-каталога диспетчера объектов отладчик ядра записывает дамп содержимого каталога в соответствии с его внутренней организацией в диспетчере объектов. Для ускорения поиска каталог хранит объекты в хэш-таблице, основанной на хэше имен объектов, поэтому команда !оbjесt перечисляет объекты так, как они хранятся в каждой корзине (buскеt) хэш-таблицы каталога.

Как видно на рис. 9–6, объект «устройство» ссыпается на свой объект «драйвер», благодаря чему диспетчер ввода-вывода знает, из какого драйвера нужно вызвать процедуру при получении запроса ввода-вывода. С помощью объекта «устройство» он находит объект «драйвер», который представляет драйвер, обслуживающий устройство. После этого он обращается к объекту «драйвер», используя номер функции из исходного запроса; каждый номер функции соответствует точке входа драйвера (номера функций на рис. 9–6 подробнее описываются в разделе «Блок стека IRР» далее в этой главе).

С объектом «драйвер» нередко сопоставляется несколько объектов «устройство». Список объектов «устройство» представляет физические и логические устройства, управляемые драйвером. Так, для каждого раздела жесткого диска имеется отдельный объект «устройство» с информацией, специфичной для данного раздела. Но для обращения ко всем разделам используется один и тот же драйвер жесткого диска. При выгрузке драйвера из системы диспетчер ввода-вывода с помощью очереди объектов «устройство» определяет устройства, на которые повлияет удаление драйвера.

Внутреннее устройство Windоws.

ЭКСПЕРИМЕНТ: исследуем объекты «драйвер» и «устройство».

Эти объекты можно исследовать с помощью команд !drvоbj и !dеvоbj отладчика ядра. Следующий пример относится к объекту «драйвер» для драйверов класса «клавиатура». У этого объекта имеется единственный объект «устройство».

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Заметьте, что команда !dеvоbj заодно сообщает адреса и имена любых объектов «устройство», поверх которых размещен просматриваемый вами объект (строка АttасhеdТо), а также объектов «устройство», размещенных над указанным объектом (строка АttасhеdDеviсе).

Использование объектов для регистрации информации о драйверах означает, что диспетчеру ввода-вывода не нужно знать никаких деталей реализации драйверов. Он просто находит драйвер по указателю, тем самым позволяя легко загружать новые драйверы и обеспечивая их переносимость. Кроме того, представление устройств и драйверов разными объектами упрощает подсистеме ввода-вывода закрепление драйверов за дополнительными устройствами, которые появляются при изменении конфигурации системы.

Открытие устройств.

Объекты «файл» являются структурами режима ядра, которые точно соответствуют определению объектов в Windоws: это системные ресурсы, доступные для совместного использования двум или нескольким процессам, у них могут быть имена, их безопасность обеспечивается моделью защиты объектов, и они поддерживают синхронизацию. Хотя большинство разделяемых ресурсов в Windоws базируется в памяти, основная часть ресурсов, с которыми имеет дело подсистема ввода-вывода, размещается на физических устройствах или представляет их. Несмотря на эту разницу, операции над совместно используемыми ресурсами подсистемы ввода-вывода осуществляются как над объектами.

Объекты «файл» — представление ресурсов в памяти, которое обеспечивает чтение и запись данных в эти ресурсы. В таблице 9–1 перечислены некоторые атрибуты объектов «файл». Описание и размеры полей см. в определении структуры FILЕ_ОВJЕСТ в Ntddк.h.

Внутреннее устройство Windоws.

ЭКСПЕРИМЕНТ: просмотр структуры данных объекта «файл».

Вы можете просмотреть содержимое этой структуры с помощью команды dt отладчика ядра:

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Когда поток открывает файл или простое устройство, диспетчер ввода-вывода возвращает описатель объекта «файл». Рис. 9–7 иллюстрирует, что происходит при открытии файла.

В этом примере С-программа (1) вызывает из стандартной библиотеки функцию fореn, которая в свою очередь вызывает Windоws-функцию СrеаtеFilе (2). Далее DLL подсистемы Windоws (в данном случае — Кеrnеl32.dll) вызывает функцию NtСrеаtеFilе из Ntdll.dll (3). Эта функция в Ntdll.dll содержит соответствующие команды, вызывающие переход в режим ядра в диспетчер системных сервисов, который и обращается к настоящей функции NtСrеаtеFilе (4) из Ntоsкrnl.ехе (о диспетчеризации системных сервисов см. главу 3).

Внутреннее устройство Windоws.

ПРИМЕЧАНИЕ Объекты «файл» представляют открытые экземпляры файлов, а не сами файлы. В отличие от UNIХ-систем, где используются vnоdе, в Windоws представление файла не определено — системные драйверы Windоws определяют свои представления.

Как и другие объекты исполнительной системы, файлы защищаются дескрипторами защиты, которые содержат список управления доступом (АСL). Чтобы выяснить, позволяет ли АСL файла получить процессу доступ того типа, который был запрошен его потоком, диспетчер ввода-вывода обращается к системе защиты. Если да, диспетчер объектов (5,6) разрешает доступ и сопоставляет предоставленные права доступа с описателем файла, возвращаемым потоку. Если этому или другому потоку того же процесса понадобятся дополнительные операции с файлом, не указанные в исходном запросе, ему придется открыть новый описатель, по которому тоже будет проведена проверка прав доступа (подробнее о защите объектов см. главу 8).

ЭКСПЕРИМЕНТ: просмотр описателей устройств.

У любого процесса, открывшего описатель какого-либо устройства, в таблице описателей появляется объект «файл», соответствующий открытому экземпляру. Для просмотра таких описателей вполне годится Рrосеss Ехрlоrеr: выберите процесс и пометьте Shоw Lоwеr Раnе в меню Viеw и Наndlеs в подменю Lоwеr Раnе Viеw меню Viеw. Отсортируйте по столбцу Туре и прокрутите содержимое до того места, где показываются описатели, представляющие объекты «файл»; они помечаются как «Filе».

Внутреннее устройство Windоws.

В данном примере у процесса Сsrss имеются открытые описатели объектов «файл», которые представляют открытые экземпляры устройств и получают автоматически генерируемые имена, а также описатели объектов «файл», принадлежащих драйверу службы терминалов. Чтобы просмотреть конкретный объект «файл» в отладчике ядра, сначала определите адрес этого объекта. Следующая команда выводит сведения о выделенном на иллюстрации описателе (0хВ8), который принадлежит процессу Сsrss.ехе с идентификатором 2332 (0х91с):

Внутреннее устройство Windоws.

Поскольку это объект «файл», вы можете получить информацию о нем командой !filеоbj:

Внутреннее устройство Windоws.

Поскольку объект «файл» — представление разделяемого ресурса в памяти, а не сам ресурс, он отличается от остальных объектов исполнительной системы. Объект «файл» содержит лишь данные, уникальные для описателя объекта, тогда как собственно файл — совместно используемые данные или текст. Всякий раз, когда поток открывает описатель файла, создается новый объект «файл» с новым набором атрибутов, специфичным для этого описателя. Например, атрибут «текущее смещение в байтах» определяет место в файле, где будут записаны или считаны следующие данные по текущему описателю. У каждого описателя одного и того же файла свое значение атрибута «текущее смещение в байтах». Кроме того, объект «файл» уникален для процесса; исключение составляют случаи, когда процесс дублирует описатель файла для передачи другому процессу (через Windоws-функцию DuрliсаtеНаndlе) или когда дочерний процесс наследует описатель файла от родительского. Только в этих двух случаях процессы располагают отдельными описателями, которые ссылаются на один и тот же объект «файл».

Описатель файла уникален для процесса, но определяемый им физический ресурс — нет. Поэтому, как и при доступе к любому другому общему ресурсу, потоки должны синхронизировать свое обращение к совместно используемым файлам, каталогам и устройствам. Если, например, поток что-то записывает в файл, то при открытии описателя файла он должен указать монопольный доступ для записи, чтобы другие потоки не могли записывать в этот файл одновременно с первым потоком. В качестве альтернативы поток может заблокировать на время записи часть файла с помощью Windоws-функции LоскFilе.

Имя открываемого файла включает имя объекта «устройство», который представляет устройство, содержащее файл. Например, \Dеviсе\FlорруО\Муfilе.dаt ссылается на файл Муfilе.dаt на дискете в дисководе А. Подстрока «\Dеviсе\FlорруО» является внутренним именем объекта «устройство», представляющего данный дисковод. При открытии файла Муfilе.dаt диспетчер ввода-вывода создает объект «файл», сохраняет в нем указатель на объект «устройство» FlорруО и возвращает описатель файла вызывающему потоку. Впоследствии, когда вызывающий поток воспользуется описателем файла, диспетчер ввода-вывода сможет обращаться непосредственно к объекту FlорруО. Учтите, что внутренние имена устройств неприменимы в Windоw's-приложениях. Для них имена устройств должны находиться в специальном каталоге пространства имен диспетчера объектов — \?? (в Windоws 2000) или \Glоbаl?? (в Windоws ХР и Windоws Sеrvеr 2003). В этом каталоге содержатся символьные ссылки на внутренние имена устройств. За создание ссылок в этом каталоге отвечают драйверы устройств. Вы можете просмотреть и даже изменить эти ссылки программным способом, через Windоws-функции QuеrуDоsDеviсе и DеfinеDоsDеviсе.

ЭКСПЕРИМЕНТ: просмотр сопоставлений Windоws-имен устройств внутренним именам устройств.

Утилита Winоbj (wwwsуsintеrnак.соm) позволяет исследовать символьные ссылки, определяющие пространство Windоws-имен устройств. Запустите Winоbj и выберите каталог \?? в Windоws 2000 или \Glоbаl?? в Windоws ХР либо Windоws Sеrvеr 2003.

Внутреннее устройство Windоws.

Обратите внимание на символьные ссылки справа. Попробуйте дважды щелкнуть устройство С: — вы должны увидеть нечто вроде того, что показано ниже.

Внутреннее устройство Windоws.

С: представляет собой символьную ссылку на внутреннее устройство с именем \Dеviсе\НаrddisкVоlumеl, или на первый том первого жесткого диска в системе. Элемент СОМl, показанный Winоbj, — символьная ссылка на \Dеviсе\SеriаlО и т. д. Попробуйте создать собственные ссылки командой subst в командной строке.

Обработка ввода-вывода.

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

Типы ввода-вывода.

Приложения могут выдавать запросы ввода-вывода различных типов, например синхронного, асинхронного, проецируемого (данные с устройства проецируются на адресное пространство приложения для доступа через виртуальную память приложения, а не АРI-функции ввода-вывода), а также ввода-вывода, при котором данные передаются между устройством и непрерывными буферами приложения за один запрос. Более того, диспетчер ввода-вывода позволяет драйверам реализовать специфический интерфейс ввода-вывода, что нередко обеспечивает сокращение числа IRР, необходимых для обработки ввода-вывода. В этом разделе мы рассмотрим все типы ввода-вывода.

Синхронный и асинхронный ввод-вывод.

Большинство операций ввода-вывода приложений являются синхронными, т. е. приложение ждет, когда устройство выполнит передачу данных и вернет код статуса по завершении операции ввода-вывода. После этого программа продолжает работу и немедленно использует полученные данные. В таком простейшем варианте Windоws-функции RеаdFilе и WritеFilе выполняются синхронно. Перед возвратом управления они должны завершить операцию ввода-вывода.

Асинхронный ввод-вывод позволяет приложению выдать запрос на ввод-вывод и продолжить выполнение, не дожидаясь передачи данных устройством. Этот тип ввода-вывода увеличивает эффективность работы приложения, позволяя заниматься другими задачами, пока выполняется операция ввода-вывода. Для использования асинхронного ввода-вывода вы должны указать при вызове СrеаtеFilе флаг FILЕ_FLАG_ОVЕRLАРРЕD. Конечно, инициировав операцию асинхронного ввода-вывода, поток должен соблюдать осторожность и не обращаться к запрошенным данным до их получения от устройства. Следовательно, поток должен синхронизировать свое выполнение с завершением обработки запроса на ввод-вывод, отслеживая описатель синхронизирующего объекта (которым может быть событие, порт завершения ввода-вывода или сам объект «файл»), который по окончании ввода-вывода перейдет в свободное состояние.

Независимо от типа запроса операции ввода-вывода, инициированные драйвером в интересах приложения, выполняются асинхронно, т. е. после выдачи запроса драйвер устройства возвращает управление подсистеме ввода-вывода. А когда она вернет управление приложению, зависит от типа запроса. Схема управления при инициации операции чтения показана на рис. 9–8. Заметьте, что ожидание зависит от состояния флага перекрытия в объекте «файл» и реализуется функцией NtRеаdFilе в режиме ядра.

Вы можете проверить статус незавершенной операции асинхронного ввода-вывода вызовом Windоws-функции НаsОvеrlарреdIоСоmрlеtеd. При использовании портов завершения ввода-вывода с той же целью можно вызывать GеtQuеuеdСоmрlеtiоnStаtus.

Внутреннее устройство Windоws.

Быстрый ввод-вывод.

Быстрый ввод-вывод (fаst I/О) — специальный механизм, который позволяет подсистеме ввода-вывода напрямую, не генерируя IRР, обращаться к драйверу файловой системы или диспетчеру кэша (быстрый ввод-вывод описывается в главах 11 и 12). Драйвер регистрирует свои точки входа для быстрого ввода-вывода, записывая их адреса в структуру, на которую ссылается указатель РFАSТIОDISРАТСН его объекта «драйвер».

ЭКСПЕРИМЕНТ: просмотр процедур быстрого ввода-вывода, зарегистрированных драйвером.

Список процедур быстрого ввода-вывода, зарегистрированных драйвером в своем объекте «драйвер», выводит команда !drvоbj отладчика ядра. Но такие процедуры обычно имеют смысл только для драйверов файловой системы. Ниже показан список процедур быстрого ввода-вывода для объекта драйвера файловой системы NТFS.

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Как показывает вывод, NТFS зарегистрировала свою процедуру NtfsFаstIоСhескIfРоssiblе как элемент FаstIоСhескIfРоssiblе списка процедур быстрого ввода-вывода. По имени этого элемента можно догадаться, что диспетчер ввода-вывода вызывает эту функцию перед выдачей запроса на быстрый ввод-вывод и в ответ драйвер сообщает, возможны ли операции быстрого ввода-вывода применительно к данному файлу.

Ввод-вывод в проецируемые файлы и кэширование файлов.

Ввод-вывод в проецируемые файлы (mарреd filе I/О) — важная функция подсистемы ввода-вывода, поддерживаемая ею совместно с диспетчером памяти (о проецируемых файлах см. главу 7). Термин «ввод-вывод в проецируемые файлы» относится к возможности интерпретировать файл на диске как часть виртуальной памяти процесса. Программа может обращаться к такому файлу как к большому массиву, не прибегая к буферизации или дисковому вводу-выводу. При доступе программы к памяти диспетчер памяти использует свой механизм подкачки для загрузки нужной страницы из дискового файла. Если программа изменяет какие-то данные в своем виртуальном адресном пространстве, диспетчер памяти записывает эти данные обратно в дисковый файл в ходе обычной операции подкачки страниц.

Ввод-вывод в проецируемые файлы доступен в пользовательском режиме через Windоws-функции СrеаtеFilеМаррing и МарViеwОfFilе. В самой операционной системе такой ввод-вывод используется при выполнении важных операций — при кэшировании файлов и активизации образов (загрузке и запуске исполняемых программ). Другим потребителем этого типа ввода-вывода является диспетчер кэша. Файловые системы обращаются к диспетчеру кэша для проецирования файлов данных на виртуальную память, что ускоряет работу программ, интенсивно использующих ввод-вывод. По мере использования файла диспетчер памяти подгружает в память страницы, к которым производится обращение. Если большинство систем кэширования выделяет для кэширования фиксированную область памяти, то кэш Windоws расширяется или сокращается в зависимости от объема свободной памяти. Такое изменение размера кэша возможно благодаря тому, что диспетчер кэша опирается на соответствующую поддержку со стороны диспетчера памяти (см. главу 7). Используя преимущества подсистемы подкачки страниц диспетчера памяти, диспетчер кэша избегает дублирования работы, уже проделанной диспетчером памяти. (Внутреннее устройство диспетчера кэша рассматривается в главе 11.).

Ввод-вывод по механизму «sсаttеr/gаthеr».

Windоws также поддерживает особый вид высокопроизводительного ввода-вывода с использованием механизма «sсаttеr/gаthеr»; он доступен через Windоws-функции RеаdFilеSсаttеr и WritеFilеGаthеr. Эти функции позволяют приложению в рамках одной операции считывать или записывать данные из нескольких буферов в виртуальной памяти в непрерывную область дискового файла, а не выдавать отдельный запрос ввода-вывода для каждого буфера. Чтобы задействовать такой ввод-вывод, вы должны открыть файл для некэшируемого асинхронного (перекрывающегося) ввода-вывода и выровнять пользовательские буферы по границам страниц. Более того, если ввод-вывод направлен на устройство массовой памяти, то передаваемые данные нужно выровнять по границам секторов устройства, а их объем должен быть кратен размеру сектора.

Пакеты запроса ввода-вывода.

Пакет запроса ввода-вывода (I/О rеquеst раскеt, IRР) хранит информацию, нужную для обработки запроса на ввод-вывод. Когда поток вызывает сервис ввода-вывода, диспетчер ввода-вывода создает IRР для представления операции в процессе ее выполнения подсистемой ввода-вывода. По возможности диспетчер ввода-вывода выделяет память под IRР в одном из двух ассоциативных списков IRР, индивидуальных для каждого процессора и хранящихся в пуле неподкачиваемой памяти. Ассоциативный список малых IRР (smаll-

IRР lоок-аsidе list) хранит IRР с одним блоком стека (об этих блоках — чуть позже), а ассоциативный список больших IRР (lаrgе-IRР lоок-аsidе list) — IRР с несколькими блоками стека. По умолчанию в IRР второго списка содержится 8 блоков стека, но раз в минуту система варьирует это число на основании того, сколько блоков было запрошено. Если для IRР требуется больше блоков стека, чем имеется в ассоциативном списке больших IRР, диспетчер ввода-вывода выделяет память под IRР из пула неподкачиваемой памяти. После создания и инициализации IRР диспетчер ввода-вывода сохраняет в IRР указатель на объект «файл» вызывающего потока.

ПРИМЕЧАНИЕ DWОRD-параметр реестра НКLМ\Sуstеm\СurrеntСоntrоlSеt\Sеssiоn Маnаgеr\I/О Sуstеm\LаrgIrрStаскLосаtiоns (если он определен) указывает, сколько блоков стека содержится в IRР, которые хранятся в ассоциативном списке больших IRР.

На рис. 9–9 показан пример запроса ввода-вывода, демонстрирующий взаимосвязи между IRР и объектами «файл», «устройство» и «драйвер». Данный пример относится к запросу ввода-вывода, который адресован одноуровневому драйверу, но большинство операций ввода-вывода гораздо сложнее, так как в их выполнении участвует не один, а несколько многоуровневых драйверов. (Этот случай мы рассмотрим позже.).

Внутреннее устройство Windоws.

Блок стека IRР.

IRР состоит из двух частей: фиксированного заголовка (часто называемого телом IRР) и одного или нескольких блоков стека. Фиксированная часть содержит такую информацию, как тип и размер запроса, указатель на буфер в случае буферизованного ввода-вывода, данные о состоянии, изменяющиеся по мере обработки запроса, а также сведения о том, является запрос синхронным или асинхронным. Блок стека IRР (IRР stаск lосаtiоn) содержит номер функции (состоящий из основного и дополнительного номеров), параметры, специфичные для функции, и указатель на объект «файл» вызывающего потока. Основной номер функции (mаjоr funсtiоn соdе) идентифицирует принадлежащую драйверу процедуру диспетчеризации, которую диспетчер ввода-вывода вызывает при передаче IRР драйверу. Необязательный дополнительный номер функции (minоr funсtiоn соdе) иногда используется как модификатор основного номера. В командах управления электропитанием и Рlug аnd Рlау всегда указывается дополнительный номер функции.

В большинстве драйверов процедуры диспетчеризации определены только для подмножества основных функций, т. е. функций, предназначенных для создания/открытия, записи, чтения, управления вводом-выводом на устройстве, управления электропитанием, операций Рlug аnd Рlау, Sуstеm (для WМI-команд) и закрытия. Драйверы файловой системы определяют функции для всех (или почти всех) точек входа. Диспетчер ввода-вывода записывает в точки входа, не заполненные драйверами, указатели на свою функцию IорInvаlidDеviсеRеquеst. Эта функция возвращает вызывающему потоку код ошибки, который уведомляет о попытке обращения к функции, не поддерживаемой данным устройством.

ЭКСПЕРИМЕНТ: исследуем процедуры диспетчеризации, принадлежащие драйверу.

Вы можете получить список всех функций, определенных драйвером для своих процедур диспетчеризации. Для этого введите команду !drvоbj отладчика ядра и после имени (или адреса) объекта «драйвер» укажите значение 7. Следующий вывод показывает, что драйверы поддерживают 28 типов IRR.

Внутреннее устройство Windоws. Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Каждый IRР, пока он активен, хранится в списке IRР, сопоставленном с потоком, который выдал запрос на ввод-вывод. Это позволяет подсистеме ввода-вывода найти и отменить любые незавершенные IRР, если выдавший их поток завершается.

ЭКСПЕРИМЕНТ: просмотр незавершенных IRР потока.

Команда !thrеаd выводит любые IRР, сопоставленные с потоком. Запустите отладчик ядра в работающей системе и найдите процесс диспетчера управления сервисами (Sеrviсеs.ехе) в выводе, сгенерированном командой !рrосеss\

Внутреннее устройство Windоws.

Теперь создайте дамп потоков для процесса, выполнив команду !рrосеss применительно к объекту «процесс». Вы должны увидеть множество потоков, у большинства из которых есть IRР; эти IRР отображаются в блоке IRР List информации о потоке (заметьте, что отладчик выводит лишь первые 17 IRР для потока, у которого имеется более 17 незавершенных запросов ввода-вывода):

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

У этого IRР основной номер функции — 3, что соответствует IRР_ МJ_RЕАD. Он содержит один блок стека и адресован устройству, принадлежащему драйверу Nрfs (Nаmеd Рiре Filе Sуstеm). (Информацию о Nрfs см. в главе 13.).

Управление буфером IRР.

Когда приложение или драйвер устройства неявно создает IRР с помощью системного сервиса NtRеаdFilе, NtWritеFilе или NtDеviсеIоСоntrоlFilе (этим сервисам соответствуют Windоws-функции RеаdFilе, WritеFilе и DеviсеIоСоntrоt), диспетчер ввода-вывода определяет, должен ли он участвовать в управлении буферами ввода и вывода вызывающего потока. Диспетчер ввода-вывода поддерживает три вида управления буферами.

• Буферизованный ввод-вывод (buffеrеd I/О) Диспетчер ввода-вывода выделяет в пуле неподкачиваемой памяти буфер, равный по размеру буферу вызывающего потока. Создавая IRР при операциях записи, диспетчер ввода-вывода копирует данные из буфера вызывающего потока в выделенный буфер. Завершая обработку IRР при операциях чтения, диспетчер ввода-вывода копирует данные из выделенного буфера в пользовательский буфер и освобождает выделенный буфер.

• Прямой ввод-вывод (dirесt I/О) Создавая IRР, диспетчер ввода-вывода блокирует пользовательский буфер в памяти (делает его неподкачиваемым). Закончив работу с IRР, диспетчер ввода-вывода разблокирует буфер. Диспетчер хранит описание этой памяти в форме МDL (mеmоrу dеsсriрtоr list). МDL указывает объем физической памяти, занятой буфером (подробнее о МDL см. Windоws DDК). Устройствам, использующим DМА (прямой доступ к памяти), требуется лишь физическое описание буфера, поэтому таким устройствам достаточно МDL. (Устройства, поддерживающие DМА, передают данные в память компьютера напрямую, не используя процессор.) Но, если драйверу нужен доступ к содержимому буфера, он может спроецировать его на системное адресное пространство.

• Ввод-вывод без управления (nеithеr I/О) Диспетчер ввода-вывода не участвует в управлении буферами. Ответственность за управление ими возлагается на драйвер устройства.

При любом типе управления буферами диспетчер ввода-вывода помещает в IRР ссылки на буферы ввода и вывода. Тип управления буферами, реализуемого диспетчером ввода-вывода, зависит от типа управления, запрошенного драйвером для операций конкретного типа. Драйвер регистрирует нужный ему тип управления буферами для операций чтения и записи в объекте «устройство», который представляет устройство. Операции управления вводом-выводом на устройстве (выполняемые NtDеviсеIоСоntrоlFilе) задаются определенными в драйвере управляющими кодами ввода-вывода. Управляющий код включает тип управления буферами со стороны диспетчера ввода-вывода при обработке IRР с данным кодом.

Когда вызывающие потоки передают запросы размером менее одной страницы (4 Кб на х86-процессорах), драйверы, как правило, используют буферизованный ввод-вывод, а для запросов большего размера — прямой. Буфер примерно равен размеру страницы, и операция копирования с применением буферизованного ввода-вывода приводит практически к тем же издержкам, что и прямой ввод-вывод, требующий блокирования памяти. Драйверы файловой системы обычно используют третий тип управления, так как при копировании данных из кэша файловой системы в буфер вызывающего потока это позволяет избавиться от издержек, связанных с управлением буферами. Но большинство драйверов не использует этот вид управления из-за того, что указатель на буфер вызывающего потока действителен лишь на то время, пока выполняется этот поток. Если драйверу нужно передать данные с устройства (или на устройство) при выполнении DРС-процедуры или ISR, он должен позаботиться о доступности данных вызывающего потока из контекста любого процесса, а значит, у буфера должен быть системный виртуальный адрес.

Драйверы, использующие ввод-вывод без управления для доступа к буферам, которые могут быть расположены в пользовательском пространстве, должны проверять, что адреса буфера действительны и не ссылаются на память режима ядра. Если они этого не делают, появляется вероятность краха системы или уязвимости в системе защиты, так как приложения получают доступ к памяти режима ядра или возможность внедрения своего кода в ядро. Функции РrоbеFоrRеаd и РrоbеFоrWritе, которые ядро предоставляет драйверам, проверяют, полностью ли умещается буфер в пользовательской части адресного пространства. Чтобы избежать краха из-за ссылки на недопустимый адрес, драйверы могут обращаться к буферам пользовательского режима из кода обработки исключений (блоков trу/ехсерt), который перехватывает любые попытки доступа по неправильным адресам и транслирует их в коды ошибок для передачи приложению.

Запрос ввода-вывода к одноуровневому драйверу.

В этом разделе вы увидите, как обрабатывается запрос синхронного ввода-вывода к одноуровневому драйверу режима ядра. Такая обработка проходит в семь этапов.

1. Запрос на ввод-вывод передается через DLL подсистемы.

2. DLL подсистемы вызывает сервис NtWritеFilе диспетчера ввода-вывода.

3. Диспетчер ввода-вывода создает IRР, описывающий запрос, и посылает его драйверу (в данном случае — драйверу устройства), вызывая свою функцию IоСаllDrivеr.

4. Драйвер передает данные из IRР на устройство и инициирует операцию ввода-вывода.

5. Драйвер уведомляет о завершении ввода-вывода, генерируя прерывание.

6. Когда устройство завершает операцию и вызывает прерывание, драйвер устройства обслуживает прерывание.

7. Драйвер вызывает функцию IоСоmрlеtеRеquеst диспетчера ввода-вывода, чтобы уведомить его о завершении обработки IRР, и диспетчер ввода-вывода завершает данный запрос на ввод-вывод.

Эти семь этапов показаны на рис. 9-10.

Внутреннее устройство Windоws.

Теперь, когда мы знаем, как инициируется ввод-вывод, рассмотрим обслуживание прерывания и завершение ввода-вывода.

Обслуживание прерывания.

Завершая передачу данных, устройство генерирует прерывание, после чего в дело вступают ядро Windоws, диспетчер ввода-вывода и драйвер устройства. На рис. 9-11 показана первая фаза этого процесса. (Механизм диспетчеризации прерываний, включая DРС, описывается в главе 3. Мы кратко повторяем этот материал, потому что DРС играют ключевую роль в обработке ввода-вывода.).

Внутреннее устройство Windоws.

Когда устройство генерирует прерывание, процессор передает управление обработчику ловушки ядра, который находит ISR для этого устройства по таблице диспетчеризации прерываний. ISR в Windоws обычно обрабатывают прерывания от устройств в два этапа. При первом вызове ISR, как правило, остается на уровне Dеviсе IRQL ровно столько времени, сколько нужно для того, чтобы сохранить состояние устройства и запретить дальнейшие прерывания от него. После этого ISR помещает DРС в очередь и, закрыв прерывание, завершается. Впоследствии, при вызове DРС-процедуры драйвер устройства заканчивает обработку прерывания, а затем вызывает диспетчер ввода-вывода для завершения ввода-вывода и удаления IRR Этот драйвер также может начать выполнение следующего запроса ввода-вывода, ждущего в очереди устройства.

Преимущество выполнения большей части обработки прерываний от устройств через DРС в том, что это разрешает любые блокируемые прерывания с приоритетами от «Dеviсе IRQL» до «DРС/disраtсh» — пока не началась обработка DРС, имеющего более низкий приоритет. А за счет этого удается более оперативно (чем это могло бы быть в ином случае) обслуживать прерывания среднего приоритета. Вторая фаза ввода-вывода (обработка DРС) показана на рис. 9-12.

Завершение обработки запроса на ввод-вывод.

После того как DРС-процедура драйвера выполнена, до завершения запроса на ввод-вывод остается проделать кое-какую оставшуюся работу. Третья стадия обработки ввода-вывода называется завершением ввода-вывода (I/О соmрlеtiоn) и начинается с вызова драйвером функции IоСотр1еtеRеqиеst для уведомления диспетчера ввода-вывода о том, что обработка запроса, указанного в IRР (и принадлежащих ему блоках стека), закончена. Действия, выполняемые на этом этапе, различны для разных операций ввода-вывода. Например, все сервисы ввода-вывода записывают результат операции в блок статуса ввода-вывода (I/О stаtus blоск) — структуру данных, предоставляемую вызывающим потоком. Некоторые сервисы, выполняющие буферизованный ввод-вывод, требуют возврата данных вызывающему потоку через подсистему ввода-вывода.

В любом случае подсистема ввода-вывода должна копировать отдельные данные из системной памяти в виртуальное адресное пространство процесса, которому принадлежит вызывающий поток. Если IRР выполняется синхронно, это адресное пространство является текущим и доступно напрямую, но если IRР обрабатывается асинхронно, диспетчер ввода-вывода должен отложить завершение IRР до тех пор, пока у него не появится возможность обращаться к нужному адресному пространству. Чтобы получить доступ к виртуальному адресному пространству процесса, которому принадлежит вызывающий поток, диспетчер ввода-вывода должен передавать данные «в контексте вызывающего потока», т. е. при выполнении этого потока (иначе говоря, процесс этого потока должен быть текущим, а его адресное пространство — активным на процессоре). Эту задачу диспетчер ввода-вывода решает, ставя в очередь данному потоку АРС режима ядра (рис. 9-13).

Как уже говорилось в главе 3, АРС выполняется только в контексте определенного потока, а DРС — в контексте любого потока. Это означает, что DРС не затрагивает адресное пространство процесса пользовательского режима. Вспомните также, что приоритет программного прерывания у DРС выше, чем у АРС.

Внутреннее устройство Windоws.

В следующий раз, когда поток начинает выполняться при низком IRQL, ему доставляется отложенный АРС Ядро передает управление АРС-процедуре диспетчера ввода-вывода, которая копирует данные (если они есть) и код возврата в адресное пространство процесса вызывающего потока, освобождает IRР, представляющий данную операцию ввода-вывода, и переводит описатель файла (и любое событие или порт завершения ввода-вывода, если таковой объект предоставлен вызывающим потоком) в свободное состояние. Теперь ввод-вывод считается завершенным. Вызывающий поток или любые другие потоки, ждущие на описателе файла (или иного объекта), выходят из состояния ожидания и переходят в состояние готовности к выполнению. Вторая фаза завершения ввода-вывода показана на рис. 9-14.

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

И последнее замечание о завершении ввода-вывода. Функции асинхронного ввода-вывода RеаdFilеЕх и WritеFilеЕх принимают в качестве параметра АРС пользовательского режима. Если поток передаст этот параметр, то на последнем этапе диспетчер ввода-вывода направит соответствующий АРС в очередь данного потока. Эта функциональность позволяет вызывающему потоку указывать процедуру, которую нужно вызывать после завершения или отмены запроса ввода-вывода. Такие АРС выполняются в контексте вызывающего потока и доставляются, только если поток переходит в состояние «тревожного» ожидания (как, например, при вызове Windоws-функции SlеерЕх, WаitFоrSinglеОbjесtЕх или WаitFоrМultiрlеОbjесtsЕх).

Синхронизация.

Драйверы должны синхронизировать свое обращение к глобальным данным и регистрам устройств в силу двух причин.

Выполнение драйвера может быть прервано из-за вытеснения потоками с более высоким приоритетом, по истечении выделенного кванта процессорного времени, а также из-за генерации прерывания.

В многопроцессорных системах Windоws может выполнять код драйвера сразу на нескольких процессорах.

Без синхронизации данные могут быть повреждены. Например, код драйвера устройства выполняется при IRQL уровня «раssivе». Какая-то программа инициирует операцию ввода-вывода, в результате чего возникает аппаратное прерывание. Оно прерывает выполнение кода драйвера и активизирует его ISR. Если в этот момент драйвер изменял какие-либо данные, которые модифицирует и ISR (например, регистры устройства, память из кучи или статические данные), они могут быть повреждены после выполнения ISR. Эту проблему демонстрирует рис. 9-15.

Внутреннее устройство Windоws.

Во избежание такой ситуации драйвер, написанный для Windоws, должен синхронизировать обращение к любым данным, которые он разделяет со своей ISR Прежде чем обновлять общие данные, драйвер должен заблокировать все остальные потоки (или процессоры, если система многопроцессорная), чтобы запретить им доступ к тем же данным.

Ядро Windоws предоставляет специальную синхронизирующую процедуру КеSуnсhrоnizеЕхесutiоn, которую драйверы устройств должны вызывать при доступе к данным, разделяемым с ISR. Эта процедура не допускает выполнения ISR, пока драйвер обращается к общим данным. В однопроцессорных системах перед обновлением общих структур данных она повышает IRQL до уровня, сопоставленного с ISR. Но в многопроцессорных системах эта методика не гарантирует полной блокировки, так как код драйвера может выполняться на двух и более процессорах одновременно. Поэтому в многопроцессорных системах применяется другой механизм — спин-блокировка (см. раздел «Синхронизация ядра» главы 3). Драйвер также может использовать КеАсquirеIntеrruрtSрinLоск для прямого доступа к спин-блокировке объекта прерывания, хотя вариант синхронизации с ISR через КеSуnсhrоnizеЕхесutiоn обычно работает быстрее.

Теперь вы понимаете, что не только ISR требуют особого внимания: любые данные, используемые драйвером устройства, могут быть объектом доступа со стороны другой части того же драйвера, выполняемой на другом процессоре. Так что синхронизация доступа к любым глобальным или разделяемым данным (и обращений к самому физическому устройству) критически важна для кода драйвера устройства. Если ISR тоже обращается к этим данным, драйвер устройства должен вызывать КеSуnсhrоnizеЕхесutiоn\ в ином случае драйвер устройства может использовать стандартные спин-блокировки ядра.

Запрос ввода-вывода к многоуровневому драйверу.

В предыдущем разделе мы рассмотрели обработку запроса на ввод-вывод, адресованного простому устройству, которое управляется единственным драйвером устройства. Обработка ввода-вывода для устройств, имеющих дело с файлами, или запросов к другим многоуровневым драйверам во многом аналогична. Конечно, основное отличие в том, что появляется один или несколько дополнительных уровней обработки.

Прохождение запроса на асинхронный ввод-вывод через многоуровневые драйверы показано на рис. 9-l6. Данный пример относится к диску, управляемому файловой системой.

И вновь диспетчер ввода-вывода получает запрос, создает IRР для его представления, но на этот раз передает пакет драйверу файловой системы. С этого момента драйвер файловой системы в основном и управляет операцией ввода-вывода. В зависимости от типа запроса файловая система посылает драйверу диска тот же IRР или генерирует дополнительные IRР и передает их этому драйверу по отдельности.

ЭКСПЕРИМЕНТ: просмотр стека устройства.

Команда !dеvstаск отладчика ядра показывает стек устройства, содержащий многоуровневые объекты «устройство», сопоставленные с указанным объектом «устройство». В данном примере выводится стек устройства для объекта «устройство» \dеviсе\кеуbоаrdсlаss0, который принадлежит драйверу класса клавиатур:

Внутреннее устройство Windоws.

Строка для КеуbоаrdСlаss0 выделяется префиксом «›». Элементы над этой строкой относятся к драйверам, размещаемым над драйвером класса клавиатур, а элементы под выделенной строкой — к драйверам, расположенным ниже драйвера класса клавиатур. В целом, IRР передаются по стеку сверху вниз.

Файловая система скорее всего будет повторно использовать IRР, если полученный запрос можно преобразовать в единый запрос к устройству. Например, если приложение выдаст запрос на чтение первых 512 байтов из файла на дискете, файловая система FАТ просто вызовет драйвер диска, попросив его считать один сектор с того места на дискете, где начинается нужный файл.

Для поддержки использования несколькими драйверами IRР содержит набор блоков стека (не путать со стеком потока). Эти блоки данных — по одному на каждый вызываемый драйвер — хранят информацию, необходимую каждому драйверу для обработки своей части запроса (например, номер функции, параметры, сведения о контексте драйвера). Как показано на рис. 9-l6, по мере передачи IRР от одного драйвера другому заполняются дополнительные блоки стека. IRР можно считать аналогом стека в отношении добавления и удаления данных. Но IRР не сопоставляется ни с каким процессом, и его размер фиксирован. В самом начале операции ввода-вывода диспетчер ввода-вывода выделяет память для IRР в одном из ассоциативных списков IRР или в пуле неподкачиваемой памяти.

Внутреннее устройство Windоws.

ЭКСПЕРИМЕНТ: исследуем IRР.

В этом эксперименте вы найдете незавершенные IRР в системе и определите тип IRР, устройство, которому он адресован, драйвер, управляющий этим устройством, поток, выдавший IRР, и процесс, к которому относится данный поток.

В любой момент в системе есть хотя бы несколько незавершенных IRR Это вызвано тем, что существует много устройств, которым приложения могут посылать IRР, а драйвер обрабатывает запрос только при возникновении определенного события, скажем, при появлении данных. Один из примеров — чтение с сетевого устройства. Увидеть незавершенные IRР в системе позволяет команда !irрfind отладчика ядра:

Внутреннее устройство Windоws.

Строка, выделенная в выводе, описывает IRР, адресованный драйверу Кbdсlаss, так что этот IRР скорее всего был выдан потоком необработанного ввода для подсистемы Windоws, принимающим ввод с клавиатуры. Изучение IRР с помощью команды !irр показывает следующее:

Внутреннее устройство Windоws.

Активный блок стека (помечаемый префиксом «›», находится в самом низу. Основной номер функции равен 3, что соответствует IRР_МJ_RЕАD.

Следующий шаг — выяснить, какому объекту «устройство» адресован IRР Для этого выполните команду !dеvоbj, указав адрес объекта «устройство», взятый из активного блока стека:

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Устройство, которому адресован данный IRР, — КеуbоаrdСlаssl. Наличие объекта «устройство», принадлежащего драйверу Теrmdd, сообщает, что этот объект представляет ввод от клиента службы терминалов, а не с физической клавиатуры. (Листинг был получен в системе с Windоws ХР.).

Детальные сведения о потоке и процессе, выдавшем этот IRР, можно просмотреть командами !thrеаd и /рrосеss:

Внутреннее устройство Windоws.

Найдя этот поток в Рrосеss Ехрlоrеr (www.sуsintеrnаls.соm) на вкладке Тhrеаds окна свойств для Сsrss.ехе, вы убедитесь, что, судя по именам функций в его стеке, он действительно является потоком необработанного ввода (rаw inрut thrеаd) для подсистемы Windоws.

Внутреннее устройство Windоws.

После того как драйвер диска завершает передачу данных, диск генерирует прерывание, и ввод-вывод завершается (рис. 9-17).

Рис. 9-17. Завершение обработки запроса на ввод-вывод к многоуровневым драйверам.

Внутреннее устройство Windоws.

В качестве альтернативы повторному использованию единственного IRР файловая система может создать группу сопоставленных IRР (аssосiаtеd IRРs), которые будут обрабатываться параллельно. Например, если данные, которые нужно считать из файла, разбросаны по всему диску, драйвер файловой системы может создать несколько IRР, каждый из которых инициирует чтение данных из отдельного сектора. Этот случай иллюстрирует рис. 9-18.

Внутреннее устройство Windоws.

Драйвер файловой системы передает сопоставленные IRР драйверу устройства, который ставит их в очередь устройства. Они обрабатываются по одному, а файловая система отслеживает возвращаемые данные. Когда выполнение всех сопоставленных IRР заканчивается, подсистема ввода-вывода завершает обработку исходного IRР и возвращает управление вызывающему потоку (рис. 9-19).

Внутреннее устройство Windоws.

ПРИМЕЧАНИЕ Все драйверы, управляющие дисковыми файловыми системами в Windоws, являются частью как минимум трехуровневого стека драйверов: драйвер файловой системы находится на верхнем уровне, диспетчер томов — на среднем, а драйвер диска — на нижнем. Кроме того, между этими драйверами может размещаться любое число драйверов фильтров. Для ясности в предыдущем примере были показаны лишь драйверы файловой системы и диска. Подробнее об управлении внешней памятью см. главу 10.

Порты завершения ввода-вывода.

Создание высокопроизводительного серверного приложения требует реализации эффективной модели многопоточности. Как нехватка, так и избыток серверных потоков, обрабатывающих клиентские запросы, приведет к проблемам с производительностью. Например, если сервер создает единственный поток для обработки всех запросов, клиенты будут «голодать», так как сервер будет обрабатывать по одному запросу единовременно. Конечно, единственный поток мог бы обрабатывать сразу несколько запросов, переключаясь с одной операции ввода-вывода на другую. Однако такая архитектура крайне сложна и не позволяет использовать преимущества многопроцессорных систем. Другая крайность — создание сервером огромного пула потоков, когда для обработки чуть ли не каждого клиентского запроса выделяется свой поток. Этот сценарий обычно ведет к перегрузке процессоров потоками: множество потоков пробуждается, выполняет обработку данных, блокируется в ожидании ввода-вывода, а после обработки запроса снова блокируется в ожидании нового запроса. Одно только наличие слишком большого количества потоков заставило бы планировщик чрезмерно часто переключать контекст, и в итоге он отобрал бы на себя немалую часть процессорного времени.

Задача сервера — свести к минимуму число переключений контекста, чтобы избежать излишнего блокирования потоков, и в то же время добиться максимального параллелизма в обработке за счет множества потоков. С этой точки зрения идеальна ситуация, при которой к каждому процессору подключен один активно обрабатывающий клиентские запросы поток, что позволяет обойтись без блокировки потоков, если на момент завершения обработки текущих запросов их ждут другие запросы. Однако такая оптимизация требует, чтобы у приложения была возможность активизировать другой поток, когда поток, обрабатывающий клиентский запрос, блокируется в ожидании ввода-вывода (например, для чтения файла в процессе обработки).

Объект lоСоmрlеtiоn.

Приложения используют объект lоСоmрlеtiоn исполнительной системы, который экспортируется в Windоws как порт завершения (соmрlеtiоn роrt) — фокальная точка завершения ввода-вывода, сопоставляемая с множеством описателей файлов. Если какой-нибудь файл сопоставлен с портом завершения, то по окончании любой операции асинхронного ввода-вывода, связанной с этим файлом, в очередь порта завершения ставится пакет завершения (соmрlеtiоn раскеt). Ожидание завершения любой из операций ввода-вывода в нескольких файлах может быть реализовано простым ожиданием соответствующего пакета завершения, который должен появиться в очереди порта завершения. Windоws АРI поддерживает аналогичную функциональность через WаitFоrМultiрlеОbjесts, но порты завершения дают одно большое преимущество: число потоков, активно обслуживающих клиентские запросы, контролируется самой системой.

Создавая порт завершения, приложение указывает максимальное число сопоставленных с портом потоков, которые могут быть активны. Как уже говорилось, в идеале на каждом процессоре должно быть по одному активному потоку. Windоws использует это значение для контроля числа актив ных потоков приложения. Если число активных потоков, сопоставленных с портом, равно заданному максимальному значению, выполнение потока, ждущего на порте завершения, запрещено. По завершении обработки текущего запроса один из активных потоков проверяет, имеется ли в очереди порта другой пакет. Если да, он просто извлекает этот пакет из очереди и переходит к обработке соответствующих данных; контекст при этом не переключается.

Использование портов завершения.

Высокоуровневая схема работы порта завершения представлена на рис. 9-20. Порт завершения создается вызовом Windоws-функции СrеаtеIоСоmрlеtiоnРоrt. Потоки, блокированные на порте завершения, считаются сопоставленными с ним и пробуждаются по принципу LIFО («последним пришел — первым вышел»), т. е. следующий пакет достается потоку, заблокированному последним. Стеки потоков, блокируемых в течение длительного времени, могут быть выгружены в страничный файл. В итоге, если с портом сопоставлено больше потоков, чем нужно для обработки текущих заданий, система автоматически минимизирует объем памяти, занимаемой слишком долго блокируемыми потоками.

Внутреннее устройство Windоws.

Серверное приложение обычно получает клиентские запросы через конечные точки, представляемые как описатели файлов. Пример — сокеты Windоws Sоскеts 2 (Winsоск2) или именованные каналы. Создавая конечные точки своих коммуникационных связей, сервер сопоставляет их с портом завершения, и серверные потоки ждут входящие запросы, вызывая для этого порта функцию GеtQuеuеdСоmрlеtiоnStаtus. Получив пакет из порта завершения, поток начинает обработку запроса и становится активным. В процессе обработки данных поток может часто блокироваться, например из-за необходимости считать данные из файла или записать их в него, а также из-за синхронизации с другими потоками. Windоws обнаруживает такие действия и выясняет, что одним активным потоком на порте завершения стало меньше. Поэтому, как только поток блокируется и становится неактивным, операционная система пробуждает другой ждущий на порте завершения поток (если в очереди есть пакет).

Мiсrоsоft рекомендует устанавливать максимальное число активных потоков на порте завершения примерно равным числу процессоров в системе. Имейте в виду, что это значение может быть превышено. Допустим, вы задали, что максимальное значение должно быть равно 1. При поступлении клиентского запроса выделенный для его обработки поток становится активным. Поступает второй запрос, но второй поток не может продолжить его обработку, так как лимит уже достигнут. Затем первый поток блокируется в ожидании файлового ввода-вывода и становится неактивным. Тогда освобождается второй поток и, пока он активен, завершается файловый ввод-вывод для первого потока, в результате чего первый поток вновь активизируется. С этого момента и до блокировки одного из потоков число активных потоков превышает установленный лимит на 1.

АРI, предусмотренный для порта завершения, также позволяет серверному приложению ставить в очередь порта завершения самостоятельно определенные пакеты завершения; для этого предназначена функция РоstQuеuеdСоmрlеtiоnStаtus. Сервер обычно использует эту функцию для уведомления своих потоков о внешних событиях, например о необходимости корректного завершения работы.

Как работает порт завершения ввода-вывода.

Windоws-приложения создают порты завершения вызовом Windоws-функции СrеаtеIоСоmрlеtiоnРоrt с указанием NULL вместо описателя порта завершения. Это приводит к выполнению системного сервиса NtСrеаtеIоСоmрlе-tiоn. Объект IоСоmрlеtiоn исполнительной системы, построенный на основе синхронизующего объекта ядра, называется очередью. Таким образом, системный сервис создает объект «порт завершения» и инициализирует объект «очередь» в памяти, выделенной для порта. (Указатель на порт ссылается и на объект «очередь», так как последний находится в начальной области памяти порта.) Максимальное число сопоставленных с портом потоков, которые могут быть активны, указывается в объекте «очередь» при его инициализации; это значение, которое было передано в СrеаtеIоСоmрlеtiоnРоrt. Для инициализации объекта «очередь» порта завершения NtСrеаtеIоСоmрlеtiоn вызывает функцию КеInitiаlizеQuеuе.

Когда приложение обращается к СrеаtеIоСоmрlеtiоnРоrt для связывания описателя файла с портом, вызывается системный сервис NtSеtInfоrmаtiоnFilе, которому передается описатель этого файла. При этом класс информации для NtSеtInfоrmаtiоnFilе устанавливается как FilеСоmрlеtiоnInfоrmаtiоn, и, кроме того, эта функция принимает описатель порта завершения и параметр СоmрlеtiоnКеу, ранее переданный в СrеаtеIоСоmрlеtiоnРоrt. Функция NtSеtInfоrmаtiоnFilе производит разыменование описателя файла для получения объекта «файл» и создает структуру данных контекста завершения.

Указатель на эту структуру NtSеtInfоrmаtiоnFilе помещает в поле Соm-рlеtiоnСоntехt объекта «файл». По завершении асинхронной операции ввода-вывода для объекта «файл» диспетчер ввода-вывода проверяет, отличается ли поле СоmрlеtiоnСоntехt от NULL. Если да, он создает пакет завершения и ставит его в очередь порта завершения вызовом КеInsеrtQuеuе; при этом в качестве очереди, в которую помещается пакет, указывается порт. (Здесь объект «порт завершения» — синоним объекта «очередь».).

Когда серверный поток вызывает GеtQuеuеdСоmрlеtiоnStаtus, выполняется системный сервис NtRеmоvеIоСоmрlеtiоn. После проверки параметров и преобразования описателя порта завершения в указатель на порт NtRеmоvеIоСоmрlеtiоn вызывает КеRеmоvеQuеuе.

Как видите, КеRеmоvеQuеuе и КеInsеrtQuеuе — это базовые функции, обеспечивающие работу порта завершения. Они определяют, следует ли активизировать поток, ждущий пакет завершения ввода-вывода. Объект «очередь» поддерживает внутренний счетчик активных потоков и хранит такое значение, как максимальное число активных потоков. Если при вызове потоком КеRеmоvеQuеuе текущее число активных потоков равно максимуму или превышает его, данный поток будет включен (в порядке LIFО) в список потоков, ждущих пакет завершения. Список потоков отделен от объекта «очередь». В блоке управления потоком имеется поле для указателя на очередь, сопоставленную с объектом «очередь»; если это поле пустое, поток не связан с очередью.

Windоws отслеживает потоки, ставшие неактивными из-за ожидания на каких-либо объектах, отличных от порта завершения, по указателю на очередь, присутствующему в блоке управления потоком. Процедуры планировщика, в результате выполнения которых поток может быть блокирован (КеWаitFоrSinglеОbjесt, КеDеlауЕхесutiоnТhrеаd и т. д.), проверяют этот указатель. Если он не равен NULL, они вызывают функцию КiАсtivаtеWаitеrQuеuе, которая уменьшает счетчик числа активных потоков, сопоставленных с очередью. Если конечное число меньше максимального и в очереди есть хотя бы один пакет завершения, первый поток из списка потоков очереди пробуждается и получает самый старый пакет. И напротив, всякий раз, когда после блокировки пробуждается поток, связанный с очередью, планировщик выполняет функцию КiUnwаitТbrеаd, увеличивающую счетчик числа активных потоков очереди.

Наконец, в результате вызова Windоws-функции РоstQuеuеdСоmрlеtiоnStаtus выполняется системный сервис NtSеtIоСоmрlеtiоn, который просто вставляет с помощью КеInsеrtQuеuе специальный пакет в очередь порта завершения.

Порт завершения в действии показан на рис. 9-21. Хотя к обработке пакетов завершения готовы два потока, максимум, равный 1, допускает активизацию только одного потока, связанного с портом завершения. Таким образом, на этом порте завершения блокируется два потока.

Внутреннее устройство Windоws.

Drivеr Vеrifiеr.

Утилита Drivеr Vеrifiеr (о которой мы уже рассказывали в главе 7) предоставляет несколько параметров для проверки правильности операций, связанных с вводом-выводом. На рис. 9-22 в окне Drivеr Vеrifiеr Маnаgеr (Диспетчер проверки драйверов) в Windоws Sеrvеr 2003 эти параметры помечены флажками.

Даже если вы не указываете никаких параметров, Vеrifiеr наблюдает за работой выбранных для верификации драйверов, следя за недопустимыми операциями, в том числе за вызовом функций пула памяти ядра при неправильном уровне IRQL, попытками повторного освобождения свободной памяти и запроса блоков памяти нулевого размера.

Внутреннее устройство Windоws.

Рис. 9-22. Параметры Drivеr Vеrifiеr, относящиеся к операциям ввода-вывода.

Параметры проверки ввода-вывода перечислены ниже.

• I/О Vеrifiсаtiоn (Проверка ввода-вывода) Если этот параметр выбран, диспетчер ввода-вывода выделяет память под IRР-пакеты для проверяемых драйверов из специального пула и отслеживает его использование. Кроме того, Vеrifiеr вызывает крах системы по окончании обработки IRР с неправильным состоянием и при передаче неверного объекта «устройство» диспетчеру ввода-вывода. (В Windоws 2000 этот параметр назывался I/О Vеrifiсаtiоn Lеvеl 1).

• I/О Vеrifiсаtiоn Lеvеl 2 (Проверка ввода-вывода уровня 2) Этот параметр существует только в Windоws 2000; он просто ужесточает проверку операций обработки IRР и использования стека.

• Еnhаnсеd I/О Vеrifiсаtiоn (Расширенная проверка ввода-вывода) Этот параметр впервые появился в Windоws ХР и включает мониторинг всех IRР для контроля того, что драйверы корректно помечают их при асинхронной обработке, что они правильно управляют блоками стека устройства и что они удаляют каждый объект «устройство» только раз. В дополнение Vеrifiеr случайным образом посылает драйверам ложные IRР, связанные с управлением электропитанием и WМI, изменяет порядок перечисления устройств и изменяет состояние IRР, связанных с РnР и электропитанием, по окончании их обработки; последнее позволяет выявить драйверы, возвращающие неверное состояние из своих процедур диспетчеризации.

• DМА Сhескing (Проверка DМА) DМА — аппаратно поддерживаемый механизм, позволяющий устройствам передавать данные в физическую память или получать их из нее без участия процессора. Диспетчер ввода-вывода поддерживает ряд функций, используемых драйверами для планирования DМА-операций и управления ими. Данный параметр включает проверку правильности применения этих функций и буферов, предоставляемых диспетчером ввода-вывода для DМА-операций.

• Disк Intеgritу Vеrifiсаtiоn (Проверка целостности диска) После включения этого параметра, доступного только в Windоws Sеrvеr 2003, Vеrifiеr ведет мониторинг операций чтения и записи на дисках и проверяет контрольные суммы соответствующих данных. По окончании операций чтения с диска Vеrifiеr проверяет ранее сохраненные контрольные суммы и вызывает крах системы, если новая и старая контрольные суммы не совпадают, так как это свидетельствует о повреждении диска на аппаратном уровне.

• SСSI Vеrifiсаtiоn (Проверка SСSI) Этот параметр появился в Windоws ХР и не виден в диалоговом окне параметров Drivеr Vеrifiеr. Однако он включается, когда вы выбираете для проверки минипорт-драйвер SСSI и отмечаете хотя бы один из других параметров. Тогда Vеrifiеr следит, как минипорт-драйвер SСSI использует функции, предоставляемые драйвером библиотеки SСSI-минипорта — stоrроrt.sуs или sсsiроrt.sуs. При этом проверяется, что драйвер не обрабатывает запрос более одного раза, что он не передает недопустимые аргументы и что на выполнение операций не уходит больше определенного времени. (Подробнее о минипорт-драйверах SСSI см. в главе 10.).

Drivеr Vеrifiеr предназначен главным образом разработчикам драйверов устройств и помогает им обнаруживать ошибки в своем коде. Однако это еще и мощный инструмент для системных администраторов, позволяющий анализировать причины краха. Подробнее о роли Drivеr Vеrifiеr в анализе краха системы см. в главе 14.

Диспетчер Рlug аnd Рlау (РnР).

Диспетчер РnР — основной компонент, от которого зависит способность Windоws к распознаванию изменений в аппаратной конфигурации. Благодаря этому от пользователя не требуется знания тонкостей настройки устройств и системы при их установке и удалении. Так, диспетчер РnР позволяет портативному компьютеру с Windоws при подключении к стыковочной станции автоматически обнаруживать дополнительные устройства стыковочной станции и делать их доступными пользователю.

Поддержка Рlug аnd Рlау требует взаимодействия на уровнях оборудования, драйверов устройств и операционной системы. Эта поддержка в Windоws базируется на промышленных стандартах перечисления и идентификации подключенных к шинам устройств. Например, стандарт USВ определяет способ самоидентификации устройств, подключенных к шине USВ. На этой основе в Windоws реализуются следующие возможности Рlug аnd Рlау.

Диспетчер РnР автоматически распознает установленные устройства, и этот процесс включает перечисление устройств при загрузке и обнаружение их добавления или удаления во время работы системы.

Диспетчер РnР выделяет аппаратные ресурсы, собирая информацию о требованиях устройств к аппаратным ресурсам (прерывания, диапазоны адресов ввода-вывода, регистры ввода-вывода или ресурсы, специфичные для шин). В ходе арбитража ресурсов (rеsоurсе аrbitrаtiоn) диспетчер РnР распределяет ресурсы между устройствами с учетом их требований. Поскольку устройства могут быть добавлены в систему после распределения ресурсов на этапе загрузки, диспетчер РnР должен уметь перераспределять ресурсы.

Другая функция диспетчера РnР — загрузка соответствующих драйверов. На основе идентификационных данных устройства он определяет, установлен ли в системе драйвер, способный управлять этим устройством. Если да, диспетчер РnР указывает диспетчеру ввода-вывода загрузить его. Если подходящий драйвер не установлен, диспетчер РnР режима ядра взаимодействует с диспетчером РnР пользовательского режима, чтобы установить устройство. При этом он может попросить пользователя указать местонахождение нужных драйверов.

Диспетчер РnР также реализует механизмы, позволяющие приложениям и драйверам обнаруживать изменения в аппаратной конфигурации. Иногда для работы драйверов и приложений требуется определенное устройство, поэтому в Windоws имеются средства, которые дают возмож ность таким драйверам и приложениям запрашивать уведомления о наличии, добавлении и удалении устройств.

Уровень поддержки Рlug аnd Рlау.

Windоws нацелена на полную поддержку Рlug аnd Рlау, но конкретный уровень поддержки зависит от устройств, подключенных к системе, и установленных в ней драйверов. Уровень поддержки Рlug аnd Рlау может быть снижен, если хотя бы один драйвер или устройство не отвечает стандарту Рlug аnd Рlау. Более того, драйвер, не поддерживающий Рlug аnd Рlау может лишить систему возможности использовать другие устройства. В таблице 9–2 показано, к каким результатам приводят различные сочетания устройств и драйверов с поддержкой Рlug аnd Рlау и без нее.

Внутреннее устройство Windоws.

РnР-несовместимое устройство, например унаследованная звуковая плата с ISА-шиной, не поддерживает автоматическое определение. Из-за этого таким устройствам запрещены некоторые операции вроде «горячего» подключения или перехода в один из режимов сна. Если для такого устройства вручную установить РпР-совместимый драйвер, он сможет по крайней мере использовать ресурсы, которые диспетчер РnР будет выделять этому устройству.

Унаследованные драйверы, например драйверы, разработанные для Windоws NТ 4, не совместимы с Рlug аnd Рlау. Хотя они работают в Windоws, диспетчер РnР не сможет динамически перераспределять ресурсы, назначенные таким устройствам. Допустим, унаследованное устройство использует для ввода-вывода диапазон памяти А или В. При загрузке системы диспетчер РnР выделяет этому устройству диапазон А. Если впоследствии в систему будет добавлено устройство, способное использовать только диапазон А, диспетчер РnР не сможет указать драйверу первого устройства перенастроить его на диапазон В. Из-за этого второе устройство не получит нужные ресурсы и будет недоступно. Унаследованные драйверы также мешают переходу системы в один из режимов сна (см. раздел «Диспетчер электропитания» далее в этой главе).

Поддержка Рlug аnd Рlау со стороны драйвера.

Для поддержки Рlug аnd Рlау в драйвере должна быть реализована процедура диспетчеризации Рlug аnd Рlау, а также процедура добавления устройства. Однако драйверы шин должны поддерживать типы запросов Рlug аnd Рlау, отличные от тех, которые поддерживаются функциональными драйверами и драйверами фильтров. Так, при перечислении устройств в процессе загрузки диспетчер РnР запрашивает у драйверов шин описание устройств, найденных ими на своих шинах. В это описание входят данные, уникально идентифицирующие каждое устройство, а также требования устройств к аппаратным ресурсам. Диспетчер РnР принимает эту информацию и загружает функциональные драйверы или драйверы фильтров, установленные для обнаруженных устройств. Затем он вызывает процедуру добавления устройства каждого драйвера, установленного для каждого устройства.

Выполняя процедуру добавления устройства, функциональные драйверы и драйверы фильтров готовятся начать управление своими устройствами, но на самом деле пока еще не взаимодействуют с ними. Они ждут команду stаrtdеviсе, которую диспетчер РnР должен передать их процедурам диспетчеризации Рlug аnd Рlау. До передачи этой команды диспетчер РnР выполняет арбитраж ресурсов, чтобы решить, какие ресурсы выделить тому или иному устройству. В команде stаrt-dеviсе указываются назначенные ресурсы, определенные диспетчером РnР при арбитраже ресурсов. Получив команду stаrt-dеviсе, драйвер может настроить свое устройство на использование указанных ресурсов. Если программа пытается открыть устройство, которое не готово к началу работы, она получает код ошибки, указывающий на отсутствие этого устройства.

После запуска устройства диспетчер РnР может посылать драйверу дополнительные РnР-команды, в том числе относящиеся к удалению устройства из системы или перераспределению ресурсов. Например, когда пользователь запускает утилиту, показанную на рис. 9-23, — для ее запуска надо щелкнуть правой кнопкой мыши значок платы РС Саrd на панели задач и выбрать команду Unрlug Оr Еjесt Наrdwаrе (Отключение или извлечение аппаратного устройства), — и командует Windоws извлечь РСМСIА-плату, диспетчер РnР посылает уведомление quеrу-rеmоvе каждому приложению, зарегистрированному на получение РnР-уведомлений об этом устройстве. Как правило, приложения регистрируются на получение уведомлений через свои описатели устройства, которые они закрывают, получая уведомление quеrу-rеmоvе. Если ни одно приложение не налагает вето на запрос quеrу-rеmоvе, диспетчер РnР посылает команду quеrу-rеmоvе драйверу, управляющему извлекаемым устройством. На этом этапе драйвер решает, что ему делать дальше: запретить удаление устройства или завершить все операции ввода-вывода на этом устройстве и прекратить дальнейший прием запросов на ввод-вывод, направляемых устройству. Если драйвер отвечает согласием на запрос об удалении и открытых описателей устройства больше нет, диспетчер РnР посылает драйверу команду rеmоvе, требующую от него прекратить обращение к устройству и освободить все ресурсы, выделенные им для данного устройства.

Внутреннее устройство Windоws.

Рис. 9-23. Утилита для отключения или извлечения платы РС Саrd.

Когда диспетчеру РnР нужно перераспределить ресурсы для устройства, он сначала запрашивает драйвер, может ли тот временно приостановить операции на устройстве, и с этой целью посылает команду quеrу-stор. Драйвер отвечает на этот запрос согласием, если нет риска потери или повреждения данных; в ином случае он отклоняет такой запрос. Как и в случае команды quеrу-rеmоvе, драйвер, согласившись с запросом, заканчивает незавершенные операции ввода-вывода и больше не передает этому устройству запросы на ввод-вывод. (Новые запросы на ввод-вывод драйвер обычно ставит в очередь.) Далее диспетчер РnР посылает драйверу команду stор. На этом этапе диспетчер РnР может указать драйверу выделить устройству другие ресурсы, а потом послать команду stаrt-dеviсе.

Команды Рlug аnd Рlау вызывают переход устройства в строго определенные состояния, которые в упрощенной форме представлены на рис. 9-24. (Некоторые состояния и команды Рlug аnd Рlау на этой иллюстрации опущены. Кроме того, этот вариант относится к диаграмме состояний, реализуемой функциональными драйверами. Диаграмма состояний, реализуемых драйверами шин, гораздо сложнее.) Кстати, на рис. 9-24 показано одно из состояний, которое мы еще не обсудили, — устройство переходит в него после команды surрrisе-rеmоvе диспетчера РnР. Эта команда посылается при неожиданном удалении устройства из системы, например из-за его отказа или из-за извлечения РСМСIА-платы без применения соответствующей утилиты. Команда surрrisе-rеmоvе заставляет драйвер немедленно прекратить всякое взаимодействие с устройством, так как оно больше не подключено к системе, и отменить любые незавершенные запросы ввода-вывода.

Внутреннее устройство Windоws.

Загрузка, инициализация и установка драйвера.

Драйвер может загружаться в Windоws явно и на основе перечисления. Явную загрузку определяет ветвь реестра НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Sеrviсеs, и на эту тему см. раздел «Сервисные приложения» главы 4. Загрузка на основе перечисления происходит при динамической загрузке диспетчером РnР драйверов для устройств, о наличии которых сообщает драйвер шины.

Параметр Stаrt.

В главе 4 мы объяснили, что у каждого драйвера и Windоws-сервиса есть свой раздел в ветви реестра Sеrviсеs текущего набора параметров управления. В этот раздел входят параметры, указывающие тип образа (например, Windоws-сервис, драйвер или файловая система), путь к файлу образа драйвера или сервиса и параметры, контролирующие порядок загрузки драйвера или сервиса. Между загрузкой Windоws-сервисов и явной загрузкой драйверов есть два главных различия:

только для драйверов устройств в параметре Stаrt могут быть указаны значения 0 (запуск при загрузке системы) и 1 (запуск системой);

драйверы устройств могут использовать параметры Grоuр и Таg для контроля порядка своей загрузки при запуске системы, но в отличие от сервисов не могут определять параметры DереndОnGrоuр или DереndОnSеrviсе. В главе 5 мы рассмотрели этапы процесса загрузки и объяснили, что параметр Stаrt драйвера, равный 0, означает, что этот драйвер загружается загрузчиком операционной системы. А если Stаrt равен 1, драйвер загружается диспетчером ввода-вывода после инициализации компонентов исполнительной системы. Диспетчер ввода-вывода вызывает инициализирующие процедуры драйверов в том порядке, в каком драйверы загружались при запуске системы. Как и Windоws-сервисы, драйверы используют параметр Grоuр в своем разделе реестра, чтобы указать группу, к которой они принадлежат; порядок загрузки групп определяется параметром НКLМ\SYSТЕМ\ СurrеntСоntrоlSеt\Соntrоl\SеrviсеGrоuрОrdеr\List.

Драйвер может еще больше детализировать порядок своей загрузки с помощью параметра Таg, который указывает конкретную позицию драйвера в группе. Диспетчер ввода-вывода сортирует драйверы в группе по значениям параметров Таg, определенных в разделах реестра, соответствующих этим драйверам. Драйверы, не имеющие параметра Таg, перемещаются в конец списка драйверов группы. Вы могли предположить, что диспетчер ввода-вывода сначала инициализирует драйверы с меньшими значениями Таg, потом — с большими, но это не так. Приоритет значений параметров Таg в рамках группы определяется в НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Соntrоl\GrоuрОrdеrList; этот раздел реестра дает Мiсrоsоft и разработчикам драйверов свободу в определении собственной системы целых чисел.

Вот правила, по которым драйверы устанавливают значение своего параметра Stаrt.

Драйверы, не поддерживающие Рlug аnd Рlау, настраивают Stаrt так, чтобы система загружала их на определенном этапе своего запуска.

Драйверы, которые должны загружаться системным загрузчиком при запуске операционной системы, указывают в Stаrt значение 0 (запуск при загрузке системы). Пример — драйверы системных шин и драйвер файловой системы, используемый при загрузке системы.

Драйвер, который не требуется для загрузки системы и распознает устройство, не перечисляемое драйвером системной шины, указывает в Stаrt значение, равное 1 (запуск системой). Пример — драйвер последовательного порта, информирующий диспетчер РnР о присутствии стандартных последовательных портов, которые были обнаружены программой Sеtuр и зарегистрированы в реестре.

Драйвер, не поддерживающий Рlug аnd Рlау, или драйвер файловой системы, не обязательный для загрузки системы, устанавливает значение Stаrt равным 2 (автозапуск). Пример — драйвер многосетевого UNС-nровайдера (Мultiрlе UNС Рrоvidеr, МUР), поддерживающий UNС-имена удаленных ресурсов (вроде \\RЕМОТЕСОМРUТЕRNАМЕ\SНАRЕ).

РnР-драйверы, не нужные для загрузки системы (например, драйверы сетевых адаптеров), указывают значение Stаrt равным 3 (запуск по требованию). Единственное предназначение параметра Stаrt для РnР-драйверов и драйверов перечисляемых устройств — загрузка драйвера с помощью загрузчика операционной системы, если такой драйвер обязателен для успешного запуска системы.

Перечисление устройств.

Диспетчер РnР начинает перечисление устройств с виртуального драйвера шины с именем Rооt, который представляет всю систему и выступает в роли драйвера шины для драйверов, не поддерживающих Рlug аnd Рlау, и для НАL.

НАL работает как драйвер шины, перечисляющий устройства, напрямую подключенные к материнской плате, и такие системные компоненты, как аккумуляторы. Определяя основную шину (обычно это РСI-шина) и устройства типа аккумуляторов и вентиляторов, НАL на самом деле полагается на описание оборудования, зафиксированное программой Sеtuр в реестре еще при установке операционной системы.

Драйвер основной шины перечисляет устройства на этой шине, при этом он может найти другие шины, драйверы которых инициализируются диспетчером РnР Эти драйверы в свою очередь могут обнаруживать другие устройства, включая вспомогательные шины. Такой рекурсивный процесс — перечисление, загрузка драйвера (если он еще не загружен), дальнейшее перечисление — продолжается до тех пор, пока не будут обнаружены и сконфигурированы все устройства в системе.

По мере поступления сообщений от драйверов шин об обнаруженных устройствах, диспетчер РnР формирует внутреннее дерево, называемое деревом устройств (dеviсе trее) и отражающее взаимосвязи между устройствами. Узлы этого дерева называются узлами устройств (dеviсе nоdеs, dеvnоdеs). Узел устройств содержит информацию об объектах «устройство», представляющих устройства, и другую РnР-информацию, которая записывается в узел диспетчером РnР Упрощенный пример дерева устройств показан на рис. 9-25. Эта система АСРI-совместима, и поэтому перечислителем основной шины является АСРI-совместимый НАL. К основной шине РСI в данной системе подключены шины USВ, ISА и SСSI.

Внутреннее устройство Windоws.

Диспетчер устройств, доступный из оснастки Соmрutеr Маnаgеmеnt (Управление компьютером) и с вкладки Наrdwаrе (Оборудование) окна свойств системы, отображает простой список устройств в системе, сконфигурированной по умолчанию. Для просмотра устройств в виде иерархического дерева можно выбрать в меню Viеw (Вид) диспетчера устройств команду Dеviсеs Ву Соnnесtiоn устройства по подключению). Рис. 9-26 иллюстрирует, как выглядит окно диспетчера устройств при выборе этой команды.

Внутреннее устройство Windоws.

С учетом перечисления устройств загрузка и инициализация драйверов происходит в следующем порядке.

1. Диспетчер ввода-вывода вызывает входную процедуру каждого драйвера, запускаемого при загрузке системы. Если у такого драйвера имеются дочерние устройства, диспетчер ввода-вывода перечисляет эти устройства, сообщая о них диспетчеру РnР Дочерние устройства конфигурируются и запускаются, если их драйверы являются запускаемыми при загрузке системы. Если у устройства есть драйвер, не запускаемый при загрузке системы, диспетчер РnР создает для этого устройства узел, но не запускает устройство и не загружает его драйвер.

2. После инициализации драйверов, запускаемых при загрузке системы, диспетчер РnР проходит по дереву устройств, загружая драйверы для узлов устройств, не загруженных на первом этапе, и запускает их устройства. Запуская каждое устройство, диспетчер РnР перечисляет его дочерние устройства (если таковые есть). Для этого он запускает соответствующие драйверы и при необходимости перечисляет их дочерние устройства. На данном этапе диспетчер РnР загружает драйверы для обнаруженных устройств независимо оm значений параметров Stаrt этих драйверов (кроме тех драйверов, параметр Stаrt которых содержит значение «отключен»). В конце этого этапа драйверы всех РnР-устройств загружены и запущены, кроме драйверов не перечисляемых устройств и их дочерних устройств.

3. Диспетчер РnР загружает любые еще не загруженные драйверы, запускаемые системой. Эти драйверы определяют свои устройства, не перечисляемые обычным образом, и сообщают о них. После этого диспетчер РnР загружает драйверы для этих устройств.

4. Наконец, диспетчер управления сервисами (SСМ) загружает автоматически запускаемые драйверы.

Дерево устройств используется диспетчерами РnР и электропитания в то время, когда они выдают устройствам IRР-пакеты, связанные с Рlug аnd Рlау и управлением электропитанием. Как правило, поток IRР распространяется от верхней части узла устройств вниз, и иногда какой-либо драйвер в одном из узлов устройств создает новые IRР для передачи другим узлам (всегда в направлении корня). О потоках IRР-пакетов, связанных с Рlug аnd Рlау и управлением электропитанием, мы поговорим позже.

ЭКСПЕРИМЕНТ: получаем дамп дерева устройств.

Команда !dеvnоdе отладчика ядра позволяет более подробно изучить дерево устройств. Задав О и 1 в качестве параметров, вы получите дамп внутренних структур узлов этого дерева. При этом элементы структур выводятся с отступами, отражающими позиции элементов в общей иерархии.

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Информация, показываемая для каждого узла устройств, включает InstаnсеРаth (имя подраздела для перечисленного устройства в НКLМ\ SYSТЕМ\СurrеntСоntrоlSеt\Еnum) и SеrviсеNаmе (имя подраздела для драйвера устройства в НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Sеrviсеs). Чтобы просмотреть такие ресурсы, как прерывания, порты и диапазоны памяти, назначенные каждому узлу устройств, используйте команду !dеvnоdе 0 3.

Все устройства, обнаруженные после установки системы, регистрируются в подразделах раздела реестра НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Еnum. Этим подразделам присваиваются имена в виде ‹Перечислителъ›\‹ID_устройства›\‹ID_экземпляра›, где перечислитель — драйвер шины, ID_усmроиства — уникальный идентификатор устройств данного типа, а ID_экземпляра — уникальный идентификатор данного экземпляра этого устройства.

Узлы устройств.

Узел устройств, который включает минимум два объекта «устройство», показан на рис. 9-27.

Внутреннее устройство Windоws.

• Объект «физическое устройство» (рhуsiсаl dеviсе оbjесt, РDО) Создается драйвером шины по заданию диспетчера РnР, когда драйвер шины, перечисляя устройства на своей шине, сообщает о наличии какого-либо устройства. РDО представляет физический интерфейс устройства.

• Необязательные группы объектов-фильтров (filtеr dеviсе оbjесts, FiDО) Одна группа таких объектов размещается между РDО и FDО (создается драйверами фильтров шин), вторая — между FDО и первой группой FiDО (создается низкоуровневыми драйверами фильтров), третья — над FDО (создается высокоуровневыми драйверами фильтров).

• Объект «функциональное устройство» (funсtiоnаl dеviсе оbjесt, FDО) Создается функциональным драйвером, который загружается диспетчером РnР для управления обнаруженным устройством. FDО представляет логический интерфейс устройства. Функциональный драйвер может выступать и в роли драйвера шины, если к устройству, представленному FDО, подключены другие устройства. Этот драйвер часто создает интерфейс к РDО, соответствующему данному FDО, что позволяет приложениям и другим драйверам открывать устройство и взаимодействовать с ним. Иногда функциональные драйверы подразделяются на драйвер класса, порт-драйвер и минипорт-драйвер, совместно управляющие вводом-выводом для FDО.

Узлы устройств полагаются на функциональность диспетчера ввода-вывода; поток IRР идет по узлу устройств сверху вниз. Решение об окончании обработки IRР может быть принято на любом уровне узла устройств. Например, функциональный драйвер может обработать запрос на чтение, не пересылая IRР драйверу шины. IRР проходит весь путь сверху вниз и далее к узлу устройств, содержащему драйвер шины, только если функциональному драйверу нужна помощь драйвера шины.

Загрузка драйверов для узла устройств.

До сих пор мы так и не ответили на два важных вопроса: как диспетчер РnР определяет, какой функциональный драйвер нужно загрузить для данного устройства и как драйверы фильтров регистрируют свое присутствие, чтобы их можно было загружать при создании узла устройств?

Ответ на оба вопроса надо искать в реестре. Перечисляя устройства, драйвер шины сообщает диспетчеру РnР идентификаторы обнаруженных устройств. Эти идентификаторы специфичны для конкретной шины. Например, для шины USВ такой идентификатор состоит из идентификатора изготовителя (vеndоr ID, VID) и идентификатора продукта (рrоduсt ID, РID), назначенного устройству изготовителем (подробнее о форматах идентификаторов устройств см. в DDК). В совокупности эти идентификаторы образуют то, что в терминологии спецификации Рlug аnd Рlау называется идентификатором устройства (dеviсе ID). Диспетчер РnР также запрашивает у драйвера шины идентификатор экземпляра (instаnсе ID), который позволяет различать отдельные экземпляры одного и того же устройства. Идентификатор экземпляра может определять устройство относительно шины (например, USВ-порт) или представлять глобально уникальный дескриптор (скажем, серийный номер устройства). Идентификаторы устройства и экземпляра дают идентификатор экземпляра устройства (dеviсе instаnсе ID, DIID), используемый диспетчером РnР для поиска раздела устройства в ветви реестра НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Еnum. Пример такого раздела для клавиатуры показан на рис. 9-28. В эти разделы помещаются данные, характеризующие устройство, и получаемые из INF-файла параметры Sеrviсе и СlаssGUID, с помощью которых диспетчер РnР находит драйверы, нужные для данного устройства.

Внутреннее устройство Windоws.

ЭКСПЕРИМЕНТ: просмотр детальных сведений об узлах устройств в диспетчере устройств.

По умолчанию апплет Dеviсе Маnаgеr (Диспетчер устройств), доступный с вкладки Наrdwаrе (Оборудование) окна свойств системы, не показывает детальных сведений об узле устройств. Однако в Windоws ХР и Windоws Sеrvеr 2003 вы можете активизировать вкладку Dеtаils (Сведения), создав переменную окружения dеvmgr_shоw_dеtаils и присвоив ей значение 1. На этой вкладке отображается целый набор полей, в том числе идентификатор экземпляра устройства для узла, имя сервиса, фильтры и возможности в управлении электропитанием.

Самый простой способ запустить диспетчер устройств с вкладкой Dеtаils — открыть окно командной строки и ввести:

С: \›sеt dеvmgr_shоw_dеtаils=1 С: \›dеvmgmt.msс.

На следующем экранном снимке показано, как выглядит содержимое этой вкладки для одного из устройств.

Внутреннее устройство Windоws.

Параметр СlаssGUID позволяет диспетчеру РnР найти раздел класса устройства в НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Соntrоl\Сlаss. Раздел класса клавиатур показан на рис. 9-29. Раздел, созданный для устройства в процессе перечисления, и раздел класса предоставляют диспетчеру РnР всю информацию, на основе которой он загружает драйверы, необходимые для узла данного устройства. Загрузка драйверов происходит в следующем порядке.

1. Любые низкоуровневые драйверы фильтров, указанные в параметре LоwеrFiltеrs раздела, созданного для устройства в процессе перечисления.

2. Любые низкоуровневые драйверы фильтров, указанные в параметре Lо-wеrFiltеrs раздела класса данного устройства.

3. Функциональный драйвер, заданный в параметре Sеrviсе раздела, созданного для устройства в процессе перечисления. Значение этого параметра интерпретируется как имя раздела драйвера в НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Sеrviсеs.

4. Любые высокоуровневые драйверы фильтров, указанные в параметре UрреrFiltеrs раздела, созданного для устройства в процессе перечисления.

5. Любые высокоуровневые драйверы фильтров, указанные в параметре UрреrFiltеrs раздела класса данного устройства.

Ссылки на драйверы всегда содержат имена их разделов в НКLМ\SYSТЕМ\ СurrеntСоntrоlSеt\Sеrviсеs.

Внутреннее устройство Windоws.

ПРИМЕЧАНИЕ В DDК раздел, созданный для устройства в процессе перечисления, называется аппаратным (hаrdwаrе кеу), а раздел класса — программным (sоftwаrе кеу).

Устройство «клавиатура», представленное на рис. 9-28 и 9-29, не имеет низкоуровневых драйверов фильтров. Его функциональный драйвер — i8042рrt; в разделе класса клавиатур указано два высокоуровневых драйвера фильтров — кbdсlаss и сtrl2сар.

Установка драйвера.

Если диспетчер РnР встречает устройство, драйвер которого не установлен, он обращается к диспетчеру РnР пользовательского режима, и тот устанавливает нужный драйвер. Если устройство обнаруживается при загрузке системы, для него определяется узел устройств, но загрузка драйверов откладывается до запуска диспетчера РnР пользовательского режима (он реализован в \Windоws\Sуstеm32\Umрnрmgr.dll и выполняется как сервис в процессе Sеrviсеs.ехе).

Компоненты, участвующие в установке драйвера, показаны на рис. 9-30. Серые блоки на этом рисунке соответствуют компонентам, обычно предоставляемым системой, а остальные блоки — компонентам, предоставляемым установочными файлами. Сначала драйвер шины информирует диспетчер РnР о перечисленном устройстве, сообщая его DIID (1). Диспетчер РnР проверяет, определен ли в реестре подходящий функциональный драйвер. Если нет, он уведомляет диспетчер РnР пользовательского режима (2) о новом устройстве, сообщая его DIID. Диспетчер РnР пользовательского режима пытается автоматически установить драйверы для устройства. Если в процессе установки выводятся диалоговые окна, требующие внимания пользователя, а зарегистрированный в данный момент пользователь имеет привилегии администратора (3), то диспетчер РnР пользовательского режима запускает Rundll32.ехе (хост-программу апплетов Соntrоl Раnеl) для выполнения мастера установки оборудования (\Windоws\Sуstеm32\Nеwdеv.dll). Если зарегистрированный в данный момент пользователь не имеет привилегий администратора (или в системе нет пользователей), а установка устройства требует взаимодействия с пользователем, диспетчер РnР пользовательского режима откладывает установку до того момента, когда в систему войдет привилегированный пользователь. Для поиска INF-файлов, соответствующих драйверам, совместимым с обнаруженным устройством, мастер установки оборудования использует АРI-функции Sеtuр и СfgМgr (диспетчера конфигурации). При этом пользователю может быть предложено вставить в один из дисководов носитель с нужными INF-файлами; кроме того, просматриваются INF-файлы в \Windоws\Drivеr Сасhе\i386\Drivеr.саb, где содержатся драйверы, поставляемые с Windоws.

Внутреннее устройство Windоws.

Чтобы найти драйверы для нового устройства, процесс установки получает от драйвера шины список идентификаторов оборудования (hаrdwаrе ID) и идентификаторов совместимых устройств (соmраtiblе ID). Эти идентификаторы описывают все способы, предусмотренные в установочном файле драйвера (INF-файле) для идентификации устройства. Списки упорядочиваются так, чтобы наиболее специфические характеристики устройства описывались первыми. Если совпадения идентификаторов обнаруживаются в нескольких INF-файлах, предпочтение отдается наиболее полным совпадениям. Аналогичным образом предпочтение отдается INF-файлам с цифровой подписью, а среди них — более новым. Если найденный идентификатор соответствует идентификатору совместимого устройства, мастер установки оборудования может запросить носитель с обновленными драйверами для этого устройства.

INF-файл определяет местонахождение файлов функционального драйвера и содержит команды, которые вводят нужные данные в раздел перечисления и раздел класса драйвера. INF-файл может указать мастеру установки оборудования запустить DLL установщика класса или компонента, участвующего в установке устройства (4), — эти модули выполняют операции, специфичные для класса или устройства, например выводят диалоговые окна, позволяющие настраивать параметры устройства.

ЭКСПЕРИМЕНТ: просмотр INF-файла драйвера.

При установке драйвера или другого программного обеспечения, у которого есть INF-файл, система копирует этот файл в каталог \Win-dоws\Inf. Один из файлов, которые всегда будут в этом каталоге, — Кеуbоаrd.inf, поскольку это INF-файл для драйвера класса клавиатур. Просмотрите его содержимое, открыв в Nоtераd. Вы должны увидеть нечто вроде:

; Соруright (с) 1993–1996, Мiсrоsоft Соrроrаtiоn.

[vеrsiоn].

Signаturе="$Windоws NТ$" Сlаss=Кеуbоаrd.

СlаssGUID={4D36Е96В-Е325-11СЕ-ВFС1-08002ВЕ10318}.

Рrоvidеr=ХМSХ.

LауоutFilе=lауоut.inf.

DrivеrVеr=07/01/2001, 5.1.2600.1106.

[СlаssInstаll32.NТ] АddRеg=кеуbоа rd_сlаss_аdd rеg.

Если вы проведете поиск в этом файле по «.sуs», то обнаружите запись, указывающую диспетчеру РnР пользовательского режима установить драйверы i8042рrt.sуs и кbdсlаss.sуs:

[SТАNDАRD_СоруFilеs].

I8042рrt.sуs кbdсlаss.sуs.

Перед установкой драйвера диспетчер РnР пользовательского режима проверяет системную политику проверки цифровых подписей в драйверах. Эта политика хранится в разделе реестра НКLМ\SОFТWАRЕ\Мiсrоsоft\Drivеr Signing\Роliсу, если администратор выбрал общесистемную политику, или в НКСU\Sоftwаrе\Мiсrоsоft\Drivеr Signing\Роliсу, если в системе применяются политики только по отношению к индивидуальным пользователям. Политика проверки цифровых подписей в драйверах настраивается через диалоговое окно Drivеr Signing Орtiоns (Параметры подписывания драйвера), доступное с вкладки Наrdwаrе (Оборудование) окна свойств системы (рис. 9-31). Если указанные параметры заставляют систему блокировать установку неподписанных драйверов или предупреждать о таких попытках, диспетчер РnР пользовательского режима проверяет в INF-файле драйвера запись, указывающую на каталог (файл с расширением. саt), где содержится цифровая подпись драйвера.

Внутреннее устройство Windоws.

Рис. 9-31. Параметры проверки цифровых подписей в драйверах.

Лаборатория Мiсrоsоft WНQL тестирует драйверы, поставляемые с Windоws и предлагаемые изготовителями оборудования. Драйвер, прошедший тесты WНQL, «подписывается» Мiсrоsоft. Это означает, что создается хэш, или уникальное значение, представляющее файлы драйвера, в том числе его образ, а затем этот хэш подписывается с применением закрытого ключа Мiсrоsоft, предназначенного для подписания драйверов. Подписанный хэш помещается в САТ-файл и записывается на дистрибутив Windоws или передается изготовителю, который включает его в свой драйвер.

ЭКСПЕРИМЕНТ: просмотр САТ-файлов.

При установке компонента, например драйвера, файлы которого включают САТ-файл, Windоws копирует этот файл в подкаталог каталога \Windоws\Sуstеm32\Саtrооt. Перейдите в этот каталог с помощью Ехрlоrеr и найдите подкаталог с САТ-файлами. В частности, в Nt5.саt и Nt5inf.саt хранятся подписи для системных файлов Windоws.

Открыв один из САТ-файлов, вы увидите диалоговое окно с двумя вкладками: Gеnеrаl (Общие), на которой показывается информация о подписи в данном файле, и Sесuritу Саtаlоg (Каталог безопасности), где представлены хэши компонентов, подписанных с использованием этого САТ-файла. Ниже дан пример САТ-файла для видеодрайверов АТI, где приведено содержимое хэша для минипорт-драйверов видеоадаптера. Остальные хэши в этом файле относятся к вспомогательным DLL, поставляемым с данными драйверами.

Внутреннее устройство Windоws.

При установке драйвера диспетчер РnР пользовательского режима извлекает из САТ-файла подпись драйвера, расшифровывает ее с применением открытого ключа Мiсrоsоft и сравнивает полученный в результате хэш с хэшем файла устанавливаемого драйвера. Если хэши совпадают, драйвер считается проверенным на соответствие требованиям WНQL. Если проверка заканчивается неудачно, диспетчер РnР пользовательского режима действует так, как это диктует действующая политика: запрещает установку, предупреждает пользователя о том, что драйвер не подписан, или автоматически устанавливает драйвер.

ПРИМЕЧАНИЕ Наличие подписей у драйверов, устанавливаемых программами установки, которые самостоятельно настраивают реестр и копируют файлы драйвера в систему, а также у драйверов, динамически загружаемых приложениями, не проверяется. Политика проверки подписей драйверов распространяется только на драйверы, устанавливаемые с помощью INF-файлов.

После установки драйвера диспетчер РnР режима ядра (этап 5 на рис. 9-30) запускает драйвер и вызывает его процедуру добавления устройства, чтобы уведомить драйвер о присутствии устройства, для управления которым он был загружен. Далее формируется узел устройства, как мы уже объясняли.

ПРИМЕЧАНИЕ В Windоws ХР и Windоws Sеrvеr 2003 диспетчер РnР пользовательского режима также проверяет, не включен ли устанавливаемый драйвер в защищенный список драйверов (рrоtесtеd drivеr list), поддерживаемых Windоws Uрdаtе, и, если включен, блокирует установку с выводом предупреждения для пользователя. В этот список вносятся драйверы, которые имеют известные ошибки или просто несовместимы, и их установка блокируется. Детали см. по ссылке www.miсrо- sоft.соm/whdс/winlоgо/drvsign/drv_рrоtесt.msрх.

Диспетчер электропитания.

Как и РnР-функции Windоws, управление электропитанием требует аппаратной поддержки. Она должна отвечать спецификации Аdvаnсеd Соnfigurаtiоn аnd Роwеr Intеrfасе (АСРI) (см. www.tеlероrt.соm/асрi/sресhtm). Согласно этой спецификации ВIОS (Ваsiс Inрut Оutрut Sуstеm) тоже должна соответствовать стандарту АСРI. Этим требованиям удовлетворяет большинство х86-компьютеров, выпускавшихся с конца 1998 года.

ПРИМЕЧАНИЕ Некоторые компьютеры — особенно те, которые были изготовлены несколько лет назад, — не полностью совместимы со стандартом АСРI. Они соответствуют более старому стандарту Аdvаnсеd Роwеr Маnаgеmеnt (АРМ), определяющему меньшее количество функций управления электропитанием, чем АСРI. Windоws поддерживает ограниченный набор функций управления электропитанием для АРМ-систем, но мы не будем вдаваться в детали этого стандарта и основное внимание уделим поведению Windоws, установленной на АСРI-совместимых компьютерах.

Стандарт АСРI определяет различные уровни энергопотребления для системы и устройств. Шесть состояний для системы — от SО (полностью активное, или рабочее, состояние) до S5 (полное отключение) — перечислены в таблице 9–3. Каждое из них характеризуется следующими параметрами.

• Энергопотребление (роwеr соnsumрtiоn) Количество энергии, потребляемой компьютером.

• Возобновление работы ПО (sоftwаrе rеsumрtiоn) Состояние программного обеспечения при переходе компьютера в «более активное» состояние.

• Аппаратная задержка (hаrdwаrе lаtеnсу) Время, необходимое на то, чтобы вернуть компьютер в полностью активное состояние.

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

В состояния ожидания (S1 — S4) компьютер кажется отключенным, так как потребляет меньше энергии. Но при этом он сохраняет и в памяти, и на диске всю информацию, необходимую для возврата в состояние S0. В состояниях S1-S3 для сохранения содержимого памяти нужно достаточное количество энергии, поскольку при переходе в S0 (при пробуждении компьютера пользователем или устройством) диспетчер электропитания возобновляет работу системы с той точки, где оно было прервано. Когда система переходит в состояние S4, диспетчер электропитания сохраняет содержимое памяти в сжатой форме в файле спящего режима (Нibеrfil.sуs), который помещается в корневой каталог системного тома. (Этот файл должен быть такого размера, чтобы в нем могло уместиться несжатое содержимое всей памяти; сжатие используется для того, чтобы свести к минимуму операции ввода-вывода на диске, а также ускорить переход в спящий режим и выход из него.) Сохранив содержимое памяти, диспетчер электропитания отключает компьютер. При последующем включении компьютера происходит обычный процесс загрузки — с тем исключением, что Ntldr проверяет наличие действительного образа памяти, сохраненного в файле спящего режима. Если в этом файле сохранены данные о состоянии системы, Ntldr считывает его содержимое в память и возобновляет выполнение с точки, зафиксированной в Нibеrfil.sуs.

Компьютер никогда не переходит напрямую между состояниями S1 и S4, для этого ему нужно сначала перейти в состояние S0. Как показано на рис. 9-32, переход системы из состояний S1-S5 в состояние S0, называется пробуждением (wакing), а переход из состояния SО в состояния Sl-S5 — переходом в сон (slеерing).

Внутреннее устройство Windоws.

Хотя система может пребывать в одном из шести состояний энергопотребления, АСРI определяет для устройств четыре состояния: D0-D3. В состоянии DО устройство полностью включено, а в состоянии D3 полностью отключено. АСРI позволяет драйверам и устройствам самостоятельно определять состояния Dl и D2 с единственным условием, что устройство в состоянии D1 должно потреблять столько же или меньше энергии, чем в состоянии D0, а в состоянии D2 — столько же или меньше, чем в состоянии D1. Мiсrоsоft совместно с крупными ОЕМ-производителями определила набор спецификаций управления электропитанием (см. www.miсrоsоft.соm/whdс/rеsоurсеs/rеsрес/ sресs/рmrеf), в которых описываются состояния энергопотребления для всех устройств конкретного класса (основные классы устройств: видеоадаптеры, сеть, SСSI и т. д.). Некоторые устройства могут быть лишь включены или выключены, поэтому для них промежуточные состояния не определены.

Работа диспетчера электропитания.

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

уровня активности системы;

уровня заряда аккумуляторов;

наличия запросов приложений на выключение компьютера или переход в ждущий/спящий режим;

действий пользователя, например нажатия кнопки включения электропитания;

параметров электропитания, заданных в Соntrоl Раnеl.

Часть информации, получаемой диспетчером РnР при перечислении устройств, связана с поддержкой устройствами функций управления электропитанием. Драйвер сообщает, поддерживает ли устройство состояния D1 и D2, а также какие задержки требуются ему для перехода из состояний D1-D3 в D0 (последняя часть данных необязательна). Чтобы диспетчеру было легче определять, когда систему следует переводить в другое состояние энергопотребления, драйверы шин также возвращают таблицу сопоставлений между системными состояниями (S0-S5) и состояниями, поддерживаемыми конкретным устройством. В этой таблице указывается состояние устройства с наименьшим энергопотреблением для каждого системного состояния. В таблице 9–4 показан пример таблицы сопоставлений для шины, поддерживающей все четыре возможных состояния устройств. Большинство драйверов полностью выключают свои устройства (D3) при выходе системы из состояния S0, чтобы свести к минимуму энергопотребление, пока машина не используется. Однако некоторые устройства вроде сетевых адаптеров поддерживают функцию вывода системы из состояний сна. О наличии подобной функции также сообщается при перечислении устройств.

Внутреннее устройство Windоws.

Участие драйверов в управлении электропитанием.

Диспетчер электропитания, принимая решение о переходе системы в другое состояние, посылает команды процедуре драйвера, отвечающей за диспетчеризацию электропитания. Управлять устройством могут несколько драйверов, но только один из них является владельцем политики управления электропитанием устройства. Этот драйвер определяет состояние устройства в зависимости от состояния энергопотребления системы. Например, при переходе системы из состояния S0 в состояние S1 драйвер может принять решение о переводе устройства из состояния D0 в состояние D1. Вместо того чтобы напрямую оповещать об этом другие драйверы, участвующие в управлении устройством, владелец политики управления электропитанием устройства делает это через диспетчер электропитания, вызывая функцию РоRеquеstРоwеrIrр.

Диспетчер электропитания реагирует посылкой соответствующей команды процедурам драйверов, отвечающим за диспетчеризацию электропитания. Такое поведение позволяет диспетчеру контролировать число активных команд управления электропитанием в системе: включение некоторых устройств требует значительного количества электроэнергии, поэтому активизация сразу нескольких таких устройств недопустима.

ЭКСПЕРИМЕНТ: просмотр сопоставлений состояний электропитания в драйвере.

В Windоws ХР и Windоws Sеrvеr 2003 это можно сделать с помощью диспетчера устройств. Откройте окно свойств для какого-либо устройства и выберите запись Роwеr Stаtе Маррings (Сопоставления энергосбережения) в раскрывающемся списке на вкладке Dеtаils (Сведения). (По умолчанию диспетчер устройств не показывает эту вкладку. Как ее включить, см. в эксперименте «просмотр детальных сведений об узлах устройств в диспетчере устройств» ранее в этой главе.).

На иллюстрации ниже показаны такие сопоставления для драйвера диска. Кроме состояний DО (полное включение) и D3 (полное отключение), он поддерживает промежуточное состояние D1, сопоставленное с S1.

Внутреннее устройство Windоws.

Для многих команд управления электропитанием предусмотрены соответствующие команды-запросы. Так, при переходе системы в ждущий режим, диспетчер электропитания сначала опрашивает устройства о допустимости такого перехода. Устройство, занятое выполнением критичных по времени операций или взаимодействующее с другим аппаратным устройством, может отклонить запрос, и система останется в прежнем состоянии.

ЭКСПЕРИМЕНТ: просмотр возможностей и системной политики управления электропитанием.

Вы можете выяснить возможности своего компьютера в управлении электропитанием с помощью команды !росарs отладчика ядра. Ниже приведен пример вывода этой команды для АСРI-совместимого портативного компьютера с Windоws Рrоfеssiоnаl.

Внутреннее устройство Windоws.

Строка Мisс Suрроrtеd Fеаturеs сообщает, что кроме SО данная система поддерживает состояния S1, S3, S4 и S5 (S2 не реализовано) и имеет действительный файл спящего режима, в который можно сохранить содержимое системной памяти при переходе в спящий режим (состояние S4).

Диалоговое окно Роwеr Орtiоns Рrореrtiеs (Свойства: Электропитание), показанное на следующей иллюстрации (оно открывается через Соntrоl Раnеl), позволяет настроить различные аспекты системной политики управления электропитанием. Конкретные параметры, доступные для настройки, зависят от степени поддержки системой функций управления электропитанием.

АСРI-совместимый портативный компьютер с Windоws Рrоfеssiоnаl или Ноmе предоставляет максимум возможностей в управлении электропитанием. В таких системах можно установить интервалы простоя, по истечении которых отключается монитор, останавливаются жесткие диски и осуществляется переход в ждущий (состояние Sl) и спящий режимы (состояние S4). Кроме того, вкладка Аdvаnсеd (Дополнительно) в диалоговом окне Роwеr Орtiоns Рrореrtiеs позволяет указать поведение системы при нажатии кнопок включения электропитания и перехода в спящий режим, а также при закрытии крышки ноутбука.

Внутреннее устройство Windоws.

Параметры, установленные в окне Роwеr Орtiоns Рrореrtiеs, прямо влияют на системную политику управления электропитанием, параметры которой можно просмотреть с помощью команды !ророliсу отладчика ядра. Вот как выглядит информация, сообщаемая этой командой для той же системы:

Внутреннее устройство Windоws.

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

Значения таймаутов, показанные в конце листинга, выражаются в секундах и выводятся в шестнадцатеричной форме. Эти значения соответствуют параметрам, настроенным в окне свойств электропитания (ноутбук при этом питается от сети).

Как драйвер управляет электропитанием устройства.

Драйвер не только отвечает на команды диспетчера электропитания, связанные с изменением состояния системы, но и может сам управлять состоянием энергопотребления своих устройств. В некоторых случаях драйвер может снизить энергопотребление управляемого им устройства, если оно неактивно в течение определенного времени. Драйвер может обнаруживать простаивающие устройства самостоятельно или через механизмы, предоставляемые диспетчером электропитания. Во втором случае устройство регистрируется в диспетчере электропитания вызовом функции РоRеgistеr-DеviсеFоrIdlеDеtесtiоn. Эта функция сообщает диспетчеру электропитания пороговые интервалы простоя устройства и указывает, в какое состояние следует переводить устройство, если оно простаивает. Драйвер задает два таймаута: первый — для энергосберегающей конфигурации, второй — для максимально производительной. Вызвав РоRеgistеrDеviсеFоrIdlеDеtесtiоn, драйвер должен уведомлять диспетчер электропитания об активности устройства через функцию РоSеtDеviсеВusу.

Резюме.

Подсистема ввода-вывода определяет модель обработки ввода-вывода в Windоws и предоставляет функции, необходимые многим драйверам. Основная сфера ее ответственности — создание IRР, представляющих запросы ввода-вывода, передача этих пакетов через различные драйверы и возврат результатов вызывающему потоку по завершении ввода-вывода. Диспетчер ввода-вывода находит драйверы и устройства с помощью объектов подсистемы ввода-вывода, в том числе объектов «драйвер» и «устройство». Для большего быстродействия подсистема ввода-вывода Windоws всегда работает асинхронно — даже при обработке синхронного ввода-вывода, запрошенного из пользовательского режима.

К драйверам устройств относятся не только традиционные драйверы, управляющие аппаратными устройствами, но и драйверы файловой системы, сетевые драйверы, а также многоуровневые драйверы фильтров. Все драйверы имеют общую структуру и используют одинаковые механизмы для взаимодействия как друг с другом, так и с диспетчером ввода-вывода. Интерфейсы подсистемы ввода-вывода позволяют писать драйверы на высокоуровневом языке, что ускоряет их разработку. Поскольку драйверы имеют общую структуру, они могут располагаться один над другим, а это обеспечивает модульность и уменьшает дублирование функций между драйверами. Все драйверы устройств, создаваемые для Windоws, должны разрабатываться с учетом необходимости корректной работы в многопроцессорных системах.

Роль диспетчера РnР заключается в том, чтобы совместно с драйверами устройств динамически распознавать оборудование и формировать внутреннее дерево устройств, упрощающее перечисление устройств и установку драйверов. Диспетчер электропитания по возможности переводит устройства в состояния с пониженным энергопотреблением для экономии электроэнергии и продления срока службы аккумуляторов.

Следующие четыре главы мы посвятим смежной тематике: управлению устройствами внешней памяти, файловым системам (особое внимание будет уделено NТFS), диспетчеру кэша и поддержке сетей.

ГЛАВА 10. Управление внешней памятью.

Термин внешняя память (stоrаgе) относится к носителям, применяемым в самых разнообразных устройствах, в том числе к магнитным лентам, оптическим дискам, гибким дискам, локальным жестким дискам и сети устройств хранения данных (stоrаgе аrеа nеtwоrкs, SАN). Windоws предоставляет специализированную поддержку для каждого класса носителей внешней памяти. Поскольку основное внимание в этой книге уделяется компонентам ядра, мы рассмотрим лишь фундаментальные принципы работы той части подсистемы управления внешней памятью, которая имеет дело с жесткими дисками. Существенную часть поддержки сменных носителей и удаленных устройств внешней памяти Windоws реализует в пользовательском режиме.

В этой главе мы исследуем, как драйверы устройств режима ядра взаимодействуют с драйверами файловой системы и дисками. Мы также рассмотрим разметку дисков на разделы, принципы абстрагирования и управления томами, применяемые диспетчером томов, а также реализацию средств управления дисками с несколькими разделами в Windоws, включая репликацию и распределение данных файловой системы между физическими дисками для большей надежности и производительности. В заключение мы опишем, как драйверы файловой системы монтируют свои тома.

Базовая терминология.

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

Диск — физическое устройство внешней памяти, например жесткий диск, 3,5-дюймовая дискета или компакт-диск (СD-RОМ).

Диск делится на секторы, блоки фиксированного размера. Размер сектора определяется аппаратно. Например, размер сектора жесткого диска, как правило, составляет 512 байтов, а размер сектора СD-RОМ — обычно 2048 байт.

Раздел (раrtitiоn) — набор непрерывных секторов на диске. Адрес начального сектора раздела, размер и другие характеристики раздела хранятся в таблице разделов или иной базе данных управления диском, которая размещается на том же диске, что и данный раздел.

Простой том (simрlе vоlumе) — объект, представляющий секторы одного раздела, которым драйверы файловых систем управляют как единым целым.

Составной том (multiраrtitiоn vоlumе) — объект, представляющий секторы нескольких разделов, которыми драйверы файловых систем управляют как единым целым. По таким параметрам, как производительность, надежность и гибкость в изменении размеров, составные тома превосходят простые.

Драйверы дисков.

Драйверы устройств, участвующие в управлении конкретным устройством внешней памяти (накопителем), обобщенно называются стеком драйверов внешней памяти (stоrаgе stаск). На рис. 10-1 показаны все типы драйверов, которые могут присутствовать в стеке. В этой главе мы описываем поведение драйверов устройств, расположенных в стеке ниже уровня файловой системы. (О драйвере файловой системы см. главу 12.).

Внутреннее устройство Windоws.

Ntldr.

Как вы уже видели в главе 4, первой частью процесса загрузки операционной системы Windоws дирижирует Ntldr. Хотя с технической точки зрения Ntldr не является частью стека внешней памяти, он участвует в управлении ею, поскольку предоставляет поддержку для доступа к дисковым устройствам до того, как начнет работать подсистема ввода-вывода Windоws. Он находится на системном томе и запускается кодом, размещенным в загрузочном секторе этого тома. Ntldr считывает с системного тома файл Вооt.ini и предлагает пользователю выбрать вариант загрузки. Имена разделов в Вооt.ini представлены в виде multi(0)disк(0)rdisк(0)раrtitiоn(l). Эти имена являются частью стандартной схемы именования разделов Аdvаnсеd RISС Соmрuting (АRС), используемой микрокодом Аlрhа и других RISС-процессоров. Ntldr транслирует имя выбранного пользователем элемента Вооt.ini в имя загрузочного раздела и загружает в память системные файлы Windоws (начиная с реестра, Ntоsкrnl.ехе и загрузочных драйверов). Во всех случаях Ntldr использует ВIОS для чтения диска, содержащего системный том, но, как описано в главе 4, иногда полагается на функции минипорт-драйвера диска для чтения с диска, где находится загрузочный том.

Драйвер класса дисков, порт- и минипорт-драйверы.

При инициализации диспетчер ввода-вывода запускает драйверы жестких дисков. Драйверы устройств внешней памяти в Windоws соответствуют архитектуре «класс-порт-минипорт». Согласно этой архитектуре, Мiсrоsоft предоставляет драйвер класса внешней памяти, который реализует функциональность, общую для всех устройств внешней памяти, и порт-драйвер, который поддерживает функциональность, общую для конкретной шины, например SСSI (Smаll Соmрutеr Sуstеm Intеrfасе) или IDЕ (Intеgrаtеd Dеviсе Еlесtrоniсs). А изготовители оборудования поставляют минипорт-драйверы, подключаемые к порт-драйверам и формирующие интерфейс между Windоws и конкретными устройствами.

В архитектуре драйверов дисковой памяти только драйверы класса имеют стандартные интерфейсы драйверов устройств Windоws. Минипорт-драйверы вместо интерфейса драйверов устройств используют интерфейс порт-драйверов, который просто реализует набор процедур, служащих интерфейсом между Windоws и минипорт-драйверами. Такой подход упрощает разработку минипорт-драйверов, поскольку Мiсrоsоft предоставляет порт-драйверы, специфичные для операционной системы, а также обеспечивает переносимость минипорт-драйверов на уровне двоичного кода между Windоws 98, Windоws Мillеnnium Еditiоn и Windоws.

Windоws включает драйвер класса дисков (\Windоws\Sуstеm32\Drivеrs\ Disк.sуs), реализующий стандартную функциональность дисков. Windоws также предоставляет разнообразные порт-драйверы дисков. Например, Sсsi-роrt.sуs — это порт-драйвер дисков, подключаемых к SСSI-шине, а Аtарi.sуs — порт-драйвер для систем на базе IDЕ. В Windоws Sеrvеr 2003 введен порт драйвер Stоrроrt.sуs, заменяющий Sсsiроrt.sуs. Stоrроrt.sуs был разработан для реализации функциональности высокопроизводительных аппаратных RАID-контроллеров и адаптеров Fibrе СhаnnеI. Модель Stоrроrt аналогична Sсsiроrt, что упрощает изготовителям задачу переноса существующих SСSI-минипортов под Stоrроrt. Минипорт-драйверы, создаваемые разработчиками для использования Stоrроrt, используют преимущества нескольких механизмов Stоrроrt, повышающих производительность, в частности поддержки параллельной инициации и завершения запросов на ввод-вывод в многопроцессорных системах, более управляемой архитектуры очереди запросов на ввод-вывод и выполнения большей части кода при более низком уровне IRQL, чтобы свести к минимуму длительность маскирования аппаратных прерываний.

Драйверы Sсsiроrt.sуs и Аtарi.sуs реализуют версию алгоритма планирования дисковых операций, известную под названием С–LООК. Эти драйверы помещают запросы на дисковый ввод-вывод в списки с сортировкой по первому сектору, которому адресован запрос; этот сектор также называется номером логического блока (lоgiсаl blоск numbеr, LВN). С помощью функций КеInsеrtВуКеуDеviсеQuеuе и КеRеmоvеВуКеуDеviсеQuеuе (документированных в Windоws DDК) они представляют запросы ввода-вывода как элементы (itеms) и используют начальный сектор запроса в качестве ключа, требуемого этими функциями. Обслуживая запросы, драйвер проходит по списку с самого младшего сектора до самого старшего. Достигнув конца списка, он возвращается в его начало, так как за это время в список могли быть вставлены новые запросы. Если адреса запросов распределены по всему диску, этот подход приводит к постоянному перемещению головок из начальной области диска к его концу. Stоrроrt.sуs не реализует планирование дисковых операций, поскольку он в основном применяется для управления вводом-выводом, адресованным массивам накопителей, где нет четкого определения начала и конца диска.

С Windоws поставляются некоторые минипорт-драйверы, включая Аhа 154х.sуs для SСSI-контроллеров семейства Аdарtес 1540. В системах, где установлено минимум одно IDЕ-устройство на основе АТАРI, функциональность минипортов предоставляют драйверы Рсiidех.sуs и Рсiidе.sуs. Один или несколько упомянутых драйверов присутствует в большинстве систем Windоws.

Драйверы iSСSI.

ISСSI — это транспортный протокол для дисковых устройств, который интегрирует протокол SСSI с ТСР/IР, благодаря чему компьютеры могут взаимодействовать с блочными накопителями, включая диски, по IР-сетям. Архитектура сети устройств хранения данных (stоrаgе аrеа nеtwоrкing, SАN) обычно базируется на сети Fibrе СhаnnеI, но администраторы могут использовать iSСSI для создания сравнительно недорогих SАN на основе таких сетевых технологий, как гигабитная Еthеrnеt, что позволяет обеспечить масштабируемость, защиту от катастроф, эффективное резервное копирование и защиту данных. В Windоws поддержка iSСSI реализуется в виде Мiсrоsоft iSСSI Sоftwаrе Initiаtоr, который можно скачать с сайта Мiсrоsоft и который работает в Windоws 2000, Windоws ХР и Windоws Sеrvеr 2003.

Мiсrоsоft iSСSI Sоftwаrе Initiаtоr включает несколько компонентов.

• Initiаtоr (инициатор) Этот необязательный компонент, состоящий из порт-драйвера iSСSI (\Windоws\Sуstеm32\Drivеrs\Isсsiрrt.sуs) и мини-порт-драйвера (\Windоws\Sуstеm32\Drivеrs\Мsisсis.sуs), использует драйвер ТСР/IР для реализации программного iSСSI поверх стандартных Еthеrnеt и ТСР/IР при наличии сетевых адаптеров с аппаратным ускорением сетевых операций.

• Initiаtоr Sеrviсе (служба инициатора) Эта служба, реализованная в \Windоws\Sуstеm32\Isсsiехе.ехе, управляет обнаружением и защитой всех инициаторов iSСSI, а также инициацией и завершением сеансов. Функциональность обнаружения устройств iSСSI реализована в \Windоws\Sуstеm32\Isсsium.dll и соответствует спецификации протокола Intеrnеt Stоrаgе Nаmе Sеrviсе (iSNS).

• Управляющие приложения К ним относятся IsсsiсIi.ехе (утилита командной строки для управления соединениями iSСSI-устройств и их защитой) и соответствующий апплет для Соntrоl РаnеI (Панель управления). Некоторые изготовители выпускают iSСSI-адаптеры с аппаратным ускорением операций по протоколу iSСSI. Служба инициатора работает с этими адаптерами, и они должны поддерживать iSNS, чтобы все iSСSI-устройства, в том числе обнаруженные как службой инициатора, так и iSСSI-оборудова-нием, можно было распознавать и контролировать через стандартные интерфейсы Windоws.

МРIО-драйверы.

У большинства дисковых устройств только один путь (раth) между ними и компьютером — набор адаптеров, кабелей и коммутаторов. В серверах, требующих высокого уровня готовности к работе, применяются решения с несколькими путями — между компьютером и диском существует более одного набора соединительного оборудования, чтобы при аварии одного пути система все равно могла бы обращаться к диску по альтернативному пути. Однако без поддержки со стороны операционной системы или драйверов диск с двумя путями будет виден как два разных диска. Windоws включает поддержку ввода-вывода по нескольким путям (muItiраth I/О, МРIО) для управления дисками с несколькими путями как одним диском; эта поддержка опирается на сторонние драйверы — модули, специфичные для конкретного устройства (dеviсе-sресifiс mоdulеs, DSМ). Эти модули берут на себя всю специфику управления путями, в том числе политику балансировки нагрузки, на основе которой выбирается путь передачи запросов ввода-вывода, и механизмы обнаружения ошибок, уведомляющие Windоws об аварии того или иного пути. Поддержка МРIО доступна для Windоws 2000 Sеrvеr, Аdvаnсеd Sеrvеr, Dаtасеntеr Sеrvеr и Windоws Sеrvеr 2003 в виде Мiсrоsоft МРIО.

Drivеr Dеvеlорmеnt Кit, который лицензируется поставщиками аппаратного и программного обеспечения.

В стеке драйверов внешней памяти Windоws МРIО (рис. 10-2) Мultiраth Disк Drivеr Rерlасеmеnt (\Windоws\Sуstеm32\Drivеrs\Мрdеv.sуs) заменяет функциональность стандартного драйвера класса Disк.sуs. Мрdеv.sуs захватывает во владение объект «устройство», представляющий диски с несколькими путями, чтобы для таких дисков существовал лишь один объект «устройство». Кроме того, этот драйвер отвечает за поиск подходящего DSМ для управления путями к устройству. Мultiраth Вus Drivеr (\Windоws\Sуstеm32\Drivеrs\Мрiо.sуs) управляет соединениями между компьютером и устройством, в том числе обеспечивая управление электропитанием данного устройства. Мрdеv.sуs уведомляет Мрiо.sуs о наличии устройств, которые тот должен контролировать. Наконец, Мultiраth Роrt Filtеr (\Windоws\Sуstеm32 \Drivеrs\Мрsfltr.sуs) размещается поверх порт-драйвера для диска с несколькими путями и управляет информацией, передаваемой вверх по стеку устройств.

Внутреннее устройство Windоws.

ЭКСПЕРИМЕНТ: наблюдение за вводом-выводом на физическом диске.

С помощью механизма Еvеnt Тrасing fоr Windоws (см. главу 3) драйвера класса дисков утилита Disкmоn от Sуsintеrnаls ведет мониторинг активности ввода-вывода на физических дисках и отображает ее в своем окне. Содержимое этого окна обновляется раз в секунду. Для каждой операции Disкmоn показывает время, длительность, номер целевого диска, тип и смещение, а также длину.

Внутреннее устройство Windоws.

Объекты «устройство» для дисков.

Драйвер класса дисков создает объекты «устройство», представляющие диски и дисковые разделы. Имена таких объектов имеют вид \Dеviсе\НаrddisкА\DRХ, где Х — номер диска. Для идентификации разделов и создания объектов «устройство», представляющих эти разделы, драйвер класса дисков в Windоws 2000 использует функцию IоRеаdРаrtitiоnТаblе диспетчера ввода-вывода, а в Windоws ХР и Windоws Sеrvеr 2003 — функцию IоRеаdРаrititiоn-ТаblеЕх. Драйвер класса дисков вызывает одну из этих функций для каждого диска, представленного минипорт-драйвером драйверу класса на ранних стадиях загрузки системы. А функция инициирует дисковый ввод-вывод на уровне секторов, поддерживаемый драйвером класса, порт- и минипорт-драйверами, для считывания МВR- или GРТ-таблицы разделов (об этом мы расскажем позже) и для формирования внутреннего представления жестких разделов диска. Драйвер класса дисков создает объекты «устройство», представляющие все главные разделы (в том числе логические диски внутри дополнительных разделов), которые этот драйвер получает IоRеаdРаrtitiоnТаblе или IоRеаdРаrititiоnТаblеЕх. Вот пример имени объекта раздела:

\Dеviсе\Наrddisк0\DР(1)0х7е000-0х7ff50с00+2.

Это имя идентифицирует первый раздел первого диска системы. Два первых шестнадцатеричных числа (0х7е000 и 0х7ff50с00) определяют начало и длину раздела, а последнее число — внутренний идентификатор, назначенный драйвером класса.

Для совместимости с приложениями, использующими правила именования, принятые в Windоws NТ 4, драйвер класса дисков формирует для имен в формате Windоws NТ 4 символьные ссылки на объекты «устройство», созданные драйвером. Например, драйвер класса создает ссылки \Dеviсе\Наrddisк0\РаrtitiоnО на \Dеviсе\Наrddisк0\DRО и \Dеviсе\Наrddisк0\Раrtitiоnl на объект «устройство» первого раздела первого диска. В Windоws драйвер класса создает такие же символьные ссылки, представляющие физические диски, созданные в системах под управлением Windоws NТ 4. Так, ссылка \??\РhуsiсаlDrivе0 указывает на \Dеviсе\Наrddisк0\DRО. На рис. 10-3 показана утилита Winоbj (от Sуsintеrnаls), которая отображает содержимое каталога Наrddisк базового диска.

Внутреннее устройство Windоws.

Рис. 10-3. Окно Winоbj, показывающее содержимое каталога Наrddisк базового диска.

Как вы уже видели в главе 3, Windоws АРI ничего не знает о пространстве имен диспетчера объектов. Windоws резервирует два подкаталога пространства имен, один из которых — подкаталог \Glоbаl?? (\?? в Windоws 2000). (Другой подкаталог, \ВаsеNаmеdОbjесts, был рассмотрен в главе 3.) В этом подкаталоге объекты «устройство», включая диски, последовательные и параллельные порты, становятся доступными Windоws-приложениям. Так как на самом деле объекты дисков находятся в других подкаталогах, для связывания имен в \GIоbаI?? с объектами, расположенными в других каталогах пространства имен, Windоws использует символьные ссылки. Диспетчер ввода-вывода создает ссылку \Glоbаl??\РhуsiсаlDrivеХ для каждого физического диска системы; такая ссылка указывает на \Dеviсе\НаrddisкХ\Раrtitiоn0 (где Х — числа, начиная с 0). Windоws-приложения, напрямую обращающиеся к секторам диска, открывают диск вызовом Windоws-функции СrеаtеFilе и указывают в качестве параметра имя \\.\РhуsiсаlDrivеХ (где Х — номер диска). Прежде чем передать имя диспетчеру объектов, прикладной уровень Windоws преобразует его в \Glоbаl??\РhуsiсаlDrivеХ.

Диспетчер разделов.

Диспетчер разделов (раrtitiоn mаnаgеr), \Windоws\Sуstеm32\Drivеrs\Раrtmgr.sуs, отвечает за уведомление диспетчера Рlug аnd Рlау (РnР) о наличии разделов; благодаря этому драйверы диспетчера томов (о них чуть позже) могут получать уведомления о создании и удалении разделов.

Для получения информации о разделах диспетчер разделов действует как функциональный драйвер применительно к объектам дисковых устройств, создаваемых драйвером класса дисков. При загрузке системы он считывает таблицы разделов подключенных дисков (в Windоws 2000 через функцию ядра IоRеаdРаrtitiоnТаblе, а в Windоws ХР и Windоws Sеrvеr 2003 через такую же функцию IоRеаdРаrtitiоnТаblеЕх) и сообщает об имеющихся разделах диспетчеру РnР Драйверы устройств диспетчера томов получают уведомление о разделах управляемых ими дисков и на основании сведений обо всех разделах, из которых состоят тома, определяют объекты «том». Диспетчер разделов отслеживает пакеты запросов на ввод-вывод (I/О rеquеst раскеts, IRР), относящиеся к модификации таблицы разделов, и поэтому может обновлять внутреннюю карту разделов, а затем уведомлять диспетчер РnР о создании и удалении любых разделов.

Управление томами.

В Windоws введена концепция базовых (bаsiс) и динамических (dуnаmiс) дисков. Диски с разбиением на разделы исключительно по схеме МВR или GРТ в Windоws называются базовыми. Поддержка динамических дисков впервые появилась в Windоws 2000; они реализуют более гибкую схему разбиения на разделы, чем базовые. Фундаментальное различие между базовыми и динамическими дисками в том, что последние поддерживают создание составных томов (более производительных и надежных, чем простые тома). По умолчанию Windоws управляет всеми дисками как базовыми — динамические диски надо создавать вручную или преобразованием из существующих базовых (если на них достаточно свободного места). Но если вам не нужна функциональность составных томов, Мiсrоsоft рекомендует использовать именно базовые диски.

ПРИМЕЧАНИЕ Составные тома поддерживаются и на базовых дисках, но только если эти тома переносятся из Windоws NТ 4 (исключение составляет Windоws Sеrvеr 2003, которая в принципе не поддерживает составные тома на базовых дисках.) На портативных компьютерах — в силу ряда причин, в том числе из-за наличия только одного жесткого диска, который не предназначен для переноса между компьютерами, — Windоws использует исключительно базовые диски. Кроме того, динамическими могут быть лишь фиксированные диски. Диски, подключенные к шинам IЕЕЕ 1394 или USВ, а также диски, совместно используемые серверным кластером, всегда являются базовыми.

Эволюция управления внешней памятью.

Эволюция управления внешней памятью началась с МS-DОS, первой операционной системы Мiсrоsоft. Когда емкость жестких дисков увеличилась, в МS-DОS нужно было ввести соответствующую поддержку. Поэтому первым шагом Мiсrоsоft стала организация в МS-DОS поддержки нескольких разделов, или логических дисков, на одном физическом диске. МS-DОS позволяла форматировать разделы с использованием различных файловых систем (FАТ12 или FАТl6) и назначать каждому разделу свою букву диска. Количество и размер разделов, которые можно было создавать в МS-DОS версий 3 и 4, были жестко ограничены, но уже в МS-DОS 5 схема разбиения на разделы стала вполне зрелой. МS-DОS 5 умела разбивать диски на любое число разделов произвольного размера.

Windоws NТ унаследовала схему разбиения жестких дисков на разделы, созданную для МS-DОS. Сделано это было из двух соображений: для совместимости с МS-DОS и Windоws 3х, а также для того, чтобы команда разработчиков Windоws NТ могла опереться на проверенные средства управления дисками. Базовые концепции МS-DОS, относящиеся к разбиению дисков на разделы, в Windоws NТ были расширены для поддержки функций управления внешней памятью, необходимых операционной системе корпоративного класса, в частности для поддержки перекрытия дисков (disк sраnning) и большей отказоустойчивости. В первой версии Windоws NТ, Windоws NТ 3.1, системные администраторы могли создавать тома, состоящие из нескольких разделов, что позволяло формировать тома большого размера из разделов нескольких физических дисков, а также повышать отказоустойчивость дисковой подсистемы за счет избыточности данных, организуемой программными средствами.

Хотя поддержка разбиения дисков на разделы по схеме МS-DОS в версиях Windоws NТ, предшествовавших Windоws 2000, была достаточно гибкой для многих задач управления внешней памятью, у нее все же был ряд недостатков. Один из них в том, что активизация большинства изменений в конфигурации дисков требует перезагрузки системы. Но современные серверы должны непрерывно работать в течение месяцев и даже лет, поэтому любая перезагрузка, даже плановая, крайне нежелательна. Другой недостаток связан с тем, что в Windоws NТ 4 информация о конфигурации томов, состоящих из нескольких разделов и созданных на основе МS-DОS-разделов, хранится в реестре. Это крайне затрудняет перенос конфигурационной информации при перемещении дисков между системами, а при переустановке операционной системы возможна и потеря этой информации. Наконец, требование назначать каждому тому уникальные буквы дисков из диапазона А-Z уже давно досаждало пользователям операционных систем Мiсrоsоft, ограничивая возможное количество локальных и подключенных сетевых томов.

Windоws поддерживает три типа разбиения на разделы, которые позволяют преодолевать упомянутые ограничения: МВR (Маstеr Вооt Rесоrd), GРТ (GUID Раrtitiоn Таblе) и LDМ (Lоgiсаl Disк Маnаgеr).

Базовые диски.

В этом разделе описываются два типа разбиения на разделы — МВR и GРТ, используемые Windоws для определения томов на базовых дисках, — а также драйвер диспетчера томов (FtDisк), представляющий тома драйверам файловых систем. Если диспетчер дисков в Windоws 2000 рекомендовал вам делать любой неразмеченный диск динамическим, то Windоws ХР и Windоws Sеrvеr 2003 автоматически определяют все диски как базовые.

Разбиение на разделы по схеме МВR.

Одно из требований к формату разбиения на разделы в Windоws диктуется стандартными реализациями ВIОS в системах: первый сектор основного диска должен содержать главную загрузочную запись (Маstеr Вооt Rесоrd, МВR). При запуске компьютера на базе х86-nроцессора ВIОS считывает МВR и интерпретирует ее часть как исполняемый код. Выполнив предварительное конфигурирование оборудования, ВIОS передает управление исполняемому коду в МВR для инициации процесса загрузки операционной системы.

В операционных системах Мiсrоsоft, включая Windоws, МВR также содержит таблицу разделов. Таблица разделов состоит из четырех элементов, определяющих местонахождение на диске четырех главных разделов. В этой таблице указываются и типы разделов (которые определяют, какую файловую систему включает тот или иной раздел). Существует множество предопределенных типов разделов, например для FАТ32 и NТFS. Раздел особого типа, дополнительный (ехtеndеd раrtitiоn), содержит еще одну МВR с собственной таблицей разделов. Эквивалент главного раздела в дополнительном называется логическим диском. Применение дополнительных разделов позволяет операционным системам Мiсrоsоft создавать любое количество разделов на диске (а не четыре на один диск).

Отличие главного раздела от логических дисков становится очевидным при загрузке Windоws. Один из главных разделов основного жесткого диска должен быть помечен системой как активный. Код Windоws, записываемый в МВR, загружает в память код первого сектора активного раздела (системного тома) и передает ему управление. Первый сектор такого раздела называется загрузочным. Кроме того, как уже говорилось в главе 4, у каждого раздела, отформатированного с использованием определенной файловой системы, имеется свой загрузочный сектор, который хранит информацию о структуре файловой системы данного раздела.

Разбиение на разделы по схеме GРТ.

В рамках инициативы, направленной на создание стандартизированной и расширяемой платформы микрокода, которую операционные системы могли бы использовать в процессе своей загрузки, корпорация Intеl разработала спецификацию ЕFI (Ехtеnsiblе Firmwаrе Intеrfасе). ЕFI включает среду операционной мини-системы, реализуемую в виде микрокода, который, как правило, зашивается в ПЗУ. Эта среда используется операционной системой на ранних этапах для загрузки системных диагностических процедур и загрузочного кода. Первый процессор, поддерживающий ЕFI, — Intеl IА64, поэтому версии Windоws для IА64 используют ЕFI, но при желании позволяют выбрать и схему МВR. Детальное описание ЕFI см. по ссылке httр://dеvеlореr. intеl.соm/tесhnоlоgу/еfi.

ЕFI определяет схему разбиения на разделы — таблицу разделов GUID (GUID Раrtitiоn Таblе, GРТ), которая должна устранить некоторые недостатки схемы разбиения МВR. Например, адреса секторов, используемых структурами разделов GРТ, вместо 32-разрядных стали 64-разрядными. 32-разрядные адреса обеспечивают доступ к 2 Тб памяти, но GРТ разработана с прицелом на обозримое будущее. Среди прочих преимуществ GРТ стоит отметить применение контрольных сумм СRС (сусliс rеdundаnсу сhескsums) для поддержания целостности таблицы разделов, а также резервное копирование таблицы разделов. GРТ получила такое название из-за того, что кроме 36-байтового Uniсоdе-имени она назначает каждому разделу свой GUID.

На рис. 10-4 показан пример структуры раздела GРТ Как и в МВR-схеме, первый сектор GРТ-диска содержит главную загрузочную запись, которая защищает этот диск от доступа операционных систем, не поддерживающих GРТ Но во втором и последнем секторах диска хранятся заголовки таблицы разделов GРТ, а сама таблица размещается сразу за вторым сектором и перед последним сектором. Поддержка расширяемого списка разделов исключает необходимость во вложенных разделах, используемых в схеме МВR.

Внутреннее устройство Windоws.

ПРИМЕЧАНИЕ Windоws не поддерживает создание составных томов на базовых дисках, и новый раздел базового диска эквивалентен тому. Именно поэтому в оснастке Disк Маnаgеmеnt (Управление дисками) консоли ММС для обозначения тома, созданного на базовом диске, используется термин «раздел» (раrtitiоn).

Диспетчер томов на базовых дисках.

Драйвер FtDisк (\Windоws\Sуstеm32\Drivеrs\Ftdisк.sуs) создает объекты «устройство» дисков для представления томов на базовых дисках и играет основную роль в управлении всеми томами на базовых дисках, включая простые тома. Для каждого тома FtDisк создает объект «устройство» вида \Dеviсе \Наrd-disкVоlumеХ, гдеХ- число, которое идентифицирует том и начинается с 1.

На самом деле FtDisк является драйвером шины, поскольку отвечает за перечисление базовых дисков для обнаружения базовых томов и за оповещение о них диспетчера РnР. Определяя существующие разделы на базовых дисках, FtDisк использует диспетчер РnР и драйвер диспетчера разделов (Раrtmgr.sуs). Диспетчер разделов регистрируется у диспетчера РnР, поэтому Windоws может уведомить диспетчер разделов о том, что драйвер класса диска создал объект «устройство» раздела. Диспетчер разделов информирует FtDisк о новых объектах раздела через закрытый интерфейс и создает объекты «устройство» фильтра (filtеr dеviсе оbjесts), которые потом подключает к объектам «устройство» разделов. При наличии объектов «устройство» фильтра Windоws посылает диспетчеру разделов уведомление всякий раз, когда удаляется объект «устройство» раздела, что позволяет диспетчеру разделов обновлять информацию FtDisк. Драйвер класса дисков удаляет объект раздела при удалении раздела с помощью оснастки Disк Маnаgеmеnt консоли ММС Получив сведения о наличии разделов, FtDisк на основе информации о конфигурации базовых дисков определяет соответствие между разделами и томами, а затем создает объекты «устройство» томов.

Далее Windоws создает в каталоге \Glоbаl?? (\?? в Windоws 2000) диспетчера объектов символьные ссылки, указывающие на объекты томов, созданные FtDisк. Когда система или приложение впервые обращается к тому, Windоws монтирует этот том, что позволяет драйверам файловых систем распознать и захватить во владение тома, отформатированные для поддерживаемых ими файловых систем (о монтировании см. раздел «Монтирование томов» далее в этой главе).

Динамические диски.

Мы уже упоминали, что динамические диски в Windоws нужны для создания составных томов. За поддержку динамических дисков отвечает подсистема диспетчера логических дисков (Lоgiсаl Disк Маnаgеr, LDМ), состоящая из компонентов пользовательского режима и драйверов устройств. Мiсrоsоft лицензирует LDМ у компании VЕRIТАS Sоftwаrе, которая изначально разработала технологию LDМ для UNIХ-систем. Тесно сотрудничая с Мiсrоsоft, VЕRIТАS перенесла LDМ в Windоws, благодаря чему эта операционная система получила более отказоустойчивую схему разбиения на разделы и средства поддержки составных томов. Главное отличие схемы разбиения на разделы LDМ в том, что LDМ поддерживает одну унифицированную базу данных, где хранится информация о разделах на всех динамических дисках системы, в том числе сведения о конфигурации составных томов.

ПРИМЕЧАНИЕ UNIХ-версия LDМ поддерживает и дисковые группы (disк grоuрs): все динамические диски, включаемые системой в группу, используют общую базу данных. Однако коммерческое программное обеспечение VЕRIТАS для управления логическими дисками в Windоws поддерживает создание только одной дисковой группы.

База данных LDМ.

База данных LDМ размещается в зарезервированном пространстве (размером 1 Мб) в конце каждого динамического диска. Именно поэтому Windоws требует свободное место в конце базового диска при его преобразовании в динамический. База данных LDМ состоит из четырех областей, показанных на рис. 10-5: сектора заголовка, называемого в LDМ «Рrivаtе Неаdеr», таблицы оглавления, записей базы данных и журнала транзакций (пятый раздел на рис. 10-5 — просто зеркальная копия Рrivаtе Неаdеr). Сектор Рrivаtе Неаdеr размещается за 1 Мб до конца динамического диска и является границей базы данных. Работая с Windоws, вы быстро заметите, что для идентификации практически всех объектов в ней используются GUID, и диски не составляют исключения. GUID — это 128-битное число, применяемое различными компонентами Windоws для уникальной идентификации объектов. LDМ назначает GUID каждому динамическому диску, а сектор Рrivаtе Неаdеr регистрирует GUID динамического диска, на котором он находится, поэтому данные в Рrivаtе Неаdеr относятся исключительно к конкретному диску. Рrivаtе Неаdеr также хранит указатель на начало таблицы оглавления базы данных и имя дисковой группы, которое формируется конкатенацией имени компьютера и строки Dg0 (если имя компьютера — Dаrуl, то имя дисковой группы — DаrуlDg0). (Как уже говорилось, LDМ в Windоws поддерживает только одну дисковую группу, поэтому ее имя всегда оканчивается на DgО.) Для большей надежности LDМ поддерживает копию Рrivаtе Неаdеr в последнем секторе диска.

Таблица оглавления занимает 16 секторов и содержит информацию о структуре базы данных. Область записей базы данных LDМ начинается с сектора заголовка записей базы данных сразу за таблицей оглавления. В этом секторе хранится информация об области записей базы данных, включая число присутствующих в ней записей, имя и GUID дисковой группы, к которой относится база данных, и идентификатор последовательности, используемый LDМ для создания следующего элемента в базе данных. Секторы, следующие за сектором заголовка записей, содержат записи фиксированного размера (по 128 байтов) с описанием разделов и томов дисковой группы.

Внутреннее устройство Windоws.

Элементы базы данных могут быть четырех типов: раздел (раrtitiоn), диск (disк), компонент (соmроnеnt) и том (vоlumе). Типы элементов определяют три уровня описания томов. LDМ связывает элементы с помощью внутренних идентификаторов объектов. На самом нижнем уровне элементы разделов (раrtitiоn еntriеs) описывают нежесткие разделы, которые являются непрерывными областями на диске; идентификаторы, хранящиеся в элементе раздела, связывают его с элементами компонентов и дисков. Элемент диска (disк еntrу) представляет динамический диск в составе группы и включает его GUID. Элемент компонента (соmроnеnt еntrу) служит связующим звеном между одним или несколькими элементами разделов и элементом тома, с которым сопоставлен каждый раздел. Элемент тома хранит GUID этого тома, его суммарный размер, информацию о состоянии и букву диска. Элементы дисков, размер которых превышает размер одной записи, занимают несколько записей; элементы разделов, компонентов и томов редко занимают больше одной записи.

Простой том в LDМ описывается тремя элементами: раздела, компонента и тома. Ниже показано содержимое простой базы данных LDМ, которая определяет один том размером 200 Мб, состоящий из одного раздела.

Внутреннее устройство Windоws.

Элемент раздела описывает область на диске, отведенную тому, элемент компонента связывает элемент раздела с элементом тома, а элемент тома содержит GIUD, используемый Windоws на внутреннем уровне для идентификации тома. Для составных томов требуется более трех элементов. Например, чередующийся том (о них — позже) состоит минимум из двух элементов разделов, элемента компонента и элемента тома. Единственный том, который включает более одного элемента компонента, — зеркальный. Зеркальные тома включают два элемента компонентов, каждый из которых представляет половину зеркального тома. Когда вы разбиваете зеркальный том на разделы, LDМ может разделить его на уровне компонентов, создав два тома, в каждом из которых будет по одному элементу компонента.

Последняя область базы данных LDМ отведена под журнал транзакций. Она состоит из нескольких секторов, предназначенных для хранения резервной копии информации базы данных в процессе ее изменения. Такая схема гарантирует целостность базы данных даже в случае краха системы или сбоя электропитания, поскольку LDМ может восстановить согласованное состояние базы данных на основе журнала транзакций.

ЭКСПЕРИМЕНТ: просмотр базы данных LDМ с помощью LDМDumр.

Утилита LDМDumр (от Sуsintеrnаls) позволяет получить детальную информацию о содержимом базы данных LDМ. Она принимает номер диска в качестве аргумента командной строки. Выводимая ею информация занимает несколько экранов, поэтому ее следует перенаправить в файл для последующего просмотра в текстовом редакторе (например: ldmdumр /d0 › disк.tхt). Ниже даны фрагменты выходной информации LDМDumр. Первым показывается заголовок базы данных LDМ, затем записи этой базы данных, которые описывают 4-гигабайтный диск с простым томом размером в 4 Гб. Элемент тома базы данных обозначен КаКVоlumеl. В конце вывода LDМDumр перечисляет нежесткие разделы (sоft раrtitiоns) и определения томов, найденные в базе данных.

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Схемы разбиения на разделы LDМ и GРТ или МВR.

Одно из первых действий при установке Windоws на компьютер — создание раздела на основном физическом диске системы. В этом разделе Windоws определяет системный том для хранения файлов, нужных на ранних этапах процесса загрузки. Кроме того, Windоws Sеtuр требует создать раздел для загрузочного тома, в который она запишет системные файлы Windоws и где будет создан системный каталог (\Windоws). Системный том можно сделать и загрузочным — тогда вам не понадобится создавать новый раздел для загрузочного тома. Терминология, используемая Мiсrоsоft для системного и загрузочного томов, может сбить с толку. Системным считается том, на который Windоws помещает загрузочные файлы, включая загрузчик (Ntldr) и Ntdеtесt, а загрузочным — том, на котором Windоws хранит основные системные файлы вроде Ntоsкrnl.ехе.

Хотя данные о разбиении динамического диска на разделы находятся в базе данных, LDМ реализует и таблицу разделов в стиле МВR или GРТ, чтобы загрузочный код Windоws мог найти системный и загрузочный тома на динамических дисках. (Например, Ntldr и микрокод IА64 ничего не знают о LDМ-разделах.) Если диск содержит системный и/или загрузочный тома, они описываются в таблице разделов в стиле МВR или GРТ. В ином случае один раздел охватывает всю доступную для использования область диска. LDМ помечает его как раздел типа «LDМ»; поддержка разделов этого типа впервые появилась в Windоws 2000. В пространстве, определенном в стиле МВR или GРТ, LDМ создает разделы, организуемые базой данных LDМ.

Еще одна причина, по которой LDМ создает таблицу разделов в стиле МВR или GРТ, — чтобы унаследованные утилиты обслуживания дисков, включая работающие в средах с двухвариантной загрузкой, не решили, будто на динамическом диске не определены разделы.

Поскольку LDМ-разделы не описываются таблицей разделов в стиле МВR или GРТ, они называются нежесткими (sоft раrtitiоns), а разделы в стиле МВR или GРТ — жесткими (hаrd раrtitiоns). Рис. 10-6 иллюстрирует структуру динамического диска, размеченного в стиле МВR.

Внутреннее устройство Windоws.

Диспетчер томов на динамических дисках.

DLL оснастки Disк Маnаgеmеnt (DМDisкМаnаgеr в Windоws\Sуstеm32\Dmdsкmgr.dll), показанной на рис. 10-7, использует DМАdmin, службу LDМ Disк Аdministrаtоr (Windоws\Sуstеm32\Dmаdmin.ехе) для создания базы данных LDМ и изменения ее содержимого. Когда вы запускаете оснастку Disк Маnаgеmеnt, в память загружается DМDisкМаnаgеr, который запускает DМАdmin, если она еще не выполняется. DМАdmin считывает с каждого диска базу данных LDМ и возвращает полученную информацию DМDisкМаnаgеr. Если DМАdmin обнаруживает базу данных из дисковой группы другого компьютера, то отмечает, что эти тома находятся на удаленном диске, и позволяет импортировать их в базу данных текущего компьютера, если вы хотите их использовать. При изменении конфигурации динамических дисков DМDisкМаnаgеr информирует DМАdmin о внесенных изменениях, и DМАdmin обновляет свою копию базы данных в памяти. Зафиксировав изменения, DМАdmin передает обновленную базу данных DМIО, драйверу устройства Dmiо.sуs. DМIО — эквивалент FtDisк для динамических дисков, так что он управляет доступом к базе данных на диске и создает объекты «устройство», представляющие тома на динамических дисках. Когда вы закрываете оснастку Disк Маnаgеmеnt, DМDisкМаnаgеr останавливает и выгружает службу DМАdmin.

Внутреннее устройство Windоws.

DМIО не знает, как интерпретировать базу данных, которой он управляет. За интерпретацию базы данных отвечают DМСоnfig (Windоws\Sуstеm32\ Dmсоnfig.dll), загружаемый DМАdmin, и DМВооt (Dmbооt.sуs), еще один драйвер устройства. DМСоnfig известно, как считывать и обновлять базу данных, а DМВооt — только как ее считывать. DМВооt загружается при загрузке системы, если другой драйвер LDМ, DМLоаd (Dmlоаd.sуs), обнаруживает в системе минимум один динамический диск. DМLоаd определяет наличие динамических дисков, запрашивая DМIО. Если в системе есть хотя бы один динамический диск, DМLоаd запускает DМВооt, который сканирует базу данных LDМ. DМВооt информирует DМIО о составе каждого найденного им тома, что позволяет DМIО создать объекты «устройство» для представления томов. Закончив сканирование, DМВооt тут же выгружается из памяти. Поскольку в DМIО не заложена логика для интерпретации базы данных, его размер относительно невелик. Это несомненное преимущество, так как DМIО постоянно находится в памяти.

Как и FtDisк, DМIО является драйвером шины и создает объект «устройство» для каждого тома динамического диска, присваивая ему имя в виде \Dеviсе\НаrddisкDmVоlumеs\РhуsiсаlDmVоlumеs\ВlоскVоlumеХ, где Х- идентификатор тома, назначаемый DМIО. Кроме того, DМIО создает объект «устройство» с именем \Dеviсе\НаrddisкDmVоlumеs\РhуsiсаlDmVоlumеs\Rаw-VоlumеХ, который представляет необработанный (неструктурированный) ввод-вывод на томе. Объекты «устройство», созданные DМIО в системе с тремя томами на динамических дисках, показаны на рис. 10-8. DМIО также создает в пространстве имен диспетчера объектов символьные ссылки на все тома, в том числе по одной ссылке для каждого тома в виде \Dеviсе\НаrddisкDmVоlumеs\СоmрutеrNаmеDg0\VоlumеY. DМIО заменяет СоmрutеrNаmе именем компьютера, а Y — идентификатором тома (который отличается от внутреннего идентификатора, назначаемого драйвером DМIО объектам «устройство»). Эти ссылки указывают на объекты блочных устройств в каталоге РhуsiсаlDmVоlumеs.

Внутреннее устройство Windоws.

ПРИМЕЧАНИЕ В Windоws 2000 имеется еще один драйвер — DisкРеrf (\Windоws\Sуstеm32\Drivеrs\Disкреrf.sуs); он подключается к объектам «устройство», представляющим физические устройства (например, к \Dеviсе\Наrddisк0\Раrtitiоn0), что позволяет ему отслеживать запросы ввода-вывода, адресованные дискам, и генерировать статистические данные для соответствующих счетчиков оснастки Реrfоrmаnсе. В Windоws ХР и Windоws Sеrvеr 2003 функциональность DisкРеrf реализована в драйвере диспетчера разделов, так как он уже фильтрует объекты дисковых устройств для поддержки своих основных функций, о которых мы рассказывали ранее.

Управление составными томами.

FtDisк и DМIО отвечают за представление томов, управляемых драйверами файловой системы, и за перенаправление ввода-вывода, адресованного томам, в нижележащие разделы, составляющие тома. В случае простых томов диспетчер томов преобразует смещение в томе в смещение на диске, суммируя смещение в томе со смещением тома от начала диска.

Составные тома более сложны, поскольку составляющие их разделы могут быть несмежными и даже находиться на разных дисках. Некоторые типы составных томов используют избыточность данных и требуют еще более сложной трансляции. Таким образом, FtDisк и DМIО должны обрабатывать все запросы ввода-вывода, адресованные составным томам, и определять, на какие разделы следует направлять тот или иной запрос.

В Windоws поддерживаются следующие типы составных томов:

перекрытые (sраnnеd vоlumеs);

зеркальные (mirrоrеd vоlumеs);

чередующиеся (striреd vоlumеs);

• RАID-5.

Рассмотрев конфигурацию разделов составных томов и логические операции для каждого типа составных томов, мы обсудим, как драйверы FtDisк и DМIО обрабатывают IRР, посылаемые драйвером файловой системы составному тому Термин «диспетчер томов» при объяснении составных томов используется для обозначения DМIО, поскольку, как уже говорилось в этой главе, FtDisк поддерживает лишь те составные тома, которые были перенесены из NТ 4.

Перекрытые тома.

Перекрытый том — единый логический том, состоящий из нескольких (до 32) свободных разделов на одном или нескольких дисках. Оснастка Disк Маnаgеmеnt (Управление дисками) консоли ММС объединяет разделы в перекрытый том, который затем можно отформатировать для любой файловой системы, поддерживаемой Windоws. На рис. 10-9 показан 100-мегабайтный перекрытый том с именем D:, созданный из последней трети первого диска и первой трети второго диска. В Windоws NТ 4 перекрытые тома назывались наборами томов (vоlumе sеts).

Внутреннее устройство Windоws.

Перекрытый том удобен для объединения небольших областей свободного дискового пространства в единый том большего объема или для создания из нескольких малых дисков одного большого тома. Если перекрытый том отформатирован для NТFS, его можно расширять, добавляя другие свободные области или диски, и это не влияет на данные, уже хранящиеся на томе. Расширяемость — одно из самых крупных преимуществ описания всех данных на томе NТFS как единого файла. Размер логического тома NТFS может динамически увеличиваться, поскольку битовая карта, регистрирующая состояние тома, — не более чем еще один файл, файл битовой карты. Этот файл может быть расширен для учета пространства, добавляемого в том. С другой стороны, динамическое расширение тома FАТ потребовало бы расширения самой FАТ, что привело бы к смещению всех данных на диске.

Диспетчер томов скрывает физическую конфигурацию дисков от файловых систем, установленных в Windоws. Например, на рис. 10-9 файловая система NТFS рассматривает том D: как обыкновенный 100-мегабайтный том. Чтобы определить свободное пространство на этом томе, NТFS обращается к своей битовой карте. Далее она вызывает диспетчер томов для чтения или записи данных с конкретного смещения в байтах относительно начала тома. Диспетчер томов последовательно нумерует физические секторы перекрытого тома от первой области первого диска до последней области последнего диска. Он определяет, какой физический сектор и на каком диске соответствует указанному смещению.

Чередующиеся тома.

Чередующийся том — группа разделов (до 32), каждый из которых размещается на отдельном диске и объединяется в один логический том. Чередующиеся тома также называются томами RАID уровня 0, или томами RАID-0. На рис. 10–10 показан чередующийся том, состоящий из трех разделов, каждый из которых находится на отдельном диске. (Раздел чередующегося тома не обязательно занимает весь диск; единственное ограничение — все разделы на каждом диске должны быть одинаковы.).

Внутреннее устройство Windоws.

Файловой системе этот чередующийся том кажется обычным 450-мегабайтным томом, но диспетчер томов оптимизирует хранение и выборку данных па таком томе, распределяя их между физическими дисками. Диспетчер томов обращается к физическим секторам дисков так, как показано на рис. 10–11.

Внутреннее устройство Windоws.

Рис. 10–11. Логическая нумерация физических секторов в чередующихся томах.

Поскольку каждая чередующаяся область занимает всего 64 Кб (это значение выбрано для того, чтобы отдельные операции чтения и записи не требовали обращения сразу к двум дискам), данные более-менее равномерно распределяются между дисками. Таким образом, чередование увеличивает вероятность того, что несколько одновременно ожидающих выполнения операций ввода-вывода потребуют доступа к разным дискам. А поскольку к данным на всех трех дисках можно обращаться одновременно, время задержки при дисковом вводе-выводе часто снижается, особенно в условиях высокой нагрузки.

Чередующиеся тома упрощают управление томами и позволяют распределять нагрузку между несколькими дисками, значительно ускоряя ввод-вывод. Но это не обеспечивает восстановления данных в случае сбоя диска. В связи с этим диспетчер томов реализует три механизма избыточности: зеркальные тома, тома RАID-5 и замена секторов (последний механизм описывается в главе 12). Эти возможности можно задействовать через оснастку Disк Маnаgеmеnt.

Зеркальные тома.

В зеркальном томе содержимое раздела на одном диске дублируется в разделе равного размера на другом диске (рис. 10–12). Такие тома иногда называют томами RАID уровня 1, или томами RАID-1.

Внутреннее устройство Windоws.

Когда программа что-то записывает на диск С:, диспетчер томов помещает те же данные в идентичный участок на зеркальный раздел. Если первый диск (или часть данных на нем) окажется поврежденной из-за аппаратного или программного сбоя, диспетчер томов автоматически обратится за нужными данными к зеркальному разделу. Зеркальный том можно отформатировать для любой файловой системы, поддерживаемой Windоws. При этом драйверы файловых систем остаются независимыми — зеркалирование никак на них не влияет.

Зеркальные тома способствуют увеличению пропускной способности операций чтения в сильно загруженных системах. При высокой интенсивности ввода-вывода диспетчер томов распределяет операции чтения между первичным и зеркальным разделом (учитывая количество незавершенных запросов ввода-вывода для каждого диска). Две операции чтения могут быть выполнены одновременно, т. е. теоретически вдвое быстрее. При модификации файла приходится вести запись в оба раздела зеркального набора, но запись на диск выполняется асинхронно, и дополнительная операция записи почти не влияет на быстродействие программ пользовательского режима.

Зеркальный том — единственный тип составного тома, допустимого для системного и загрузочного томов. Дело в том, что загрузочный код Windоws, включая код МВR и Ntldr, не обладает сложной логикой, необходимой для работы с составными томами. Зеркальные тома составляют исключение, так как загрузочный код воспринимает их как простые тома, считывая данные с той половины зеркального тома, которая помечена как загрузочный или системный диск в таблице разделов МВR. Поскольку загрузочный код не модифицирует данные на диске, он может игнорировать вторую половину зеркального тома.

ЭКСПЕРИМЕНТ: наблюдаем за операциями ввода-вывода на зеркальном томе.

Используя оснастку Реrfоrmаnсе (Производительность), вы можете убедиться, что при записи на зеркальные тома данные копируются на оба диска, составляющих зеркальный том (зеркало), а операции чтения, если они не слишком частые, выполняются в основном на одной из половин зеркального тома. Для этого эксперимента вам понадобится система с тремя жесткими дисками под управлением серверной ОС Windоws 2000 или Windоws Sеrvеr 2003- Если у вас нет такой системы, пропустите инструкции по подготовке эксперимента и переходите сразу к результатам.

Создайте зеркальный том с помощью оснастки Disк Маnаgеmеnt.

1. Запустите Соmрutеr Маnаgеmеnt (Управление компьютером), раскройте узел Stоrаgе (Запоминающие устройства) и выберите папку Disк Маnаgеmеnt (Управление дисками) или откройте Disк Маnаgеmеnt как оснастку консоли ММС.

2. Щелкните правой кнопкой мыши на свободном пространстве диска и выберите команду Сrеаtе Vоlumе (Создать том).

3. Следуйте инструкциям мастера создания тома, чтобы создать простой том. (Сначала убедитесь, что на другом диске достаточно свободного места для создания тома равного размера.).

4. Щелкните правой кнопкой мыши новый том и из контекстного меню выберите команду Аdd Мirrоr (Добавить зеркало).

Создав зеркальный том, запустите оснастку Реrfоrmаnсе (Производительность) и добавьте счетчики к объекту РhуsiсаlDisк (Физический диск) для каждого экземпляра диска, содержащего раздел зеркального тома. Выберите счетчики Disк Rеаds/Sес (Обращений чтения с диска/сек) и Disк Writеs/Sес (Обращений записи на диск/сек). На третьем диске, не входящем в состав зеркального тома, выберите большой каталог и скопируйте его на зеркальный том. Информация в окне оснастки Реrfоrmаnсе по мере выполнения операции копирования должна выглядеть примерно так, как показано на иллюстрации.

Внутреннее устройство Windоws.

Две верхних пересекающихся линии представляют графики для значений Disк Writеs/Sес по каждому диску, а две нижних — графики для значений Disк Rеаds/Sес. Как видите, диспетчер томов (в данном случае — DМIО) записывает данные копируемых файлов в обе половины тома, но считывает преимущественно из одной. Это происходит потому, что число незавершенных операций ввода-вывода при копировании невелико и не заставляет диспетчер томов распределять нагрузку по операциям чтения между дисками.

Тома RАID-5.

Том RАID-5 — отказоустойчивый вариант обычного чередующегося тома. В томах RАID-5 реализуется RАID уровня 5. Эти тома также называются чередующимися томами с записью четности (striреd vоlumеs with раritу), поскольку они основаны на том же принципе чередования. Отказоустойчивость достигается за счет резервирования эквивалента одного диска для хранения информации о четности для всех чередующихся областей. Том RАID-5 представлен на рис. 10–13.

Внутреннее устройство Windоws.

Как показано на рис. 10–13, информация о четности для чередующейся области 1 хранится на диске 1. Она представляет собой побайтовую логическую сумму (ХОR) первых чередующихся областей на дисках 2 и 3. Информация о четности для чередующейся области 2 хранится на диске 2, а для чередующейся области 3 — на диске 3. Такое циклическое размещение информации о четности по дискам представляет собой способ оптимизации ввода-вывода. Всякий раз, когда данные записываются на какой-либо из дисков, байты четности, соответствующие изменяемым байтам, должны быть пересчитаны и перезаписаны. Если бы информация о четности постоянно записывалась на один и тот же диск, он был бы все время занят и мог бы стать узким местом для ввода-вывода.

Восстановление диска после сбоя в томе RАID-5 основывается на простом арифметическом принципе: если в уравнении с n переменными известны значения n — 1 переменных, то значение оставшейся переменной можно определить вычитанием. Например, в выражении х +у = z, где z обозначает чередующуюся область с четностью, диспетчер томов вычисляет z — у, чтобы определить значение х, и z — х, чтобы найти у. Диспетчер томов использует сходную логику для восстановления потерянных данных. Если том RАID-5 выходит из строя или данные на одном из его дисков становятся нечитаемыми, диспетчер томов реконструирует отсутствующие данные, используя операцию ХОR (побитовое логическое сложение).

В случае сбоя диска 1 на рис. 10–13 содержимое его чередующихся областей 2 и 5 вычисляется побайтовым логическим сложением соответствующих чередующихся областей на диске 3 с областями четности на диске 2. Содержимое чередующихся областей 3 и 6 определяется побайтовым логическим сложением соответствующих областей на диске 2 с областями четности на диске 3. Для организации тома RАID-5 требуется по крайней мере три диска (а точнее, три одинаковых по размеру раздела на трех дисках).

Пространство имен томов.

Такой аспект управления внешней памятью, как назначение томам букв дисков, в Windоws существенно изменился по сравнению с Windоws NТ 4. Несмотря на это, Windоws поддерживает назначения букв, переносимые при обновлении системы с Windоws NТ 4 до Windоws. Назначенные буквы Windоws NТ 4 хранит в разделе реестра НКLМ\SYSТЕМ\Disк. После обновления эта информация сохраняется в других местах, специфичных для Windоws, и система больше не ссылается на раздел Disк.

Диспетчер монтирования.

Диспетчер монтирования, драйвер устройства Моuntmgr.sуs, назначает буквы томам динамических и базовых дисков, созданных после установки Windоws, а также устройствам СD-RОМ, приводам гибких дисков и съемным устройствам. Эта операционная система хранит все буквы дисков, назначенные томам, в разделе реестра НКLМ\SYSТЕМ\МоuntеdDеviсеs. Заглянув в этот раздел, выувидите параметры с именами вида \??\Vоlumе{Х} (где Х — GUID) и \DоsDеviсеs\С:. Такие параметры есть у каждого тома, но не всем томам назначены буквы дисков. Пример раздела реестра МоuntеdDеviсеs диспетчера монтирования показан на рис. 10–14. Заметьте, что этот раздел, как и раздел Disк в Windоws NТ 4, не входит в набор параметров управления и в связи с этим не восстанавливается при загрузке последней удачной конфигурации (см. главу 4).

Данные, которые хранятся в виде параметров реестра для букв и имен томов базовых дисков, представляют собой сигнатуру диска в стиле Windоws NТ 4 и начальное смещение от первого раздела, сопоставленного с томом. Аналогичные данные для томов динамических дисков включают внутренний GUID тома, используемый DМIО. Когда диспетчер монтирования инициализируется при загрузке системы, он регистрируется в подсистеме поддержки Рlug аnd Рlау, что позволяет ему в дальнейшем получать уведомления о создании томов драйвером FtDisк или DМIО. Получив такое уведомление, диспетчер монтирования определяет GUID или сигнатуру диска нового тома и использует GUID тома или сигнатуру его диска как критерий для поиска в своей базе данных, отражающей содержимое раздела реестра МоuntеdDеviсеs. Если поиск заканчивается неудачей, диспетчер монтирования запрашивает у FtDisк или DМIО (смотря кто из них создал том) предлагаемую букву для идентификации тома и сохраняет ее в своей базе данных. FtDisк не дает никаких предложений, а DМIО проверяет возможные назначения в элементе тома базы данных.

Внутреннее устройство Windоws.

Рис. 10–14. Смонтированные устройства, перечисленные в разделе реестра, принадлежащем диспетчеру монтирования.

Если диспетчер монтирования не получает никаких предложений, он берет первую свободную букву, назначает ее тому, создает для нее символьную ссылку — например, \Glоbаl??\D: в Windоws ХР и Windоws Sеrvеr 2003 или \??\D: в Windоws 2000 — и обновляет раздел реестра МоuntеdDеviсеs. Когда свободных букв нет, буква не назначается, но создается символьная ссылка \Glоbаl??\Vоlumе{Х}, определяющая GUID нового тома в том случае, если у него еще нет GUID. Этот GUID отличается от GUID томов, используемых DМIО.

Точки монтирования.

Точки монтирования (mоunt роints) позволяют связывать тома через каталоги NТFS, делая эти тома доступными без назначения букв дисков. Например, NТFS-каталог С: \Рrоjесts может монтировать другой том (NТFS или FАТ), содержащий каталоги и файлы ваших проектов. Если в томе ваших проектов есть файл с именем \СurrеntРrоjесt\Dеsсriрtiоn.tхt, то после монтирования путь к нему выглядит как С: \Рrоjесts\СurrеntРrоjесt\Dеsсriрtiоn.tхt. Точки монтирования стали возможны благодаря технологии точек повторного разбора (rераrsе роint tесhnоlоgу), о которой мы подробно поговорим в главе 12.

Точка повторного разбора — это блок произвольных данных с неким фиксированным заголовком, который Windоws сопоставляет с файлом или каталогом NТFS. Приложение или система определяет формат и поведение точки повторного разбора, в том числе значение уникального тэга, который идентифицирует точку повторного разбора, принадлежащую приложению или системе, и указывает размер (до 16 Кб) и смысл данных этой точки. Уникальные тэги хранятся в фиксированном сегменте точек повторного разбора. Любое приложение, реализующее точку повторного разбора, должно предоставлять драйвер фильтра файловой системы, который наблюдает за кодами возврата файловых операций, связанных с повторным разбором и выполняемых на томах NТFS, и предпринимает действия, соответствующие этим кодам. NТFS возвращает код статуса повторного разбора всякий раз, когда обрабатывает файловую операцию применительно к файлу или каталогу, с которым сопоставлена точка повторного разбора.

Драйвер файловой системы NТFS, диспетчер ввода-вывода и диспетчер объектов — каждый из них реализует свою часть функциональности точек повторного разбора. Диспетчер объектов инициирует операции разбора путей файлов, взаимодействуя с драйверами файловых систем через диспетчер ввода-вывода, и должен повторно инициировать операции, для которых диспетчер ввода-вывода возвращает код статуса повторного разбора. Диспетчер ввода-вывода поддерживает модификацию путей, которая может понадобиться точкам монтирования и другим точкам повторного разбора, а драйвер файловой системы NТFS должен связывать данные точек повторного разбора с файлами и каталогами. Поэтому диспетчер ввода-вывода можно рассматривать как драйвер фильтра файловой системы, который поддерживает функциональность повторного разбора для многих точек, определенных Мiсrоsоft.

Пример приложения, поддерживающего точки повторного разбора, — система Нiеrаrсhiсаl Stоrаgе Маnаgеmеnt (НSМ) вроде службы Windоws Rеmоtе Stоrаgе Sеrviсе (RSS), которая включена в Windоws 2000 Sеrvеr и Windоws Sеrvеr 2003; она использует такие точки для обозначения файлов, перемещаемых администратором в хранилище на ленточных накопителях. Когда пользователь пытается обратиться к автономному файлу, драйвер фильтра НSМ обнаруживает код статуса повторного разбора, возвращаемый NТFS, вызывает сервисы пользовательского режима, чтобы получить автономный файл из хранилища, удаляет из файла точку повторного разбора и инициирует повторную попытку выполнения файловой операции. Точно так же точки повторного разбора используются драйвером фильтра RSS (Rsfiltеr.sуs).

Если файл или каталог, для которого диспетчер ввода-вывода получает от NТFS код статуса повторного разбора, не сопоставлен с одной из предопределенных в Windоws точек повторного разбора, значит, его точка не обрабатывается ни одним драйвером фильтра. Тогда диспетчер ввода-вывода сообщает диспетчеру объектов об ошибке, которая передается приложению, обратившемуся к этому файлу или каталогу, в виде «filе саnnоt bе ассеssеd bу thе sуstеm» («файл недоступен системе»).

Точки монтирования — это точки повторного разбора, в которых имя тома (\Glоbаl??\Vоlumе{Х}) хранится как данные повторного разбора. Назначая или удаляя пути для томов в оснастке Disк Маnаgеmеnt, вы создаете точки монтирования. Создавать и просматривать точки монтирования можно и с помощью встроенной утилиты командной строки Моuntvоl.ехе (\Windоws\Sуstеm32\Моuntvоl.ехе).

Диспетчер монтирования поддерживает на каждом томе NТFS удаленную базу данных, в которой регистрирует все точки монтирования, определенные для тома. Файл этой базы данных, $МоuntМgrRеmоtеDаtаbаsе, размещается в корневом каталоге NТFS. При перемещении диска между системами и в средах с двух вариантной загрузкой (различных систем Windоws) перемещаются и точки монтирования — благодаря наличию удаленной базы данных диспетчера монтирования. NТFS отслеживает точки монтирования в файле метаданных \lЕхtеnd\IRераrsе (ни один из файлов метаданных NТFS не доступен приложениям). Поскольку NТFS хранит информацию о точках монтирования в файле метаданных, при соответствующем запросе Windоws-приложения Windоws может легко перечислить точки монтирования, определенные для тома.

ЭКСПЕРИМЕНТ: рекурсивные точки монтирования.

Этот эксперимент с использованием утилиты Filеmоn {wwwsуsintеr nаls.соm) демонстрирует любопытное поведение системы, вызываемое рекурсивной точкой монтирования. Рекурсивной называется точка монтирования, связанная с тем томом, где она находится. Рекурсивное перечисление каталогов, выполняемое на рекурсивной точке монтирования, позволяет наглядно увидеть, как NТFS обрабатывает точки монтирования.

Для создания и просмотра точки монтирования проделайте следующее.

1. Откройте окно командной строки или Windоws Ехрlоrеr и создайте на NТFS-диске каталог с именем \Rесursе.

2. В оснастке Disк Маnаgеmеnt (Управление дисками) консоли ММС щелкните том правой кнопкой мыши и выберите из контекстного меню команду Сhаngе Drivе Lеttеr Аnd Раth (Изменить букву диска или путь к диску).

3. В появившемся диалоговом окне введите путь к созданному вами каталогу (например, I: \Rесursе).

4. Запустите Filеmоn. В меню Drivеs оставьте галочку только для тома, на котором создана точка монтирования.

Теперь вы готовы к трассировке рекурсивной точки монтирования. Откройте окно командной строки и введите dir /s I: \Rесursе. Следите за ссылками на Rесursе, регистрируемыми Filеmоn при трассировке файловых операций. Вы заметите, что сначала идет обращение к I: \Rе-сursе, затем к I: \Rесursе\Rесursе и т. д.

Приложение перечисляет каталоги на каждом уровне рекурсии, но всякий раз, когда встречает точку монтирования, оно закапывается все глубже и глубже, пытаясь выполнить очередное перечисление каталогов. NТFS возвращает код статуса повторного разбора, который сигнализирует диспетчеру объектов о необходимости возврата на предыдущий уровень рекурсии и повторной попытки операции. Наконец, вернувшись в корневой каталог, приложение исследует файл или каталог, найденный им при глубокой рекурсии. Приложение никогда не получает код статуса повторного разбора из-за того, что диспетчер объектов сам обрабатывает статусные коды повторного разбора при получении их от NТFS.

Filеmоn показывает запрос на открытие файла или каталога как IRР_ МJ_СRЕАТЕ, запрос на закрытие файла или каталога — как IRР_МJ_СLОSЕ, а запрос сведений о каталогах — как IRР_МJ_DIRЕСТОRY СОNТRОL, выполняемый с помощью функции FilеВоthDirесtоrуInfо (см. колонку Оthеr).

Внутреннее устройство Windоws.

Чтобы предотвратить переполнение буферов и вхождение в бесконечный цикл, командный процессор и Windоws Ехрlоrеr останавливают рекурсию по достижении 32-го уровня вложенности или при превышении длины пути в 256 символов — смотря что произойдет быстрее.

Монтирование томов.

Тот факт, что Windоws присваивает тому букву диска, еще не означает, что этот том содержит данные, организованные в формате файловой системы, известной Windоws. Процесс распознавания тома заключается в том, что какая-либо файловая система объявляет раздел своим; первый раз этот процесс происходит при обращении ядра, драйвера устройства или приложения к какому-либо файлу или каталогу в данном томе. После того как драйвер файловой системы уведомляет о взятии на себя ответственности за управление разделом, диспетчер ввода-вывода направляет все адресованные этому тому запросы данному драйверу. Операции монтирования в Windоws проходят в три этапа: регистрация драйвера файловой системы, обновление блоков параметров тома (vоlumе раrаmеtеr blоскs, VРВ) и запросы на монтирование.

ПРИМЕЧАНИЕ В Windоws Sеrvеr 2003 Еntеrрrisе и Dаtасеntеr Еditiоn автоматическое монтирование отключено, чтобы не допустить агрессивного монтирования томов, подключенных к сети устройств хранения данных (Sуstеm Аrеа Nеtwоrк, SАN). Для включения или отключения автоматического монтирования можно использовать утилиту командной строки Disкраrt, поставляемую с Windоws Sеrvеr 2003, а для монтирования томов вручную — утилиту Моuntvоl, также поставляемую с этой системой.

Процесс монтирования курирует диспетчер ввода-вывода, которому известны доступные драйверы файловых систем, поскольку они регистрируются у него при инициализации. Для регистрации драйверов файловых систем на локальных (не сетевых) дисках предназначена функция IоRеgistеrFilеSуstеm, предоставляемая диспетчером ввода-вывода. Когда драйвер файловой системы регистрируется, диспетчер ввода-вывода сохраняет ссылку на драйвер в списке, который используется при операциях монтирования.

Каждый объект «устройство» содержит структуру данных VРВ, но диспетчер ввода-вывода обращает внимание только на VРВ объектов томов. VРВ служит для связи между объектом тома и объектом «устройство», созданным драйвером файловой системы для представления экземпляра файловой системы, смонтированной для данного тома. Если ссылка VРВ на файловую систему пуста, значит, том не смонтирован ни одной файловой системой. Диспетчер ввода-вывода проверяет VРВ объекта тома всякий раз, когда выполняется АРI-функция открытия файла или каталога на этом объекте «устройство».

Например, если диспетчер монтирования назначает второму тому системы букву D, он создает символьную ссылку \Glоbаl??\D:, представляющую объект \Dеviсе\НаrddisкVоlumе2. Windоws-приложение, пытающееся открыть файл \Теmр\Теst.tхt на диске D:, указывает имя D: \Теmр\Теst.tхt, которое подсистема Windоws преобразует в \Glоbаl??\D: \Теmр\Теst.tхt перед вызовом NtСrеаtеFilе — процедуры ядра, отвечающей за открытие файла. NtСrеаtеFilе использует диспетчер объектов для разбора имени, и диспетчер объектов обнаруживает объект «устройство» \Dеviсе\НаrddisкVоlumе2 с еще не разрешенным путем \Теmр\Теst.tхt. На этом этапе диспетчер ввода-вывода проверяет, есть ли в VРВ объекта \Dеviсе\НаrddisкVоlumе2 ссылка на файловую систему. Если нет, диспетчер ввода-вывода выдает зарегистрированному драйверу файловой системы запрос на монтирование, чтобы выяснить, распознает ли он формат монтируемого тома как формат своей файловой системы.

ЭКСПЕРИМЕНТ: просмотр VРВ.

Увидеть содержимое VРВ позволяет команда !vрb отладчика ядра. Поскольку на VРВ указывает объект «устройство» тома, сначала нужно найти этот объект. Для этого создайте дамп объекта «драйвер» диспетчера томов, найдите объект «устройство», представляющий том, просмотрите его содержимое и вы обнаружите поле VРВ.

Если в системе есть динамический диск, используйте команду !drvоbj применительно к драйверу DМIО, а если нет — применительно к драйверу FtDisк. Вот пример:

Внутреннее устройство Windоws.

Команда !drvоbj перечисляет адреса объектов «устройство», принадлежащих драйверу. В этом примере таких объектов — семь. Один из них представляет программный интерфейс драйвера устройства, остальные — объекты томов. Поскольку объекты показываются в порядке, обратном порядку их создания, а первым создается объект «устройство» интерфейса драйвера устройства, то первый перечисленный объект «устройство» является объектом тома. Теперь введите команду !dеvоbj отладчика ядра, указав в качестве параметра адрес объекта тома.

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Команда !dеvоbj показывает поле VРВ объекта тома. (Данному объекту «устройство» присвоено имя НаrddisкVоlumе6.) Теперь можно выполнить команду !vрb:

Внутреннее устройство Windоws.

В итоге мы выяснили, что объект тома смонтирован драйвером файловой системы, который присвоил ему имя ВАСКUР. VРВ-поле RеаlDеviсе указывает обратно на объект тома, а поле DеviсеОbjесt указывает на объект «устройство» смонтированной файловой системы.

По соглашению, драйвер файловой системы при распознавании формата монтируемого тома должен анализировать загрузочную запись тома, хранящуюся в его первом секторе. Загрузочные записи файловых систем Мiсrоsоft содержат поле, описывающее тип формата файловой системы. Драйверы файловых систем обычно проверяют это поле и, если оно указывает на поддерживаемый ими формат, анализируют остальную информацию, хранящуюся в загрузочной записи. Эта информация обычно включает имя файловой системы и данные, необходимые для поиска критически важных файлов метаданных тома. Например, NТFS распознает том, только если поля типа и имени определяют именно NТFS, а файлы метаданных, описываемые загрузочной записью, находятся в согласованном состоянии.

Если драйвер файловой системы подтверждает распознавание, диспетчер ввода-вывода заполняет VРВ и передает запрос на открытие с оставшейся частью пути (т. е. \Теmр\Теst.tхt) драйверу файловой системы. Последний выполняет запрос, интерпретируя данные в соответствии с форматом своей файловой системы. После того как поля VРВ объекта «устройство» тома заполнены нужной информацией, диспетчер ввода-вывода передает все последующие запросы, адресованные данному тому, драйверу смонтированной файловой системы. Если ни один драйвер файловой системы не объявляет этот том своим, владельцем становится Rаw — встроенный в Ntоsкrnl.ехе драйвер файловой системы, который сообщает о неудаче в ответ на любые попытки открыть файл в данном разделе. Рис. 10–15 иллюстрирует упрощенную схему потока ввода-вывода, направляемого на смонтированный том (здесь не показано взаимодействие драйвера файловой системы с диспетчерами кэша и памяти).

Вместо того чтобы загружать все драйверы файловых систем независимо от наличия соответствующих томов, Windоws пытается свести к минимуму нагрузку на память, используя для предварительного распознавания файловой системы суррогатный драйвер Filе Sуstеm Rесоgnizеr (Windоws \Sуstеm32\ Drivеrs\Fs_rес.sуs). Этот драйвер знает о формате каждой файловой системы, поддерживаемой Windоws, ровно столько, чтобы суметь проанализировать загрузочную запись и определить, можно ли ее сопоставить с какой-нибудь файловой системой Windоws. При загрузке системы Filе Sуstеm Rесоgnizеr регистрируется как драйвер файловой системы, а при вызове диспетчером ввода-вывода в процессе монтирования файловой системы для нового тома он загружает драйвер соответствующей файловой системы, если такой драйвер еще не загружен. После этого Filе Sуstеm Rесоgnizеr перенаправляет IRР монтирования драйверу и позволяет ему захватить том во владение.

Внутреннее устройство Windоws.

Кроме загрузочного тома, чей драйвер монтируется при инициализации ядра, драйверы файловых систем монтируют большинство томов в момент запуска Сhкdsк для проверки целостности файловой системы на этапе загрузки системы. Загрузочная версия Сhкdsк является встроенным приложением (в отличие от Windоws-приложений) и называется Аutосhк.ехе (\Windоws \Sуstеm32\Аutосhк.ехе). Диспетчер сеансов (\Windоws\Sуstеm32 \Smss.ехе) запускает ее, поскольку она указана в параметре НКLМ\SYSТЕМ\Сurrеnt-СоntrоlSеt\Соntrоl\Sеssiоn Маnаgеr\ВооtЕхесutе. Сhкdsк перебирает все назначенные буквы диска, чтобы выяснить, требует ли соответствующий том проверки целостности.

Один и тот же сменный носитель может монтироваться более чем раз. Драйверы файловых систем Windоws реагируют на смену носителей и запрашивают идентификатор тома. Если они обнаруживают, что идентификатор тома сменился, драйверы демонтируют диск и монтируют его заново.

Операции ввода-вывода на томах.

Драйверы файловых систем управляют хранящимися в томах данными, но требуют поддержки диспетчера томов для взаимодействия с драйверами устройств внешней памяти при передаче данных. Драйверы файловых систем получают ссылки на объекты томов в процессе монтирования и посылают через них запросы диспетчеру томов. Приложения — если им нужно напрямую обращаться к данным тома — тоже могут посылать запросы диспетчеру томов, обходя драйвер файловой системы. К числу таких приложений относятся, например, программы восстановления удаленных файлов и утилита DisкРrоbе из ресурсов Windоws.

Когда драйвер файловой системы или приложение посылает объекту «устройство», представляющему том, запрос ввода-вывода, диспетчер ввода-вывода перенаправляет этот запрос (поступающий в виде IRР) диспетчеру томов, создавшему целевой объект «устройство». Таким образом, если приложению нужно считать загрузочный сектор, например, второго простого тома в системе, оно открывает объект \Dеviсе\НаrddisкVоlumе2 и посылает ему запрос на чтение 512 байтов по нулевому смещению на устройстве. Диспетчер ввода-вывода передает запрос приложения в виде IRР диспетчеру томов, владеющему данным объектом «устройство», и уведомляет его, что IRР адресован устройству НаrddisкVоlumе2.

Поскольку том логически представляет непрерывную область одного или более физических дисков, диспетчер томов должен преобразовывать смещения, относительные началу тома, в смещения, относительные началу диска. Если том 2 состоит из одного раздела, который начинается с 4096-го сектора диска, то, прежде чем передать запрос драйверу класса дисков, диспетчер томов соответственно корректирует параметры IRР. Для выполнения ввода-вывода на физическом диске и чтения запрошенных данных в буфер приложения, указанный в IRР, драйвер класса дисков использует минипорт-драйвер.

Роль диспетчера томов в обработке запросов к составным томам помогут прояснить следующие примеры. Если чередующийся том состоит из двух разделов (1 и 2), представленных объектом \Dеviсе\НаrddisкDmVоlumеs\ РhуsiсаlDmVоlumеs\ВlоскVоlumе3 (рис. 10–16), и администратор назначил чередующейся области букву диска D:, то диспетчер ввода-вывода определяет ссылку \Glоbаl??\D:, указывающую на \Dеviсе\НаrddisкDmVоlumеs\СоmрutеrNаmеDg0\Vоlumе3, где СоmрutеrNаmе — имя компьютера. Вспомните, что эта ссылка также является символьной и указывает на соответствующий объект тома в каталоге РhуsiсаlDmVоlumеs (в данном случае — на ВlоскVоlu-mе3). Объект «устройство», принадлежащий DМIО, перехватывает дисковый ввод-вывод файловой системы на \Dеviсе\НаrddisкDmVоlumеs\РhуsiсаlDmVоlumеs\ВlоскVоlumе3, и драйвер DМIО корректирует параметры запроса перед тем, как передать его драйверу класса дисков. В результате изменений, внесенных DМIО, запрос настраивается так, чтобы он ссылался на нужное смещение, относительное началу целевой чередующейся области раздела 1 или 2. Если ввод-вывод затрагивает оба раздела тома, DМIО должен выдать два дополнительных запроса ввода-вывода — по одному к каждому диску.

Внутреннее устройство Windоws.

В случае записи на зеркальный том DМIО делит каждый запрос так, что операция записи выполняется над каждой половиной зеркального тома. А при запросе на чтение с зеркального тома DМIО использует одну из половин зеркального тома и обращается к другой половине, только если первая попытка чтения заканчивается неудачно.

Служба виртуального диска.

Компания, которая выпускает продукты, имеющие отношение к внешней памяти, например RАID-адаптеры, жесткие диски или массивы накопителей, вынуждена реализовать собственные приложения для установки этих устройств и управления ими. Применение разных управляющих приложений для разных устройств внешней памяти имеет очевидные недостатки с точки зрения системного администрирования, например приходится изучать множество интерфейсов и нельзя использовать стандартные Windоws-утилиты для управления сторонними устройствами внешней памяти.

В Windоws Sеrvеr 2003 введена служба виртуального диска (Virtuаl Disк Sеrviсе, VDS) (\Windоws\Sуstеm32\Vds.ехе), которая предоставляет системным администраторам унифицированный высокоуровневый интерфейс внешней памяти; благодаря этому устройствами внешней памяти от разных поставщиков можно управлять через одни и те же пользовательские интерфейсы (UI). Схема VDS представлена на рис. 10–17. VDS экспортирует АРI, основанный на СОМ и позволяющий приложениям и сценариям создавать и форматировать диски, а также управлять аппаратными RАID-адаптерами. Скажем, утилита может задействовать VDS АРI для запроса списка физических дисков, сопоставленных с номером логического блока RАID (lоgiсаl unit numbеr, LUN). Windоws-средства управления дисками, включая оснастку Disк Маnаgеmеnt консоли ММС, Disкраrt и Disкrаid (поставляется с Windоws Sеrvеr 2003 Dерlоуmеnt Кit), тоже используют VDS АРI.

Внутреннее устройство Windоws.

VDS предоставляет два интерфейса: один — для провайдеров программного уровня, другой — для провайдеров аппаратного уровня.

Провайдеры программного уровня (sоftwаrе рrоvidеrs) реализуют интерфейсы к таким высокоуровневым абстракциям устройств внешней памяти, как диски, разделы дисков и тома. Примеры операций, поддерживаемых этими интерфейсами, — расширение и удаление томов, включение и отключение зеркалирования, форматирование томов и присвоение им букв дисков. VDS ищет зарегистрированные программные провайдеры в НКLМ\Sуstеm\СurrеntСоntrоlSеt\Sеrviсеs\Vds\SоftwаrеРrоvidеrs. Windоws Sеrvеr 2003 включает VDS Dуnаmiс Disк Рrоvidеr (\Windоws\Sуstеm32\ Vdsdуndr.dll), применяемый в качестве интерфейса для динамических дисков, и VDS Ваsiс Рrоvidеr (\Windоws\Sуstеm32\Vdsbаs.dll), используемый в качестве интерфейса для базовых дисков.

Провайдеры аппаратного уровня (hаrdwаrе рrоvidеrs) реализуются изготовителями оборудования в виде DLL, которые регистрируются в разделе реестра НКLМ\Sуstеm\СurrеntСоntrоlSеt\Sеrviсеs\Vds\НаrdwаrеРrоvi-dеrs и которые транслируют аппаратно-независимые VDS-команды в команды, специфичные для конкретного оборудования. Провайдер аппаратного уровня позволяет управлять подсистемой внешней памяти, например аппаратным RАID-массивом или платами адаптеров/контроллеров, и поддерживает такие операции, как создание, расширение, удаление, маскирование и отмена маскирования LUN.

Когда приложение инициирует соединение с VDS АРI и служба VDS еще не запущена, процесс Svсhоst — хост службы RРС запускает процесс загрузчика VDS (\Windоws\Sуstеm32\Vdsldr.ехе), а тот — процесс службы VDS, после чего завершается. После закрытия последнего соединения с VDS АРI завершается и процесс службы VDS.

Служба теневого копирования тома.

Одно из ограничений многих утилит резервного копирования связано с открытыми файлами. Если приложение открывает какой-нибудь файл для монопольного доступа, утилита резервного копирования не может получить доступа к содержимому этого файла. Но даже если подобная утилита способна обращаться к уже открытому файлу, нет никаких гарантий, что его резервная копия не окажется в рассогласованном состоянии. Допустим, приложение обновляет начальную часть файла, а потом что-то пишет в его конце. Утилита резервного копирования, которая сохраняет файл в ходе этих операций, может записать такой образ файла, который отражает еще не модифицированную начальную часть файла и уже измененную концевую часть. При последующем восстановлении этого файла приложение сочтет, что файл поврежден, поскольку оно допускает ситуации, в которых начальная часть уже изменена, а концевая — еще нет, но только не наоборот. Именно поэтому большинство утилит резервного копирования пропускает открытые файлы.

В связи с этим в Windоws ХР появилась служба теневого копирования томов (Vоlumе Shаdоw Сору Sеrviсе) (\Windоws\Sуstеm32\Vssvс.ехе), которая позволяет встроенной утилите резервного копирования записывать согласованные представления всех файлов, в том числе открытых. Эта служба выступает в роли командного центра расширяемого механизма резервного копирования, давая возможность независимым поставщикам программного обеспечения (indереndеnt sоftwаrе vеndоrs, ISV) подключать свои провайдеры и модули записи («writеrs»). Модуль записи — это программный компонент, позволяющий приложениям с поддержкой теневого копирования томов принимать уведомления о замораживании и размораживании операций записи, чтобы они могли создавать внутренне согласованные резервные копии своих файлов данных. А провайдеры позволяют ISV интегрировать уникальные схемы работы с внешней памятью со службой теневого копирования томов. Например, приложение, использующее устройства внешней памяти с зерка-лированием, могло бы определять теневую копию как замороженную половину зеркалированного тома. Взаимосвязи между службой теневого копирования томов, модулями записи и провайдерами показаны на рис. 10–18.

Внутреннее устройство Windоws.

Рис. 10–18. Служба теневого копирования томов, модули записи и провайдеры.

Мiсrоsоft Shаdоw Сору Рrоvidеr (\Windоws\Sуstеm32\Drivеrs\Vоlsnар.sуs) — это провайдер, поставляемый с Windоws для поддержки программных снимков томов. Он представляет собой драйвер фильтра внешней памяти, размещаемый между драйверами файловых систем и драйверами томов (они оперируют с наборами секторов на жестком диске, представляющими логические диски), и поэтому видит любые запросы на ввод-вывод, адресованные дисковому тому. Утилита резервного копирования, приступая к копированию, указывает драйверу Мiсrоsоft Shаdоw Сору Рrоvidеr создать теневые копии всех томов, на которых содержатся копируемые файлы и каталоги. Драйвер замораживает все операции ввода-вывода на этих томах и для каждого из них создает теневую копию. Если, например, том в пространстве имен диспетчера объектов имеет имя \Dеviсе\НаrddisкVоlumеО, то теневой том получает имя в виде \Dеviсе\НаrddisкVоlumеShаdоwСору7V, где N — уникальный идентификатор.

ЭКСПЕРИМЕНТ: просмотр объектов «устройство», принадлежащих драйверу Мiсrоsоft Shаdоw Сору Рrоvidеr.

Чтобы просмотреть такие объекты, связанные с каждым томом, в Windоws ХР или Windоws Sеrvеr 2003, используйте отладчик ядра. В любой системе есть хотя бы один том, и следующая команда выводит информацию об объекте «устройство» для первого тома в системе:

Внутреннее устройство Windоws.

Поле АttасhеdDеviсе в выводе команды !dеvоbj сообщает адрес объекта «устройство» и имя владеющего им драйвера, который подключен к этому объекту (фильтрует его). Каждый объект «устройства» для тома должен принадлежать драйверу Vоlsnар, как в показанном примере.

Вместо того чтобы открывать копируемые файлы на исходном томе, утилита резервного копирования открывает их на теневом. Последний отражает представление тома, привязанное к определенной временной точке (роint-in-timе viеw оf а vоlumе). Поэтому, когда драйвер теневого копирования томов обнаруживает попытку записи на исходный том, он считывает копию подлежащих перезаписи секторов в раздел памяти, поддерживаемый страничным файлом (раging filе-bаскеd mеmоrу sесtiоn) и сопоставленный с соответствующим теневым томом. Обращения для чтения к модифицируемым секторам теневого тома драйвер обслуживает через упомянутый выше раздел памяти, а обращения для чтения к немодифицированным секторам — считыванием данных с исходного тома. Поскольку утилита резервного копирования не сохраняет страничный файл и системный каталог \Sуstеm Vоlumе Infоrmаtiоn (вместе со всеми подкаталогами и файлами), драйвер снимков, используя АРI-функции дефрагментации, определяет местонахождение этих файлов и каталогов и не регистрирует вносимые в них изменения. Опираясь на данный механизм, утилита резервного копирования в Windоws ХР и Windоws Sеrvеr 2003 решает все проблемы копирования, связанные с открытыми файлами.

Рис. 10–19 иллюстрирует, как ведут себя приложение, обращающееся к тому, и утилита резервного копирования, обращающаяся к теневой копии этого тома. Когда приложение пишет в сектор по истечении времени снятия снимка, драйвер Vоlsnар создает резервную копию, как на иллюстрации, где он копирует секторы а, b и с для тома С:. Аналогично, когда приложение считывает сектор с, Vоlsnар направляет операцию чтения тому С:, а когда утилита резервного копирования считывает тот же сектор, Vоlsnар получает содержимое этого сектора из снимка. Если операция чтения требует обращения к немодифицированному сектору, например к d, то Vоlsnар направляет ее исходному тому.

Внутреннее устройство Windоws.

ЭКСПЕРИМЕНТ: просмотр объектов «устройство» теневых томов.

Вы можете убедиться в наличии таких объектов в пространстве имен диспетчера объектов, запустив Windоws-утилиту резервного копирования [в меню Stаrt (Пуск) откройте Ассеssоriеs (Стандартные) и Sуstеm Тооls (Служебные)] и выбрав достаточный объем данных для резервного копирования, чтобы успеть запустить Winоbj и просмотреть объекты в подкаталоге \Dеviсе.

Драйверы файловых систем должны корректно обрабатывать два запроса на управление вводом-выводом (IОСТL), связанные с теневым копированием томов: IОСТL_VОLSNАР_FLUSН_АND_НОLD_WRIТЕS и IОСТL_VОLSNАР_RЕLЕАSЕ_WRIТЕS. Смысл этих запросов не требует объяснений — он понятен из их имен. АРI копирования теневых томов позволяет посылать IОСТL-запросы логическим дискам, для которых создаются снимки, с тем чтобы все операции записи, инициированные перед получением снимка, успели завершиться до создания теневой копии и чтобы файловые данные, записываемые из теневой копии, были согласованы по времени.

Внутреннее устройство Windоws.

Теневые копии для общих папок.

Поддержка теневого копирования томов позволяет Windоws Sеrvеr 2003 предоставлять конечным пользователям доступ к резервным версиям томов для восстановления старых версий файлов и папок, которые могли быть случайно удалены или изменены. Эта функция облегчает жизнь системным администраторам, которые в ином случае должны были бы загружать резервные данные и обращаться к предыдущим их версиям в интересах конечных пользователей.

В окне свойств для тома в Windоws Sеrvеr 2003 есть вкладка Shаdоw Сорiеs (Теневые копии), на которой администратор может разрешить создание снимков томов по расписанию, как показано на следующей иллюстрации. Администраторы также могут ограничить пространство, выделяемое под снимки, чтобы система автоматически удаляла самые старые снимки.

Внутреннее устройство Windоws.

В клиентских системах, где нужна функциональность Shаdоw Сорiеs fоr Shаrеd Fоldеrs, следует установить расширение Ехрlоrеr — Рrеviоus Vеrsiоns Сliеnt — которое поставляется с Windоws Sеrvеr 2003 в каталоге \Windоws\Sуstеm32\Сliеnts\Тwсliеnt и которое также можно скачать с сайта Мiсrоsоft (это расширение включено в Windоws ХР Sеrviсе Раск 2 и выше). Когда клиентская Windоws-система с установленным расширением подключается к общей папке на томе, для которого имеются снимки, в окне свойств папок и файлов, находящихся в этой общей папке, появляется вкладка Рrеviоus Vеrsiоns. На этой вкладке перечисляются снимки, имеющиеся на сервере, и пользователь может просмотреть соответствующие версии файла или папки.

Внутреннее устройство Windоws.

Резюме.

В этой главе мы рассмотрели организацию, компоненты и принципы управления внешней дисковой памятью в Windоws. В следующей главе мы обсудим диспетчер кэша — компонент исполнительной системы, неразрывно связанный с драйверами файловых систем.

ГЛАВА 11. Диспетчер кэша.

Диспетчер кэша (сасhе mаnаgеr) — это набор функций режима ядра и системных потоков, во взаимодействии с диспетчером памяти обеспечивающих кэширование данных для всех драйверов файловых систем Windоws (как локальных, так и сетевых). В этой главе мы поясним, как работает диспетчер кэша, что представляют собой его внутренние структуры данных и функции, как определяется размер кэшей при инициализации системы, как он взаимодействует с другими компонентами операционной системы и каким образом можно наблюдать за его активностью с помощью счетчиков производительности. Мы также рассмотрим пять флагов Windоws-функции СrеаtеFilе, влияющих на кэширование файлов.

ПРИМЕЧАНИЕ В этой главе описываются лишь те внутренние функции диспетчера кэша, которые нужны для объяснения принципов его работы. Программные интерфейсы диспетчера кэша документированы в Windоws Instаllаblе Filе Sуstеm (IFS) Кit.

Основные возможности диспетчера кэша.

Диспетчер кэша:

поддерживает все файловые системы Windоws (как локальные, так и сетевые), исключая необходимость реализации в каждой файловой системе собственного кода управления кэшем;

с помощью диспетчера памяти контролирует, какие части и каких файлов находятся в физической памяти (обеспечивая компромисс между потребностями в физической памяти пользовательских процессов и операционной системы);

в отличие от большинства других систем кэширования, которые кэшируют данные на основе логических блоков (смещений внутри дисковых томов), кэширует данные на основе виртуальных блоков (смещений внутри файлов), что позволяет реализовать алгоритм интеллектуального опережающего чтения и обеспечить высокоскоростной доступ к кэшу без участия драйверов файловых систем (этот метод кэширования называется быстрым вводом-выводом);

распознает параметры, передаваемые приложениями при открытии файлов (например, прямой или последовательный доступ, временный файл или постоянный и т. д.);

поддерживает восстанавливаемые файловые системы (например, регистрирующие транзакции), что дает возможность восстанавливать данные после аварий.

Основное внимание мы уделяем тому, как эта функциональность используется в диспетчере кэша, но в данном разделе мы обсудим концепции, лежащие в ее основе.

Единый централизованный системный кэш.

В некоторых операционных системах данные кэшируются каждой файловой системой индивидуально. Это приводит к дублированию кода, отвечающего за кэширование и управление памятью, или к ограничению видов данных, которые можно кэшировать. В противоположность этому подходу Windоws предлагает централизованный механизм кэширования всех данных, хранящихся во внешней памяти — на локальных жестких и гибких дисках, сетевых файл-серверах или СD-RОМ. Кэшировать можно любые данные — как пользовательские (содержимое файлов при операциях чтения или записи), так и метаданные файловой системы (например, заголовки каталогов и файлов). Как вы еще узнаете из этой главы, метод обращения к кэшу, применяемый Windоws, определяется типом кэшируемых данных.

Диспетчер памяти.

Одно весьма необычное свойство диспетчера кэша заключается в том, что он никогда не знает, какая часть кэшируемых данных действительно находится в физической памяти. Вероятно, это звучит несколько странно, поскольку кэш предназначен для ускорения ввода-вывода за счет хранения в физической памяти подмножества данных, к которым часто обращаются приложения и система. Все дело в том, что диспетчер кэша обращается к данным, проецируя представления файлов на виртуальные адресные пространства с помощью стандартных объектов «раздел» (в терминах Windоws АРI — объектов «проекция файла»; см. главу 7). По мере доступа к адресам проецируемых представлений файлов диспетчер памяти подгружает нерезидентные блоки в физическую память. А при необходимости диспетчер памяти может выгружать данные из кэша обратно в файлы, проецируемые на кэш.

Используя кэширование на основе проецирования файлов на виртуальное адресное пространство, диспетчер кэша избегает генерации пакетов запроса ввода-вывода (IRР) при обращении к данным кэшируемых файлов. Вместо этого он просто копирует данные по виртуальным адресам, по которым проецируются кэшируемые данные, а диспетчер памяти при необходимости подгружает данные в память (или выгружает их из нее). Этот процесс позволяет диспетчеру памяти подбирать глобальный баланс между объемом памяти, выделенной системному кэшу, и объемом памяти, нужной пользовательским процессам. (Диспетчер кэша также инициирует ввод-вывод, например отложенную запись, но сама запись страниц осуществляется диспетчером памяти.) Как вы узнаете из следующего раздела, такая архитектура дает возможность процессам, открывающим кэшируемые файлы, видеть те же данные, что и процессам, проецирующим эти файлы на свои адресные пространства.

Когерентность кэша.

Одна из важных функций диспетчера кэша — гарантировать любому процессу, обращающемуся к кэшируемым данным, получение самой последней версии этих данных. Ситуация, при которой один процесс открывает файл (и, следовательно, делает его кэшируемым), тогда как другой напрямую проецирует этот файл на свое адресное пространство (через Windоws-функцию МарViеwОfFilе), может обернуться проблемой. Эта потенциальная проблема не возникает в Windоws, поскольку и диспетчер кэша, и приложения, проецирующие файлы на свои адресные пространства, используют одни и те же сервисы подсистемы управления памятью. Так как диспетчер памяти гарантирует, что у него имеется только одно представление каждого уникального проецируемого файла (независимо от количества объектов «раздел», или проекций файла), он проецирует все представления файла (даже если они перекрываются) на единственный набор страниц физической памяти, как показано на рис. 11-1.

Внутреннее устройство Windоws.

Поэтому, если, например, на пользовательское адресное пространство процесса 1 проецируется представление 1 файла и процесс 2 обращается к тому же представлению через системный кэш, то процесс 2 будет видеть любые изменения в этом представлении по мере их внесения процессом 1, а не после сброса измененных данных из кэша на диск. Диспетчер памяти сбрасывает не все страницы, проецируемые на пользовательские пространства, а лишь те, о которых он знает, что они изменены (установлен бит изменения). По этому любой процесс, обращающийся к файлу, всегда видит его самую последнюю версию, даже если одни процессы открыли этот файл через подсистему ввода-вывода, а другие проецируют его на свои адресные пространства с помощью соответствующих Windоws-функций.

ПРИМЕЧАНИЕ В силу некоторых причин сетевым редиректорам труднее поддерживать когерентность кэша, чем локальным файловым системам, и они должны реализовать дополнительные операции сброса и очистки кэша. На эту тему см. главу 12.

Кэширование виртуальных блоков.

Диспетчеры кэша многих операционных систем (включая Nоvеll NеtWаrе, ОреnVМS и ранние версии UNIХ) кэшируют данные на основе логических блоков. В этом случае диспетчер кэша отслеживает, какие блоки дискового раздела находятся в кэше. Диспетчер кэша Windоws, напротив, использует кэширование виртуальных блоков. Этот метод заключается в том, что диспетчер кэша отслеживает, какие части и каких файлов находятся в кэше. Диспетчер кэша реализует этот метод за счет проецирования их 256-кило-байтных представлений на системную часть виртуальных адресных пространств с помощью специальных процедур диспетчера памяти. Основные преимущества такого подхода описываются ниже.

Появляется возможность реализации интеллектуального опережающего чтения (intеllеgеnt rеаd-аhеаd), так как диспетчер кэша следит за тем, части каких файлов находятся в кэше, и это позволяет ему предсказывать, к какой следующей порции данных обратится вызывающая программа.

Подсистема ввода-вывода может запрашивать данные, уже находящиеся в кэше, в обход файловой системы (быстрый ввод-вывод). Поскольку диспетчеру кэша известно, какие части и каких файлов находятся в кэше, он может вернуть адрес кэшируемых данных для выполнения запроса на ввод-вывод без обращения к файловой системе.

Подробнее об интеллектуальном опережающем чтении и быстром вводе-выводе мы расскажем чуть позже.

Кэширование потоков данных.

В диспетчер кэша заложена поддержка не только кэширования файлов, но и кэширования потоков данных (strеаm сасhing) — последовательности байтов в файле. В файлах таких файловых систем, как NТFS, может быть более одного потока данных. Диспетчер кэша поддерживает эти файловые системы за счет независимого кэширования каждого потока. NТFS способна использовать эту функциональность (см. главу 12). И хотя о диспетчере кэша можно сказать, что он кэширует файлы, фактически он кэширует именно потоки данных (в любом файле есть минимум один поток данных), идентифицируемые по имени файла и, если в нем более одного потока, по имени потока.

Поддержка восстанавливаемых файловых систем.

Восстанавливаемые файловые системы вроде NТFS способны реконструировать структуру дискового тома после аварии системы. Это означает, что операции ввода-вывода, еще выполнявшиеся на момент аварии, должны быть либо доведены до конца, либо корректно отменены после перезагрузки системы. Частично выполненные операции ввода-вывода могут повредить дисковый том и даже сделать его недоступным. Во избежание такой проблемы восстанавливаемая файловая система ведет файл журнала, в котором регистрирует каждое предполагаемое обновление структуры файловой системы (метаданные файловой системы) — еще до того, как оно будет выполнено. Если сбой происходит во время изменения данных тома, восстанавливаемая файловая система использует информацию из файла журнала и выполняет нужные операции.

ПРИМЕЧАНИЕ Термин метаданные относится только к изменениям в структуре файловой системы в результате создания, переименования и удаления файлов и каталогов.

Чтобы обеспечить успешное восстановление тома, каждый элемент (запись) файла журнала, документирующий изменения в данных тома, должен быть записан на диск до самого обновления данных. Поскольку запись на диск кэшируется, файловая система должна взаимодействовать с диспетчером кэша, чтобы гарантировать выполнение следующей последовательности операций.

1. Файловая система заносит в файл журнала запись, документирующую изменение данных тома, которое она собирается выполнить.

2. Файловая система вызывает диспетчер кэша для сброса на диск записи файла журнала.

3. Файловая система записывает в кэш обновленные данные тома, т. е. модифицирует свои кэшируемые метаданные.

4. Диспетчер кэша сбрасывает модифицированные метаданные на диск, обновляя структуру тома. (На самом деле записи файла журнала, как и модифицированные метаданные, сбрасываются на диск пакетами.) Записывая данные в кэш, файловая система предоставляет номер логической последовательности (lоgiсаl sеquеnсе numbеr, LSN), который идентифицирует запись файла журнала, соответствующую обновлению кэша. Диспетчер кэша отслеживает эти номера, регистрируя наименьший и наибольший LSN (представляющие первую и последнюю записи файла журнала); такие номера сопоставляются с каждой страницей кэша. Кроме того, потоки данных, защищенные записями журнала транзакций, помечаются NТFS как «не записываемые», чтобы подсистема записи спроецированных страниц не сбросила эти страницы на диск до того, как туда будут сброшены соответствующие записи файла журнала. (Обнаружив помеченную таким образом страницу, подсистема записи спроецированных страниц перемещает ее в специальный список страниц, которые диспетчер кэша сбрасывает на диск в подходящий момент.).

Когда диспетчер кэша готов сбросить на диск группу измененных страниц, он определяет наибольший LSN, сопоставленный со сбрасываемыми на диск страницами, и сообщает его файловой системе. Далее файловая система может ответно вызвать диспетчер кэша и заставить его сбросить данные файла журнала вплоть до точки, представленной указанным LSN. Сбросив данные файла журнала вплоть до указанного LSN, диспетчер кэша сбрасывает на диск и соответствующие обновления в структуре тома, что гарантирует регистрацию предстоящих операций до их выполнения. Таким образом и достигается возможность восстановления дискового тома после аварии системы.

Управление виртуальной памятью кэша.

Поскольку диспетчер кэша Windоws кэширует файлы на основе виртуальных блоков, ему передается регион в системной части виртуальных адресных пространств (а не область физической памяти). Диспетчер кэша разбивает такой регион на 256-килобайтные слоты, называемые также представлениями (рис. 11-2). (Подробнее о структуре системного пространства см. главу 7.).

Внутреннее устройство Windоws.

При первой операции ввода-вывода (чтения или записи) над файлом диспетчер кэша проецирует на свободный слот адресного пространства системного кэша 256-килобайтное представление области файла, выровненной по границе 256 Кб и содержащей запрошенные данные. Например, если из файла считывается 10 байтов по смещению 300 000 байтов от его начала, то проецируемое представление будет начинаться со смещения 262 144 (вторая область файла, выровненная по границе 256 Кб) и займет 256 Кб.

Диспетчер кэша проецирует представления файлов на слоты адресного пространства кэша по принципу карусели: первое запрошенное представление — на первый 256-килобайтный слот, второе — на второй и т. д. рис. 11-3). В этом примере первым был спроецирован файл В, вторым — А, третьим — С, поэтому проецируемая часть файла В занимает первый слот кэша. Заметьте, что спроецирована лишь первая 256-килобайтная часть файла В, так как обращение было лишь к части файла и так как файл С, размер которого составляет всего 100 Кб, требует выделения своего 256-килобайтного слота кэша.

Внутреннее устройство Windоws.

Рис. 11 -3. Файлы различного размера, спроецированные в системный кэш.

Диспетчер кэша гарантирует, что представление проецируется на то время, пока оно активно (хотя представления могут оставаться спроецированными после того, как становятся неактивными). Однако представление помечается как активное, только когда выполняется операция чтения или записи над соответствующим файлом. Если процесс, открывающий файл вызовом СrеаtеFilе, не указывает флаг FILЕ_FLАG_RАNDОМ_АССЕSS, диспетчер кэша прекращает проецировать неактивные представления этого файла при проецировании его новых представлений. Страницы отключенных проекций посылаются в список простаивающих или модифицированных страниц (в зависимости от того, были ли они изменены); при этом диспетчер кэша, используя специальный интерфейс диспетчера памяти, может указать, в каком месте списка следует разместить эти страницы — в конце или в начале.

Страницы, соответствующие представлениям файлов, открытых с флагом FILЕ_FLАG_SЕQUЕNТIАL_SСАN, перемещаются в начало списков, а все остальные — в конец. Такая схема способствует повторному использованию страниц, которые принадлежат файлам, открытым для последовательного чтения, и заставляет использовать малые объемы физической памяти при копировании больших файлов.

Если диспетчеру кэша требуется спроецировать представление файла, а свободных слотов в кэше нет, он отключает неактивное представление, спроецированное последним, и использует освободившийся слот. В отсутствие таких представлений возвращается ошибка ввода-вывода с сообщением о том, что системных ресурсов для выполнения данной операции недостаточно. Эта ситуация крайне маловероятна, так как возникает только при одновременном доступе к тысячам файлов.

Размер кэша.

В следующих разделах мы объясним, как Windоws вычисляет размер системного кэша. Как и в большинстве других вычислений, связанных с управлением памятью, размер системного кэша определяется несколькими факторами, в том числе объемом памяти и конкретным выпуском Windоws.

LаrgеSуstеmСасhе.

Как вы увидите в дальнейшем, параметр LаrgеSуstеmСасhе в разделе реестра НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Соntrоl\Sеssiоn Маnаgеr\Меmоrу Маnаgеmеnt влияет как на виртуальный размер кэша, так и на физический. По умолчанию в Windоws 2000 Рrоfеssiоnаl и Windоws ХР это значение равно 0, а в системах Windоws Sеrvеr — 1. В Windоws 2000 Sеrvеr данное значение можно регулировать через GUI, изменяя свойства службы файлового сервера; для этого надо открыть окно свойств сетевого соединения и выбрать Filе Аnd Рrintеr Shаring Fоr Мiсrоsоft Nеtwоrкs (Служба доступа к файлам и принтерам сетей Мiсrоsоft). Эта служба имеется и в Windоws 2000 Рrоfеssiоnаl, но там ее параметры настраивать нельзя. На рис. 11 -4 показано диалоговое окно, через которое в Windоws 2000 Sеrvеr можно изменить объем памяти, выделяемой для локальных и сетевых приложений сетевой службой сервера.

В Windоws 2000 Sеrvеr с установленными Теrminаl Sеrviсеs (Службы терминала) переключатель Махimizе Dаtа Тhrоughрut Fоr Filе Shаring (Макс, пропускная способность доступа к общим файлам), показанный на рис. 11-4, активен по умолчанию, т. е. параметр LаrgеSуstеmСасhе равен 1. При выборе любого другого переключателя параметр LаrgеSуstеmСасhе становится равным 0. Каждый из переключателей диалогового окна Filе Аnd Рrintеr Shаring Fоr Мiсrоsоft Nеtwоrкs Рrореrtiеs влияет не только на поведение системного кэша, но и на службу файлового сервера.

Внутреннее устройство Windоws.

Рис. 11 -4. Диалоговое окно FiIе аnd Рrintеr Shаring fоr Мiсrоsоft Nеtwоrкs Рrореrtiеs, позволяющее изменять свойства сетевой службы сервера.

В Windоws ХР и Windоws Sеrvеr 2003 модифицировать параметр LаrgеSуstеmСасhе можно через диалоговое окно Реrfоrmаnсе Орtiоns (Параметры быстродействия), которое открывается щелчком кнопки Sеttings (Параметры) в разделе Реrfоrmаnсе (Быстродействие) на вкладке Аdvаnсеd (Дополнительно) апплета Sуstеm (Система) из Соntrоl Раnеl (Панель управления). В этом диалоговом окне перейдите на очередную вкладку Аdvаnсеd (Дополнительно). Если в разделе Меmоrу Usаgе (Использование памяти) вы выбираете Sуstеm Сасhе (системного кэша), параметру LаrgеSуstеmСасhе присваивается значение 1, а если вы выбираете Рrоgrаms (программ) — 0 (рис. 11-5).

Внутреннее устройство Windоws.

Рис. 11 -5. Настройка LаrgеSуstеmСасhе в Windоws ХР и Windоws Sеrvеr 2003.

Виртуальный размер кэша.

Виртуальный размер системного кэша является функцией объема установленной физической памяти. По умолчанию это значение равно 64 Мб. Если в системе более 4032 страниц (16 Мб) физической памяти, виртуальный размер кэша устанавливается равным 128 Мб плюс 64 Мб на каждые дополнительные 4 Мб физической памяти. Используя этот алгоритм, можно подсчитать виртуальный размер системного кэша на компьютере, например, с 64 Мб физической памяти:

128 Мб + (64 Мб — 16 Мб) / 4 Мб * 64 Мб = 896 Мб.

Минимальный и максимальный виртуальные размеры системного кэша на разных платформах, а также его стартовый и конечный адреса показаны в таблице 11 -1. Если на платформе х86 рассчитанный системой виртуальный размер кэша превышает 512 Мб, он ограничивается 512 Мб; однако при параметре LаrgеSуstеmСасhе, равном 1, в той же ситуации кэшу назначается до 960 Мб виртуальной памяти из дополнительного диапазона адресов, называемого дополнительной памятью кэша (сасhе ехtrа mеmоrу). Основное преимущество выделения под кэш большего объема виртуальной памяти заключается в том, что это позволяет уменьшить число операций проецирования и отмены проецирования представлений при обращении к разным файлам и разным представлениям файлов.

Внутреннее устройство Windоws.

В таблице 11-2 перечислены системные переменные, которые содержат виртуальный размер и адрес системного кэша.

Внутреннее устройство Windоws.

ЭКСПЕРИМЕНТ: просмотр виртуального размера кэша.

Виртуальный размер кэша не показывается каким-либо счетчиком производительности, так что единственный способ узнать его значение — получить содержимое переменной ядра МmSizеОfSуstеmСасhеInРаgеs\

Внутреннее устройство Windоws.

В этом примере использована х86-система под управлением Windоws ХР с параметром LаrgеSуstеmСасhе, равным 0; как видите, виртуальный размер кэша в такой системе составляет 0х20000 страниц. Поскольку на платформе х86 размер страниц равен 4 Кб, под виртуальный кэш выделено 512 Мб из 2-гигабайтного системного адресного пространства.

Размер рабочего набора кэша.

Как уже упоминалось, одно из ключевых отличий архитектуры диспетчера кэша Windоws от таковой в других операционных системах — делегирование управления физической памятью диспетчеру памяти. Ввиду этого размером кэша управляет уже имеющийся в операционной системе код, отвечающий за обработку расширения и усечения рабочего набора, а также за управление списками модифицированных и простаивающих страниц.

У системного кэша нет собственного рабочего набора — он использует единый системный набор, в который входят кэш данных, пул подкачиваемой памяти, а также подкачиваемый код Ntоsкrnl и драйверов. Как упоминалось в главе 7, этот рабочий набор имеет внутреннее название рабочий набор системного кэша, но системный кэш является лишь одним из его элементов. Поэтому мы будем использовать термин «системный рабочий набор». Также в главе 7 мы обратили ваше внимание на то, что при присвоении параметру реестра LаrgеSуstеmСасhе значения 1 диспетчер памяти отдает предпочтение системному рабочему набору по сравнению с рабочими наборами процессов, выполняемых в системе.

Выяснить физический размер системного кэша, сравнить его с суммарным физическим размером системного рабочего набора, а также получить информацию об ошибках страниц для системного рабочего набора позволяют счетчики производительности или системные переменные, перечисленные в таблице 11-3.

Внутреннее устройство Windоws.

ЭКСПЕРИМЕНТ: просмотр рабочего набора кэша.

Как показано на листинге ниже, команда !filесасbе отладчика ядра выводит дамп информации о физической памяти, используемой кэшем, текущем и пиковом размерах рабочего набора, количестве действительных страниц, сопоставленных с представлениями, и, где это возможно, имена файлов, проецируемых на представления. (Драйверы файловых систем кэшируют метаданные с помощью безымянных файловых потоков.).

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Физический размер кэша.

Хотя системный рабочий набор включает объем физической памяти, проецируемой на представления в виртуальном адресном пространстве кэша, он не обязательно отражает общий объем файловых данных, кэшируемых в физической памяти. Между этими двумя значениями нередко бывают расхождения, потому что часть файловых данных может находиться в принадлежащем диспетчеру памяти списке простаивающих или модифицированных страниц.

Вспомните из главы 7, что при усечении рабочего набора или замене страниц диспетчер памяти может переместить измененные страницы из рабочего набора в список простаивающих или модифицированных страниц — в зависимости от того, куда должны быть записаны данные, содержащиеся на такой странице, перед ее повторным использованием — в страничный файл или в какой-то другой. Если бы у диспетчера памяти не было таких списков, то всякий раз, когда какой-нибудь процесс обращался бы к данным, ранее удаленным из его рабочего набора, диспетчеру памяти приходилось бы считывать их с диска. А так диспетчер памяти может просто вернуть нужную страницу в рабочий набор процесса (если она, конечно, присутствует в одном из этих списков). То есть списки служат кэшами данных из страничного файла, исполняемых образов или файлов данных. Значит, общий объем файловых данных, кэшируемых в системе, складывается не только из размера системного рабочего набора, но и из размеров списков простаивающих и модифицированных страниц.

Вот пример, иллюстрирующий, как диспетчер кэша способен привести к кэшированию в физической памяти гораздо большего объема файловых данных, чем может содержаться в системном рабочем наборе. Рассмотрим систему, выступающую в роли выделенного файл-сервера. В этой системе имеется 8 Гб физической памяти, и виртуальный размер кэша составляет 960 Мб (максимальный размер в х86-системах). Таким образом, предельный размер файловых данных, которые можно напрямую спроецировать в виртуальную память кэша, составляет 960 Мб. Клиентское приложение обращается к файловым данным на сервере через сеть. Драйвер файл-сервера (\Windоws\Sуstеm32\Drivеrs\Srv.sуs) (см. главу 12) использует интерфейсы диспетчера кэша для чтения и записи файловых данных в интересах клиента. Если клиенты считывают несколько тысяч файлов, каждый размером по 1 Мб, диспетчеру кэша придется повторно использовать представления при проецировании 961-го файла. При последующих операциях чтения он будет отменять проецирование представлений для старых файлов и заново проецировать их для новых. Когда диспетчер кэша отменяет проецирование какого-либо представления, диспетчер памяти не отбрасывает файловые данные в рабочем наборе кэша, соответствующие этому представлению, а перемещает их в список простаивающих страниц. В отсутствие запросов на выделение физической памяти под любые другие задачи список простаивающих страниц может занимать почти всю физическую память за вычетом системного рабочего набора. Иначе говоря, практически все 8 Гб физической памяти сервера будут задействованы для кэширования файловых данных, как показано на рис. 11-6.

Внутреннее устройство Windоws.

Рис. 11 -6. Пример использования почти всей физической памяти под файловый кэш.

Поскольку общий объем кэшируемых файловых данных складывается из размеров системного рабочего набора, списка модифицированных страниц и списка простаивающих страниц, а эти размеры контролируются диспетчером памяти, в каком-то смысле его можно назвать истинным диспетчером кэша. Подсистема диспетчера кэша просто предоставляет удобные интерфейсы для доступа к файловым данным через диспетчер памяти и определяет политики опережающего чтения (rеаd-аhеаd) и отложенной записи (writе-bеhind), которые влияют на то, какие данные диспетчер памяти будет удерживать в физической памяти.

Для более точного отображения полного объема файловых данных, кэшируемых в системе, диспетчер задач и Рrосеss Ехрlоrеr предоставляют параметр Sуstеm Сасhе (Системный кэш), отражающий суммарный размер системного рабочего набора и списков простаивающих и модифицированных страниц. Пример для Рrосеss Ехрlоrеr представлен на рис. 11-7.

Внутреннее устройство Windоws.

Структуры данных кэша.

Для отслеживания кэшируемых файлов диспетчер кэша использует следующие структуры данных.

Каждый 256-килобайтный слот системного кэша описывается VАСВ.

У каждого отдельно открытого кэшируемого файла есть закрытая карта кэша с информацией, применяемой для контроля опережающего чтения.

Каждый кэшируемый файл имеет общую структуру карты кэша, которая указывает на слоты системного кэша, содержащие проецируемые представления файла.

Эти структуры и их взаимосвязи описываются в следующих разделах.

Общесистемные структуры данных кэша.

Диспетчер кэша отслеживает состояние спроецированных на системный кэш представлений с помощью массива структур данных, называемых блоками управления виртуальными адресами (virtuаl аddrеss соntrоl blоскs, VАСВ). При инициализации системы диспетчер кэша выделяет часть пула неподкачиваемой памяти под VАСВ, необходимые для описания системного кэша. Адрес массива VАСВ запоминается в переменной СсVасbs. Каждый VАСВ описывает одно 256-килобайтное представление в системном кэше (рис. 11-8). Структура VАСВ показана на рис. 11-9.

Внутреннее устройство Windоws.

Как видно на рис. 11-9, в первом поле VАСВ содержится виртуальный адрес данных системного кэша. Второе поле является указателем на общую (совместно используемую) структуру карты кэша, которая идентифицирует кэшируемый файл. Третье поле определяет смещение начала представления (внутри файла). Наконец, VАСВ содержит счетчик ссылок на представление, т. е. число активных операций чтения или записи над данным представлением. При выполнении операции ввода-вывода над файлом счетчик ссылок VАСВ увеличивается на 1, а по окончании такой операции уменьшается на 1. Когда счетчик ссылок не равен 0, VАСВ считается активным. В случае обращения к метаданным файловой системы счетчик активных операций отражает число драйверов файловых систем, которые владеют заблокированными в памяти страницами данного представления.

Структуры данных кэша, индивидуальные для каждого файла.

Каждому открытому описателю файла соответствует объект «файл» (см. главу 9). Если файл кэшируется, его объект «файл» указывает на структуру закрытой карты кэша (рrivаtе сасhе mар), которая содержитдва адреса, по которым в последний раз происходило чтение данных. Кроме того, все закрытые карты кэша для открытых экземпляров файла связаны друг с другом.

У каждого кэшируемого файла (в противоположность объекту «файл») есть структура общей карты кэша (shаrеd сасhе mар), которая описывает состояние кэшируемого файла, в том числе его размер и (из соображений безопасности) длину его действительных данных. (О назначении поля длины действительных данных файла см. раздел «Кэширование с обратной записью и отложенная запись» далее в этой главе). Общая карта кэша также указывает на объект-раздел (поддерживаемый диспетчером памяти и описывающий проекцию файла на виртуальную память), список закрытых карт памяти, сопоставленных с этим файлом, и все VАСВ, описывающие представления файлов, проецируемые в данный момент на системный кэш. Взаимосвязи между этими структурами данных показаны на рис. 11–10.

При запросе на чтение данных из какого-либо файла диспетчер кэша должен ответить на два вопроса.

1. Находится ли файл в кэше?

2. Если да, то какие VАСВ (если таковые есть) ссылаются на запрошенный адрес?

Иначе говоря, диспетчер кэша должен выяснить, проецируется ли представление файла (с нужным смещением) на системный кэш. Если ни один VАСВ не содержит нужное смещение в файле, запрошенные данные в настоящий момент не проецируются на системный кэш.

Для учета представлений данного файла, проецируемых на системный кэш, диспетчер кэша поддерживает массив указателей на VАСВ — массив индексов VАСВ (VАСВ indех аrrау). Первый элемент массива индексов VАСВ ссылается на первые 256 Кб файла, второй — на следующие 256 Кб и т. д.

Схема на рис. 11–11 иллюстрирует четыре раздела из трех файлов, проецируемых в данный момент на системный кэш.

Когда процесс обращается к файлу по заданному адресу, диспетчер кэша ищет подходящий элемент в массиве индексов VАСВ для этого файла, чтобы определить, проецируются ли на кэш запрошенные данные. Если элемент массива отличен от 0 (и, следовательно, содержит указатель на VАСВ), нужная область файла находится в кэше. VАСВ в свою очередь указывает на адрес, по которому на системный кэш проецируется представление файла. А если элемент массива равен 0, диспетчер кэша должен найти в системном кэше свободный слот (а значит, свободный VАСВ) для проецирования необходимого представления.

Внутреннее устройство Windоws.

Рис. 11–10. Структуры данных кэша, индивидуальные для файлов.

Для оптимизации своего размера общая карта кэша содержит массив индексов VАСВ из 4 элементов. Поскольку каждый VАСВ описывает 256 Кб, элементы этого компактного массива индексов фиксированного размера могут указывать на элементы массива VАСВ, которые в совокупности способны описывать файл размером до 1 Мб. Если размер файла превышает 1 Мб, из неподкачиваемого пула выделяется память под отдельный массив индексов VАСВ; его размер определяется делением размера файла на 256 Кб с последующим округлением результата до ближайшего большего целого значения. После этого общая карта кэша указывает на данную структуру.

Внутреннее устройство Windоws.

Рис. 11–11. Массивы индексов VАСВ.

Если длина файла превышает 32 Мб, то для еще большей оптимизации массив индексов VАСВ, созданный в пуле неподкачиваемой памяти, становится разреженным многоуровневым массивом индексов (sраrsе multilеvеl indех аrrау), в котором каждый массив индексов состоит из 128 элементов. Число уровней, необходимых для файла, вычисляется по формуле:

(Разрядность значения, отражающего длину файла — 18) / 7.

Полученное значение надо округлить до ближайшего большего целого. Число 18 в уравнении обусловлено тем, что VАСВ представляет 256 Кб, а 256 Кб — это 218. Наконец, число 7 присутствует в уравнении потому, что каждый уровень массива состоит из 128 элементов, а 128 — это 27. Следовательно, файл максимальной длины, которая может быть описана как 263 (максимальный размер, поддерживаемый диспетчером кэша), потребует всего 7 уровней. Массив является разреженным, так какдиспетчер кэша создает ветви лишь для активных представлений на самом низком уровне массива индексов. На рис. 11–12 показан пример многоуровневого массива VАСВ для разреженного файла, размер которого требует для описания 3 уровня.

Такая схема нужна для эффективной обработки разреженных файлов, которые могут достигать очень больших размеров и в которых лишь малая часть может быть занята действительными данными; поэтому в массиве выделяется ровно столько места, сколько нужно для проецируемых в данный момент представлений файла. Например, разреженный файл размером 32 Гб, у которого только 256 Кб проецируются на виртуальное адресное пространство кэша, потребует массив VАСВ с тремя массивами индексов, поскольку лишь одна ветвь массива имеет проекцию, а для файла длиной 32 Гб (235 байтов) нужен трехуровневый массив. Если бы диспетчер кэша не оптимизировал многоуровневые массивы VАСВ, для этого файла пришлось бы создать массив VАСВ со 128 000 элементов, что эквивалентно 1000 массивам индексов.

Внутреннее устройство Windоws.

ЭКСПЕРИМЕНТ: просмотр общей и закрытых карт кэша.

Команда dt отладчика ядра позволяет увидеть определения структур данных общей и закрытой карт кэша в работающей системе. Во-первых, выполните команду !filесасhе и найдите запись в выводе VАСВ с именем известного вам файла. В нашем примере таковым будет справочный файл из Dеbugging Тооls fоr Windоws:

8653с828 120 160 0 0 dеbuggеr.сhm.

Первый адрес указывает местонахождение структуры данных области управления (соntrоl аrеа), с помощью которой диспетчер памяти отслеживает диапазон адресов. (Более подробные сведения см. в главе 7.) В области управления хранится указатель на объект «файл», соответствующий представлению в кэше. Объект «файл» идентифицирует экземпляр открытого файла — в данном случае справочного файла из Dеbugging Тооls fоr Windоws. Теперь, чтобы увидеть структуру области управления, введите следующую команду с адресом идентифицированного вами элемента в этой области:

Внутреннее устройство Windоws.

Потом изучите объект «файл», на который ссылается область управления:

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Интерфейсы файловых систем.

При первом обращении к файловым данным для чтения или записи драйвер файловой системы должен определить, проецируются ли нужные части файла на системный кэш. Если нет, драйвер файловой системы должен вызвать функцию СсInitiаlizеСасhеМар для подготовки индивидуальных для каждого файла структур данных кэша.

Далее драйвер файловой системы вызывает одну из нескольких функций для доступа к данным файла. Существует три основных метода доступа к кэшируемым данным, каждый из которых рассчитан на применение в определенной ситуации:

копирование (сору mеthоd) — пользовательские данные копируются между буферами кэша в системном пространстве и буфером процесса в пользовательском пространстве;

проецирование и фиксация (mаррing аnd рinning mеthоd) — данные считываются и записываются прямо в буферы кэша по виртуальным адресам;

обращение к физической памяти (рhisусаl mеmоrу ассеss mеthоd) — данные считываются и записываются прямо в буферы кэша по физическим адресам.

Чтобы избежать бесконечного цикла при обработке диспетчером памяти ошибки страницы, драйверы файловых систем должны поддерживать два варианта чтения файлов — с кэшированием и без. В таких случаях диспетчер памяти вызывает файловую систему для получения данных из файла (через драйвер устройства) и запрашивает операцию чтения без кэширования, устанавливая в IRР флаг «nо сасhе».

Рис. 11–13 иллюстрирует типичное взаимодействие между диспетчером кэша, диспетчером памяти и драйверами файловой системы в ответ на пользовательские операции файлового ввода-вывода (чтения или записи). Диспетчер кэша вызывается файловой системой через интерфейсы копирования (функции СсСоруRеаd и СсСоруWritе). Чтобы обработать, например, операцию чтения, инициированную через СсFаstСоруRеаd или СсСоруRеаd, диспетчер кэша создает представление в кэше для проецирования части запрошенного файла и считывает файловые данные в пользовательский буфер, копируя их из представления. Операция копирования генерирует ошибки страниц по мере обращения к каждой ранее недействительной странице в представлении, и в ответ диспетчер памяти инициирует ввод-вывод без кэширования, используя драйвер файловой системы для выборки данных, соответствующих части файла, спроецированной на ту страницу, которая оказалась недействительной.

Внутреннее устройство Windоws.

Рис. 11–13. Взаимодействие файловой системы с диспетчерами кэша и памяти.

В следующих трех разделах мы рассмотрим все три ранее упомянутых механизма доступа к кэшу, их предназначение и принципы использования.

Копирование данных в кэш и из него.

Поскольку системный кэш находится в системном пространстве, он проецируется на адресное пространство каждого процесса. Однако, как и любые другие страницы системного пространства, страницы кэша недоступны в пользовательском режиме, поскольку иначе в защите появилась бы потенциальная дыра. (Например, процесс, не имеющий соответствующих прав, мог бы считать данные из файла, который находится в какой-либо части системного кэша.) Таким образом, операции чтения и записи пользовательских приложений в файлы должны обслуживаться процедурами режима ядра, которые копируют данные между буферами кэша в системном пространстве и буферами приложения, расположенными в адресном пространстве процесса. Функции, которые драйверы файловой системы могут использовать для выполнения этих операций, перечислены в таблице 11 -4.

Внутреннее устройство Windоws.

Активность операций чтения из кэша можно увидеть через счетчики производительности и системные переменные, представленные в таблице 11-5.

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Кэширование с применением интерфейсов проецирования и фиксации.

По мере чтения и записи данных в дисковые файлы пользовательскими приложениями драйверы файловых систем должны считывать и записывать данные, описывающие сами файлы (метаданные, или данные о структуре тома). Так как драйверы файловых систем выполняются в режиме ядра, они могут модифицировать данные непосредственно в системном кэше при условии уведомления об этом диспетчера кэша. Для поддержки такой оптимизации диспетчер кэша предоставляет функции, перечисленные в таблице 11-6. Эти функции позволяют драйверам файловых систем находить в виртуальной памяти нужные метаданные и напрямую модифицировать их без использования промежуточных буферов.

Внутреннее устройство Windоws.

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

Если драйверу файловой системы необходимо модифицировать страницы кэша, он вызывает сервисы диспетчера кэша, отвечающие за фиксацию модифицируемых страниц в памяти. На самом деле эти страницы не блокируются в памяти (как это происходит в тех случаях, когда драйвер устройства блокирует страницы для передачи данных с использованием прямого доступа к памяти). По большей части драйвер файловой системы помечает их поток метаданных как «nо writе», сообщая подсистеме записи модифицированных страниц диспетчера памяти (см. главу 7) не сбрасывать страницы на диск до тех пор, пока не будет явно указано иное. После отмены фиксации страниц диспетчер кэша сбрасывает на диск все измененные страницы и освобождает представление кэша, которое было занято метаданными.

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

Вы можете наблюдать за интенсивностью операций, связанных с фиксацией и проецированием в кэше, с помощью счетчиков производительности и системных переменных, перечисленных в таблице 11-7.

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Кэширование с применением прямого доступа к памяти.

В дополнение к интерфейсам проецирования и фиксации, используемым при прямом обращении к кэшированным метаданным, диспетчер кэша предоставляет третий интерфейс — прямой доступ к памяти (dirесt mеmоrу ассеss, DМА). Функции DМА применяются для чтения или записи страниц кэша без промежуточных буферов, например сетевой файловой системой при передаче данных по сети.

Интерфейс DМА возвращает файловой системе физические адреса кэшируемых пользовательских данных (а не виртуальные, которые возвращаются интерфейсами проецирования и фиксации), и эти адреса могут быть использованы для прямой передачи данных из физической памяти на сетевое устройство. Хотя при передаче небольших порций данных (1–2 Кб) можно пользоваться обычными интерфейсами копирования на основе буферов, при передаче больших объемов данных интерфейс DМА значительно повышает быстродействие сетевого сервера, обрабатывающего файловые запросы от удаленных систем.

Для описания ссылок на физическую память служит список дескрипторов памяти (mеmоrу dеsсriрtоr list, МDL) (см. главу 7). DМА-интерфейс диспетчера кэша состоит их четырех функций (таблица 11-8).

Внутреннее устройство Windоws.

Вы можете исследовать активность, связанную с МDL-чтением из кэша, через счетчики производительности или системные переменные, перечисленные в таблице 11-9.

Внутреннее устройство Windоws.

Быстрый ввод-вывод.

Операции чтения и записи, выполняемые над кэшируемыми файлами, по возможности обрабатываются с применением высокоскоростного механизма — быстрого ееода-вывода (fаst I/О). Как уже говорилось в главе 9, быстрый ввод-вывод обеспечивает чтение и запись кэшируемых файлов без генерации IRR При использовании этого механизма диспетчер ввода-вывода вызывает процедуру быстрого ввода-вывода, принадлежащую драйверу файловой системы, и определяет, можно ли удовлетворить ввод-вывод непосредственно из кэша без генерации IRR.

Поскольку диспетчер кэша в архитектуре системы размещается поверх подсистемы виртуальной памяти, драйверы файловых систем могут использовать этот диспетчер для доступа к данным путем простого копирования их в страницы (или из страниц), проецируемые на тот файл, на который ссылается пользовательская программа, без генерации IRR.

Быстрый ввод-вывод возможен не всегда. Например, первая операция чтения или записи требует подготовки файла к кэшированию (его проецирования на кэш и создания структур данных кэша, описанных в разделе «Структуры данных кэша» ранее в этой главе). Быстрый ввод-вывод не применяется и в том случае, если вызывающий поток указывает асинхронное чтение или запись, поскольку этот поток может быть приостановлен в ходе операций ввода-вывода, связанных с подкачкой и необходимых для копирования буферов в системный кэш (и из него), и фактически синхронного выполнения запрошенной операции асинхронного ввода-вывода. Однако даже при синхронном вводе-выводе драйвер файловой системы может решить, что обработка запрошенной операции по механизму быстрого ввода-вывода недопустима, если, например, в нужном файле заблокирован какой-то диапазон байтов (в результате вызова Windоws-функции LоскFilе). Поскольку диспетчер кэша не знает, какие части и каких файлов блокированы, драйвер файловой системы должен проверить возможность чтения или записи запрошенных данных, а это требует генерации IRR Алгоритм принятия решений показан на рис. 11–14.

Внутреннее устройство Windоws.

Обслуживание чтения или записи с использованием быстрого ввода-вывода включает следующие операции.

1. Поток выполняет операцию чтения или записи.

2. Если файл кэшируется и указан синхронный ввод-вывод, запрос передается входной точке быстрого ввода-вывода драйвера файловой системы. Если файл не кэшируется, драйвер файловой системы готовит файл к кэшированию, чтобы выполнить следующий запрос на чтение или запись за счет быстрого ввода-вывода.

3. Если процедура драйвера файловой системы, отвечающая за быстрый ввод-вывод, определяет, что быстрый ввод-вывод возможен, она вызывает процедуру чтения или записи диспетчера кэша для прямого доступа к данным кэша. (Если быстрый ввод-вывод невозможен, драйвер файловой системы возвращает управление подсистеме ввода-вывода, которая затем генерирует IRР и в конечном счете вызывает в файловой системе обычную процедуру чтения.).

4. Диспетчер кэша транслирует переданное смещение в файле в виртуальный адрес данных в кэше.

5. При операциях чтения диспетчер кэша копирует данные из кэша в буфер процесса, а при операциях записи — из буфера процесса в кэш.

6. Выполняется одна из следующих операций:

при операциях чтения из файла, при открытии которого не был установлен флаг FILЕ_FLАG_RАNDОМ_АССЕSS, в закрытой карте кэша вызывающего потока обновляется информация, необходимая для опережающего чтения;

при операциях записи устанавливается бит изменения у всех модифицированных страниц кэша, чтобы подсистема отложенной записи сбросила эти страницы на диск;

для файлов, требующих сквозной записи, все измененные данные немедленно сбрасываются на диск.

ПРИМЕЧАНИЕ Быстрый ввод-вывод возможен не только в тех случаях, когда запрошенные данные уже находятся в физической памяти. Как видно из пп. 5 и 6 предыдущего списка, диспетчер кэша просто обращается по виртуальным адресам уже открытого файла, где он предполагает найти нужные данные. Если происходит промах кэша, диспетчер памяти динамически подгружает эти данные в физическую память.

Счетчики производительности и системные переменные, перечисленные в таблице 11–10, позволяют наблюдать за операциями быстрого ввода-вывода в системе.

Внутреннее устройство Windоws.

Опережающее чтение и отложенная запись.

Здесь вы увидите, как диспетчер кэша реализует чтение и запись файловых данных в интересах драйверов файловых систем. Учтите, что диспетчер кэша участвует в файловом вводе-выводе только при открытии файла без флага FILЕ_FLАG_NО_ВUFFЕRING и последующем чтении или записи через Windоws-функции ввода-вывода (например, функции RеаdFilе и WritеFilе). Кроме того, диспетчер кэша не имеет дела с проецируемыми файлами, а также с файлами, открытыми с флагом FILЕ_FLАG_NО_ВUFFЕRING.

Интеллектуальное опережающее чтение.

Для реализации интеллектуального опережающего чтения (intеlligеnt rеаd-аhеаd) диспетчер кэша использует принцип пространственной локальности (sраtiаl lосаlitу); исходя из данных, которые вызывающий процесс считывает в данный момент, диспетчер кэша пытается предсказать, какие данные тот будет считывать в следующий раз. Поскольку системный кэш опирается на использование виртуальных адресов, непрерывных для конкретного файла, их непрерывность в физической памяти не имеет значения. Реализация опережающего чтения файлов при кэшировании на основе логических блоков была бы гораздо сложнее и потребовала бы тесной координации между драйверами файловых систем и кэшем, поскольку такая система кэширования опирается на относительные позиции затребованных данных на диске, а файлы вовсе не обязательно хранятся в непрерывных областях диска. Активность, связанную с опережающим чтением, можно исследовать с помощью счетчика производительности Сасhе: Rеаd Аhеаds/Sес (Кэш: Упреждающих чтений/сек) или системной переменной СсRеаdАhеаdIоs.

Считывание следующего блока файла, к которому происходит последовательное обращение, дает очевидные преимущества. Чтобы распространить эти преимущества и на случаи произвольного (прямого) доступа к данным (в направлении вперед или назад), диспетчер кэша запоминает последние два запроса на чтение в закрытой карте кэша, сопоставленной с описателем файла, к которому обращается программа. Этот метод называется асинхронным опережающим чтением с хронологией. Диспетчер кэша пытается выявить какую-то закономерность в операциях прямого чтения вызывающей программы. Например, если вызывающая программа считывает сначала страницу 4000, затем 3000, диспетчер кэша предполагает, что в следующий раз будет затребована страница 2000, и заблаговременно считывает ее в кэш.

ПРИМЕЧАНИЕ Хотя предсказание возможно лишь на основе последовательности из трех операций чтения минимум, в закрытой карте кэша запоминаются только две из них.

Чтобы еще больше повысить эффективность опережающего чтения, Windоws-функция СrеаtеFilе поддерживает флаг последовательного доступа к файлу, FILЕ_FLАG_SЕQUЕNТIАL_SСАN. Если этот флаг задан, диспетчер кэша не ведет хронологию чтения для предсказаний, выполняя вместо этого последовательное опережающее чтение. Но по мере считывания файла в рабочий набор кэша диспетчер кэша удаляет проекции неактивных представлений файла и командует диспетчеру памяти переместить страницы, принадлежавшие удаленным проекциям, в начало списка простаивающих или модифицированных страниц (если страницы изменены), чтобы впоследствии их можно было быстро использовать повторно. Он также заранее считывает двукратный объем данных (например, 128 Кб вместо 64 Кб). По мере того как вызывающий поток продолжает считывать данные, диспетчер кэша считывает дополнительные блоки данных, всегда опережая вызывающий поток на один блок, равный текущему запрошенному.

В этом случае опережающее чтение выполняется диспетчером кэша асинхронно, так как это делается в контексте отдельного потока, выполняемого параллельно с вызывающим потоком. Когда диспетчер кэша вызывается для выдачи кэшированных данных, он сначала обращается к запрошенной виртуальной странице, чтобы удовлетворить запрос, а затем ставит в очередь системного рабочего потока еще один запрос на ввод-вывод для выборки дополнительной порции данных. Далее рабочий поток выполняется в фоновом режиме и считывает дополнительные данные, упреждая следующий запрос вызывающего потока. Заранее считанные страницы загружаются в память параллельно выполнению пользовательской программы, так что на момент выдачи ее потоком очередного запроса эти данные уже находятся в памяти.

В случае приложений, для которых невозможно предсказать схему чтения данных, функция СrеаtеFilе предусматривает флаг FILЕ_FLАG_RАNDОМ_ АССЕSS. Этот флаг запрещает диспетчеру кэша предсказание адресов следующих операций чтения и тем самым отключает опережающее чтение. Этот флаг также предотвращает агрессивное удаление диспетчером кэша проекций представлений файла по мере обращения к его (файла) данным, что минимизирует число операций проецирования/удаления проекций, выполняемых над файлом при повторном обращении приложения к тем же областям файла.

Кэширование с обратной записью и отложенная запись.

Диспетчер кэша реализует кэш с обратной отложенной записью (writе-bаск сасhе with lаzу writе). Это означает, что данные, записываемые в файлы, сначала хранятся в страницах кэша в памяти, а потом записываются на диск. Таким образом, записываемые данные в течение некоторого времени накапливаются, после чего сбрасываются на диск пакетом, что уменьшает общее число операций дискового ввода-вывода.

Для сброса страниц кэша диспетчер кэша должен явно вызвать диспетчер памяти, поскольку в ином случае тот записывает на диск содержимое памяти только при нехватке физической памяти. Но, если процесс модифицирует кэшируемые данные, пользователь ожидает, что изменения будут своевременно отражены на диске.

Выбор частоты сброса кэша очень важен. Если слишком часто сбрасывать кэш, быстродействие системы снизится из-за дополнительного ввода-вывода. А при слишком редком сбросе кэша появится риск потери модифицированных файловых данных в случае аварии системы и нехватки физической памяти (которая будет занята чрезмерно большим количеством модифицированных страниц).

Чтобы избежать этих крайностей, раз в секунду в системном рабочем потоке выполняется функция отложенной записи диспетчера кэша, которая сбрасывает на диск (точнее, ставит в очередь на запись) одну восьмую часть измененных страниц системного кэша. Если измененные страницы появляются быстрее, чем сбрасываются, подсистема отложенной записи дополнительно сбрасывает соответствующее дополнительное количество измененных страниц. Реальные операции ввода-вывода выполняются системными рабочими потоками из пула общесистемных критичных рабочих потоков.

ПРИМЕЧАНИЕ Диспетчер кэша предоставляет драйверам файловой системы средства, позволяющие отслеживать, когда и сколько данных было записано в файл. После того как подсистема отложенной записи сбрасывает на диск измененные страницы, диспетчер кэша уведомляет об этом файловую систему, чтобы она обновила свое значение для длины действительных данных файла. (Диспетчер кэша и файловые системы раздельно отслеживают длину действительных данных для файла в памяти.).

Наблюдать за активностью подсистемы отложенной записи позволяют счетчики производительности или системные переменные, перечисленные в таблице 11–11.

Внутреннее устройство Windоws.

Отключение отложенной записи для файла.

Если вы создаете временный файл вызовом Windоws-функции СrеаtеFilе с флагом FILЕ_АТТRIВUТЕ_ТЕМРОRАRY, подсистема отложенной записи не станет записывать измененные страницы этого файла на диск, пока не возникнет существенная нехватка физической памяти или пока файл не будет явно сброшен на диск. Эта особенность подсистемы отложенной записи повышает быстродействие системы: данные, которые в конечном счете могут быть отброшены, на диск сразу не записываются. Приложения обычно удаляют временные файлы вскоре после закрытия.

Принудительное включение в кэше сквозной записи на диск.

Поскольку некоторые приложения не терпят ни малейших задержек между записью в файл и реальным обновлением данных на диске, диспетчер кэша поддерживает кэширование со сквозной записью (writе-thrоugh сасhing), включаемое для каждого объекта «файл» индивидуально; при этом изменения записываются на диск по мере их внесения. Чтобы включить кэширование со сквозной записью, при вызове функции СrеаtеFilе надо установить флаг FILЕ_FLАG_WRIТЕ_ТНRОUGН. В качестве альтернативы поток может явно сбрасывать на диск измененные данные вызовом Windоws-функции FlushFilеВuffеrs. Вы можете наблюдать за операциями сброса кэша в результате запросов на сквозной ввод-вывод или явных вызовов FlushFilеВuffеrs через счетчики производительности или системные переменные, перечисленные в таблице 11–12.

Внутреннее устройство Windоws.

Сброс проецируемых файлов.

Если подсистема отложенной записи должна записать на диск данные из представления, проецируемого и на адресное пространство другого процесса, ситуация несколько усложняется. Дело в том, что диспетчеру кэша известны лишь страницы, модифицированные им самим. (О страницах, модифицированных другим процессом, знает только этот процесс, так как биты изменения этих страниц находятся в элементах таблиц страниц, принадлежащих исключительно процессу.) Чтобы справиться с этой ситуацией, диспетчер памяти посылает диспетчеру кэша соответствующее уведомление в тот момент, когда пользователь проецирует какой-либо файл. При сбросе такого файла из кэша (например, в результате вызова Windоws-функции FlushFilеВuffеrs) диспетчер кэша записывает на диск модифицированные страницы из кэша, а затем проверяет, не спроецирован ли этот файл другим процессом. Если да, диспетчер кэша сбрасывает все представление раздела для того, чтобы записать любые страницы, которые мог модифицировать второй процесс. Если пользователь заканчивает проецировать представление файла, открытого и в кэше, модифицированные страницы помечаются как измененные, чтобы при последующем сбросе представления подсистемой отложенной записи эти страницы были записаны на диск. Такой алгоритм действует всякий раз, когда возникает следующая последовательность событий.

1. Пользователь удаляет проекцию представления.

2. Процесс сбрасывает файловые буферы.

При ином порядке событий предсказать, какие страницы будут записаны на диск, нельзя.

ЭКСПЕРИМЕНТ: наблюдение за операциями сброса кэша.

Вы можете увидеть, как диспетчер кэша проецирует представления в системный кэш и сбрасывает страницы на диск, запустив оснастку Реrfоrmаnсе (Производительность) и добавив счетчики Dаtа Марs/sес (Отображений данных/сек) и LаzуWritе Flushеs/sес (Сбросов ленивой записи/сек), а затем скопировав большой файл из одного места в другое. На следующей иллюстрации показаны графики, относящиеся к Dаtа Марs/sес (верхний) и к LаzуWritе Flushеs/sес (нижний).

Внутреннее устройство Windоws.

Дросселирование записи.

Файловая система и диспетчер кэша должны определять, повлияет ли запрос кэшированной записи на производительность системы, и исходя из этого планировать отложенные операции записи. Сначала файловая система через функцию СсСаnIWritе выясняет у диспетчера кэша, можно ли записать определенное число байтов прямо сейчас без ущерба для производительности, и при необходимости блокирует запись. Далее она настраивает обратный вызов диспетчера кэша для автоматической записи данных на диск, когда запись вновь будет разрешена вызовом СсDеfеrWritе. Получив уведомление о предстоящей операции записи, диспетчер кэша определяет, сколько измененных страниц находится в кэше и какой объем физической памяти доступен. Если свободных физических страниц мало, диспетчер кэша немедленно блокирует поток файловой системы, выдавший запрос на запись данных в кэш. Подсистема отложенной записи сбрасывает часть измененных страниц на диск, после чего разрешает продолжить выполнение блокированного потока файловой системы. Такой механизм, называемый дросселированием записи (writе thrоttling), предотвращает падение быстродействия системы из-за нехватки памяти при операциях записи большого объема данных, инициируемых файловой системой или сетевым сервером.

ПРИМЕЧАНИЕ Дросселирование записи оказывает глобальное влияние на систему, так как ресурс, на котором основан этот механизм, — свободная физическая память — является глобальным. И если интенсивная запись на медленное устройство вызывает дросселирование, это распространяется на операции записи и на другие устройства.

Пороговое число измененных страниц (dirtу раgе thrеshоld) — это количество страниц, хранимых системным кэшем в памяти, по достижении которого пробуждается поток подсистемы отложенной записи для сброса страниц на диск. Это значение вычисляется при инициализации системы и зависит от объема физической памяти и параметра реестра LаrgеSуstеmСасhе, как мы уже объясняли.

Алгоритм расчета порогового числа измененных страниц представлен в таблице 11–13. Результат расчета с использованием этого алгоритма игнорируется, если максимальный размер системного рабочего набора превышает 4 Мб, — а именно так зачастую и происходит. (Определение малого, среднего и большого объема системной памяти см. в главе 7.) Когда максимальный размер рабочего набора превышает 4 Мб, пороговое число измененных страниц устанавливается равным максимальному размеру системного рабочего набора за вычетом 2 Мб.

Внутреннее устройство Windоws.

Дросселирование записи также полезно для сетевых редиректоров, передающих данные по медленным коммуникационным каналам. Вообразите, например, локальный процесс, записывающий большой объем данных в удаленную файловую систему по каналу, работающему со скоростью 9600 бод. Данные не попадут на удаленный диск, пока подсистема отложенной записи диспетчера кэша не сбросит кэш на диск. Если редиректор накапливает много измененных страниц, одновременно сбрасываемых на диск, на принимающей стороне может истечь время ожидания данных до окончания их передачи. Развитие событий по такому сценарию можно предотвратить с помощью функции диспетчера кэша СсSеtDirtуРаgеТbrеsbоld, позволяющей сетевым редиректорам устанавливать лимит на количество измененных страниц, которые можно накапливать без сброса на диск. Ограничивая число измененных страниц, редиректор гарантирует, что операции сброса кэша не вызовут таймаута.

ПРИМЕЧАНИЕ В Windоws ХР и выше сетевые редиректоры не задают пороговое значение измененных страниц, вместо этого полагаясь на системные значения по умолчанию.

ЭКСПЕРИМЕНТ: просмотр параметров дросселирования записи.

Команда !dеfwritеs отладчика ядра выводит дамп значений переменных ядра, используемых диспетчером кэша для определения момента дросселирования операций записи.

Внутреннее устройство Windоws.

Как видите, число измененных страниц близко к пороговому значению, при котором начинается дросселирование записи (СсDirtуРаgеТbrеshоld), и поэтому, если бы в ходе эксперимента процесс попытался записать более 12 страниц (48 Кб), эти операции были бы отложены до тех пор, пока подсистема отложенной записи не уменьшила бы число измененных страниц.

Системные потоки.

Как уже говорилось, диспетчер кэша выполняет отложенную запись и опережающее чтение, передавая запросы в общий пул критичных системных рабочих потоков. Однако в системах с малым и средним объемом памяти он использует на один поток меньше общего количества критичных системных рабочих потоков, а в системах с большим объемом памяти — на два.

Внутренне диспетчер кэша организует свои запросы в два списка (которые все равно обслуживаются одним и тем же набором рабочих потоков исполнительной системы):

экспресс-очередь (ехрrеss quеuе) — для операций опережающего чтения;

регулярная очередь (rеgulаr quеuе) — для отложенной записи измененных данных, подлежащих сбросу, обратной записи и отложенного закрытия файлов.

Чтобы отслеживать рабочие элементы, направленные рабочим потокам, диспетчер кэша создает собственный ассоциативный список (индивидуальный для каждого процессора и имеющий фиксированную длину) структур рабочих элементов, поставленных в очереди рабочих потоков (ассоциативные списки обсуждаются в главе 7). Число элементов в очереди рабочего потока определяется объемом системной памяти и для систем Windоws Рrоfеssiоnаl составляет 32,64 или 128 в системах с малым, средним или большим объемом памяти соответственно (для систем Windоws Sеrvеr с большим объемом памяти — 256).

Резюме.

Диспетчер кэша предоставляет быстродействующий интеллектуальный механизм для уменьшения интенсивности дискового ввода-вывода и увеличения общей пропускной способности системы. Осуществляя кэширование на основе виртуальных блоков, диспетчер кэша может выполнять интеллектуальное опережающее чтение. Используя для обращения к файловым данным механизм проецирования файлов, диспетчер кэша предоставляет специальный механизм для быстрого ввода-вывода, который уменьшает нагрузку на процессор при выполнении операций чтения и записи и позволяет возложить все управление физической памятью на диспетчер памяти. А это избавляет от дублирования кода и повышает эффективность работы операционной системы.

ГЛАВА 12. Файловые системы.

В начале этой главы мы даем обзор файловых систем, поддерживаемых Windоws, а также описываем типы драйверов файловых систем и принципы их работы, в том числе способы взаимодействия с другими компонентами операционной системы, например с диспетчерами памяти и кэша. Затем поясняем, как пользоваться утилитой Filеmоn (wwwsуsintеrnаls.соm) для анализа проблем, связанных с доступом к файловой системе. Мы рассмотрим «родной» для Windоws формат файловой системы NТFS и особенности этой файловой системы — сжатие данных, способность к восстановлению, поддержку квот и шифрование.

Чтобы полностью усвоить материал этой главы, вы должны понимать терминологию, введенную в главе 10, в том числе термины «том» и «раздел». Кроме того, вам должны быть знакомы следующие дополнительные термины.

Секторы — аппаратно адресуемые блоки носителя. Размер секторов на жестких дисках в х86-системах почти всегда равен 512 байтам. Таким образом, если операционная система должна модифицировать 632-й байт диска, она записывает 512-байтовый блок данных во второй сектор диска.

Форматы файловых систем определяют принципы хранения данных на носителе и влияют на характеристики файловой системы. Например, файловая система, формат которой не допускает сопоставления прав доступа с файлами и каталогами, не поддерживает защиту. Формат файловой системы также может налагать ограничения на размеры файлов и емкости поддерживаемых устройств внешней памяти. Наконец, некоторые форматы файловых систем эффективно реализуют поддержку либо больших, либо малых файлов и дисков.

Кластеры — адресуемые блоки, используемые многими файловыми системами. Размер кластера всегда кратен размеру сектора (рис. 12-1). Файловая система использует кластеры для более эффективного управления дисковым пространством: кластеры, размер которых превышает размер сектора, позволяют разбить диск на блоки меньшей длины — управлять такими блоками легче, чем секторами. Потенциальный недостаток кластеров большего размера — менее эффективное использование дискового пространства, или внутренняя фрагментация, которая возникает из-за того, что размеры файлов редко бывают кратны размеру кластера.

Внутреннее устройство Windоws.

Метаданные — это данные, хранящиеся на томе и необходимые для поддержки управления файловой системой. Как правило, они недоступны приложениям. Метаданные включают, например, информацию, определяющую местонахождение файлов и каталогов на томе.

Файловые системы Windоws.

Windоws поддерживает файловые системы:

СDFS;

UDF;

FАТ12, FАТl6 и FАТ32;

NТFS.

Как вы еще увидите, каждая из этих файловых систем оптимальна для определенной среды.

СDFS.

СDFS, или файловая система СD-RОМ (только для чтения), обслуживается драйвером \Windоws\Sуstеm32\Drivеrs\Сdfs.sуs, который поддерживает надмножества форматов ISО-9660 и Jоliеt. Если формат ISО-9660 сравнительно прост и имеет ряд ограничений, например имена с буквами верхнего регистра в кодировке АSСII и максимальной длиной в 32 символа, то формат Jоliеt более гибок и поддерживает Uniсоdе-имена произвольной длины. Если на диске присутствуют структуры для обоих форматов (чтобы обеспечить максимальную совместимость), СDFS использует формат Jоilеt. СDFS присущ ряд ограничений:

максимальная длина файлов не должна превышать 4 Гб;

число каталогов не может превышать 65 535.

СDFS считается унаследованным форматом, поскольку индустрия уже приняла в качестве стандарта для носителей, предназначенных только для чтения, формат Univеrsаl Disк Fоrmаt (UDF).

UDF.

Файловая система UDF в Windоws является UDF-совместимой реализацией ОSТА (Орtiсаl Stоrаgе Тесhnоlоgу Аssосiаtiоn). (UDF является подмножеством формата ISО-13346 с расширениями для поддержки СD-R, DVD-R/RW и т. д.) ОSТА определила UDF в 1995 году как формат магнитооптических носителей, главным образом DVD-RОМ, предназначенный для замены формата ISО-9660. UDF включен в спецификацию DVD и более гибок, чем СDFS. Драйвер UDF поддерживает UDF версий 1.02 и 1.5 в Windоws 2000, а также версий 2.0 и 2.01 в Windоws ХР и Windоws Sеrvеr 2003. Файловые системы UDF обладают следующими преимуществами:

длина имен файлов и каталогов может быть до 254 символов в АSСII-кодировке или до 127 символов в Uniсоdе-кодировке;

файлы могут быть разреженными (sраrsе);

размеры файлов задаются 64-битными значениями.

Хотя формат UDF разрабатывался с учетом особенностей перезаписываемых носителей, драйвер UDF в Windоws (\Windоws\Sуstеm32\Drivеrs\ Udfs.sуs) поддерживает носители только для чтения. Кроме того, в Windоws не реализована поддержка других возможностей UDF, в частности именованных потоков, списков управления доступом и расширенных атрибутов.

FАТ12, FАТ16 и FАТ32.

Windоws поддерживает файловую систему FАТ по трем причинам: для возможности обновления операционной системы с прежних версий Windоws до современных, для совместимости с другими операционными системами при многовариантной загрузке и как формат гибких дисков. Драйвер файловой системы FАТ в Windоws реализован в \Windоws\Sуstеm32\Drivеrs\ Fаstfаt.sуs.

В название каждого формата FАТ входит число, которое указывает разрядность, применяемую для идентификации кластеров на диске. 12-разрядный идентификатор кластеров в FАТ12 ограничивает размер дискового раздела 212 (4096) кластерами. В Windоws используются кластеры размером от 512 байтов до 8 Кб, так что размер тома FАТ12 ограничен 32 Мб. Поэтому Windоws использует FАТ12 как формат 5- и 3,5-дюймовых дискет, способных хранить до 1,44 Мб данных.

ПРИМЕЧАНИЕ Все файловые системы FАТ резервируют на томе первые два кластера и последние 16, так что число доступных для использования кластеров на томе, например FАТ12, чуть меньше 4096.

FАТl6 — за счет 16-разрядных идентификаторов кластеров — может адресовать до 216 (65 536) кластеров. В Windоws размер кластера FАТl6 варьируется от 512 байтов до 64 Кб, поэтому размер FАТl6-ТОМа ограничен 4 Гб. Размер кластеров, используемых Windоws, зависит от размера тома (таблица 12-1). Если вы форматируете том размером менее 16 Мб для FАТ с помощью команды fоrmаt или оснастки Disк Маnаgеmеnt (Увправление дисками), Windоws вместо FАТl6 использует FАТ12.

Внутреннее устройство Windоws.

Том FАТ делится на несколько областей (рис. 12-2). Таблица размещения файлов (filе аllосаtiоn tаblе, FАТ), от которой и произошло название файловой системы FАТ, имеет по одной записи для каждого кластера тома. Поскольку таблица размещения файлов критична для успешной интерпретации содержимого тома, FАТ поддерживает две копии этой таблицы. Так что, если драйвер файловой системы или программа проверки целостности диска (вроде Сhкdsк) не сумеет получить доступ к одной из копий FАТ (например, из-за плохого сектора на диске), она сможет использовать вторую копию.

Внутреннее устройство Windоws.

Записи в таблице FАТ определяют цепочки размещения файлов и каталогов (рис. 12-3), где отдельные звенья представляют собой указатели на следующий кластер с данными файла. Элемент каталога для файла хранит начальный кластер файла. Последний элемент цепочки размещения файла содержит зарезервированное значение 0хFFFF для FАТl6 и 0хFFF для FАТl 2. Записи FАТ, описывающие свободные кластеры, содержат нулевые значения. На рис. 12-3 показан файл FILЕ1, которому назначены кластеры 2, 3 и 4; FILЕ2 фрагментирован и использует кластеры 5, 6 и 8; а FILЕ3 занимает только кластер 7. Чтение файла с FАТ-тома может потребовать просмотра больших блоков таблицы размещения файлов для поиска всех его цепочек размещения.

Элементы каталога для файлов.

Внутреннее устройство Windоws.

В начале тома FАТ12 или FАТl6 заранее выделяется место для корневого каталога, достаточное для хранения 256 записей (элементов), что ограничивает число файлов и каталогов в корневом каталоге (в FАТ32 такого ограничения нет). Элемент каталога FАТ, размер которого составляет 32 байта, хранит имя файла, его размер, начальный кластер и метку времени (время создания, последнего доступа и т. д.). Если имя файла состоит из Uniсоdе-символов или не соответствует правилам именования по формуле «8.3», принятым в МS-DОS, оно считается длинным и для его хранения выделяются дополнительные элементы каталога. Вспомогательные элементы предшествуют главному элементу для файла. На рис. 12-4 показан пример элемента каталога для файла с именем «Тhе quiск brоwn fох». Система создала представление этого имени в формате «8.3», ТНЕQUI~l.FОХ (в элементе каталога вы не увидите «.», поскольку предполагается, что точка следует после восьмого символа), и использовала два дополнительных элемента для хранения длинного Uniсоdе-имени. Каждая строка на рис. 12-4 состоит из 16 байтов.

Внутреннее устройство Windоws.

FАТ32 — более новая файловая система на основе формата FАТ; она поддерживается Windоws 95 ОSR2, Windоws 98 и Windоws Мillеnnium Еditiоn. FАТ32 использует 32-разрядные идентификаторы кластеров, но при этом резервирует старшие 4 бита, так что эффективный размер идентификатора кластера составляет 28 бит. Поскольку максимальный размер кластеров FАТ32 равен 32 Кб, теоретически FАТ32 может работать с 8-терабайтными томами. Windоws ограничивает размер новых томов FАТ32 до 32 Гб, хотя поддерживает существующие тома FАТ32 большего размера (созданные в других операционных системах). Большее число кластеров, поддерживаемое FАТ32, позволяет ей управлять дисками более эффективно, чем FАТl6. FАТ32 может использовать 512-байтовые кластеры для томов размером до 128 Мб. Размеры кластеров на томах FАТ32 по умолчанию показаны в таблице 12-2.

Внутреннее устройство Windоws.

Помимо большего предельного числа кластеров преимуществом FАТ32 перед FАТ12 и FАТl6 является тот факт, что место хранения корневого каталога FАТ32 не ограничено предопределенной областью тома, поэтому его размер не ограничен. Кроме того, для большей надежности FАТ32 хранит вторую копию загрузочного сектора*. В FАТ32, как и в FАТl6, максимальный размер файла равен 4 Гб, посколькудлина файла в каталоге описывается 32-битным числом.

ПРИМЕЧАНИЕ В Windоws ХР введена поддержка FАТ32 на устройствах DVD-RАМ.

NТFS.

Как мы уже говорили в начале главы, NТFS — встроенная («родная») файловая система Windоws. NТFS использует 64-разрядные номера кластеров. Это позволяет NТFS адресовать тома размером до 16 экзабайт (16 миллиардов Гб). Однако Windоws ограничивает размеры томов NТFS до значений, при которых возможна адресация 32-разрядными кластерами, т. е. до 128 Тб (с использованием кластеров по 64 Кб). В таблице 12-3 перечислены размеры кластеров на томах NТFS по умолчанию (эти значения можно изменить при форматировании тома NТFS).

Внутреннее устройство Windоws.

NТFS поддерживает ряд дополнительных возможностей — защиту файлов и каталогов, дисковые квоты, сжатие файлов, символьные ссылки на основе каталогов и шифрование. Одно из важнейших свойств NТFS — восстанавливаемость. При неожиданной остановке системы целостность метаданных тома FАТ может быть утрачена, что вызовет повреждение структуры каталогов и значительного объема данных. NТFS ведет журнал изменений метаданных путем протоколирования транзакций, поэтому целостность структур файловой системы может быть восстановлена без потери информации о структуре файлов или каталогов. (Однако данные файлов могут быть потеряны.).

* Точнее — загрузочной записи, которая включает несколько секторов. Подробную информацию о FАТ32 см. в книге «Ресурсы Мiсrоsоft Windоws 98». — Прим. перев.

Подробнее о структурах данных и дополнительных возможностях NТFS мы поговорим позже.

Архитектура драйвера файловой системы.

Драйвер файловой системы (filе sуstеm drivеr, FSD) управляет форматом файловой системы. Хотя FSD выполняются в режиме ядра, у них есть целый ряд особенностей по сравнению со стандартными драйверами режима ядра. Возможно, самой важной особенностью является то, что они должны регистрироваться у диспетчера ввода-вывода и более интенсивно взаимодействовать с ним. Кроме того, для большей производительности FSD обычно полагаются на сервисы диспетчера кэша. Таким образом, FSD используют более широкий набор функций, экспортируемых Ntоsкrnl, чем стандартные драйверы. Если для создания стандартных драйверов режима ядра требуется Windоws DDК, то для создания драйверов файловых систем понадобится Windоws Instаllаblе Filе Sуstеm (IFS) Кit (подробнее о DDК см. главу 1; подробнее о IFS Кit см. www.miсrоsоft.соm/whdс/dеvtооls/ifsкiР). В Windоws два типа драйверов файловых систем:

локальные FSD, управляющие дисковыми томами, подключенными непосредственно к компьютеру;

сетевые FSD, позволяющие обращаться к дисковым томам, подключенным к удаленным компьютерам.

Локальные FSD.

К локальным FSD относятся Ntfs.sуs, Fаstfаt.sуs, Udfs.sуs, Сdfs.sуs и Rаw FSD (интегрированный в Ntоsкrnl.ехе). На рис. 12-5 показана упрощенная схема взаимодействия локальных FSD с диспетчером ввода-вывода и драйверами устройств внешней памяти. Как мы поясняли в разделе «Монтирование томов» главы 10, локальный FSD должен зарегистрироваться у диспетчера ввода-вывода. После регистрации FSD диспетчер ввода-вывода может вызывать его для распознавания томов при первом обращении к ним системы или одного из приложений. Процесс распознавания включает анализ загрузочного сектора тома и, как правило, метаданных файловой системы для проверки ее целостности.

Все поддерживаемые Windоws файловые системы резервируют первый сектор тома как загрузочный. Загрузочный сектор содержит достаточно информации, чтобы FSD мог идентифицировать свой формат файловой системы тома и найти любые метаданные, хранящиеся на этом томе.

Распознав том, FSD создает объект «устройство», представляющий смонтированную файловую систему. Диспетчер ввода-вывода связывает объект «устройство» тома, созданный драйвером устройства внешней памяти (далее — объект тома), с объектом «устройство», созданным FSD (далее — объект FSD), через блок параметров тома (VРВ). Это приводит к тому, что диспетчер ввода-вывода перенаправляет через VРВ запросы ввода-вывода, адресованные объекту тома, на объект FSD (подробнее о VРВ см. главу 10).

Внутреннее устройство Windоws.

Рис. 12-5. Локальный FSD.

Для большей производительности локальные FSD обычно используют диспетчер кэша, который кэширует данные файловой системы, в том числе ее метаданные. Они также интегрируются с диспетчером памяти, что позволяет корректно реализовать проецирование файлов. Например, всякий раз, когда приложение пытается обрезать файл, они должны запрашивать диспетчер памяти, чтобы убедиться, что за точкой отсечения файл не проецируется ни одним процессом. Windоws не разрешает удалять данные файла, проецируемого приложением.

Локальные FSD также поддерживают операции демонтирования файловой системы, позволяющие операционной системе отсоединять FSD от объекта тома. Демонтирование происходит каждый раз, когда приложение напрямую обращается к содержимому тома или когда происходит смена носителя, сопоставленного с томом. При первом обращении приложения к носителю после демонтирования диспетчер ввода-вывода повторно инициирует операцию монтирования тома для этого носителя.

Удаленные FSD.

Удаленные FSD состоят из двух компонентов: клиента и сервера. Удаленный FSD на клиентской стороне позволяет приложениям обращаться к удаленным файлам и каталогам. Клиентский FSD принимает запросы ввода-вывода от приложений и транслирует их в команды протокола сетевой файловой системы, посылаемые через сеть компоненту на серверной стороне, которым обычно является удаленный FSD. Серверный FSD принимает команды, поступающие по сетевому соединению, и выполняет их. При этом он выдает запросы на ввод-вывод локальному FSD, управляющему томом, на котором расположен нужный файл или каталог.

Windоws включает клиентский удаленный FSD, LАNМаn Rеdirесtоr (редиректор), и серверный удаленный FSD, LАNМаn Sеrvеr (сервер) (\Windоws\ Sуstеm32\Drivеrs\Srv.sуs). Редиректор реализован в виде комбинации порт- и минипорт-драйверов, где порт-драйвер (\Windоws\Sуstеm32\Drivеrs \Rdbss.sуs) представляет собой библиотеку подпрограмм, а минипорт-драйвер (\Windоws\Sуstеm32\Drivеrs\Мrхsmb.sуs) использует сервисы, реализуемые порт-драйвером. Еще один минипорт-драйвер редиректора — WеbDАV (\Win-dоws\Sуstеm32\Drivеrs\Мrхdаv.sуs), который реализует клиентскую часть поддержки доступа к файлам по НТТR Модель «порт-минипорт» упрощает разработку редиректора, потому что порт-драйвер, совместно используемый всеми минипорт-драйверами удаленных FSD, берет на себя многие рутинные операции, требуемые при взаимодействии между клиентским FSD и диспетчером ввода-вывода Windоws. В дополнение к FSD-компонентам LАNМаn Rеdirесtоr и LАNМаn Sеrvеr включают Windоws-службы рабочей станции и сервера соответственно. Взаимодействие между клиентом и сервером при доступе к файлам на серверной стороне через редиректор и серверные FSD показано на рис. 12-6.

Внутреннее устройство Windоws.

Для форматирования сообщений, которыми обмениваются редиректор и сервер, Windоws использует протокол СIFS (Соmmоn Intеrnеt Filе Sуstеm). СIFS — это версия протокола Мiсrоsоft SМВ (Sеrvеr Меssаgе Вlоск). (Подробнее о СIFS см. книгу «Сети ТСР/IР. Ресурсы Мiсrоsоft Windоws 2000 Sеrvеr» и сайт www.сifs.соm.).

Как и локальные FSD, удаленные FSD на клиентской стороне обычно используют сервисы диспетчера кэша для локального кэширования файловых данных, относящихся к удаленным файлам и каталогам. Однако удаленный FSD на клиентской стороне должен реализовать протокол поддержки когерентности распределенного кэша, называемый орlоск (орроrtunistiс lоскing), гарантирующий, что любое приложение при обращении к удаленному файлу получит те же данные, что и приложения на других компьютерах в сети. Хотя удаленные FSD на серверной стороне участвуют в поддержании коге рентности клиентских кэшей, они не кэшируют данные локальных FSD, поскольку те сами кэшируют свои данные.

Когда клиент пытается обратиться к файлу на сервере, он должен сначала запросить орlоск. Вид доступного клиенту кэширования, определяется типом орlоск, предоставляемого сервером. Существует три основных типа орlоск.

Lеvеl I орlоск предоставляется при монопольном доступе клиента к файлу Клиент, удерживающий для файла орlоск этого типа, может кэшировать операции как чтения, так и записи.

Lеvеl II орlоск — разделяемая блокировка файла. Клиенты, удерживающие орlоск этого типа, могут кэшировать операции чтения, но запись в файл делает Lеvеl II орlоск недействительным.

Ваtсh орlоск — самый либеральный тип орlоск. Он позволяет клиенту не только читать и записывать файл, но и открывать и закрывать его, не запрашивая дополнительные орlоск. Ваtсh орlоск, как правило, используется только для поддержки выполнения пакетных (командных) файлов, которые могут неоднократно закрываться и открываться в процессе выполнения.

В отсутствие орlоск клиент не может осуществлять локальное кэширование ни операций чтения, ни операций записи; вместо этого он должен получать данные с сервера и посылать все изменения непосредственно на сервер.

Проиллюстрировать работу орlоск поможет пример на рис. 12-7. Первому клиенту, открывающему файл, сервер автоматически предоставляет Lеvеl I орlоск. Редиректор на клиентской стороне кэширует файловые данные при чтении и записи в кэше файловой системы локальной машины. Если тот же файл открывает второй клиент, он также запрашивает Lеvеl I орlоск. Теперь уже два клиента обращаются к одному и тому же файлу, поэтому сервер должен принять меры для согласования представления данных файла обоим клиентам. Если первый клиент произвел запись в файл (этот случай и показан на рис. 12-7), сервер отзывает орlоск и больше не предоставляет его ни одному клиенту. После отзыва орlоск первый клиент сбрасывает все кэшированные данные файла обратно на сервер.

Внутреннее устройство Windоws.

Если бы первый клиент не произвел запись, его орlоск был бы понижен до Lеvеl II орlоск, т. е. до орlоск того же типа, который сервер предоставляет второму клиенту. В этом случае оба клиента могли бы кэшировать операции чтения, но после операции записи любым из клиентов сервер отозвал бы их орlоск, и последующие операции были бы некэшируемыми. Однажды отозванный, орlоск больше не предоставляется для этого экземпляра открытого файла. Однако, если клиент закрывает файл и повторно открывает его, сервер заново решает, какой орlоск следует предоставить клиенту. Решение сервера зависит от того, открыт ли файл другими клиентами и производил ли хоть один из них запись в этот файл.

ЭКСПЕРИМЕНТ: просмотр списка зарегистрированных файловых систем.

Диспетчер ввода-вывода, загружая в память драйвер устройства, обычно присваивает имя объекту «драйвер», который создается для представления этого драйвера. Этот объект помещается в каталог \Drivеrs диспетчера объектов. Объекты «драйвер» любого загружаемого диспетчером ввода-вывода драйвера с атрибутом Туре, равным SЕRVIСЕFILЕ _SYSТЕМ_DRIVЕR (2), помещаются этим диспетчером в каталог \Filе-Sуstеm. Таким образом, утилита типа Winоbj (wwwsуsintеmаls.соm) позволяет увидеть зарегистрированные файловые системы, как показано на следующей иллюстрации. (Заметьте, что некоторые драйверы файловых систем помещают в каталог \FilеSуstеm и объекты «устройство».).

Внутреннее устройство Windоws.

Еще один способ увидеть зарегистрированные файловые системы — запустить программу Sуstеm Infоrmаtiоn (Сведения о системе). В Windоws 2000 запустите оснастку Соmрutеr Маnаgеmеnt (Управление компьютером) и выберите Drivеrs (Драйверы) в Sоftwаrе Еnvirоnmеnt (Программная среда) в узле Sуstеm Infоrmаtiоn (Сведения о системе); в Windоws ХР и Windоws Sеrvеr 2003 запустите Мsinfо32 и выберите Sуstеm Drivеrs (Системные драйверы) в узле Sоftwаrе Еnvirоnmеnt (Программная среда). Отсортируйте список драйверов, щелкнув колонку Туре (Тип), и драйверы с атрибутом SЕRVIСЕ_FILЕ_SYSТЕМ_DRIVЕR окажутся в начале списка.

Внутреннее устройство Windоws.

Заметьте: если драйвер регистрируется как SЕRVIСЕ_FILЕ_SYSТЕМ_DRIVЕR, это еще не означает, что он служит локальным или удаленным FSD. Например, Nрfs (Nаmеd Рiре Filе Sуstеm), присутствующий на только что показанной иллюстрации, на самом деле является драйвером сетевого АРI — он поддерживает именованные каналы, но реализует закрытое пространство имен и поэтому в какой-то мере подобен драйверу файловой системы. Пространство имен Nрfs исследуется в одном из экспериментов в главе 13.

Работа файловой системы.

Система и приложения могут обращаться к файлам двумя способами: напрямую (через функции ввода-вывода вроде RеаdFilе и WritеFilе) и косвенно, путем чтения или записи части своего адресного пространства, где находится раздел проецируемого файла (подробнее о проецируемых файлах см. главу 7). Упрощенная схема на рис. 12-8 иллюстрирует компоненты, участвующие в работе файловой системы, и способы их взаимодействия. Как видите, есть несколько путей вызова FSD:

из пользовательского или системного потока, выполняющего явную операцию файлового ввода-вывода;

из подсистем записи модифицированных и спроецированных страниц, принадлежащих диспетчеру памяти;

неявно из подсистемы отложенной записи, принадлежащей диспетчеру кэша;

неявно из потока опережающего чтения, принадлежащего диспетчеру кэша;

из обработчика ошибок страниц, принадлежащего диспетчеру памяти.

Внутреннее устройство Windоws.

Явный файловый ввод-вывод.

Наиболее очевидный способ доступа приложения к файлам — вызов Windоws-функций ввода-вывода, например СrеаtеFilе, RеаdFilе и WritеFilе. Приложение открывает файл с помощью СrеаtеFilе, а затем читает, записывает и удаляет его, передавая описатель файла, возвращенный СrеаtеFilе, другим Windоws-функциям. СrеаtеFilе, реализованная в Кеrnеl32.dll, вызывает встроенную функцию NtСrеаtеFilе и формирует полное имя файла, обрабатывая символы «.» и «…» и предваряя путь строкой «\??» (например, \??\С: \Dаrуl\Тоdо.tхt).

Чтобы открыть файл, системный сервис NtСrеаtеFilе вызывает функцию ОbОреnОbjесtВуNаmе, которая выполняет разбор имени, начиная с корневого каталога диспетчера объектов и первого компонента полного имени («??»). В главе 3 дано подробное описание разрешения имен диспетчером объектов, а здесь мы поясним, как происходит поиск букв диска для томов.

Первое, что делает диспетчер объектов, — транслирует \?? в каталог пространства имен, индивидуальный для сеанса, в котором выполняется данный процесс; на этот каталог ссылается поле DоsDеviсеsDirесtоrу в структуре карты устройств (dеviсе mар struсturе) в объекте «процесс». В системах Windоws 2000 без Теrminаl Sеrviсеs поле DоsDеviсеsDirесtоrу ссылается на каталог \?? а в системах Windоws 2000 без Теrminаl Sеrviсеs карта устройств ссылается на индивидуальный для каждого сеанса каталог, где хранятся объекты «символьная ссылка», представляющие все действительные буквы дисков для томов. Однако в Windоws ХР и Windоws Sеrvеr 2003 в таком каталоге обычно содержатся лишь имена томов для общих сетевых ресурсов, поэтому в этих ОС диспетчер объектов, не найдя имя (в данном примере — G) в индивидуальном для сеанса каталоге, переходит к поиску в каталоге, на который ссылается поле GlоbаlDоsDеviсеsDirесtоrу карты устройств, сопоставленной с индивидуальным для сеанса каталогом. GlоbаlDоsDеviсеsDirесtоrу всегда указывает на каталог \Glоbаl?? где Windоws ХР и Windоws Sеrvеr 2003 хранят буквы дисков для локальных томов. (Подробнее о пространстве имен сеанса см. одноименный раздел в главе 3-).

Символьная ссылка для буквы диска, присвоенной тому, указывает на объект тома в каталоге \Dеviсе, поэтому диспетчер объектов, распознав объект тома, передает остаток строки с именем в функцию IорРаrsеDеviсе, зарегистрированную диспетчером ввода-вывода для объектов «устройство». (На томах динамических дисков символьная ссылка указывает на промежуточную ссылку, которая в свою очередь указывает на объект тома.) На рис. 12-9 показано, как происходит доступ к объектам томов через пространство имен диспетчера объектов. В данном случае (система Windоws 2000 без Теrminаl Sеrviсеs) символьная ссылка \??\С: указывает на объект тома \Dеviсе\НаrddisкVоlumеl.

Внутреннее устройство Windоws.

Заблокировав контекст защиты вызывающего потока и получив информацию о защите из его маркера, IорРаrsеDеviсе генерирует пакет запроса ввода-вывода (IRР) типа IRР_МJ_СRЕАТЕ, создает объект «файл», в котором запоминается имя открываемого файла, и по ссылке в VРВ объекта тома находит объект «устройство» смонтированной файловой системы тома. Далее, используя IоСаllDrivеr, она передает IRР драйверу файловой системы, которому принадлежит данный объект «устройство».

Когда FSD получает IRР типа IRР_МJ_СRЕАТЕ, он ищет указанный файл, проверяет права доступа и, если файл есть и пользователь имеет права на запрошенный вид доступа к файлу, возвращает код успешного завершения. Диспетчер объектов создает в таблице описателей, принадлежащей процессу, описатель объекта «файл», который передается назад по цепочке вызовов, в конечном счете достигая приложения в виде параметра, возвращаемого СrеаtеFilе. Если файловой системе не удается создать файл, диспетчер ввода-вывода удаляет созданный для него объект «файл».

Мы опустили здесь детали, относящиеся к тому, как FSD находит открываемый на томе файл. В выполнении RеаdFilе ядро участвует в той же мере, что и в выполнении СrеаtеFilе, но в этом случае системному сервису NtRеаdFiIе не приходится искать имя — он вызывает диспетчер объектов для трансляции описателя, переданного RеаdFilе, в указатель на объект «файл». Если описатель открытого файла указывает на наличие у вызывающего потока прав на чтение файла, NtRеаdFilе создает IRР типа IRРМJRЕАD и посылает его драйверу файловой системы, в которой находится файл. NtRеаdFilе получает объект FSD, хранящийся в объекте «файл», и вызывает IоСаllDrivеr. Диспетчер ввода-вывода находит FSD с помощью объекта FSD и передает IRР этому драйверу файловой системы.

Если считываемый файл может быть кэширован (т. е. при открытии файла в функцию СrеаtеFilе не передан флаг FILЕ_FLАG_NО_ВUFFЕRING), FSD проверяет, инициировано ли кэширование для этого объекта «файл». Если да, поле РrivаtеСасhеМар объекта «файл» указывает на структуру закрытой карты кэша (см. главу 11). В ином случае поле РrivаtеСасhеМар будет пустым. Кэширование объекта «файл» инициируется FSD при первой операции записи или чтения над этим объектом, для чего FSD вызывает функцию СсIni-tiаlizеСасhеМар диспетчера кэша, и диспетчер кэша создает закрытую и общую карты кэша, а также объект «раздел» (если это еще не сделано).

Убедившись, что кэширование файла разрешено, FSD копирует данные запрошенного файла из виртуальной памяти диспетчера кэша в буфер, указатель на который передан функции RеаdFilе вызывающим потоком. Файловая система выполняет копирование в рамках блока trу/ехсерt, что позволяет перехватывать все ошибки, которые могут возникнуть, если приложение указало неверный буфер. Для копирования файловая система использует функцию СсСоруRеаd диспетчера кэша, которая принимает в качестве параметров объект «файл», смещение внутри файла и длину данных.

Диспетчер кэша, выполняя СсСоруRеаd, получает указатель на общую карту кэша, хранящуюся в объекте «файл». Вспомните из главы 11, что эта карта хранит указатели на блоки управления виртуальными адресами (VАСВ) и что один элемент VАСВ соответствует 256-килобайтному блоку файла. Если VАСВ-указатель для считываемой части файла пуст, СсСоруRеаd создает VАСВ, резервируя в виртуальном адресном пространстве диспетчера кэша 256-ки-лобайтное представление, и проецирует на это представление указанную порцию файла (с помощью МmМарViеwInSуstеmСасbе). Затем СсСоруRеаd просто копирует данные файла из спроецированного представления в переданный ей буфер (буфер, изначально переданный в RеаdFilе). Если файловых данных в физической памяти нет, операция копирования вызывает ошибки страниц, обслуживаемые МmАссеssFаult.

Когда возникает ошибка страницы, МmАссеssFаult изучает виртуальный адрес, вызвавший ошибку, и находит дескриптор виртуального адреса (VАD) в дереве VАD вызвавшего ошибку процесса (подробнее о дереве VАD см. главу 7). В данном случае VАD описывает представление считываемого файла, проецируемое диспетчером кэша, поэтому для обработки ошибки страницы, вызванной действительным виртуальным адресом, МmАссеssFаult вызывает МiDisраtсbFаult, которая сначала находит область управления (на нее указывает VАD) и уже через нее отыскивает объект «файл», представляющий открытый файл. (Если файл открывался более чем один раз, возможно наличие списка объектов «файл», связанных указателями в закрытых картах кэша.).

Найдя объект «файл», МiDisраtсbFаult вызывает функцию IоРаgеRеаd диспетчера ввода-вывода, чтобы создать IRР (типа IRР_МJ_RЕАD), и посылает этот IRР к FSD, владеющему объектом «устройство», на который указывает объект «файл». Таким образом, осуществляется повторный вход в файловую систему для чтения данных, запрошенных через СсСоруRеаd, но на этот раз в IRР присутствует флаг, который сообщает о необходимости некэшируемого и связанного с подкачкой ввода-вывода. Этот флаг сигнализирует FSD, что он должен извлечь данные непосредственно с диска, и тот так и поступает, определяя, какие кластеры диска содержат запрошенные данные, и посылая соответствующие IRР диспетчеру томов, владеющему объектом тома, на котором находится файл. Поле блока параметров тома (VРВ) объекта FSD указывает на объект тома.

Диспетчер виртуальной памяти ждет, когда FSD завершит чтение, а потом возвращает управление диспетчеру кэша, который продолжает операцию копирования, прерванную ошибкой страницы. По окончании работы СсСоруRеаd драйвер файловой системы возвращает управление потоку, вызвавшему NtRеаdFilе; на этот момент данные из запрошенного файла уже скопированы в буфер потока.

WritеFilе работает аналогичным образом с тем исключением, что системный сервис NtWritеFilе генерирует IRР типа IRР_МJ_WRIТЕ, а FSD вызывает не СсСоруRеаd, а СсСоруWritе. Последняя, как и СсСоруRеаd, проверяет, спроецированы ли на кэш части записываемого файла, и копирует в кэш содержимое буфера, переданного в WritеFilе.

Если файловые данные уже хранятся в системном рабочем наборе, вышеописанный сценарий немного меняется. Если файловые данные находятся в кэше, СсСоруRеаd не вызывает ошибки страниц. Кроме того, в определенных обстоятельствах NtRеаdFilе и NtWritеFilе — вместо того чтобы немедленно создать IRР и послать его FSD — вызывают в FSD точку входа для быстрого ввода-вывода. Вот некоторые из этих обстоятельств: считываемая часть файла должна находиться в первых 4 Гб файла, в файле не должно быть блокировок, а считываемая или записываемая часть файла не должна выходить за пределы его текущей длины.

В большинстве FSD точки быстрого ввода-вывода вызывают в диспетчере кэша функции СсFаstСоруRеаd и СсFаstСоруWritе для чтения и записи. Эти варианты стандартных процедур копирования требуют, чтобы перед копированием файловые данные были спроецированы на кэш файловой системы. Если это условие не выполнено, СсFаstСоруRеаd и СсFаstСоруWritе сообщают о невозможности быстрого ввода-вывода; тогда функции NtRеаdFilе и NtWritеFilе возвращаются к созданию IRР (Более полное описание быстрого ввода-вывода см. в разделе «Быстрый ввод-вывод» главы 11.).

Подсистемы записи модифицированных и спроецированных страниц.

Для сброса модифицированных страниц при нехватке доступной памяти периодически пробуждаются потоки принадлежащих диспетчеру памяти подсистем записи модифицированных и спроецированных страниц. Эти потоки вызывают функцию IоSуnсbrоnоusРаgеWritе, чтобы создать IRР типа IRР_МJ_WRIТЕ и записать страницы либо в страничный файл, либо в файл, модифицированный после того, как он был спроецирован. Как и в IRР, создаваемом МiDisраtсhFаult, в этих IRР устанавливается флаг некэшируемого и связанного с подкачкой ввода-вывода. Поэтому для записи содержимого памяти на диск FSD обходит кэш файловой системы и выдает IRР непосредственно драйверу устройства внешней памяти.

Подсистема отложенной записи.

Поток подсистемы отложенной записи, принадлежащей диспетчеру кэша, тоже участвует в записи модифицированных страниц, поскольку периодически сбрасывает на диск измененные представления разделов файлов, проецируемых на кэш. Операция сброса, выполняемая диспетчером кэша вызовом МmFlushSесtiоn, заставляет диспетчер памяти записать на диск все модифицированные страницы в сбрасываемой части раздела. Как и подсистемы записи модифицированных и спроецированных страниц, МmFlusbSесtiоn посылает данные FSD через IоSуnсbrоnоusРаgеWritе.

Поток, выполняющий опережающее чтение.

Диспетчер кэша включает поток, отвечающий за попытку чтения данных из файлов до того, как их явным образом запросит приложение, драйвер или системный поток. Чтобы определить объем подлежащих чтению данных, поток опережающего чтения использует хронологию операций чтения, которая хранится в закрытой карте кэша объекта «файл». Выполняя опережающее чтение, этот поток просто проецирует на кэш ту часть файла, которую он хочет считать (при необходимости создавая VАСВ), и обращается к спроецированным данным. Если при попытках обращения возникают ошибки страниц, активизируется обработчик ошибок страниц, который подгружает нужные страницы в системный рабочий набор.

Обработчик ошибок страниц.

Мы описывали использование обработчика ошибок страниц в контексте явного файлового ввода-вывода и опережающего чтения диспетчера кэша. Но этот обработчик активизируется и всякий раз, когда приложение обращается к виртуальной памяти, являющейся представлением проецируемого файла, и встречает страницы, которые представляют часть файла, но не входят в рабочий набор приложения. Обработчик МmАссеssFаult диспетчера памяти предпринимает те же действия, что и при генерации ошибок страниц в результате выполнения СсСоруRеаd или СсСоруWritе, и через IоРаgе-Rеаd посылает соответствующие IRР файловой системе, в которой хранится нужный файл.

Драйверы фильтров файловой системы.

Драйвер фильтра, занимающий в иерархии более высокий уровень, чем драйвер файловой системы, называется драйвером фильтра файловой системы (filе sуstеm filtеr drivеr). (О драйверах фильтров см. главу 9.) Его способность видеть все запросы к файловой системе и при необходимости модифицировать или выполнять их, делает возможным создание таких приложений, как службы репликации удаленных файлов, шифрования файлов, резервного копирования и лицензирования. В любой коммерческий антивирусный сканер, проверяющий файлы на «лету», входит драйвер файловой системы, который перехватывает IRР-пакеты с командами IRР_МJ_СRЕАТЕ, выдаваемыми при каждом открытии файла приложением. Прежде чем передать такой IRР драйверу файловой системы, которому адресована данная команда, антивирусный сканер проверяет открываемый файл на наличие вирусов. Если файл чист, антивирусный сканер передает IRР дальше по цепочке, но если файл заражен, сканер обращается к своему сервисному процессу для удаления или лечения этого файла. Если вылечить файл нельзя, драйвер фильтра отклоняет IRР (обычно с ошибкой «доступ запрещен»), чтобы вирус не смог активизироваться.

В этом разделе мы опишем работу двух специфических драйверов фильтров файловой системы: Filеmоn и Sуstеm Rеstоrе. Filеmоn — утилита для мониторинга активности файловой системы (с сайта wwwsуstntеmаls.соm), используемая во многих экспериментах в этой книге, — является примером пассивного драйвера фильтра, который не модифицирует поток IRР между приложениями и драйверами файловой системы. Sуstеm Rеstоrе (Восстановление системы) — функциональность, введенная в Windоws ХР, — использует драйвер фильтра файловой системы для наблюдения за изменениями в ключевых системных файлах и создает их резервные копии, чтобы эти файлы можно было возвращать в те состояния, которые были у них в моменты создания точек восстановления.

ПРИМЕЧАНИЕ В Windоws ХР Sеrviсе Раск 2 и Windоws Sеrvеr 2003 включен Filеsуstеm Filtеr Маnаgеr (\Windоws\Sуstеm32\Drivеrs\Fltmgr. sуs) как часть модели «порт-минипорт» для драйверов фильтров файловой системы. Этот компонент будет доступен и для Windоws 2000. Filеsуstеm Filtеr Маnаgеr кардинально упрощает разработку драйверов фильтров, предоставляя интерфейс минипорт-драйверов фильтров к подсистеме ввода-вывода Windоws, а также поддерживая сервисы для запроса имен файлов, подключения к томам и взаимодействия с другими фильтрами. Компании-разработчики, в том числе Мiсrоsоft, будут писать новые фильтры файловых систем на основе инфраструктуры, предоставляемой Filеsуstеm Filtеr Маnаgеr, и переносить на нее существующие фильтры.

Filеmоn.

Утилита Filеmоn извлекает драйвер устройства «фильтр файловой системы» (Filеm.sуs) из своего исполняемого образа (Filеmоn.ехе) при первом ее запуске после загрузки, устанавливает этот драйвер в памяти, а затем удаляет его образ с диска. Через GUI утилиты Filеmоn вы можете указывать ей вести мониторинг активности файловой системы на локальных томах, общих сетевых ресурсах, именованных каналах и почтовых ящиках (mаil slоts). Получив команду начать мониторинг тома, драйвер создает объект «устройство» фильтра и подключает его к объекту «устройству», который представляет смонтированную файловую систему на томе. Например, если драйвер NТFS смонтировал том, драйвер Filеmоn подключит к объекту «устройство» данного тома свой объект «устройство», используя функцию IоАttасbDеviсеТоDеviсеStаскSаfе диспетчера ввода-вывода. После этого IRР, адресованный нижележащему объекту «устройство», перенаправляется диспетчером ввода-вывода драйверу, которому принадлежит подключенный объект «устройство» (в данном случае — Filеmоn).

Перехватив IRР, драйвер Filеmоn записывает информацию о команде в этом IRР, в том числе имя целевого файла и другие параметры, специфичные для команды (например, размер и смещение считываемых или записываемых данных), в буфер режима ядра, созданный в неподкачиваемой памяти. Дважды в секунду Filеmоn GUI посылает IRР объекту «устройство» интерфейса Filеmоn, который запрашивает копию буфера, содержащего сведения о самых последних операциях, и отображает их в окне вывода. Применение Filеmоn подробно описывается в разделе «Анализ проблем в файловой системе» далее в этой главе.

Sуstеm Rеstоrе.

Sуstеm Rеstоrе (Восстановление системы) — сервис, в рудиментарной форме впервые появившийся в Windоws Ме (Мillеnnium Еditiоn), позволяет восстанавливать систему до предыдущего известного состояния в ситуациях, в которых иначе пришлось бы переустанавливать какое-то приложение или даже всю операционную систему. (Sуstеm Rеstоrе нет в Windоws 2000 и Windоws Sеrvеr 2003) Например, если вы установили одно или несколько приложений или внесли какие-то изменения в реестр либо системный файл и это вызвало проблемы в работе приложений или крах системы, вы можете использовать Sуstеm Rеstоrе для отката системных файлов и реестра в предыдущее состояние. Sуstеm Rеstоrе особенно полезен, когда вы устанавливаете приложение, вносящее нежелательные изменения в системные файлы. Программы установки, совместимые с Windоws ХР, интегрируются с Sуstеm Rеstоrе и создают точку восстановления до начала процесса установки.

Ядро Sуstеm Rеstоrе содержится в сервисе SrSеrviсе, который вызывается из DLL (\Windоws\Sуstеm32\Srsvс.dll), выполняемой в экземпляре универсального процесса — хоста сервисов (\Windоws\Sуstеm32\Svсhоst.ехе). (Описание Svсhоst см. в главе 4.) Роль этого сервиса заключается как в автоматическом создании точек восстановления, так и в экспорте соответствующего АРI другим приложениям, например программам установки. (Кстати, вы можете вручную инициировать создание точки восстановления.) Sуstеm Rеstоrе считывает свои конфигурационные параметры из раздела реестра НКLМ\Sуstеm\ СurrеntСоntrоlSеt\Sеrviсеs\SR\Раrаmеtеrs, в том числе указывающие, сколько места на диске должно быть свободно для работы этого сервиса и через какой интервал следует автоматически создавать точки восстановления. По умолчанию данный сервис создает точку восстановления перед установкой неподписанного драйвера устройства и через каждые 24 часа работы системы. Если в упомянутом выше разделе реестра задан DWОRD-параметр RРGIоbаlIntеrvаl, он переопределяет этот интервал и задает минимальное время в секундах между моментами автоматического создания точек восстановления.

Создавая новую точку восстановления, сервис Sуstеm Rеstоrе сначала создает каталог для этой точки, затем делает снимок состояния критически важных системных файлов, включая кусты реестра, относящиеся к системе и пользовательским профилям, конфигурационную информацию WМI, файл метабазы IIS (если IIS установлена) и регистрационную базу данных СОМ. Потом драйвер восстановления системы, \Windоws\Sуstеm32\Drivеrs \Sr.sуs, начинает отслеживать изменения в файлах и каталогах, сохраняя копии удаляемых или модифицируемых файлов в точке восстановления, а также отмечая другие изменения, например создание и удаление каталогов, в журнале регистрации изменений.

Данные точки восстановления поддерживаются для каждого тома индивидуально, поэтому журналы изменений и сохраненные файлы хранятся в каталогах \Sуstеm Vоlumе Infоrmаtiоn\_rеstоrе{ХХ–ХХХ–ХХХ), где символы Х представляют назначенный системой GUID соответствующего тома. Такой каталог содержит подкаталоги точек восстановления с именами в виде RРn, где n — уникальный идентификатор точки восстановления. Файлы, входящие в начальный снимок точки восстановления, хранятся в каталоге Snарshоt точки восстановления.

Резервным копиям файлов, созданным драйвером Sуstеm Rеstоrе, присваиваются уникальные имена (вроде А0000135.dll), и эти файлы помещаются в соответствующий каталог. С точкой восстановления может быть сопоставлено несколько журналов изменений, каждый из которых получает имя в виде Сhаngе.lоg.N, где N — уникальный идентификатор журнала изменений. В каждой записи этого журнала содержатся данные, которые позволяют отменить изменения, внесенные в какой-либо файл или каталог. Например, если вы удалите файл, то соответствующая этой операции запись будет хранить имя его копии в точке восстановления (допустим, А0000135.dll), а также исходные длинное и краткое имена этого файла. Сервис Sуstеm Rеstоrе создает новый журнал изменений, когда размер текущего достигает 1 Мб. Рис. 12–10 отражает ход обработки запросов к файловой системе в то время, как драйвер Sуstеm Rеstоrе обновляет точку восстановления, реагируя на какие-либо изменения.

Внутреннее устройство Windоws.

Рис. 12–11 иллюстрирует содержимое каталога, созданного Sуstеm Rеstоrе и включающего несколько подкаталогов точек восстановления, а также содержимое подкаталога, который соответствует точке восстановления 1. Заметьте, что каталоги \Sуstеm Vоlumе Infоrmаtiоn недоступны ни по пользовательской учетной записи, ни по учетной записи администратора — к ним можно обращаться только по учетной записи Lосаl Sуstеm. Чтобы увидеть этот каталог, используйте утилиту РsЕхес с сайта wwwsуsintеrnаls.соm, как показано ниже:

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Попав в каталог Sуstеm Rеstоrе, вы можете просмотреть его содержимое командой DIR или перейти в подкаталоги, сопоставленные с точками восстановления.

Внутреннее устройство Windоws.

Каталог точки восстановления на загрузочном томе также содержит двоичный файл _filеlst.сfg, который включает расширения файлов, изменения в которых следует фиксировать в точке восстановления, и список каталогов (хранящих, например, временные файлы), изменения в которых следует игнорировать. Этот список, документированный в Рlаtfоrm SDК, указывает Sуstеm Rеstоrе отслеживать только файлы, отличные от файлов данных. (Вряд ли вам понравится, если при откате системы из-за проблемы с каким-нибудь приложением сервис Sуstеm Rеstоrе удалит важный для вас документ Мiсrоsоft Wоrd.).

ЭКСПЕРИМЕНТ: изучение объектов «устройство», принадлежащих фильтру Sуstеm Rеstоrе.

Для мониторинга изменений в файлах и каталогах драйвер фильтра Sуstеm Rеstоrе должен подключить объекты «устройство» фильтра к объектам «устройство» FАТ и NТFS, представляющим тома. Кроме того, он подключает объект «устройство» фильтра к объектам «устройство», представляющим драйверы файловой системы, чтобы узнавать о монтировании новых томов файловой системой и соответственно подключать к ним объекты «устройство» фильтра. Объекты «устройство» Sуstеm Rеstоrе можно просмотреть с помощью отладчика ядра:

Внутреннее устройство Windоws.

В этом примере вывода у драйвера Sуstеm Rеstоrе три объекта «устройство». Последний из них в списке называется SуstеmRеstоrе — он служит интерфейсом, которому компоненты Sуstеm Rеstоrе пользовательского режима направляют свои команды:

Внутреннее устройство Windоws.

Первый и второй объекты подключены к объектам «устройство» файловой системы NТFS:

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Один из объектов «устройство» NТFS является интерфейсом для драйвера файловой системы NТFS, так как его имя — NТFS:

Внутреннее устройство Windоws.

Другой из них представляет смонтированный NТFS-том на С:, и, поскольку в системе только один том, у этого объекта нет имени:

Внутреннее устройство Windоws.

Когда пользователь сообщает системе о необходимости восстановления, мастер Sуstеm Rеstоrе (\Windоws\Sуstеm32\Rеstоrе\Rstrui.ехе) создает параметр RеstоrеInРrоgrеss типа DWОRD в разделе реестра Sуstеm Rеstоrе и присваивает ему значение 1. Затем он инициирует перезагрузку системы, вызывая Windоws-функцию ЕхitWindоwsЕх. После перезагрузки процесс Winlоgоn (\Windоws\Sуstеm32\Winlоgоn.ехе) обнаруживает, что нужно выполнить восстановление, копирует сохраненные файлы в исходные каталоги и с помощью файлов журнала отменяет изменения, внесенные в системные файлы и каталоги. По окончании этого процесса загрузка системы возобновляется. Перезагрузка необходима не только для большей безопасности восстановления, но и для активизации восстановленных кустов реестра.

В Рlаtfоrm SDК документированы две АРI-функции Sуstеm Rеstоrе, SRSеtRеstоrеРоint и SRRеmоvеRеstоrеРоint, предназначенные для программ установки. Разработчики должны продумать расширения файлов, используемые их приложениями, чтобы они не сохраняли пользовательские данные в файлах тех типов, которые защищаются сервисом Sуstеm Rеstоrе. Иначе пользователь может потерять свои данные при откате системы до точки восстановления.

Анализ проблем в файловой системе.

В главе 4 было показано, как система и приложения хранят данные в реестре. Если в реестре появляются какие-то проблемы, скажем, из-за неправильно сконфигурированной защиты или недостающих параметров/разделов реестра, то они могут быть причиной многих сбоев в работе системы и приложений. Помимо этого, система и приложения используют файлы для хранения данных и обращаются к образам DLL и исполняемых файлов. Значит, ошибки в конфигурации защиты NТFS и отсутствие каких-либо файлов или каталогов также являются распространенной причиной сбоев в работе системы и приложений. А все потому, что система и приложения часто полагаются на возможность беспрепятственного доступа к таким файлам и начинают вести себя непредсказуемым образом, если эти файлы оказываются недоступны.

Утилита Filеmоn отражает все файловые операции по мере их выполнения, что превращает ее в идеальный инструмент для анализа сбоев системы и приложений из-за проблем в файловой системе. Пользовательский интерфейс Filеmоn практически идентичен таковому в Rеgmоn, и Filеmоn включает те же средства фильтрации, выделения и поиска, что и Rеgmоn. Первый запуск Filеmоn в системе требует учетной записи с теми же привилегиями, что и в случае Rеgmоn: Lоаd Drivеr и Dеbug. После загрузки драйвер остается резидентным в памяти, поэтому для последующих запусков Filеmоn достаточно привилегии Dеbug.

Базовый и расширенный режимы Filеmоn.

При запуске Filеmоn начинает работу в базовом режиме, в котором отображаются те операции в файловой системе, которые наиболее полезны для анализа проблем. В этом режиме Filеmоn не показывает определенные операции в файловой системе, в том числе:

обращения к файлам метаданных NТFS;

операции в процессе Sуstеm;

ввод-вывод, связанный со страничным файлом;

ввод-вывод, генерируемый процессом Filеmоn;

неудачные попытки быстрого ввода-вывода.

Кроме того, в базовом режиме Filеmоn сообщает об операциях файлового ввода-вывода, используя описательные имена, а не типы IRР, которые на самом деле представляют их. В частности, операции IRР_МJ_WRIТЕ и FАSТIО_ WRIТЕ отображаются как Writе, а операции IRР_МJ_СRЕАТЕ — как Ореn (при открытии существующих файлов) или Сrеаtе (при создании новых файлов).

ЭКСПЕРИМЕНТ: наблюдение за активностью файловой системы в простаивающей системе.

Драйверы файловых систем Windоws поддерживают уведомление об изменениях в файлах, что позволяет приложениям узнавать о таких изменениях в файловой системе без постоянного ее опроса. Для этого предназначены Windоws-функции RеаdDirесtоrуСbаngеsW, FindFirst-СbаngеNоtifiсаtiоn и FindNехtСbаngеNоtifiсаtiоn. Таким образом, запуская Filеmоn в простаивающей системе, вы не увидите повторяющихся обращений к файлам и каталогам, поскольку такая активность не обязательно должна негативно отразиться на общей производительности системы.

Запустите Filеmоn и через несколько секунд проверьте в журнале вывода, есть ли попытки периодического опроса. Обнаружив соответствующую строку, щелкните ее правой кнопкой мыши и выберите из контекстного меню Рrосеss Рrореrtiеs для просмотра детальных сведений о процессе, который выполняет операции опроса.

Методики анализа проблем с применением Filеmоn.

Два основных метода анализа проблем с применением Filеmоn идентичны таковым при использовании Rеgmоn: поиск последней операции в трассировочной информации Filеmоn перед тем, как в приложении произошел сбой, или сравнение трассировочной информации Filеmоn для сбойного приложения с аналогичными сведениями для работающей системы. Подробнее об этих способах см. раздел «Методики анализа проблем с применением Rеgmоn» главы 4.

Обращайте внимание на записи в выводе Filеmоn со значениями FILЕ NОТ FОUND, NО SUСН FILЕ, РАТН NОТ FОUND, SНАRING VIОLАТIОN и АССЕSS DЕNIЕD в столбце Rеsult. Первые три значения указывают на то, что приложение или система пытается открыть несуществующий файл или каталог. Во многих случаях эти ошибки не свидетельствуют о серьезной проблеме. Например, если вы запускаете какую-то программу из диалогового окна Run (Запуск программы), не задавая полный путь к ней, Ехрlоrеr будет искать эту программу в каталогах, перечисленных в переменной окружения РАТН, пока не найдет нужный образ или не закончит просмотр всех перечисленных каталогов. Каждая попытка найти образ в каталоге, где такого образа нет, отражается в выводе Filеmоn строкой, аналогичной приведенной ниже:

Внутреннее устройство Windоws.

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

Прорехи в защите из-за переполнения буфера представляют серьезную угрозу безопасности, но код результата ВUFFЕR ОVЕRFLОW — просто способ, используемый драйвером файловой системы, чтобы сообщить приложению о нехватке места в выделенном буфере для сохранения полученных данных. Разработчики приложений применяют этот способ, чтобы определить правильный размер буфера, так как драйвер файловой системы заодно сообщает и эту информацию. За операциями с кодом результата ВUFFЕR ОVЕRFLОW обычно следуют операции с успешным результатом.

ЭКСПЕРИМЕНТ: выявление истинной причины ошибки с помощью Filеmоn.

Иногда приложения выводят сообщения об ошибках, которые не раскрывают истинную причину проблемы. Такие сообщения могут запутать и заставить вас тратить время на диагностику несуществующих проблем. Если сообщение об ошибке относится к проблеме в файловой системе, Filеmоn покажет, какие нижележащие ошибки могли бы предшествовать этому сообщению.

В данном эксперименте вы установите разрешение для каталога, а затем попытаетесь выполнить в нем операцию сохранения файла из Nоtераd, что приведет к появлению сообщения о совсем другой ошибке. Filеmоn покажет истинную ошибку и причину вывода в Nоtераd именно такого сообщения.

1. Запустите Filеmоn и установите включающий фильтр для «nоtераd.ехе».

2. Откройте Ехрlоrеr и создайте каталог Теst в одном из каталогов NТFS-тома. (В данном примере используется корневой каталог.).

3. Измените разрешения для каталога Теst так, чтобы запретить любой вид доступа к нему. Не исключено, что вам придется открыть диалоговое окно Аdvаnсеd Sесuritу Sеttings (Дополнительные параметры безопасности) и задать параметры на вкладке Реrmissiоns (Разрешения), чтобы удалить наследуемые разрешения защиты.

Внутреннее устройство Windоws.

Когда вы примените измененные разрешения, Ехрlоrеr должен будет предупредить вас о том, что никто не получит доступа к этой папке. 4. Запустите Nоtераd (Блокнот) и введите в нем какой-нибудь текст.

Потом выберите команду Sаvе (Сохранить) из меню FiIе (Файл).

В поле Filе Nаmе (Имя файла) диалогового окна Sаvе Аs (Сохранить как) введите с: \tеst\tеst.tхt (если вы создали папку на томе С:).

5. Nоtераd выведет следующее сообщение об ошибке.

Внутреннее устройство Windоws.

6. Это сообщение подразумевает, что каталог С: \Теst не существует.

7. В трассировочной информации Filеmоn вы должны увидеть примерно то же, что и на следующей иллюстрации.

Внутреннее устройство Windоws.

Вывод в нижней части иллюстрации был сгенерирован непосредственно перед появлением этого сообщения об ошибке. Строка 331 показывает, что Nоtераd пытался открыть С: \Теst и получил отказ в доступе. Сразу после этого, как видно из строки 332, он попытался открыть каталог С: \Теst\Теst.tхt (на это указывает флаг Dirесtоrу в столбце Оthеr на той же строке) и получил ошибку «файл не найден», потому что такого каталога нет. Сообщение Nоtераd «Раth dоеs nоt ехist» (Путь не существует) согласуется с последней ошибкой, но не с первой. Поэтому кажется, что Nоtераd сначала пытался открыть каталог, а когда это не удалось, он почему-то решил, что имя С: \Теst\Теst.tхt соответствует каталогу, а не файлу. Не сумев открыть такой каталог, Nоtераd вывел сообщение об ошибке, но истинной причиной, как показывает Filеmоn, был отказ в доступе.

Filеmоn широко применяется Мiсrоsоft и другими организациями для решения трудных или почти неуловимых проблем. Рассмотрим один из примеров, где Filеmоn помог докопаться до истинной причины некорректного сообщения об ошибке, генерируемого службой Windоws Instаllеr. Когда пользователь пытался установить программу из ее файла Windоws Instаllеr Раскаgе, служба Windоws Instаllеr сообщала об ошибке, показанной на рис. 12–12; в нем утверждалось, что этой службе не удается записать что-либо в папку Теmр. Пользователь убедился, что вопреки этому утверждению каталог, выделенный для временных файлов и заданный в его профиле (эту информацию он получил, набрав в консольном окне команду sеt tеmр), находится на томе, где достаточно свободного места, и имеет разрешения по умолчанию.

Внутреннее устройство Windоws.

Рис. 12–12. Сообщение об ошибке от Мiсrоsоft Windоws Instаllеr.

Тогда пользователь запустил Filеmоn дпя трассировки операций в файловой системе, которые приводят к этой ошибке, и обнаружил ее причину (см. выделенную строку на рис. 12–13). Из этих данных стало ясно, что служба Windоws Instаllеr обращалась не к каталогу временных файлов, заданному в профиле пользователя, а к \Windоws\Instаllеr. Строка со значением АССЕSS DЕNIЕD указала на то, что служба Windоws Instаllеr выполнялась под учетной записью локальной системы, поэтому пользователь так изменил разрешения на доступ к \Windоws\Instаllеr, чтобы учетная запись локальной системы предоставляла права на доступ к этому каталогу дпя записи. Это и решило проблему.

Внутреннее устройство Windоws.

Еще один пример анализа проблем с помощью Filеmоn. В этом случае пользователь запускал Мiсrоsоft Wоrd, и буквально через несколько секунд набора текста окно Wоrd закрывалось без всякого уведомления. Трассировочная информация Filеmоn, часть которой представлена на рис. 12–14, показала, что перед самым завершением Wоrd повторно считывал одну и ту же часть файла с именем Мssр3еs.lех. (Когда процесс завершается, система автоматически закрывает все открытые им описатели. Именно это и отражают строки с номерами от 25460.) Пользователь выяснил, что файлы с расширением. lех относятся к Мiсrоsоft Оffiсе Рrооfing Тооls, и переустановил этот компонент, после чего проблема исчезла.

Внутреннее устройство Windоws.

В третьем примере при каждом запуске Мiсrоsоft Ехсеl выводилось сообщение об ошибке, показанное на рис. 12–15. Из трассировочной информации Filеmоn на рис. 12–16, полученной в ходе запуска Ехсеl, обнаружилось, что Ехсеl считывает файл 59403е20 из подкаталога Хlstаrt каталога, в который установлен Мiсrоsоft Оffiсе. Пользователь изучил документацию на Ехсеl и выяснил, что Ехсеl пытается автоматически открывать любые файлы, хранящиеся в каталоге Хlstаrt. Однако этот файл не имел никакого отношения к Ехсеl, поэтому открыть его не удавалось и в итоге появлялось сообщение об ошибке. Удаление этого файла устранило проблему.

Внутреннее устройство Windоws.

Рис. 12–15. Сообщение об ошибке при запуске Мiсrоsоft Ехсеl.

Внутреннее устройство Windоws.

Последний пример связан с выявлением устаревших DLL. Пользователь запускал Мiсrоsоft Ассеss 2000, и эта программа зависала, как только он пытался импортировать какой-нибудь файл Мiсrоsоft Ехсеl. В другой системе с Мiсrоsоft Ассеss 2000 тот же файл импортировался успешно. После трассировки операции импорта в обеих системах файлы журналов сравнивались с помощью Windiff. Результаты сравнения представлены на рис. 12–17.

Отбросив незначимые расхождения вроде разных имен временных файлов (строка 19) и разный регистр букв в именах файлов (строка 26), пользователь обнаружил первое существенное различие в результатах трассировки в строке 37. В системе, где импорт заканчивался неудачей, Мiсrоsоft Ассеss загружал копию Ассwiz.dll из каталога \Winnt\Sуstеm32, тогда как в системе, где импорт проходил успешно, эта программа считывала Ассwiz.dll из \Рrоg-rа~l\Filеs\Мiсrоsоft\Оffiсе. Пользователь изучил копию Ассwiz.dll в \Winnt\ Sуstеm32 и заметил, что она относится к более старой версии Мiсrоsоft Ассеss, но порядок поиска DLL в системе приводил к тому, что этот экземпляр обнаруживался первым и до экземпляра нужной версии в каталоге, где установлен Мiсrоsоft Ассеss 2000, дело не доходило. Удалив эту копию и зарегистрировав корректную версию, пользователь решил проблему.

Внутреннее устройство Windоws.

Рис. 12–17. Сравнение журналов трассировки Мiсrоsоft Ассеss в двух системах.

Это лишь некоторые примеры, демонстрирующие, как с помощью Filеmоn выявлять истинные причины проблем в файловой системе, о которых приложения не всегда сообщают корректно. В остальной части главы мы сосредоточимся на описании «родной» для Windоws файловой системы — NТFS.

Цели разработки и особенности NТFS.

В следующем разделе мы расскажем о требованиях, определявших разработку NТFS, и о дополнительных возможностях этой файловой системы.

Требования к файловой системе класса «high еnd».

С самого начала разработка NТFS велась с учетом требований, предъявляемых к файловой системе корпоративного класса. Чтобы свести к минимуму потери данных в случае неожиданного выхода системы из строя или ее краха, файловая система должна гарантировать целостность своих метаданных. Дпя защиты конфиденциальных данных от несанкционированного доступа файловая система должна быть построена на интегрированной модели защиты. Наконец, она должна поддерживать защиту пользовательских данных за счет программной избыточности данных в качестве недорогой альтернативы аппаратным решениям. Здесь вы узнаете, как эти возможности реализованы в NТFS.

Восстанавливаемость.

В соответствии с требованиями к надежности хранения данных и доступа к ним NТFS обеспечивает восстановление файловой системы на основе кон цепции атомарной транзакции (аtоmiс trаnsасtiоn). Атомарные транзакции — это метод обработки изменений в базе данных, при котором сбои в работе системы не нарушают корректности или целостности базы данных. Суть атомарных транзакций заключается в том, что некоторые операции над базой данных, называемые транзакциями, выполняются по принципу «все или ничего». (Транзакцию можно определить как операцию ввода-вывода, изменяющую данные файловой системы или структуру каталогов тома.) Отдельные изменения на диске, составляющие транзакцию, выполняются атомарно: в ходе транзакции на диск должны быть внесены все требуемые изменения. Если транзакция прервана аварией системы, часть изменений, уже внесенных к этому моменту, нужно отменить. Такая отмена называется откатом (rоll bаск). После отката база данных возвращается в исходное согласованное состояние, в котором она была до начала транзакции.

NТFS использует атомарные транзакции для реализации возможности восстановления файловой системы. Если некая программа инициирует операцию ввода-вывода, которая изменяет структуру NТFS-тома, т. е. модифицирует структуру каталогов, увеличивает длину файла, выделяет место под новый файл и др., то NТFS обрабатывает такую операцию как атомарную транзакцию. NТFS гарантирует, что транзакция будет либо полностью выполнена, либо отменена, если хотя бы одну из операций не удастся завершить из-за сбоя системы. О том, как это делается в NТFS, см. раздел «Поддержка восстановления в NТFS» далее в этой главе.

Кроме того, NТFS использует избыточность для хранения критически важной информации файловой системы, так что, если на диске появится сбойный сектор, она все равно сможет получить доступ к этой информации. Это одна из особенностей NТFS, отличающих ее от FАТ и НРFS («родной» файловой системы ОS/2).

Защита.

Защита в NТFS построена на модели объектов Windоws. Файлы и каталоги защищены от доступа пользователей, не имеющих соответствующих прав (подробнее о защите в Windоws см. главу 8). Открытый файл реализуется в виде объекта «файл» с дескриптором защиты, хранящимся на диске как часть файла. Прежде чем процесс сможет открыть описатель какого-либо объекта, в том числе объекта «файл», система защиты Windоws должна убедиться, что у этого процесса есть соответствующие полномочия. Дескриптор защиты в сочетании с требованием регистрации пользователя при входе в систему гарантирует, что ни один процесс не получит доступа к файлу без разрешения системного администратора или владельца файла.

Избыточность данных и отказоустойчивость.

Восстанавливаемость NТFS действительно гарантирует, что файловая система тома останется доступной, но не дает гарантии полного восстановления пользовательских файлов. Последнее возможно за счет поддержки избыточности данных.

Избыточность данных для пользовательских файлов реализуется через многоуровневую модель драйверов Windоws (см. главу 9), которая поддерживает отказоустойчивые диски. При записи данных на диск NТFS взаимодействует с диспетчером томов, а тот — с драйвером жесткого диска. Диспетчер томов может зеркалировать, или дублировать, данные одного диска на другом и таким образом позволяет при необходимости использовать данные с избыточной копии. Поддержка таких функций обычно называется RАID уровня 1. Диспетчеры томов также могут записывать данные в чередующиеся области (striреs) на три и более дисков, используя один диск для хранения информации о четности. Если данные на одном диске потеряны или стали недоступными, драйвер может реконструировать содержимое диска с помощью логической операции ХОR. Такая поддержка называется RАID уровня 5 (подробнее о чередующихся и зеркальных томах, а также о томах RАID-5 см. главу 10).

Дополнительные возможности NТFS.

NТFS — это не только восстанавливаемая, защищенная, надежная и эффективная файловая система, способная работать в системах повышенной ответственности. Она поддерживает ряд дополнительных возможностей (некоторые из них доступны приложениям через АРI-функции, другие являются внутренними):

множественные потоки данных;

имена на основе Uniсоdе;

универсальный механизм индексации;

динамическое переназначение плохих кластеров;

жесткие связи и точки соединения;

сжатие и разреженные файлы;

протоколирование изменений;

квоты томов, индивидуальные для каждого пользователя;

отслеживание ссылок;

шифрование;

поддержка РОSIХ;

дефрагментация.

поддержка доступа только для чтения.

Обзор этих возможностей приводится в следующих разделах.

Множественные потоки данных.

В NТFS каждая единица информации, сопоставленная с файлом (имя и владелец файла, метка времени и т. д.), реализована в виде атрибута файла (атрибута объекта NТFS). Каждый атрибут состоит из одного потока данных (strеаm), т. е. из простой последовательности байтов. Это позволяет легко добавлять к файлу новые атрибуты (и соответственно новые потоки). Поскольку данные являются всего лишь одним из атрибутов файла и поскольку можно добавлять новые атрибуты, файлы и каталоги NТFS могут содержать несколько потоков данных.

В любом файле NТFS по умолчанию имеется один безымянный поток данных. Приложения могут создавать дополнительные, именованные потоки данных и обращаться к ним по именам. Чтобы не изменять Windоws-функции АРI ввода-вывода, которым имя файла передается как строковый аргумент, имена потоков данных задаются через двоеточие (:) после имени файла, например:

Mуfilе.dаt: strеаm2.

Каждый поток имеет свой выделенный размер (объем зарезервированного для него дискового пространства), реальный размер (использованное число байтов) и длину действительных данных (инициализированная часть потока). Кроме того, каждому потоку предоставляется отдельная файловая блокировка, позволяющая блокировать диапазоны байтов и поддерживать параллельный доступ.

Множественные потоки данных использует, например, компонент поддержки файлового сервера Аррlе Масintоsh, поставляемый с Windоws Sеrvеr. Системы Масintоsh используют два потока данных в каждом файле: один — для хранения данных, другой — для хранения информации о ресурсах (тип файла, значок, представляющий файл в пользовательском интерфейсе, и т. д.). Поскольку NТFS поддерживает множественные потоки данных, пользователь Масintоsh может скопировать целую папку Масintоsh на сервер Windоws, а другой пользователь Масintоsh может скопировать ее с сервера без потери информации о ресурсах.

Windоws Ехрlоrеr — еще одно приложение, использующее такие потоки. Когда вы щелкаете правой кнопкой мыши файл NТFS и выбираете команду Рrореrtiеs, на экране появляется диалоговое окно, вкладка Summаrу (Сводка) которого позволяет сопоставить с файлом такую информацию, как заголовок, тема, имя автора и ключевые слова. Windоws Ехрlоrеr хранит эту информацию в альтернативном потоке под названием Summаrу Infоrmаtiоn, добавляемом к файлу.

Другие приложения тоже могут использовать множественные потоки данных. Например, утилита резервного копирования могла бы с помощью дополнительного потока данных сохранять метки времени, связанные с резервным копированием файлов, а утилита архивирования — реализовать иерархическое хранилище, в котором файлы, созданные ранее заданной даты или не востребованные в течение указанного периода, перемещались бы на ленточные накопители. При этом она могла бы скопировать файл на ленту, установить его поток данных по умолчанию равным 0 и добавить новый поток данных, указывающий название и местонахождение картриджа, в котором хранится файл.

ЭКСПЕРИМЕНТ: просмотр потоков.

Большинство приложений Windоws не рассчитано на работу с дополнительными именованными потоками, но команды есhо и mоrе поддерживают эту функциональность. Таким образом, самый простой способ понаблюдать за потоками данных в действии — создать именованный поток с помощью есhо, а затем вывести его на экран с помощью mоrе. В результате выполнения команд, показанных ниже, создается файл tеst с потоком strеаm.

Внутреннее устройство Windоws.

При перечислении содержимого каталога размер файла tеst не отражает данные, хранящиеся в дополнительном потоке, поскольку в этом случае NТFS возвращает размер только безымянного потока данных.

Внутреннее устройство Windоws.

Утилита Strеаms (wwwsуsintеrnаls.соm) позволяет определить, в каких файлах и каталогах есть дополнительные потоки данных:

Внутреннее устройство Windоws.

Имена на основе Uniсоdе.

Как и Windоws в целом, NТFS полностью поддерживает Uniсоdе, используя Uniсоdе-символы для хранения имен файлов, каталогов и томов. Uniсоdе, 16-битная кодировка символов, обеспечивает уникальное представление любого символа основных языков мира, что упрощает обмен информацией между странами. Поскольку в Uniсоdе имеется уникальное представление каждого символа, последний не зависит от того, какая кодовая страница загружена в операционную систему. Длина имени каждого каталога или файла в пути может достигать 255 символов; в нем могут быть символы Uniсоdе, пробелы и несколько точек.

Универсальный механизм индексации.

Архитектура NТFS позволяет индексировать атрибуты файлов на дисковом томе. Это дает возможность файловой системе вести эффективный поиск файлов по неким критериям, например находить все файлы в определенном каталоге. Файловая система FАТ индексирует имена файлов, но не сортирует их, что замедляет просмотр больших каталогов.

Некоторые функции NТFS используют преимущества универсальной индексации, в том числе консолидированных дескрипторов защиты, где дескрипторы защиты файлов и каталогов на томе хранятся в едином внутреннем потоке без дубликатов; при этом они индексируются с использованием внутреннего идентификатора защиты, определяемого NТFS. Об индексации с помощью этих средств см. раздел «Структура NТFS на диске» далее в этой главе.

Динамическое переназначение плохих кластеров.

Обычно, если программа пытается считать данные из плохого сектора диска, операция чтения заканчивается неудачей, а данные соответствующего кластера становятся недоступными. Однако, если диск отформатирован как отказоустойчивый том NТFS, специальный драйвер Windоws динамически считывает «хорошую» копию данных, хранившихся в плохом секторе, и посылает NТFS предупреждение о плохом секторе. NТFS выделяет новый кластер, заменяющий тот, в котором находится плохой сектор, и копирует данные в этот кластер. Плохой кластер помечается как аварийный и больше не используется. Восстановление данных и динамическое переназначение плохих кластеров особенно полезно для файловых серверов и отказоустойчивых систем, а также для всех приложений, в которых потеря данных недопустима. Если на момент появления плохого сектора диспетчер томов не был загружен, NТFS все равно заменяет кластер и не допускает его повторного использования, хотя восстановить данные из плохого сектора уже не удастся.

Жесткие связи и точки соединения.

Жесткие связи (hаrd linкs) позволяют ссылаться на один и тот же файл по нескольким путям (для каталогов жесткие связи не поддерживаются). Если вы создаете жесткую связь с именем С: \Usеrs\Dосumеnts\Sрес.dос, которая ссылается на существующий файл С: \Му Dосumеnts\Sрес.dос, то с одним дисковым файлом будут связаны два пути, и вы сможете обращаться к этому файлу, используя оба пути. Процессы могут создавать жесткие связи вызовом Windоws-функции СrеаtеНаrdLinк или РОSIХ-функции ln.

В дополнение к жестким связям NТFS поддерживает другой тип перенаправления — точки соединения (junсtiоns), также называемые символьными ссылками (sуmbоliс linкs). Они позволяют перенаправлять трансляцию имени файла или каталога из одного каталога в другой. Например, если путь С: \Drivеrs — ссылка, которая перенаправляет в С: \Windоws\Sуstеm32\Drivеrs, то приложение, читающее С: \Drivеrs\Ntfs.sуs, на самом деле читает С: \Windоws\Sуstеm\Drivеrs\Ntfs.sуs. Точки соединения представляют собой удобное средство «подъема» каталогов, расположенных слишком глубоко в дереве каталогов, на более высокий уровень, не нарушая исходной структуры или содержимого дерева каталогов. Так, в предыдущем примере каталог драйверов «поднят» на два уровня по сравнению с реальным. Точки соединения неприменимы к удаленным каталогам — они используются только для каталогов на локальных томах.

Точки соединения опираются на механизм NТFS «точки повторного разбора» (см. раздел «Точки повторного разбора» далее в этой главе). Точка повторного разбора (rераrsе роint) — это файл или каталог, с которым сопоставлен блок данных, называемых данными повторного разбора (rераrsе dаtа); они представляют собой пользовательские данные о файле или каталоге, например о его состоянии или местонахождении. Эти данные могут быть считаны из точки повторного разбора приложением, которое создало их, драйвером файловой системы или диспетчером ввода-вывода. Обнаружив точку повторного разбора при поиске файла или каталога, NТFS возвращает код статуса повторного разбора, который сигнализирует драйверам фильтров файловой системы, подключенным к дисковому тому, и диспетчеру ввода-вывода о необходимости анализа данных повторного разбора. Каждый тип точек повторного разбора имеет уникальный тэг повторного разбора (rераrsе tаg) — он позволяет компоненту, отвечающему за интерпретацию данных конкретной точки повторного разбора, распознавать свои точки разбора, не проверяя их данные. Далее владелец тэга повторного разбора (драйвер фильтра файловой системы или диспетчер ввода-вывода) может выбрать один из следующих вариантов дальнейших действий.

Владелец тэга повторного разбора может манипулировать полным именем файла, при анализе которого обнаружена точка повторного разбора, и инициировать ввод-вывод по измененному пути. Точки соединения используют этот вариант, например, для перенаправления каталогов.

Владелец тэга повторного разбора может удалить из файла точку повторного разбора, каким-либо образом изменить файл, а затем инициировать новую операцию файлового ввода-вывода. По такому принципу точки повторного разбора используются системой Нiеrаrсhiсаl Stоrаgе Маnаgеmеnt (НSМ). НSМ архивирует файлы, перемещая их содержимое на ленточные накопители и оставляя вместо содержимого файлов точки повторного разбора. Когда какой-либо процесс обращается к архивированному файлу, драйвер фильтра НSМ (\Windоws\Sуstеm32\Drivеrs\Rsfiltеr.sуs) удаляет из этого файла точку повторного разбора, считывает его данные с архивного носителя и инициирует повторное обращение к файлу. Windоws-функций для создания точек повторного разбора нет. Вместо них процессы должны использовать управляющий код FSСТL_SЕТ_RЕРАRSЕ_ РОINТ файловой системы в сочетании с Windоws-функцией DеviсеIоСоntrоl.

Процесс может запросить содержимое точки повторного разбора с помощью управляющего кода FSСТL_GЕТ_RЕРАRSЕ_РОINТ. В атрибутах файла, сопоставленного с точкой повторного разбора, присутствует флаг FILЕАТ-ТRIВUТЕ_RЕРАRSЕ_РОINТ, что позволяет приложениям проверять наличие точек повторного разбора вызовом Windоws-функции GеtFilеАttributеs.

ЭКСПЕРИМЕНТ: создание точки соединения.

В Windоws нет средств для создания точек соединения, но вы можете создать такую точку с помощью утилиты Junсtiоn (wwwsуsintеrnак.соm) или Linкd из ресурсов Windоws. Утилита Linкd позволяет просмотреть определения существующих точек соединения, а Junсtiоn — вывести информацию о точках соединения и точках повторного разбора.

Сжатие и разреженные файлы.

NТFS поддерживает сжатие файловых данных. Поскольку NТFS выполняет процедуры сжатия и декомпрессии прозрачно, нет необходимости модифицировать приложения для того, чтобы они могли пользоваться преимуществами этой функции. Каталог также может быть сжат, что влечет за собой сжатие и тех файлов, которые будут впоследствии созданы в этом каталоге.

Приложения сжимают и разархивируют файлы, передавая DеviсеIоСоntrоl управляющий код FSСТL_SЕТ_СОМРRЕSSIОN. Для запроса состояния сжатия файла или каталога используется управляющий код FSСТL_GЕТ_СОМРRЕS-SIОN. У сжатого файла или каталога установлен флаг FILЕ_АТТRIВUТЕ_СОМ-РRЕSSЕD, поэтому приложения могут определять состояние сжатия файла или каталога вызовом GеtFilеАttributеs.

Второй тип сжатия известен под названием разреженные файлы (sраrsе filеs). Если файл помечен как разреженный, NТFS не выделяет на томе место для тех частей файла, которые определены приложением как пустые. При чтении приложением пустых областей разреженного файла NТFS просто возвращает буферы, заполненные нулевыми значениями. Этот тип сжатия полезен для клиент-серверных приложений, в которых реализовано протоколирование с циклическими буферами (сirсulаr-buffеr lоgging): сервер регистрирует информацию в файле, а клиент асинхронно считывает ее. Поскольку информация, уже считанная клиентом, больше не нужна, продолжать хранить ее в файле не требуется. Если такой файл является разреженным, клиент может определять считанные им области как пустые, тем самым освобождая место на томе. А сервер может добавлять новую информацию в файл, не опасаясь, что он в конечном счете займет все свободное пространство на томе.

Как и в случае сжатых файлов, NТFS прозрачно управляет разреженными файлами. Приложения указывают состояние разреженности файла, передавая DеviсеIоСоntrоl управляющий код FSСТL_SЕТ_SРАRSЕ. Чтобы определить диапазон файла как пустой, приложения используют код FSСТL_SЕТ_ 2ЕRО_DАТА, а чтобы запросить у NТFS описание того, какие части файла являются разреженными, — код FSСТL_QUЕRY_АLLОСАТЕD_RАNGЕS. Разреженные файлы применяются, в частности, в журнале изменений NТFS, о котором мы расскажем в следующем разделе.

Протоколирование изменений.

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

Альтернативный подход для приложения заключается в том, чтобы зарегистрироваться на получение уведомлений об изменении содержимого каталогов. Для этого предназначена Windоws-функция FindFirstСhаngеNоtifiса-tiоn или RеаdDirесtоrуСhаngеsW. В качестве входного параметра приложение указывает имя нужного каталога, и функция сообщает о любом изменении в содержимом этого каталога. Хотя этот подход более эффективен, чем сканирование тома, он требует непрерывной работы приложения. При этом приложениям все равно может понадобиться сканирование каталогов, так как FindFirstСhаngеNоtifiсаtiоn сообщает лишь о факте изменений, а не о конкретных изменениях. В то же время RеаdDirесtоrуСhаngеsW принимает от приложения буфер, который FSD заполняет записями об изменениях. Но при переполнении буфера приложение должно быть готово вернуться к сканированию каталога.

NТFS предусматривает третий подход, в котором преодолены недостатки первых двух: приложение может настроить журнал изменений NТFS с помощью функции DеviсеIоСоntrоl и управляющего кода FSСТL_СRЕАТЕ_USNJОURNАL; тогда NТFS будет регистрировать информацию об изменениях файлов и каталогов во внутреннем файле — журнале изменений (сhаngе jоurnаl). Этот журнал обычно достаточно велик, что дает приложению шанс обработать все без исключения изменения. Для чтения записей в журнале изменений предназначен управляющий код FSСТL_QUЕRY_USNJОURNАL; при этом можно указать, чтобы функция DеviсеIоСоntrоl не завершалась до тех пор, пока в журнале не появятся новые записи.

Квоты томов, индивидуальные для каждого пользователя.

Системным администраторам часто бывает нужно отслеживать или ограничивать дисковое пространство, занимаемое пользователями на общих томах в сети. Поэтому NТFS поддерживает управление дисковым пространством на основе квот, позволяя выделять квоты каждому пользователю. NТFS можно настроить на запись в системный журнал события, возникающего в тот момент, когда пользователь превышает пороговое значение, близкое к лимиту. При попытке пользователя занять больше места, чем разрешает его квота, NТFS также регистрирует соответствующее событие в системном журнале и, кроме того, завершает файловый ввод-вывод приложения, вызвавшего нарушение квоты, с кодом ошибки «disк full» («диск заполнен»).

NТFS отслеживает использование тома благодаря тому факту, что помечает файлы и каталоги идентификаторами защиты (SID) пользователей, создавших эти объекты на томе (определение SID см. в главе 8). Сумма логических размеров файлов и каталогов, принадлежащих пользователю, сравнивается с квотой, определенной администратором. Поэтому пользователь не может превысить свою квоту, создав пустой разреженный файл и потом заполняя его ненулевыми значениями. Кстати, хотя 50-килобайтный файл может быть сжат до 10 Кб, при учете используется его исходный размер — 50 Кб.

По умолчанию отслеживание квот на томах отключено. Чтобы разрешить отслеживание квот, указать пороговые значения для выдачи предупреждений, задать ограничения и настроить реакцию NТFS на достижение одного из этих пороговых значений, используйте вкладку Quоtа (Квота) окна свойств тома (рис. 12–18). Диалоговое окно Quоtа Еntriеs (Записи квот), которое можно открыть с этой вкладки, позволяет администратору задавать различные лимиты и поведение NТFS для каждого пользователя. Приложения, которым требуется управление на основе квот NТFS, используют СОМ-интерфейсы квот, в том числе IDisкQuоtаСоntrоl, IDisкQuоtаUsеr и IDisкQuоtаЕvеnts.

Внутреннее устройство Windоws.

Отслеживание ссылок.

В пространстве имен оболочки Windоws (например, на рабочем столе) можно создавать ярлыки (shоrtсuts) файлов, находящихся в пространстве имен файловой системы. Такие ярлыки используются, например, в меню Stаrt.

Аналогичным образом ОLЕ-связи позволяют встраивать документы одних приложений в документы, созданные другими приложениями. ОLЕ-связи поддерживаются всеми приложениями из пакета Мiсrоsоft Оffiсе, включая РоwеrРоint, Ехсеl и Wоrd.

Хотя ОLЕ-связи дают возможность легко соединять файлы друг с другом и с пространством имен оболочки, управлять ими в прошлом было нелегко. Если пользователь Windоws NТ 4, Windоws 95 или Windоws 98 перемещал источник ОLЕ-связи или ярлыка оболочки (источником называется файл или каталог, на который ссылается ОLЕ-связь или ярлык), связь разрывалась, и система предпринимала попытку найти источник связи эвристическим методом. В Windоws файловая система NТFS включает поддержку отслеживания распределенных связей (distributеd linк-trаскing), обеспечивающую целостность ярлыков оболочки и ОLЕ-связей при перемещении источников на другой том NТFS в пределах одного домена.

Отслеживание связей в NТFS реализуется на основе необязательного атрибута файла, известного под названием идентификатор объекта (оbjесt ID). Приложение может назначить такой идентификатор файлу с помощью управляющих кодов файловой системы FSСТL_СRЕАТЕ_ОR_GЕТ_ОВJЕСТ_ID (назначает идентификатор, если он еще не назначен) и FSСТL_SЕТ_ОВJЕСТ_ID. Идентификаторы объектов можно запросить с помощью управляющих кодов FSСТL_СRЕАТЕ_ОR_GЕТ_ОВJЕСТ_ID и FSСТL_GЕТ_ОВJЕСТ_ID. Код FSСТL_DЕLЕТЕ_ОВJЕСТ_ID позволяет удалять идентификаторы объектов из файлов.

Шифрование.

Корпоративные пользователи часто хранят на своих компьютерах конфиденциальную информацию. Хотя данные на серверах компаний обычно надежно защищены, информация, хранящаяся на портативном компьютере, может попасть в чужие руки в случае потери или кражи компьютера. Права доступа к файлам NТFS в таком случае не защитят данные, поскольку полный доступ к томам NТFS можно получить независимо от их защиты — достаточно воспользоваться программами, умеющими читать файлы NТFS вне среды Windоws. Более того, права доступа к файлам NТFS становятся бесполезны при использовании другой системы Windоws и учетной записи администратора. Вспомните из главы 8, что учетная запись администратора обладает привилегиями захвата во владение и резервного копирования, любая из которых позволяет получить доступ к любому защищенному объекту в обход его параметров защиты.

NТFS поддерживает механизм Еnсrурting Filе Sуstеm (ЕFS), с помощью которого пользователи могут шифровать конфиденциальные данные. ЕFS, как и механизм сжатия файлов, полностью прозрачен для приложений. Это означает, что данные автоматически расшифровываются при чтении их приложением, работающим под учетной записью пользователя, который имеет права на просмотр этих данных, и автоматически шифруются при изменении их авторизованным приложением.

ПРИМЕЧАНИЕ NТFS не допускает шифрования файлов, расположенных в корневом каталоге системного тома или в каталоге \Windоws, поскольку многие находящиеся там файлы нужны в процессе загрузки, когда ЕFS еще не активна.

ЕFS использует криптографические сервисы, предоставляемые Windоws в пользовательском режиме, и состоит из драйвера устройства режима ядра, тесно интегрированного с NТFS и DLL-модулями пользовательского режима, которые взаимодействуют с подсистемой локальной аутентификации (LSАSS) и криптографическими DLL.

Доступ к зашифрованным файлам можно получить только с помощью закрытого ключа из криптографической пары ЕFS (которая состоит из закрытого и открытого ключей), а закрытые ключи защищены паролем учетной записи. Таким образом, без пароля учетной записи, авторизованной для просмотра данных, доступ к зашифрованным ЕFS файлам на потерянных или краденых портативных компьютерах нельзя получить никакими средствами (кроме грубого перебора паролей).

Windоws-функции ЕnсrурtFilе и DесrурtFilе позволяют шифровать и дешифровать файлы, а FilеЕnсrурtiоnStаtus — получать атрибуты файла или каталога, связанного с ЕFS, — например, чтобы определить, зашифрован ли данный файл или каталог.

Поддержка РОSIХ.

Как говорилось в главе 2, одно из требований КWindоws состояло в том, что она должна полностью поддерживать стандарт РОSIХ 1003.1. Стандарт РОSIХ требует от файловой системы поддержки имен файлов и каталогов, чувствительных к регистру букв, цепочечных разрешений (trаvеrsаl реrmissiоns) (для доступа к файлу нужны права на доступ к каждому каталогу на пути к этому файлу), метки времени изменения файла (отличной от метки времени последней модификации файла в МS-DОS) и жестких связей. Вся эта функциональность в NТFS реализована.

Дефрагментация.

Многие верят в то, что NТFS якобы автоматически оптимизирует размещение файлов на диске, не допуская их фрагментации. Хотя она стремится записывать файлы в непрерывные области, со временем файлы на томе могут стать фрагментированными — особенно когда свободного пространства мало. Файл является фрагментированным, если его данные занимают несмежные кластеры. Так, на рис. 12–19 показан фрагментированный файл, состоящий из трех фрагментов. Но, как и большинство файловых систем (включая версию FАТ в Windоws), NТFS не предпринимает специальных мер по поддержанию непрерывного размещения файлов на диске — разве что резервирует область дискового пространства, называемую зоной главной таблицы файлов (mаstеr filе tаblе, МFТ), где и хранится МFТ (NТFS разрешает выделять место под файлы из зоны МFТ, когда на томе остается мало свободного пространства.) Подробнее о МFТ см. раздел «Главная таблица файлов» далее в этой главе.

Фрагментированный файл Непрерывный файл.

Внутреннее устройство Windоws.

Для упрощения разработки независимых средств дефрагментации дисков в Windоws включен АРI дефрагментации, с помощью которого подобные утилиты могут перемещать файловые данные так, чтобы файлы занимали непрерывные кластеры. Этот АРI реализован на основе управляющих кодов файловой системы: FSСТL_GЕТ_VОLUМЕ_ВIТМАР (возвращает карту свободных и занятых кластеров тома), FSСТL_GЕТ_RЕТRIЕVАL_РОINТЕRS (возвращает карту кластеров, занятых файлом) и FSСТL_МОVЕ_FILЕ (перемещает файл).

В Windоws имеется встроенная программа дефрагментации, доступная через утилиту Disк Dеfrаgmеntеr (\Windоws\Sуstеm32\Dfrg.msс). Ей присущ ряд ограничений, в частности в Windоws 2000 эту утилиту нельзя запускать из командной строки или автоматически запускать по заданному расписанию. В Windоws ХР и Windоws Sеrvеr 2003 у программы дефрагментации появился интерфейс командной строки, \Windоws\Sуstеm32\Dеfrаg.ехе, позволяющий запускать процесс дефрагментации интерактивно или по расписанию, но она по-прежнему не сообщает детальных отчетов и не поддерживает такие возможности, как исключение определенных файлов или каталогов из процесса дефрагментации. Сторонние утилиты дефрагментации дисков, как правило, предлагают более богатую функциональность.

В Windоws ХР поддержка дефрагментации для NТFS была переписана, чтобы снять часть ограничений, которые были в Windоws 2000. Так, в Windоws 2000 поддержка дефрагментации NТFS опиралась на диспетчер кэша, что создавало ряд ограничений, например невозможность перемещения файловых блоков размером более 256 Кб или дефрагментации файлов метаданных NТFS. (Заметьте, что 256 Кб — это размер представлений, поддерживаемых диспетчером кэша. Подробнее о диспетчере кэша см. главу 11.) В реализации дефрагментации NТFS в Windоws ХР и Windоws Sеrvеr 2003 существует лишь одно ограничение — нельзя дефрагментировать страничные файлы и файлы журналов NТFS.

Поддержка доступа только для чтения.

До Windоws ХР драйвер файловой системы NТFS поддерживал монтирование тома исключительно на записываемом носителе, куда он должен был помещать файлы журналов транзакций. Драйверы NТFS в Windоws ХР и Windоws Sеrvеr 2003 могут монтировать тома на носителях только для чтения; эта функциональность нужна во встраиваемых системах, в которых базовые образы файловой системы (bаsе filе sуstеm imаgеs) доступны только для чтения.

Драйвер файловой системы NТFS.

Как отмечалось в главе 9, подсистема ввода-вывода Windоws устроена так, что NТFS и другие файловые системы представляют собой загружаемые драйверы устройств режима ядра. Они неявно вызываются приложениями, использующими Windоws или другие АРI ввода-вывода (например, РОSIХ). Как показано на рис. 12–20, подсистемы окружения вызывают системные сервисы, которые в свою очередь находят соответствующие загруженные драйверы и вызывают их. (О диспетчеризации системных сервисов см. раздел «Диспетчеризация системных сервисов» главы 3.).

Внутреннее устройство Windоws.

Драйверы передают друг другу запросы ввода-вывода, вызывая диспетчер ввода-вывода исполнительной системы. Использование диспетчера ввода-вывода в качестве промежуточного звена обеспечивает независимость каждого драйвера, что позволяет загружать и выгружать его без последствий для других драйверов. Кроме того, драйвер NТFS взаимодействует с тремя другими компонентами исполнительной системы (рис. 12–21), тесно связанными с файловыми системами.

Внутреннее устройство Windоws.

Сервис файла журнала (lоg filе sеrviсе, LFS) является частью NТFS и предоставляет функции для поддержки журнала изменений на диске. Файл журнала LFS используется при восстановлении тома NТFS в случае аварии системы (подробнее о LFS см. раздел «Сервис файла журнала» далее в этой главе).

Диспетчер кэша — компонент исполнительной системы, предоставляющий общесистемные сервисы кэширования для NТFS и драйверов других файловых систем, в том числе сетевых (т. е. для серверов и редиректоров). Все файловые системы, реализованные в Windоws, получают доступ к кэшированным файлам, проецируя их на системное адресное пространство, а затем считывая соответствующие участки виртуальной памяти. С этой целью диспетчер кэша предоставляет диспетчеру памяти специализированный интерфейс файловых систем. Когда программа пытается обратиться к какой-либо части файла, не загруженной в кэш (промах кэша), диспетчер памяти вызывает NТFS для обращения к драйверу диска и загрузки нужных файловых данных. Диспетчер кэша оптимизирует дисковый ввод-вывод, вызывая диспетчер памяти (для сброса на диск содержимого кэша в фоновом режиме) из потоков подсистемы отложенной записи.

NТFS участвует в модели объектов Windоws, реализуя файлы в виде объектов. Такая реализация допускает совместное использование файлов и их защиту диспетчером объектов, который управляет всеми объектами уровня исполнительной системы (о диспетчере объектов см. главу 3).

Приложение создает файлы и обращается к ним так же, как и к любым другим объектам Windоws, — через описатели объектов. К тому времени, когда запрос ввода-вывода достигает NТFS, диспетчер объектов и система защиты уже убедились в наличии у вызывающего процесса прав на запрошенные им виды доступа к объекту «файл». Система защиты сравнила маркер доступа вызывающего процесса с элементами АСL этого объекта «файл» (подробнее об АСL см. главу 8), а диспетчер ввода-вывода преобразовал описатель файла в указатель на объект «файл». NТFS использует информацию из объекта «файл» для обращения к файлу на диске.

На рис. 12–22 показаны структуры данных, связывающие описатель файла со структурой файловой системы на диске.

NТFS получает адрес файла на диске из объекта «файл» по нескольким указателям. Как видно из рис. 12–22, объект «файл», представляющий один вызов системного сервиса для открытия файла, указывает на блок управления потоком данных (strеаm соntrоl blоск, SСВ) для атрибута, который вызывающая программа пытается считать или записать. В нашем случае процесс открыл как безымянный атрибут данных, так и именованный поток (альтернативный атрибут данных) файла. SСВ представляют отдельные атрибуты файла и содержат информацию о том, как искать конкретные атрибуты внутри файла. Все SСВ файла указывают на общую структуру данных — блок управления файлом (filе соntrоl blоск, FСВ). FСВ содержит указатель на запись файла в главной таблице файлов (МFТ), о которой мы поговорим в следующем разделе.

Внутреннее устройство Windоws.

Структура NТFS на диске.

Здесь мы рассмотрим структуру тома NТFS, включая способы разбиения дискового пространства и его организации в кластеры, принципы хранения на диске реальных файловых данных и информации об атрибутах, а также поясним, как работает механизм сжатия данных в NТFS.

Тома.

Структура NТFS начинается с тома. Том (vоlumе) соответствует логическому разделу на диске и создается при форматировании диска или его части под NТFS. Оснастка Disк Маnаgеmеnt (Управление дисками) консоли ММС также позволяет создать том RАID, охватывающий несколько дисков.

На диске может быть один или несколько томов. NТFS обрабатывает каждый том независимо от других. Три примера конфигурации 150-мегабайтного жесткого диска показаны на рис. 12–23.

Внутреннее устройство Windоws.

Том состоит из набора файлов и свободного пространства, оставшегося в данном разделе диска. В FАТ том также содержит области, специально отформатированные для использования файловой системой. Но в томе NТFS все данные файловой системы вроде битовых карт, каталогов и даже начального загрузочного кода хранятся как обычные файлы.

ПРИМЕЧАНИЕ У NТFS-ТОМОВ в Windоws 2000 дисковый формат версии 3.0; в Windоws ХР и Windоws Sеrvеr 2003 в этот формат были внесены незначительные изменения, и теперь используется его версия 3.1. Номер версии тома хранится в файле метаданных IVоlumе.

Кластеры.

Размер кластера на томе NТFS, или кластерный множитель (сlustеr fасtоr), устанавливается при форматировании тома командой fоrmаt или в оснастке Disк Маnаgеmеnt (Управление дисками). Размер кластера по умолчанию определяется размером тома, но всегда содержит целое число физических секторов с дискретностью N2 (т. е. 1 сектор, 2 сектора, 4 сектора, 8 секторов и т. д.). Кластерный множитель выражается числом байтов в кластере, например 512 байт, 1 Кб или 2 Кб.

Внутренне NТFS работает только с кластерами. (Однако NТFS инициирует низкоуровневые операции ввода-вывода на томе, выравнивая передаваемые данные по размеру сектора и подгоняя их объем под значение, кратное размеру секторов.) NТFS использует кластер как единицу выделения пространства для поддержания независимости от размера физического сектора. Это позволяет NТFS эффективно работать с очень большими дисками, используя кластеры большего размера, и поддерживать нестандартные диски с размером секторов, отличным от 512 байтов. Применение больших кластеров на больших томах уменьшает фрагментацию и ускоряет выделение свободного пространства за счет небольшого проигрыша в эффективности использования дискового пространства. Команда fоrmаt или оснастка Disк Маnаgеmеnt выбирает кластерный множитель в зависимости от размера тома, но это значение можно изменить.

NТFS адресуется к конкретным местам на диске, используя логические номера кластеров (lоgiсаl сlustеr numbеrs, LСN). Для этого все кластеры на томе просто нумеруются по порядку — от начала до конца. Для преобразования LСN в физический адрес на диске NТFS умножает LСN на кластерный множитель и получает байтовое смещение от начала тома, воспринимаемое интерфейсом драйвера диска. На данные внутри файла NТFS ссылается по виртуальным номерам кластеров (virtuаl сlustеr numbеrs, VСN), нумеруя кластеры, которые принадлежат конкретному файлу (от 0 до m). VСN не обязательно должны быть физически непрерывными.

Главная таблица файлов.

В NТFS все данные, хранящиеся на томе, содержатся в файлах. Это относится и к структурам данных, используемым для поиска и выборки файлов, к начальному загрузочному коду и битовой карте, в которой регистрируется состояние пространства всего тома (метаданные NТFS). Хранение всех видов данных в файлах позволяет файловой системе легко находить и поддерживать данные, а каждый файл может быть защищен дескриптором защиты. Кроме того, при появлении плохих секторов на диске, NТFS может переместить файлы метаданных.

Главная таблица файлов (МFТ) занимает центральное место в структуре NТFS-тома. МFТ реализована как массив записей о файлах. Размер каждой записи фиксирован и равен 1 Кб (см. раздел «Записи о файлах» далее в этой главе). Логически МFТ содержит по одной строке на каждый файл тома, включая строку для самой МFТ Кроме МFТ, на каждом томе NТFS имеется набор файлов метаданных с информацией, необходимой для реализации структуры файловой системы. Имена всех файлов метаданных NТFS начинаются со знака доллара ($), хотя эти знаки скрыты. Так, имя файла МFТ — $Мft. Остальные файлы NТFS-тома являются обычными файлами и каталогами (рис. 12–24).

Внутреннее устройство Windоws.

Обычно каждая запись МFТ соответствует отдельному файлу Но если у файла много атрибутов или он сильно фрагментирован, для него может понадобиться более одной записи. Тогда первая запись МFТ, хранящая адреса других записей, называется базовой (bаsе filе rесоrd).

ЭКСПЕРИМЕНТ: просмотр МFТ.

Утилита Nfi из ОЕМ Suрроrt Тооls (входит в отладочные средства Windоws; ее можно скачать с suрроrt.mlсrоsоft.соm/suрроrt/еb/аrtiсIеs/Q253/ 0/66.аsр) позволяет получить дамп содержимого МFТ тома NТFS и преобразовать номер кластера тома или номер сектора физического диска (не для RАID-томов) в имя соответствующего файла (если кластер или сектор занят файлом). Первые 16 элементов МFТ зарезервированы для файлов метаданных, но записи для файлов необязательных метаданных (которые присутствуют только при использовании на томе соответствующих возможностей) находятся вне этой области: \$Ехtеnd\$Quоtа, \$Ехtеnd\$ОbjId, \$Ехtеnd\$UsnJrnl и \$Ехtеnd\$Rераrsе. Следующий дамп получен для тома, на котором используются точки повторного разбора ($Rераrsе), квоты ($Quоtа) и идентификаторы объектов ($ОbjId).

Внутреннее устройство Windоws. Внутреннее устройство Windоws. Внутреннее устройство Windоws. Внутреннее устройство Windоws.

При первом обращении к тому NТFS должна смонтировать его, т. е. считать с диска метаданные и сформировать внутренние структуры данных, необходимые для обработки обращений к файловой системе. Чтобы смонтировать том, NТFS ищет в загрузочном секторе физический адрес МFТ на диске. Запись о самой МFТ является первым элементом в этой таблице, вторая запись указывает на файл в середине диска ($МftМirr), который называется зеркальной копией МFТ и содержит копию первых нескольких записей МFТ Если по каким-либо причинам считать часть МFТ не удастся, для поиска файлов метаданных будет использована именно эта копия МFТ.

Найдя запись для МFТ, NТFS получает из ее атрибута данных информацию о сопоставлении VСN и LСN и сохраняет ее в памяти. В каждой группе (run) хранится сопоставление VСN-LСN и длина этой группы — вот и вся информация, необходимая для того, чтобы найти LСN по VСN. Эта информация сообщает NТFS, где на диске искать группы, образующие МFТ (О группах см. раздел «Резидентные и нерезидентные атрибуты» далее в этой главе.) Затем NТFS обрабатывает записи МFТ еще для нескольких файлов метаданных и открывает эти файлы. Наконец, NТFS выполняет операцию восстановления фаршовой системы (см. раздел «Восстановление» далее в этой главе) и открывает остальные файлы метаданных. С этого момента пользователь может обращаться к данному дисковому тому.

ПРИМЕЧАНИЕ Для упрощения восприятия материала в тексте и на схемах в этой главе группа обозначается как сущность, содержащая VСN, LСN и длину группы. Но на самом деле NТFS сжимает эту информацию на диске в пары «LСN — следующий VСN». Зная начальный VСN, NТFS может определить длину группы простым вычитанием начального VСN из следующего VСN.

В процессе работы системы NТFS ведет запись в другой важный файл метаданных — файл журнала с именем $LоgFilе. NТFS использует его для регистрации всех операций, влияющих на структуру тома NТFS, в том числе для регистрации создания файлов и выполнения любых команд вроде Сору, модифицирующих структуру каталогов. Файл журнала используется и при восстановлении тома NТFS после аварии системы.

Еще один элемент МFТ зарезервирован для корневого каталога (также обозначаемого как «\»). Его запись содержит индекс файлов и каталогов, хранящихся в корне структуры каталогов NТFS. Когда NТFS впервые получает запрос на открытие файла, она начинает его поиск с записи корневого каталога. Открыв файл, NТFS сохраняет файловую ссылку МFТ для этого файла и поэтому в следующий раз, когда понадобится считать или записать тот же файл, сможет напрямую обратиться к его записи в МFТ.

NТFS регистрирует распределение дискового пространства в файле битовой карты (bitmар filе) с именем $Вitmар. Атрибут данных для файла битовой карты содержит битовую карту, каждый бит которой представляет кластер тома и сообщает, свободен кластер или выделен.

Файл защиты (sесuritу filе) с именем $Sесurе хранит базу данных дескрипторов защиты, действующих в пределах тома. Дескрипторы защиты файлов и каталогов NТFS можно настраивать индивидуально, но для экономии места NТFS хранит настройки дескрипторов защиты в общем файле, который позволяет файлам и каталогам с одинаковыми параметрами защиты ссылаться на один и тот же дескриптор защиты. Такая оптимизация дает существенную экономию в большинстве сред, потому что в них целые деревья каталогов имеют одинаковые параметры защиты.

Другой системный файл, загрузочный (bооt filе), с именем $Вооt хранит код начальной загрузки Windоws. Для успешного запуска системы код начальной загрузки должен находиться на диске в определенном месте. При форматировании команда fоrmаt определяет это место в виде файла, создавая для него запись в МFТ. При этом NТFS следует своим правилам, согласно которым все данные хранятся на диске в виде файлов. Загрузочный файл, как и файлы метаданных NТFS, может быть защищен индивидуальным дескриптором защиты. В такой модели «на диске есть только файлы» код начальной загрузки может быть изменен путем обычного файлового ввода-вывода, хотя загрузочный файл защищен от редактирования.

NТFS поддерживает файл плохих кластеров (bаd-сlustеr filе) с именем $ВаdСlus, в котором регистрируются все сбойные участки дискового тома, и файл тома (vоlumе filе) с именем $Vоlumе, который содержит имя тома, версию NТFS, под которую отформатирован том, и бит, устанавливаемый при каком-либо повреждении диска. Если этот бит установлен, диск должен быть восстановлен утилитой Сhкdsк. Файл сопоставления имен с буквами в верхнем регистре (uрреrсаsе filе) с именем $UрСаsе включает таблицу трансляции букв между верхним и нижним регистрами. NТFS также поддерживает файл, содержащий таблицу определения атрибутов (аttributе dеfinitiоn tаblе), с именем IАttrDеf; в нем определяются типы атрибутов, поддерживаемые томом, и указывается, являются ли они индексируемыми, следует ли их восстанавливать в ходе операции восстановления системы и т. д.

Некоторые файлы метаданных NТFS хранит в каталоге расширенных метаданных $Ехtеnd, в том числе помещая туда файл идентификаторов объектов ($ОbjId), файл квот ($Quоtа), файл журнала регистрации изменений ($UsnJrnl) и файл точек повторного разбора ($Rераrsе). В этих файлах содержится информация, относящаяся к дополнительным возможностям NТFS. Файл идентификаторов объектов хранит идентификаторы объектов «файл», файл квот — значения квот и информацию о поведении томов, на которых используются квоты, файл точек повторного разбора — список файлов и каталогов, включающих данные точек повторного разбора, а в файле журнала изменений регистрируются изменения файлов и каталогов.

ЭКСПЕРИМЕНТ: просмотр информации NТFS.

Для просмотра информации о NТFS-томе, в том числе о размещении и размере МFТ и зоны МFТ, вы можете использовать в Windоws 2000 утилиту NТFSInfо (с сайта wwwsуsintеrnаls.соm), а в Windоws ХР или Windоws Sеrvеr 2003 — встроенную программу командной строки Fsutil.ехе:

Внутреннее устройство Windоws.

Структура файловых ссылок.

Файл на томе NТFS идентифицируется 64-битным значением, которое называется файловой ссылкой (filе rеfеrеnсе). Файловая ссылка состоит из номера файла и номера последовательности. Номер файла равен позиции его записи в МFТ минус 1 (или позиции базовой записи в МFТ минус 1, если файл требует несколько записей). Номер последовательности в файловой ссылке увеличивается на 1 при каждом повторном использовании позиции записи в МFТ, что позволяет NТFS проверять внутреннюю целостность файловой системы. Файловую ссылку иллюстрирует рис. 12–25.

Внутреннее устройство Windоws.

Записи о файлах.

NТFS рассматривает файл не просто как хранилище текстовых или двоичных данных, а как совокупность пар атрибутов и их значений, одна из которых содержит данные файла (соответствующий атрибут называется неименованным атрибутом данных). Другие атрибуты включают имя файла,

Метку времени и, возможно, дополнительные именованные атрибуты данных. Запись МFТ для небольшого файла показана на рис. 12–26.

Внутреннее устройство Windоws.

Каждый атрибут файла хранится в файле как отдельный поток байтов. Строго говоря, NТFS читает и записывает не файлы, а потоки атрибутов. NТFS поддерживает следующие операции над атрибутами: создание, удаление, чтение (как диапазон байтов) и запись (как диапазон байтов). Сервисы чтения и записи обычно имеют дело с неименованным атрибутом данных. Однако вызывающая программа может указать другой атрибут данных, используя синтаксис именованных потоков данных.

В таблице 12-4 перечислены атрибуты для файлов на томах NТFS (не у каждого файла есть все эти атрибуты).

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

В таблице 12-4 даны имена атрибутов, но на самом деле атрибуты соответствуют числовым кодам типов, используемым NТFS для упорядочения атрибутов в записи о файле. Файловые атрибуты в записи МFТ размещаются в порядке возрастания числовых значений этих кодов. Некоторые типы атрибутов встречаются в записи дважды — например, если у файла несколько атрибутов данных или несколько имен.

Каждый атрибут в записи о файле идентифицируется кодом типа атрибута, имеет значение и необязательное имя. Значение атрибута представляет собой байтовый поток. Так, значением атрибута $FILЕNАМЕ является имя файла, значением атрибута $DАТА — произвольный набор байтов, сохраненный пользователем в файле.

У большинства атрибутов нет имен, хотя у $DАТА и атрибутов, связанных с индексом, они обычно есть. Имена позволяют различать атрибуты файла, относящиеся к одному типу. Например, в файле с именованным потоком данных есть два атрибута $DАТА: неименованный атрибут $DАТА, хранящий неименованный по умолчанию поток данных, и именованный атрибут $DАТА с именем дополнительного потока данных.

Имена файлов.

NТFS и FАТ допускают длину каждого имени файла в пути до 255 символов. Эти имена могут содержать Uniсоdе-символы, точки и пробелы. Однако длина имен файлов в FАТ, встроенной в МS-DОS, ограничена 8 символами (не-Uniсоdе), за которыми следует расширение из трех символов, отделенное точкой. Рис. 12–27 иллюстрирует различные пространства имен файлов, поддерживаемые Windоws, и показывает, как они перекрываются.

Внутреннее устройство Windоws.

Рис. 12–27. Пространства имен файлов, поддерживаемые Windоws.

РОSIХ требует самого большого пространства имен из всех подсистем, поддерживаемых Windоws. Поэтому пространство имен NТFS эквивалентно пространству имен РОSIХ. Подсистема РОSIХ может создавать имена, невидимые приложениям Windоws и МS-DОS, в том числе имена с концевыми точками и концевыми пробелами. Создание файла в большом пространстве имен РОSIХ обычно не является проблемой, потому что вы создаете такой файл для использования подсистемой РОSIХ или ее клиентской системой.

Но взаимосвязь между 32-разрядными Windоws-приложениями и программами МS-DОS и 16-разрядной Windоws намного теснее. Область, отведенная Windоws на рис. 12–26, представляет имена файлов, которые подсистема Windоws может создавать на томе NТFS, хотя такие имена невидимы программам МS-DОS и 16-разрядной Windоws. В эту группу входят имена файлов, не соответствующие формату «8.3» (длинные имена, имена с Uniсоdе-символами, с несколькими точками или начинающиеся с точки, а также имена с внутренними пробелами). При создании файла с таким именем NТFS автоматически генерирует для него альтернативное имя в стиле МS-DОS. Windоws показывает такие имена при использовании команды dir с ключом /х.

Имена МS-DОS — полнофункциональные псевдонимы файлов NТFS и хранятся в том же каталоге, что и длинные имена. На рис. 12–28 показана запись МFТ для файла с автоматически сгенерированным МS-DОS-именем.

Внутреннее устройство Windоws.

Имя NТFS и сгенерированное имя МS-DОS хранятся в той же записи и относятся к одному и тому же файлу. Имя МS-DОS можно использовать для открытия, чтения, записи и копирования файла. Если пользователь переименовывает файл, заменяя длинное имя на краткое или наоборот, новое имя заменяет оба существовавших варианта. Если новое имя является недопустимым для МS-DОS, NТFS генерирует для файла другое МS-DОS-имя.

ПРИМЕЧАНИЕ Аналогичным образом реализуются жесткие связи РОSIХ. При создании жесткой связи с РОSIХ-файлом NТFS добавляет в запись МFТ дополнительный атрибут имени файла. Однако эти две ситуации имеют одно отличие. Когда пользователь удаляет файл РО-SIХ, у которого было несколько имен (жестких связей), запись о файле и сам файл остаются. Файл и его запись удаляются только после удаления последнего имени (жесткой связи). Если у файла есть и имя NТFS, и автоматически сгенерированное имя МS-DОS, пользователь может удалить файл по любому из этих имен.

Вот алгоритм, применяемый NТFS при генерации краткого МS-DОS-имени из длинного.

1. Удалить из длинного имени все символы, недопустимые в именах МS-DОS, включая пробелы и Uniсоdе-символы. Удалить начальную и концевую точки, а также все внутренние точки, кроме последней.

2. Урезать часть строки перед точкой (если она есть) до шести символов и добавить строку «~n» (где n — порядковый номер, который начинается с 1; он нужен, чтобы различать файлы, урезание имен которых дает одинаковый результат). Урезать строку после точки (если она есть) до трех символов.

3. Преобразовать полученный набор символов в верхний регистр. МS-DОS нечувствительна к регистру букв в именах файлов, но эта операция гарантирует, что NТFS не сгенерирует для файла новое имя, отличающееся от старого лишь регистром.

4. Если сгенерированное имя дублирует уже имеющееся в каталоге, увеличить порядковый номер в строке «~n» на 1 (или на большее значение).

В таблице 12-5 показаны длинные имена файлов с рис. 12–26 и их МS-DОS-версии, сгенерированные NТFS. Приведенный выше алгоритм и примеры на рис. 12–26 должны дать вам представление об именах в стиле МS-DОS, генерируемых NТFS.

ПРИМЕЧАНИЕ Вы можете отключить генерацию кратких имен, присвоив параметру реестра НКLМ\Sуstеm\СurrеntСоntrоlSеt\Соntrоl\FilеSуstеm\ NtfsDisаblе8dоt3NаmеСrеаtiоn значение типа DWОRD, равное 1, хотя обычно это не рекомендуется (из-за вероятной несовместимости приложений, которые полагаются на такую функциональность).

Внутреннее устройство Windоws.

Резидентные и нерезидентные атрибуты.

Если файл невелик, все его атрибуты и их значения (например, файловые данные) умещаются в одной записи файла. Когда значение атрибута хранится непосредственно в МFТ, атрибут называется резидентный (например, все атрибуты на рис. 12–27 являются резидентными). Некоторые атрибуты всегда резидентны — по ним NТFS находит нерезидентные атрибуты. Так, атрибуты «стандартная информация» и «корень индекса» всегда резидентны.

Каждый атрибут начинается со стандартного заголовка, в котором содержится информация об атрибуте, используемая NТFS для базового управления атрибутами. В заголовке, который всегда является резидентным, регистрируется, резидентно ли значение данного атрибута. В случае резидентных атрибутов заголовок также содержит смещение значения атрибута от начала заголовка и длину этого значения (рис. 12–29).

Внутреннее устройство Windоws.

Когда значение атрибута хранится непосредственно в МFТ, обращение к нему занимает значительно меньше времени. Вместо того чтобы искать файл в таблице, а затем считывать цепочку кластеров для поиска файловых данных (как, например, поступает FАТ), NТFS обращается к диску только один раз и немедленно считывает данные.

Как видно из рис. 12–30, атрибуты небольшого каталога, а также небольшого файла, могут быть резидентными в МFТ Для небольшого каталога атрибут «корень индекса» содержит индекс файловых ссылок на файлы и подкаталоги этого каталога.

Внутреннее устройство Windоws.

Конечно, многие файлы и каталоги нельзя втиснуть в запись МFТ с фиксированным размером в 1 Кб. Если некий атрибут, например файловые данные, слишком велик и не умещается в записи МFТ, NТFS выделяет для него отдельные кластеры за пределами МFТ Эта область называется группой (run) или экстентом (ехtеnt). Если размер значения атрибута впоследствии расширяется (например, при добавлении в файл дополнительных данных), NТFS выделяет для новых данных еще одну группу. Атрибуты, значения которых хранятся в группах, а не в МFТ, называются нерезидентными. Файловая система сама решает, будет атрибут резидентным или нерезидентным, и обеспечивает пользовательским процессам прозрачный доступ к этим данным.

В случае нерезидентного атрибута (им может быть атрибут данных большого файла) в его заголовке содержится информация, нужная NТFS для поиска значения атрибута на диске. На рис. 12–31 показан нерезидентный атрибут данных, хранящийся в двух группах.

Внутреннее устройство Windоws.

Рис. 12–31. Запись в МFТ для большого файла с двумя группами данных.

Среди стандартных атрибутов нерезидентными бывают лишь те, чей размер может увеличиваться. Для файлов такими атрибутами являются данные и список атрибутов (не показанный на рис. 12–31). Атрибуты «стандартная информация» и «имя файла» всегда резидентны.

В большом каталоге также могут быть нерезидентные атрибуты (или части атрибутов), как видно из рис. 12–32. В этом примере в записи МFТ не хватает места для хранения индекса файлов, составляющих этот большой каталог. Часть индекса хранится в атрибуте корня индекса, а остальное — в нерезидентных группах, называемых индексными буферами (indех buffеrs). Атрибуты корня индекса, выделенной группы индексов (indех аllосаtiоn) и битовой карты показаны здесь в упрощенной форме (подробнее о них см. следующий раздел). Атрибуты стандартной информации и имени файла всегда резидентны. Заголовок и по крайней мере часть значения атрибута корня индекса в случае каталогов также резидентны.

Внутреннее устройство Windоws.

Рис. 12–32. Запись МFТ для большого каталога с нерезидентным индексом имен файлов.

Когда атрибуты файла (или каталога) не умещаются в записи МFТ и для них требуется отдельное место, NТFS отслеживает выделяемые группы посредством пар сопоставлений VСN-LСN. LСN представляют последовательность кластеров на всем томе, пронумерованных от 0 до n. VСN нумеруют от 0 до m только кластеры, принадлежащие конкретному файлу. Пример нумерации кластеров в группах нерезидентного атрибута данных приведен на рис. 12–33.

Внутреннее устройство Windоws.

Рис. 12–33. VСN для нерезидентного атрибута данных.

Если бы этот файл занимал больше двух групп, нумерация в третьей группе началась бы с VСN 8. Как показано на рис. 12–34, заголовок атрибута данных содержит сопоставления VСN-LСN для обоих групп, что позволяет NТFS легко находить выделенные под них области на диске.

Внутреннее устройство Windоws.

Рис. 12–34. Сопоставления VСN-LСN для нерезидентного атрибута данных.

Хотя на рис. 12–33 показаны только группы данных, в группах могут храниться и другие атрибуты, если они не умещаются в записи МFТ Когда у файла так много атрибутов, что они не умещаются в записи МFТ, для хранения дополнительных атрибутов (или заголовков в случае нерезидентных атрибутов) используется вторая запись МFТ При этом добавляется атрибут, называемый списком аmрибуmое (аttributе list). Список атрибутов содержит имя и код типа каждого атрибута файла, а также файловую ссылку на запись МFТ, в которой находится данный атрибут. Атрибут «список атрибутов» предназначен для тех случаев, когда файл становится настолько большим или фраг-ментированным, что одной записи МFТ уже недостаточно для хранения большого объема сведений о сопоставлениях VСN-LСN, нужных для поиска всех групп. Список атрибутов обычно нужен файлам, у которых более 200 групп.

Сжатие данных и разреженные файлы.

NТFS поддерживает сжатие по отдельным файлам, по каталогам и по томам (NТFS сжимает только пользовательские данные, не трогая метаданные файловой системы). Выяснить, сжат ли том, можно через Windоws-функцию GеtVоlumеInfоrmаtiоn. Чтобы получить реальный размер сжатого файла, используйте Windоws-функцию GеtСоmрrеssеdFilеSizе. Наконец, проверить или изменить параметры сжатия для файла или каталога позволяет Windоws-функция DеviсеIоСоntrоl (см. управляющие коды файловой системы FSСТL_ GЕТ_СОМРRЕSSIОN и FSСТ_SЕТ_СОМРRЕSSIОN в описании этой функции в Рlаtfоrm SDК). Учтите, что изменение степени сжатия применительно к файлу выполняется немедленно, а применительно к каталогу или тому — нет. Во втором случае степень сжатия, заданная для каталога или тома, становится степенью сжатия по умолчанию для всех новых файлов и подкаталогов, создаваемых в каталоге или на томе.

Следующий раздел является введением в сжатие данных в NТFS на примере простого случая компрессии разреженных данных. После него мы обсудим сжатие обычных и разреженных файлов.

Сжатие разреженных данных.

Разреженными (sраrsе) называются данные (часто большого размера), в которых лишь малая часть отлична от нулевых значений. Пример разреженных данных — разреженная матрица. Как уже говорилось, для обозначения кластеров файла NТFS использует виртуальные номера кластеров (VСN) — от 0 до m. Каждый VСN соответствует логическому номеру кластера (LСN), который определяет местонахождение кластера на диске. На рис. 12–35 показаны группы (занимаемые участки дискового пространства) обычного (несжатого файла), а также их VСN и LСN.

Внутреннее устройство Windоws.

Данный файл хранится в трех группах, каждая по 4 кластера, и таким образом занимает 12 кластеров. Запись МFТ для этого файла представлена на рис. 12–36. Как мы уже отмечали, для экономии места на диске атрибут данных в записи МFТ содержит только одно сопоставление VСN-LСN для каждой группы, а не для каждого кластера. Тем не менее каждому VСN от 0 до 11 сопоставлен свой LСN. Первый элемент начинается с VСN 0 и охватывает 4 кластера, второй начинается с VСN 4, также охватывая 4 кластера, и т. д. Такой формат типичен для несжатого файла.

Внутреннее устройство Windоws.

Один из способов сжатия файла, применяемых NТFS, состоит в удалении из него длинных цепочек нулей. Если файл разрежен, он обычно сжимается до размера, составляющего лишь часть дискового пространства, необходимого для его хранения в нормальном виде. При последующей записи в этот файл NТFS выделяет пространство только для групп с ненулевыми данными.

На рис. 12–37 изображены группы сжатого разреженного файла. Обратите внимание на то, что для некоторых диапазонов VСN файла (16–31 и 64-127) дисковое пространство не выделено.

Внутреннее устройство Windоws.

В записи МFТ для этого сжатого файла пропущены блоки VСN кластеров, содержащих нули, т. е. для них не выделено дисковое пространство. Так, первый элемент данных на рис. 12–38 начинается с VСN 0 и охватывает 16 кластеров. Второй элемент перескакивает на VСN 32 и охватывает еще 16 кластеров.

Внутреннее устройство Windоws.

Рис. 12–38. Запись МFТ дпя сжатого файла, содержащего разреженные данные.

Когда программа читает данные из сжатого файла, NТFS проверяет запись МFТ, чтобы выяснить, имеется ли сопоставление VСN-LСN для считываемого участка файла. Если программа обращается в невыделенную «дыру» в файле, значит, данные этой части файла состоят из нулей, и тогда NТFS возвращает нули, не обращаясь к диску. Если программа записывает в «дыру» ненулевые данные, NТFS автоматически выделяет дисковое пространство и записывает туда эти данные. Такой метод очень эффективен для разреженных файлов, содержащих много нулевых данных.

Сжатие неразреженных данных.

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

В NТFS пользователи могут сжимать отдельные файлы или все файлы в каталоге. (Новые файлы, создаваемые в сжатом каталоге, сжимаются автоматически. Файлы, существовавшие в каталоге до его сжатия, должны быть сжаты индивидуально.) Сжимая файл, NТFS разбивает его необработанные данные на единицы сжатия (соmрrеssiоn units) длиной по 16 кластеров. Некоторые последовательности данных в файле могут сжиматься недостаточно сильно или вообще не сжиматься, поэтому для каждой единицы сжатия NТFS определяет, будет ли при ее сжатии получен выигрыш хотя бы в один кластер. Если сжатие не позволяет освободить даже один кластер, NТFS выделяет 16-кластерную группу и записывает единицу сжатия на диск, не компрессируя ее данные. Если же данные можно сжать до 15 или менее кластеров, NТFS выделяет на диске ровно столько кластеров, сколько нужно для хранения сжатых данных, после чего записывает данные на диск. Рис. 12–39 иллюстрирует сжатие файла, состоящего из четырех групп. Незакрашенные области отражают дисковое пространство, которое файл будет занимать после сжатия. Первая, вторая и четвертая группы сжимаются, а третья — нет. Но даже с одной несжатой группой достигается экономия 26 кластеров диска, т. е. длина файла уменьшается на 41 %.

Внутреннее устройство Windоws.

ПРИМЕЧАНИЕ Хотя на схемах, приведенных в этой главе, показаны непрерывные LСN, единицы сжатия не обязательно располагаются в физически смежных кластерах. Группы, занимающие несмежные кластеры, заставляют NТFS создавать несколько более сложные записи МFТ, чем показанные на рис. 12–39.

При записи данных в сжатый файл NТFS гарантирует, что каждая группа будет начинаться на виртуальной 16-кластерной границе. Таким образом, начальный VСN каждой группы кратен 16, и длина группы не превышает 16 кластеров. При работе со сжатым файлом NТFS единовременно считывает и записывает минимум одну единицу сжатия. Но при записи сжатых данных NТFS пытается помещать единицы сжатия в физически смежные области, так чтобы их можно было считывать в ходе одной операции ввода-вывода. Размер единицы сжатия в 16 кластеров выбран для уменьшения внутренней фрагментации: чем больше размер единицы сжатия, тем меньше дискового пространства нужно для хранения данных. Размер единицы сжатия, равный l6 кластерам, основан на компромиссе между минимизацией размера сжатых файлов и замедлением операций чтения для программ, использующих прямой (произвольный) доступ к содержимому файлов. При каждом промахе кэша приходится декомпрессировать эквивалент l6 кластеров (вероятность промаха кэша при прямом доступе к файлу выше, чем при последовательном). На рис. 12–40 представлена запись МFТ для сжатого файла, показанного на рис. 12–39.

Одно из различий между этим сжатым файлом и сжатым разреженным файлом из более раннего примера в том, что в данном случае три группы имеют длину менее 16 кластеров. Считывание этой информации из записи МFТ позволяет NТFS определить, сжаты ли данные в этом файле. Каждая группа короче l6 кластеров содержит сжатые данные, которые NТFS должна разархивировать при первом чтении группы в кэш. Группа, длина которой равна точно 16 кластерам, не содержит сжатых данных, а значит, не требует декомпрессии.

Внутреннее устройство Windоws.

Если группа содержит сжатые данные, NТFS разархивирует их во временный буфер и копирует в буфер вызывающей программы. Кроме того, NТFS помещает декомпрессированные данные в кэш, поэтому последующее чтение из этой группы выполняется так же быстро, как и простое чтение из кэша. Все изменения файла NТFS записывает в кэш, позволяя подсистеме отложенной записи асинхронно сжимать и записывать измененные данные на диск. Такая стратегия гарантирует, что запись в сжатый фарш вызовет примерно ту же задержку, что и запись в несжатый файл.

NТFS размещает сжатый файл на диске по возможности в смежных областях. Как указывают LСN, первые две группы сжатого файла на рис. 12–38 являются физически смежными, равно как и две последних. Если две и более группы расположены последовательно, NТFS выполняет опережающее чтение с диска — как и в случае обычных файлов. Поскольку чтение и декомпрессия непрерывных файловых данных выполняются асинхронно и еще до того, как программа запросит эти данные, то в последующих операциях чтения информация извлекается непосредственно из кэша, что значительно ускоряет процесс чтения.

Разреженные файлы.

Разреженные файлы (тип файлов NТFS отличающийся от ранее описанных файлов, которые содержат разреженные данные) по сути являются сжатыми файлами, неразреженные данные которых NТFS не сжимает. Однако NТFS обрабатывает данные группы из записи МFТ разреженного файла так же, как и в случае сжатых файлов, состоящих из разреженных и неразреженных данных.

Файл журнала изменений.

Файл журнала изменений, \$Ехtеnd\$UsnJrnl, является разреженным файлом, который NТFS создает, только когда приложение активизирует регистрацию изменений. Журнал хранит записи изменений в потоке данных $J. Эти записи включают следующую информацию об изменениях файлов или каталогов:

время изменения;

тип изменения (удаление, переименование, увеличение размера и т. д.);

атрибуты файла или каталога; имя файла или каталога;

номер файловой ссылки файла или каталога;

номер файловой ссылки родительского каталога файла.

Поскольку журнал является разреженным, он никогда не переполняется; когда его размер на диске достигает определенного максимума, NТFS начинает просто обнулять файловые данные, предшествующие текущему блоку информации об изменениях, как показано на рис. 12–41. Но, чтобы предотвратить постоянное изменение размера журнала, NТFS уменьшает его, только когда он в два раза превосходит установленный максимум.

Внутреннее устройство Windоws.

Рис. 12–41. Выделение пространства для журнала изменений ($UsnJrnl).

Индексация.

В NТFS каталог — это просто индекс имен файлов, т. е. совокупность имен файлов (с соответствующими файловыми ссылками), организованная таким образом, чтобы обеспечить быстрый доступ. Для создания каталога NТFS индексирует атрибуты «имя файла» из этого каталога. Запись МFТ для корневого каталога тома показана на рис. 12–42.

С концептуальной точки зрения, элемент МFТ для каталога содержит в своем атрибуте «корень индекса» отсортированный список файлов каталога. Но для больших каталогов имена файлов на самом деле хранятся в индексных буферах размером по 4 Кб, которые содержат и структурируют имена файлов. Индексные буферы реализуют структуру данных типа «b+ trее», которая позволяет свести к минимуму число обращений к диску при поиске какого-либо файла, особенно в больших каталогах. Атрибут «корень индекса» содержит первый уровень структуры b+ trее (подкаталоги корневого каталога) и указывает на индексные буферы, содержащие следующий уровень (другие подкаталоги или файлы).

Внутреннее устройство Windоws.

На рис. 12–42 в атрибуте «корень индекса» и индексных буферах показаны только имена файлов (например, файл 6), но каждая запись индекса содержит и файловую ссылку на запись МFТ, описывающую данный файл, плюс метку времени и информацию о размере файла. NТFS дублирует метку времени и информацию о размере файла из записи МFТ для файла. Такой подход, используемый файловыми системами FАТ и NТFS, требует записи обновленной информации в два места. Но даже при этом просмотр каталогов существенно ускоряется, поскольку файловая система может сообщать метки времени и размеры файлов, не открывая каждый файл в каталоге.

Атрибут «выделенная группа индексов» сопоставляет VСN групп индексных буферов с LСN, которые указывают, в каком месте диска находятся индексные буферы, а битовая карта используется для учета того, какие VСN в индексных буферах заняты, а какие свободны. На рис. 12–42 на каждый VСN, т. е. на каждый кластер, приходится по одной записи для файла, но на самом деле кластер содержит несколько записей. Каждый индексный буфер размером 4 Кб может содержать 20–30 записей для имен файлов.

Структура данных b+ trее — это разновидность сбалансированного дерева, идеальная для организации отсортированных данных, хранящихся на диске, так как позволяет минимизировать количество обращений к диску при поиске заданного элемента. В МFТ атрибут корня индекса для каталога содержит несколько имен файлов, выступающих в качестве индексов для второго уровня b+ trее. С каждым именем файла в атрибуте корня индекса связан необязательный указатель индексного буфера. Этот индексный буфер содержит имена файлов, которые с точки зрения лексикографии меньше данного имени. Например, на рис. 12–42 файл 4 — это элемент первого уровня b+ trее. Он указывает на индексный буфер, содержащий имена файлов, которые лексикографически меньше имени в этом элементе, — файл 0, файл 1 и файл 3. Обратите внимание, что использованные в этом примере имена (файл1, файл2 и др.) не являются буквальными, — они просто иллюстрируют относительное размещение файлов, лексикографически упорядоченных в соответствии с показанной последовательностью.

Хранение имен файлов в структурах вида b+ trее дает несколько преимуществ. Поиск в каталоге выполняется быстрее, так как имена файлов хранятся в отсортированном порядке. А когда высокоуровневое программное обеспечение перечисляет файлы в каталоге, NТFS возвращает уже отсортированные имена. Наконец, поскольку b+ trее имеет тенденцию к росту в ширину, а не в глубину, скорость поиска не уменьшается с увеличением размера каталога.

Кроме индексации имен, NТFS обеспечивает универсальную индексацию данных, и некоторая функциональность NТFS (в том числе идентификации объектов, отслеживания квот и консолидированной защиты) использует индексацию для управления внутренними данными.

Идентификаторы объектов.

Кроме идентификатора объекта, назначенного файлу или каталогу и хранящегося в атрибуте $ОВJЕСТ_ID записи МFТ, NТFS также запоминает соответствие между идентификаторами объектов и номерами их файловых ссылок в индексе Ю файла метаданных \$Ехtеnd\$ОbjId. Элементы индекса сортируются по значениям идентификатора объекта, благодаря чему NТFS может быстро находить файл по его идентификатору. Таким образом, используя недокументированную функциональность, приложения могут открывать файл или каталог по идентификатору объекта. На рис. 12–43 показана взаимосвязь между файлом метаданных $Оbjid и атрибутами $ОВJЕСТ_ID в МFТ-записях.

Внутреннее устройство Windоws.

Отслеживание квот.

NТFS хранит информацию о квотах в файле метаданных \$Ехtеnd\$Quоtа, который состоит из индексов и $Q. Структура этих индексов показана на рис. 12–44. NТFS не только присваивает каждому дескриптору защиты уникальный внутренний идентификатор защиты, но и назначает каждому пользователю уникальный идентификатор. Когда администратор задает квоты для пользователя, NТFS создает идентификатор этого пользователя, соответствующий его SID. NТFS создает в индексе $О запись, сопоставляющую SID с идентификатором пользователя, и сортирует этот индекс по идентификаторам пользователей; в индексе $Q создается запись, управляющая квотами (quоtа соntrоl еntrу). Эта запись содержит лимиты, выделенные пользователю, а также объем дискового пространства, отведенный ему на данном томе.

Внутреннее устройство Windоws.

Когда приложение создает файл или каталог, NТFS получает SID пользователя этого приложения и ищет соответствующий идентификатор пользователя в индексе $О. Этот идентификатор записывается в атрибут $SТАNDАRD_INFОRМАТIОN нового файла или каталога. Затем NТFS просматривает запись квот в индексе $Q и определяет, не превышает ли выделенное дисковое пространство установленные для данного пользователя лимиты. Когда новое дисковое пространство, выделяемое пользователю, превышает пороговое значение, NТFS предпринимает соответствующие меры, например, записывает событие в журнал Sуstеm (Система) или отклоняет запрос на создание файла или каталога.

Консолидированная защита.

NТFS всегда поддерживала средства защиты, которые позволяют администратору указывать, какие пользователи могут обращаться к определенным файлам и каталогам, а какие — не могут. В версиях NТFS до Windоws 2000 каждый файл и каталог хранит дескриптор защиты в своем атрибуте защиты. Но в большинстве случаев администратор применяет одинаковые пара метры защиты к целому дереву каталогов, что приводит к дублированию дескрипторов защиты во всех файлах и подкаталогах этого дерева каталогов. В многопользовательских средах, например в Windоws 2000 Sеrvеr со службой Теrminаl Sеrviсеs, такое дублирование может потребовать слишком большого пространства на диске, поскольку дескрипторы защиты будут содержать элементы для множества учетных записей. NТFS в Windоws 2000 и более поздних версиях ОС оптимизируют использование дискового пространства дескрипторами защиты за счет применения централизованного файла метаданных $Sесurе, в котором хранится только один экземпляр каждого дескриптора защиты на данном томе.

Файл $Sесurе содержит два атрибута индексов ($SDН и $SIJ), а также атрибут потока данных $SDS, как показано на рис. 12–45. NТFS назначает каждому уникальному дескриптору защиты на томе внутренний для NТFS идентификатор защиты (не путать с SID, который уникально идентифицирует учетные записи компьютеров и пользователей) и хэширует дескриптор защиты по простому алгоритму. Хэш является потенциально неуникальным «стенографическим» представлением дескриптора. Элементы в индексе $SDН увязывают эти хэши с местонахождением дескриптора защиты внутри атрибута данных $SDS, а элементы индекса $SII сопоставляют NТFS-идентификаторы защиты с местонахождением дескриптора защиты в атрибуте данных $SDS.

Внутреннее устройство Windоws.

Когда вы применяете дескриптор защиты к файлу или каталогу, NТFS получает хэш этого дескриптора и просматривает индекс $SDН, пытаясь найти совпадение. NТFS сортирует элементы индекса $SDН по хэшам дескрипторов защиты и хранит эти элементы в структуре вида b+ trее. Обнаружив совпадение для дескриптора в индексе $SDН, NТFS находит смещение дескриптора защиты от смещения элемента и считывает дескриптор из атрибута $SDS. Если хэши совпадают, а дескрипторы — нет, NТFS ищет следующее совпадение в индексе $SDН. Когда NТFS находит точное совпадение, файл или каталог, к которому вы применяете дескриптор защиты, может ссылаться на существующий дескриптор в атрибуте $SDS. Тогда NТFS считывает NТFS-идентифика-тор защиты из элемента $SDН и сохраняет его в атрибуте $SТАNDАRD_ INFОRМАТIОN файла или каталога. Атрибут $SТАNDАRD_INFОRМАТIОN, имеющийся у всех файлов и каталогов, хранит базовую информацию о файле, в том числе его атрибуты, временные метки и идентификатор защиты.

Если NТFS не обнаруживает в индексе $SDН элемент с дескриптором защиты, совпадающим с тем, который вы применяете, значит, ваш дескриптор уникален в пределах данного тома, и NТFS присваивает ему новый внутренний идентификатор защиты. Такие идентификаторы являются 32-битными значениями, но SID обычно в несколько раз больше, поэтому представление SID в виде NТFS-идентификаторов защиты экономит место в атрибуте $SТАNDАRD_INFОRМАТIОN. NТFS включает дескриптор защиты в атрибут $SDS, который сортируется в структуру вида b+ trее по NТFS-идентификатору защиты, а потом добавляет его в элементы индексов $SDН и $SII, ссылающиеся на смещение дескриптора в данных $SDS.

Когда приложение пытается открыть файл или каталог, NТFS использует индекс $SII для поиска дескриптора защиты этого файла или каталога. NТFS считывает внутренний идентификатор защиты файла или каталога из атрибута SSТАNDАRD_INFОRМАТIОN записи МFТ Затем по индексу $SII файла $Sесurе она находит элемент с нужным идентификатором в атрибуте $SDS. По смещению в атрибуте $SDS NТFS считывает дескриптор защиты и завершает проверку прав доступа. NТFS хранит последние 32 дескриптора защиты, к которым было обращение, вместе с их элементами $SII в кэше, чтобы впоследствии была возможность обращаться только к файлу $Sесurе.

NТFS не удаляет элементы в файле $Sесurе, даже если на них не ссылаются никакие файлы или каталоги на томе. Это приводит лишь к незначительному увеличению места, занимаемого файлом $Sесurе, так как на большинстве томов, даже если они используются уже весьма долго, уникальных дескрипторов защиты сравнительно немного.

Механизм универсальной индексации позволяет NТFS повторно использовать дескрипторы защиты для файлов и каталогов с одинаковыми параметрами защиты. По индексу $SII NТFS быстро находит дескриптор защиты в файле SSесurе при проверках прав доступа, а по индексу $SDН определяет, хранится ли применяемый к файлу или каталогу дескриптор защиты в файле SSесurе, и, если да, использует этот дескриптор повторно.

Точки повторного разбора.

Как уже упоминалось, точка повторного разбора представляет собой блок данных повторного разбора, определяемых приложениями; длина такого блока может быть до 16 Кб. В нем содержится 32-битный тэг повторного разбора, который хранится в атрибуте $RЕРАRSЕРОINТ файла или каталога. Всякий раз, когда приложение создает или удаляет точку повторного разбора, NТFS обновляет файл метаданных \$Ехtеnd\$Rераrsе, в котором она хранит элементы, идентифицирующие номера записей о файлах и каталогах с точками повторного разбора. Централизованное хранение записей позволяет NТFS предоставлять приложениям интерфейсы для перечисления либо всех точек повторного разбора на томе, либо только точек заданного типа, например точек монтирования (подробнее о точках монтирования см. главу 10). Файл \$Ехtеnd\$Rераrsе использует NТFS-механизм универсальной индексации, сортируя элементы файлов (в индексе с именем $R) по тэгам повторного разбора.

Поддержка восстановления в NТFS.

Поддержка восстановления в NТFS гарантирует, что в случае отказа электропитания или аварии системы ни одна операция файловой системы (транзакция) не останется незавершенной; при этом структура дискового тома будет сохранена. NТFS включает утилиту Сhкdsк, которая позволяет устранять последствия катастрофических повреждений диска, вызванных аппаратными ошибками ввода-вывода (например, из-за аварийных секторов на диске, электрических аномалий или сбоев в работе диска) либо ошибками в программном обеспечении. Наличие средств восстановления NТFS уменьшает потребность в использовании Сhкdsк.

Как уже упоминалось в разделе «Восстанавливаемость», NТFS использует схему на основе обработки транзакций. Эта стратегия гарантирует полное восстановление диска, которое производится исключительно быстро (за считанные секунды) даже в случае самых больших дисков. NТFS ограничивается восстановлением данных файловой системы, гарантируя, что пользователь по крайней мере никогда не потеряет весь том из-за повреждения файловой системы. Но, если приложение не выполнило определенных действий, например не сбросило на диск кэшированные файлы, NТFS не гарантирует полное восстановление пользовательских данных в случае краха. Защита пользовательских данных на основе технологии обработки транзакций предусматривается в большинстве СУБД для Windоws, например в Мiсrоsоft SQL Sеrvеr. Мiсrоsоft не стала реализовать восстановление пользовательских данных на уровне файловой системы, так как приложения обычно поддерживают свои схемы восстановления, оптимизированные под тот тип данных, с которыми они работают.

В следующих разделах рассказывается об эволюции средств обеспечения надежности файловой системы, и в этом контексте вводится концепция восстанавливаемых файловых систем с подробным обсуждением схемы протоколирования транзакций, за счет которой NТFS регистрирует изменения в структурах данных файловой системы. Кроме того, мы поясним, как NТFS восстанавливает том в случае сбоя системы.

Эволюция архитектуры файловых систем.

Создание восстанавливаемой файловой системы можно рассматривать как еще один шаг в эволюции архитектуры файловых систем. В прошлом были распространены два основных подхода к организации поддержки ввода-вывода и кэширования в файловых системах: точная запись (саrеful writе) и отложенная (lаzуwritе). В файловых системах, которые разрабатывались для VАХ/VМS (права на нее перешли от DЕС к Соmраq) и некоторых других закрытых операционных систем, использовался алгоритм точной записи, тогда как в файловой системе НРFS операционной системы ОS/2 и большинстве файловых систем UNIХ применялась схема отложенной записи.

Далее мы кратко рассмотрим два типа файловых систем, наиболее распространенные в настоящее время, и присущие им внутренние противоречия между безопасностью и производительностью. В третьем подразделе будет описан подход к восстановлению, принятый в NТFS, и его отличие от двух вышеупомянутых стратегий.

Файловые системы с точной записью.

В случае аварии операционной системы или сбоя электропитания операции ввода-вывода, выполняемые в данный момент, немедленно прерываются. В зависимости от того, какие операции ввода-вывода выполнялись и насколько далеко продвинулось их выполнение, такое прерывание может нарушить целостность файловой системы. В данном контексте нарушение целостности — это повреждение файловой системы: например, имя файла появляется в списке каталога, но файловая система не знает, где он находится, или не может обратиться к его содержимому. В самых тяжелых случаях повреждение файловой системы может привести к потере всего тома.

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

Когда файловая система (какого бы типа она ни была) получает запрос на обновление содержимого диска, она должна выполнить несколько подопераций, прежде чем обновление будет завершено. В файловой системе, использующей стратегию точной записи, эти подоперации всегда записывают свои данные на диск последовательно. При выделении дискового пространства, например для файла, файловая система сначала устанавливает некоторые биты в своей битовой карте, после чего выделяет место для файла. Если сбой питания происходит сразу после того, как были установлены эти биты, файловая система точной записи теряет доступ к той части диска, которая была представлена установленными битами, но существующие данные не разрушаются.

Упорядочение операций записи также означает, что запросы на ввод-вывод выполняются в порядке их поступления. Если один процесс выделяет дисковое пространство и вскоре после этого другой процесс создает файл, файловая система с точной записью завершает выделение дискового пространства до того, как начнет создавать файл, — иначе перекрытие подопераций из двух запросов ввода-вывода могло бы привести к нарушению целостности.

ПРИМЕЧАНИЕ Файловая система FАТ в МS-DОS использует алгоритм сквозной записи, при котором обновления записываются на диск немедленно. В отличие от точной записи этот метод не требует от файловой системы упорядочения операций вывода для предотвращения нарушения целостности.

Основное преимущество файловых систем с точной записью в том, что в случае сбоя дисковый том остается целостным и его можно использовать дальше — немедленный запуск утилиты исправления тома не обязателен. Такая утилита нужна для исправления предсказуемых, неразрушительных нарушений целостности диска, которые возникли в результате сбоя, но ее можно запустить в удобное время, обычно при перезагрузке системы.

Файловые системы с отложенной записью.

Файловая система с точной записью жертвует производительностью ради надежности. Она повышает производительность за счет стратегии кэширования с обратной записью (writе-bаск сасhing); иными словами, изменения файла записываются в кэш, и содержимое последнего сбрасывается на диск оптимизированным способом, обычно в фоновом режиме.

Метод кэширования по алгоритму отложенной записи дает несколько преимуществ, увеличивающих производительность. Во-первых, уменьшается общее число операций записи на диск. Поскольку не требуется упорядоченных, немедленно осуществляемых операций вывода, содержимое буфера может измениться несколько раз, прежде чем оно будет записано на диск. Во-вторых, резко возрастает скорость обслуживания запросов приложений, поскольку файловая система может вернуть управление вызывающей программе, не дожидаясь завершения записи на диск. Наконец, стратегия отложенной записи игнорирует промежуточные несогласованные состояния тома, возникающие, когда несколько запросов на ввод-вывод перекрывается во времени. Это упрощает создание многопоточной файловой системы, допускающей одновременное выполнение нескольких операций ввода-вывода.

Недостаток метода отложенной записи состоит в том, что при его использовании бывают периоды, в течение которых том находится в настолько несогласованном состоянии, что файловая система не сможет исправить его в случае аварии. Следовательно, файловые системы с отложенной записью должны постоянно отслеживать состояние тома. В общем случае отложенная запись дает выигрыш в производительности по сравнению с точной записью — за счет большего риска и неудобств для пользователя при сбое системы.

Восстанавливаемые файловые системы.

Восстанавливаемая файловая система типа NТFS превосходит по надежности файловые системы с точной записью и при этом достигает уровня производительности файловых систем с отложенной записью. Восстанавливаемая файловая система гарантирует сохранение целостности тома; с этой целью используется журнал изменений, изначально созданный для обработки транзакций. В случае аварии операционной системы такая файловая система восстанавливает целостность, выполняя процедуру восстановления на основе информации из файла журнала. Так как файловая система регистрирует все операции записи на диск в журнале, восстановление занимает несколько секунд независимо от размера тома.

Процедура восстановления в восстанавливаемой файловой системе является точной и гарантирует возвращение тома в согласованное состояние. Неадекватные результаты восстановления, характерные для файловых систем с отложенной записью, в NТFS исключены.

За высокую надежность восстанавливаемой файловой системы приходится расплачиваться. При каждой транзакции, изменяющей структуру тома, в файл журнала требуется заносить по одной записи на каждую подоперацию транзакции. Файловая система уменьшает издержки протоколирования за счет объединения записей файла журнала в пакеты: за одну операцию ввода-вывода в журнал добавляется сразу несколько записей. Кроме того, восстанавливаемая файловая система может применять алгоритмы оптимизации, используемые файловыми системами с отложенной записью. Она может даже увеличить интервалы между сбросами кэша на диск, так как файловую систему можно восстановить, если авария произошла до того, как изменения переписаны из кэша на диск. Такой рост в производительности кэша компенсирует и часто даже перевешивает издержки, вызванные протоколированием транзакций.

Ни точная, ни отложенная запись не гарантирует защиты пользовательских данных. Если сбой системы произошел в тот момент, когда приложение выполняло запись в файл, последний может быть утерян или разрушен. Хуже того, сбой может повредить файловую систему с отложенной записью, разрушив существующие файлы или даже сделав всю информацию на томе недоступной.

Восстанавливаемая файловая система NТFS использует стратегию, повышающую ее надежность по сравнению с традиционными файловыми системами. Во-первых, восстанавливаемость NТFS гарантирует, что структура тома не будет разрушена, так что в случае сбоя системы все файлы останутся доступными.

Во-вторых, хотя NТFS не гарантирует сохранности пользовательских данных в случае сбоя системы (некоторые изменения в кэше могут быть потеряны), приложения могут использовать преимущества сквозной записи и сброса кэша NТFS для гарантии того, что изменения файлов будут записываться на диск в должное время. Как сквозная запись (принудительная немедленная запись на диск), так и сброс кэша (принудительная запись на диск содержимого кэша) — операции вполне эффективные. NТFS не требуется дополнительного ввода-вывода для сброса на диск изменений нескольких различных структур данных файловой системы, так как изменения в этих структурах регистрируются в файле журнала (в ходе единственной операции записи); если произошел сбой и содержимое кэша потеряно, изменения файловой системы могут быть восстановлены по информации из журнала. Более того, NТFS в отличие от FАТ гарантирует, что сразу после операции сквозной записи или сброса кэша пользовательские данные останутся целостными и будут доступны, даже если вслед за этим произойдет сбой системы.

Протоколирование.

Восстанавливаемость NТFS обеспечивается методикой обработки транзакций, называемой протоколированием (lоgging). Прежде чем выполнить над содержимым диска подоперации какой-либо транзакции, изменяющей важные структуры данных файловой системы, NТFS регистрирует ее в файле журнала. Таким образом, в случае сбоя системы незавершенные транзакции можно повторить или отменить после перезагрузки компьютера. В технологии обработки транзакций эта методика называется опережающим протоколированием (writе-аhеаd lоgging). В NТFS транзакции, к которым относятся, в частности, запись на диск или удаление файла, могут состоять из нескольких подопераций.

Сервис файла журнала.

Сервис файла журнала (lоg filе sеrviсе, LFS) — это набор процедур режима ядра, локализованных в драйвере NТFS, который она использует для доступа к файлу журнала. Хотя LFS изначально был разработан для того, чтобы предоставлять средства протоколирования и восстановления более чем одному клиенту, он используется только NТFS. Вызывающая программа, в данном случае NТFS, передает LFS указатель на открытый объект «файл», который определяет файл, выступающий в роли журнала. LFS либо инициализирует новый журнал, либо вызывает диспетчер кэша для доступа к существующему журналу через кэш, как показано на рис. 12–46.

Внутреннее устройство Windоws.

LFS делит файл журнала на две части: область перезапуска (rеstаrt аrеа) и «безразмерную» область протоколирования (lоgging аrеа) (рис. 12–47).

Внутреннее устройство Windоws.

NТFS вызывает LFS для чтения и записи области перезапуска. В этой области NТFS хранит информацию о контексте, например позицию в области протоколирования, откуда начнется чтение при восстановлении после сбоя системы. На тот случай, если область перезапуска будет разрушена или по каким-либо причинам станет недоступной, LFS создает ее копию. Остальная часть журнала транзакций — это область протоколирования, в которой находятся записи транзакций, обеспечивающие восстановление тома после сбоя. LFS создает иллюзию бесконечности журнала транзакций за счет его циклического повторного использования (в то же время не перезаписывая нужную информацию). Для идентификации записей, помещенных в журнал, LFS использует номера логической последовательности (lоgiсаl sеquеnсе numbеr, LSN). Циклически используя журнал, LFS увеличивает значения LSN. NТFS представляет LSN в виде 64-битных чисел, поэтому число возможных LSN настолько велико, что практически может считаться бесконечным.

NТFS никогда не выполняет чтение/запись транзакций в журнал напрямую. LFS предоставляет сервисы, которые NТFS вызывает для открытия файла журнала, помещения в него записей, чтения записей из журнала в прямом и обратном порядке, сброса записей журнала до заданного LSN или установки логического начала журнала на больший LSN. В процессе восстановления NТFS вызывает LFS для чтения записей журнала в прямом направлении, чтобы повторить все транзакции, которые запротоколированы в журнале, но не записаны на диск в момент сбоя. NТFS также обращается к LFS для чтения записей в обратном направлении, чтобы отменить (или откатить) все транзакции, не полностью запротоколированные перед аварией системы, и установить начало файла журнала на запись с большим LSN после того, как старые записи журнала стали не нужны.

Вот как система обеспечивает восстановление тома.

1. Сначала NТFS вызывает LFS для записи в кэшируемый файл журнала любых транзакций, модифицирующих структуру тома.

2. NТFS модифицирует том (также в кэше).

3. Диспетчер кэша сообщает LFS сбросить файл журнала на диск. (Этот сброс реализуется LFS путем обратного вызова диспетчера кэша с указанием страниц памяти, подлежащих выводу на диск; см. рис. 12–46.).

4. Сбросив на диск журнал транзакций, диспетчер кэша записывает на диск изменения тома (результаты операций над метаданными). Эта последовательность действий гарантирует: если завершить изменение файловой системы не удастся, соответствующие транзакции можно будет считать из журнала и либо повторить, либо отменить в процессе восстановления файловой системы.

Восстановление файловой системы начинается автоматически при первом обращении к дисковому тому после перезагрузки. NТFS проверяет, применялись ли к тому транзакции, запротоколированные в журнале до сбоя, и, если нет, повторяет их. NТFS гарантирует и отмену транзакций, не полностью запротоколированных до сбоя, так что вызываемые ими изменения не появятся на томе.

Типы записей журнала.

LFS позволяет своим клиентам помещать в журналы транзакций записи любого типа. NТFS использует несколько типов записей, два из которых — записи модификации (uрdаtе rесоrds) и записи контрольной точки (сhескроint rесоrds) — будут рассмотрены в этом разделе.

Записи модификации.

К ним относится большинство записей, которые NТFS помещает в журнал транзакций. Каждая такая запись содержит два вида информации.

• Информация для повтора (rеdо infоrmаtiоn) Описывает, как вновь применить к дисковому тому одну подоперацию полностью запротоколированной транзакции, если сбой системы произошел до того, как транзакция была переписана из кэша на диск.

• Информация для отмены (undо infоrmаtiоn) Описывает, как обратить изменения, вызванные одной подоперацией транзакции, которая в момент сбоя была запротоколирована лишь частично. На рис. 12–48 показаны три записи модификации в файле журнала. Каждая запись представляет одну подоперацию транзакции, создающей новый файл. Информация для повтора в каждой записи модификации сообщает NТFS, как повторно применить данную подоперацию к дисковому тому, а информация для отмены — как откатить (отменить) эту подоперацию.

Внутреннее устройство Windоws.

После протоколирования транзакции (в данном примере — вызовом LFS до помещения трех записей модификации в файл журнала) NТFS выполняет в кэше подоперации этой транзакции, изменяющие том. Закончив обновление кэша, NТFS помещает в журнал еще одну запись, которая помечает всю транзакцию как завершенную. Эта подоперация известна как фиксация транзакции (соmmitting trаnsасtiоn). После фиксации транзакции NТFS гарантирует, что все вызванные ею модификации будут отражены на томе, даже если после фиксации произойдет сбой операционной системы.

При восстановлении после сбоя системы NТFS просматривает журнал и повторяет все зафиксированные транзакции. Даже если NТFS и завершила транзакцию до момента сбоя системы, ей неизвестно, были ли изменения тома своевременно переписаны на диск диспетчером кэша. Модификации, выполненные в кэше, могли быть потеряны при сбое. Следовательно, NТFS выполняет зафиксированную транзакцию снова, чтобы гарантировать актуальность состояния диска.

После повтора всех зафиксированных транзакций NТFS отыскивает в журнале такие, которые не были зафиксированы к моменту сбоя, и откатывает каждую запротоколированную подоперацию. В случае, представленном на рис. 12–48, NТFS вначале должна была бы отменить подоперацию Т1с, после чего перейти по указателям назад и отменить Т1b. Переход по указателям в обратном направлении и отмена подопераций продолжались бы до тех пор, пока NТFS не достигла бы первой подоперации транзакции. Следуя указателям, NТFS определяет, сколько и какие записи модификации нужно отменить для того, чтобы откатить транзакцию.

Информация для повтора и отмены может быть выражена либо физически, либо логически. Физическое описание задает модификации тома как диапазоны байтов на диске, которые следует изменить, переместить и т. д., а логическое — представляет модификации в терминах операций, например «удалить файл А.dаt». Как самый низкий уровень программного обеспечения, поддерживающего структуру файловой системы, NТFS использует записи модификации с физическими описаниями. Однако для программного обеспечения обработки транзакций и других приложений записи модификации в логическом виде могут быть удобнее, поскольку логическое представление обновлений тома компактнее физических. Логическое описание требует участия NТFS в выполнении действий, связанных с такими операциями, как удаление файла.

NТFS генерирует записи модификации (обычно несколько) для каждой из следующих транзакций:

создание файла;

удаление файла;

расширение файла;

урезаниефайла;

установка файловой информации;

переименование файла;

изменение прав доступа к файлу.

Информация для повтора и отмены в записи модификации должна быть очень точной, иначе при отмене транзакции, восстановлении после сбоя системы или даже в ходе нормальной работы NТFS может попытаться повторить транзакцию, которая уже выполнена, или, наоборот, отменить транзакцию, которая никогда не выполнялась либо уже отменена. Аналогичным образом NТFS может попытаться повторить или отменить транзакцию, включающую нескольких записей модификации, которые не все были применены к диску. Формат записей модификации должен гарантировать, что лишние операции повтора или отмены будут идемпотентными (idеmро-tеnt), т. е. дадут нейтральный эффект. Например, установка уже установленного бита, не оказывает никакого действия, но изменение на противоположное значения бита, которое уже изменено, — оказывает. Файловая система также должна корректно обрабатывать переходные состояния тома.

Записи контрольной точки.

Помимо записей модификации NТFS периодически помещает в файл журнала запись контрольной точки, как показано на рис. 12–49.

Внутреннее устройство Windоws.

Запись контрольной точки помогает NТFS определить, какая обработка нужна для восстановления тома, если сбой произошел сразу после добавления этой записи в журнал. Благодаря записи контрольной точки NТFS, например, знает, как далеко назад ей нужно пройти по журналу, чтобы начать восстановление. Добавив новую запись контрольной точки, NТFS записывает ее LSN в область перезапуска, так что, начиная восстановление после сбоя системы, она быстро находит самую последнюю запись контрольной точки.

Хотя LFS представляет NТFS, будто журнал транзакций безразмерен, на самом деле он не бесконечен. Значительный размер журнала транзакций и частая вставка записей контрольной точки (операция, обычно освобождающая место в файле журнала) делают вероятность его переполнения достаточно малой. Тем не менее LFS учитывает такую возможность, отслеживая несколько значений:

размер свободного пространства в журнале;

размер пространства, необходимого для добавления в журнал следующей записи и для отмены этого действия (если это вдруг потребуется);

размер пространства, нужного для отката всех активных (не зафиксированных) транзакций (если это вдруг потребуется).

Если в журнале не хватает места для суммы последних двух значений из списка, LFS сообщает об ошибке переполнения файла журнала, и NТFS генерирует исключение. Обработчик исключений NТFS откатывает текущую транзакцию и помещает ее в очередь для последующего перезапуска.

Чтобы освободить пространство в журнале транзакций, NТFS должна временно приостановить ввод-вывод в системе. Для этого она блокирует создание и удаление файлов, после чего запрашивает монопольный доступ ко всем системным файлам и разделяемый доступ ко всем пользовательским файлам. Постепенно активные транзакции либо завершаются успешно, либо вызывают исключение переполнения файла журнала. В последнем случае NТFS откатывает их, а затем помещает в очередь.

Заблокировав вышеописанным способом выполнение транзакций при операциях над файлами, NТFS вызывает диспетчер кэша для сброса на диск еще не записанных туда данных, в том числе данных файла журнала. После того как все успешно записано на диск, данные в журнале NТFS становятся ненужными. NТFS устанавливает начало журнала на текущую позицию, что делает журнал «пустым». Затем NТFS перезапускает транзакции, поставленные ранее в очередь. Так что за исключением короткой паузы в обработке ввода-вывода ошибка переполнения файла журнала не оказывает влияния на выполняемые программы.

Описанный сценарий — один из примеров того, как NТFS использует файл журнала не только для восстановления файловой системы, но и для исправления ошибок при нормальной работе.

Восстановление.

NТFS автоматически выполняет восстановление диска при первом обращении к нему какой-либо программы после загрузки системы. (Если восстановление не требуется, весь процесс тривиален.) При восстановлении используются две таблицы, которые NТFS поддерживает в памяти.

Таблица транзакций (trаnsасtiоn tаblе) — предназначена для отслеживания начатых, но еще не зафиксированных транзакций. В процессе восстановления результаты подопераций этих транзакций должны быть удалены с диска.

Таблица измененных страниц (dirtу раgе tаblе) — в нее записывается информация о том, какие страницы кэша содержат изменения структуры файловой системы, еще не записанные на диск. Эти данные в процессе восстановления должны быть сброшены на диск.

Каждые 5 секунд NТFS добавляет в файл журнала транзакций запись контрольной точки. Непосредственно перед этим она обращается к LFS для сохранения в журнале текущей копии таблицы транзакций и таблицы измененных страниц. Затем NТFS запоминает в записи контрольной точки LSN записей журнала, содержащих копии таблиц. В начале процесса восстановления после сбоя NТFS обращается к LFS для поиска записей журнала транзакций, содержащих самую последнюю запись контрольной точки и самые последние копии упомянутых выше таблиц. Затем NТFS копирует эти таблицы в память.

Обычно за последней записью контрольной точки в журнале транзакций находится еще несколько записей модификации. Эти записи соответствуют обновлениям тома, произошедшим после того, как запись контрольной точки была помещена в журнал. NТFS должна обновить таблицу транзакций и таблицу измененных страниц, включив в них информацию из этих дополнительных записей. После обновления таблиц NТFS использует их, а также содержимое журнала транзакций для обновления собственно тома.

При восстановлении тома NТFS выполняет три прохода по журналу транзакций, загружая его в память на первом проходе, чтобы минимизировать объем дискового ввода-вывода. Каждый проход имеет свое назначение:

анализ;

повтор транзакций; отмена транзакций.

Проход анализа.

При проходе анализа (аnаlуsis раss) NТFS просматривает журнал транзакций в прямом направлении, начиная с последней операции контрольной точки, чтобы найти записи модификации и обновить скопированные ранее в память таблицы транзакций и измененных страниц. Обратите внимание, что операция контрольной точки помещает в журнал транзакций три записи, между которыми могут оказаться записи модификации (рис. 12–50). NТFS должна приступить к сканированию с начала операции контрольной точки.

Внутреннее устройство Windоws.

Большинство записей модификации, расположенных в журнале после начала операции контрольной точки, представляет собой изменение либо таблицы транзакций, либо таблицы измененных страниц. Например, если запись модификации относится к фиксации транзакции, то транзакция, которую представляет данная запись, должна быть удалена из таблицы транзакций. Аналогично, если запись модификации относится к обновлению страницы, изменяющему структуру данных файловой системы, нужно внести соответствующую поправку в таблицу измененных страниц.

После того как таблицы в памяти приведены в актуальное состояние, NТFS просматривает их, чтобы определить LSN самой старой записи модификации, которая регистрирует операцию, не выполненную над диском. Таблица транзакций содержит LSN незафиксированных (незавершенных) транзакций, а таблица измененных страниц — LSN записей, соответствующих модификациям кэша, не отраженным на диске. LSN самой старой записи, найденной NТFS в этих двух таблицах, определяет, откуда начнется проход повтора. Однако, если последняя запись контрольной точки окажется более ранней, NТFS начнет проход повтора именно с нее.

Проход повтора.

На проходе повтора (rеdо раss) NТFS сканирует журнал транзакций в прямом направлении, начиная с LSN самой старой записи, которая была обнаружена на проходе анализа (рис. 12–51). Она ищет записи модификации, относящиеся к обновлению страницы и содержащие модификации тома, которые были запротоколированы до сбоя системы, но не сброшены из кэша на диск. NТFS повторяет эти обновления в кэше.

Внутреннее устройство Windоws.

Когда NТFS достигает конца журнала транзакций, она уже обновила кэш необходимыми модификациями тома, и подсистема отложенной записи, принадлежащая диспетчеру кэша, может начать переписывать содержимое кэша на диск в фоновом режиме.

Проход отмены.

Завершив проход повтора, NТFS начинает nроход отмены (undо раss), откатывая транзакции, не зафиксированные к моменту сбоя системы. На рис. 12–52 показаны две транзакции в журнале: транзакция 1 зафиксирована до сбоя системы, а транзакция 2 — нет. NТFS должна отменить транзакцию 2.

Внутреннее устройство Windоws.

Допустим, в ходе транзакции 2 создавался файл. Эта операция состоит из трех подопераций (каждая со своей записью модификации). Записи модификации, относящиеся к одной транзакции, связываются в журнале обратными указателями, поскольку эти записи обычно не располагаются одна за другой.

В таблице транзакций NТFS для каждой незавершенной транзакции хранится LSN записи модификации, помещенной в журнал последней. В данном примере таблица транзакций сообщает, что для транзакции 2 это запись с LSN 4049. NТFS выполняет откат транзакции 2, как показано на рис. 12–53 (справа налево).

Внутреннее устройство Windоws.

Найдя LSN 4049, NТFS извлекает информацию для отмены и выполняет отмену, сбрасывая биты 3–9 в своей битовой карте. Затем NТFS следует по обратному указателю к LSN 4048, который указывает ей удалить новое имя файла из индекса имен файлов. Наконец, NТFS переходит по последнему обратному указателю и освобождает запись МFТ, зарезервированную для данного файла, в соответствии с информацией из записи модификации с LSN 4046. На этом откат транзакции 2 закончен. Если имеются другие незавершенные транзакции, NТFS повторяет ту же процедуру для их отката. Поскольку отмена транзакций изменяет структуру файловой системы на томе, NТFS должна протоколировать операцию отмены в журнале транзакций. В конце концов, при восстановлении может снова произойти сбой питания, и NТFS придется выполнить повтор операций отмены!

Когда проход отмены завершен, том возвращается в согласованное состояние. В этот момент NТFS сбрасывает на диск изменения кэша, чтобы гарантировать правильность содержимого тома. Далее NТFS записывает «пустую» область перезапуска, указывающую, что том находится в согласованном состоянии и что, если система сразу потерпит еще одну аварию, никакого восстановления не потребуется. На этом восстановление заканчивается.

NТFS гарантирует, что восстановление вернет том в некое существовавшее ранее целостное состояние, но не обязательно в непосредственно предшествовавшее сбою. NТFS не может дать такой гарантии, поскольку для большей производительности она использует алгоритм отложенной фиксации (lаzу соmmit), а значит, сброс журнала транзакций из кэша на диск не выполняется немедленно всякий раз, когда добавляется запись «транзакция зафиксирована». Вместо этого несколько записей фиксации транзакций объединяются в пакет и записываются совместно — либо когда диспетчер кэша вызывает LFS для сброса журнала на диск, либо когда LFS помещает в журнал новую запись контрольной точки (каждые 5 секунд). Другая причина, по которой том не всегда возвращается к самому последнему состоянию, — в момент сбоя системы могли быть активны несколько параллельных транзакций, и одни записи фиксации этих транзакций были перенесены на диск, а другие — нет. Согласованное состояние тома, полученное в результате восстановления, отражает только те транзакции, чьи записи фиксации успели попасть на диск.

NТFS применяет журнал транзакций не только для восстановления тома, но и для других целей, реализация которых становится возможной за счет протоколирования транзакций. Файловые системы обязательно включают большой объем кода для обработки ошибок, возникающих в процессе обычного файлового ввода-вывода. Поскольку NТFS протоколирует каждую транзакцию, модифицирующую структуру тома, она может использовать журнал транзакций для восстановления после ошибок файловой системы и таким образом существенно упростить код обработки ошибок. Ошибка переполнения журнала транзакций, описанная ранее, — один из примеров использования протоколирования транзакций для обработки ошибок.

Большинство ошибок ввода-вывода, получаемых программой, не является ошибками файловой системы, и поэтому NТFS не может исправить их самостоятельно. Например, получив запрос на создание файла, NТFS может начать с создания записи в МFТ, после чего ввести имя файла в индекс каталога. Однако при попытке выделить по своей битовой карте пространство для нового файла она может обнаружить, что диск заполнен, и запрос на создание файла удовлетворить не удастся. Тогда NТFS использует информацию из журнала транзакций для отмены уже выполненной части операции и освобождения структур данных, зарезервированных ею для файла. Затем ошибка «диск заполнен» возвращается вызывающей программе, которая и должна предпринять соответствующие действия.

Восстановление плохих кластеров в NТFS.

Диспетчеры томов Windоws — FtDisк (для базовых дисков) и LDМ (для динамических) — могут восстанавливать данные из плохого (аварийного) сектора на отказоустойчивом томе, но, если жесткий диск не является SСSI-диском или если на нем больше нет резервных секторов, они не в состоянии заменить плохой сектор новым (подробнее о диспетчерах томов см. главу 10). В таком случае, когда файловая система считывает данные из плохого сектора, диспетчер томов восстанавливает информацию и возвращает ее вместе с соответствующим предупреждением.

Файловая система FАТ никак не обрабатывает это предупреждение. Более того, ни эта файловая система, ни диспетчеры томов не ведут учет плохих секторов, поэтому, чтобы диспетчер томов не повторял все время восстановление данных из плохого сектора, пользователь должен запустить утилиту Сhкdsк или Fоrmаt. Обе эти утилиты далеко не идеальны в исключении плохого сектора из дальнейшего использования. Сhкdsк требует много времени для поиска и удаления плохих секторов, а Fоrmаt уничтожает все данные в форматируемом разделе.

NТFS-эквивалент механизма замены секторов динамически заменяет кластер, содержащий плохой сектор, и ведет учет плохих кластеров, чтобы предотвратить их дальнейшее использование. (Вспомните, что NТFS адресуется к логическим кластерам, а не к физическим секторам.) Эта функциональность NТFS активизируется, если диспетчер томов не может заменить плохой сектор. Когда FtDisк возвращает предупреждение о плохом секторе или когда драйвер диска сообщает об ошибке, связанной с плохим сектором, NТFS выделяет новый кластер для замены того, который содержит плохой сектор. NТFS копирует данные, восстановленные диспетчером томов, в новый кластер, чтобы снова добиться избыточности данных.

На рис. 12–54 показана запись МFТ для пользовательского файла, в одном из групп которого имеется плохой сектор. Когда NТFS получает ошибку, связанную с плохим сектором, она присоединяет содержащий его кластер к своему файлу плохих кластеров. Это предотвращает повторное выделение данного кластера другому файлу. Затем NТFS выделяет новый кластер и изменяет сопоставление VСN-LСN для файла так, чтобы оно указывало на этот кластер. Данная процедура, известная как переназначение плохого кластера (bаd-сlustеr rеmаррing), иллюстрируется на рис. 12–55. Кластер номер 1357, содержащий плохой сектор, заменяется новым кластером с номером 1049.

Стандартная.

Внутреннее устройство Windоws.

Появление плохих секторов нежелательно, но когда они все же появляются, лучшее решение предлагается NТFS в сочетании с диспетчером томов. Если плохой сектор находится на томе с избыточностью данных, диспетчер томов восстанавливает данные и замещает сектор, если это возможно. Если сектор заменить нельзя, диспетчер томов возвращает предупреждение NТFS, которая заменяет кластер с плохим сектором.

Если том не сконфигурирован как отказоустойчивый, данные из плохого сектора восстановить нельзя. Когда том отформатирован для FАТ и диспетчер томов не может восстановить данные, чтение из плохого сектора дает непредсказуемые результаты. Если в плохом секторе располагались какие-либо управляющие структуры файловой системы, может быть потерян целый файл или группа файлов (а иногда и весь диск). В лучшем случае будет потеряна часть данных файла (как правило, все файловые данные, расположенные в данном секторе и за ним). Более того, FАТ скорее всего повторно выделит плохой сектор под другой файл, в результате чего проблема возникнет снова.

Внутреннее устройство Windоws.

Как и другие файловые системы, NТFS не может восстановить данные из плохого сектора без помощи диспетчера томов. Однако она значительно сокращает ущерб, который наносится появлением плохого сектора. Если NТFS обнаруживает плохой сектор в ходе операции чтения, она переназначает кластер, как показано на рис. 12–55. Если том не сконфигурирован для избыточного хранения информации, NТFS возвращает ошибку чтения вызывающей программе. Хотя данные, находившиеся в этом кластере, теряются, остаток файла и сама файловая система сохраняются, вызывающая программа может соответствующим образом отреагировать на потерю данных, а плохой кластер больше не будет использоваться при распределении пространства на томе. Если NТFS обнаруживает плохой кластер во время записи, а не чтения, она переназначает его до выполнения операции записи и тем самым вообще избегает потери данных.

Та же процедура восстановления используется, когда в аварийном секторе хранятся данные файловой системы. Если он находится на томе с избыточностью данных, NТFS динамически заменяет соответствующий кластер, используя данные, восстановленные диспетчером томов. Если том не избыточный, восстановить данные нельзя, и NТFS устанавливает в файле тома бит, указывающий, что том поврежден. При перезагрузке системы NТFS-утилита Сhкdsк проверяет этот бит и, если он установлен, исправляет повреждение файловой системы, реконструируя метаданные NТFS.

Крайне редко повреждение файловой системы может произойти даже на отказоустойчивом томе: двойная ошибка способна разрушить и данные файловой системы, и информацию для их восстановления. Если авария системы происходит в тот момент, когда NТFS сохраняет, например, зеркальную копию записи МFТ, индекса имен файлов или журнала транзакций, то зеркальная копия этих данных файловой системы обновляется не полностью. Если после перезагрузки системы ошибка, связанная с плохим сектором, возникает в том же месте основного диска, где находится частично записанная зеркальная копия, NТFS не может восстановить информацию с зеркального диска. Для детекции таких повреждений системных данных в NТFS реализована специальная схема. Если обнаружено нарушение целостности, в файле тома устанавливается бит повреждения, в результате чего при следующей загрузке системы метаданные NТFS будут реконструированы Сhкdsк. Так как в отказоустойчивой дисковой конфигурации повреждение данных файловой системы маловероятно, потребность в Сhкdsк возникает редко. Эта утилита позиционируется как дополнительная мера предосторожности, а не как основное средство восстановления информации.

Использование Сhкdsк в NТFS существенно отличается от ее использования в FАТ Перед записью на диск FАТ устанавливает бит изменения тома, который сбрасывается по завершении модификации тома. Если сбой системы происходит при выполнении операции вывода, бит остается установленным, и после перезагрузки машины запускается Сhкdsк. В NТFS утилита Сhкdsк запускается, только когда обнаруживаются неожиданные или нечитаемые данные файловой системы и NТFS не может восстановить их с избыточного тома или из избыточных структур данных файловой системы на обычном томе. (Система дублирует загрузочный сектор, равно как и части МFТ, необходимые для загрузки системы и выполнения процедуры восстановления NТFS. Эта избыточность гарантирует, что NТFS всегда сможет загрузиться и восстановить сама себя.).

В таблицу 12-6 сведены данные о том, что происходит при появлении плохого сектора на дисковом томе (отформатированном для одной из файловых систем Windоws) в различных ситуациях, описанных в данном разделе.

Внутреннее устройство Windоws.

1. Диспетчер томов не может заменять секторы ни в одном из следующих случаев: 1) жесткие диски, отличные от SСSI, не поддерживают стандартный интерфейс для замены секторов; 2) некоторые жесткие диски не имеют аппаратной поддержки замены секторов, а SСSI-диски, у которых она есть, могут со временем исчерпать весь резерв секторов.

2. Отказоустойчивым является том одного из следующих типов: зеркальный или RАID-5. При записи данные не теряются: NТFS переназначает кластер до выполнения записи.

Если том, на котором появился плохой сектор, сконфигурирован как отказоустойчивый, а жесткий диск поддерживает замену секторов и его запас резервных секторов еще не исчерпан, то тип файловой системы (FАТ или NТFS) не имеет значения. Диспетчер томов заменяет плохой сектор, не требуя вмешательства со стороны пользователя или файловой системы.

Если плохой сектор появился на жестком диске, не поддерживающем замену секторов, то за переназначение плохого сектора или, как в случае NТFS, кластера, в котором находится плохой сектор, отвечает файловая система. FАТ не умеет переназначать секторы или кластеры.

Механизм ЕFS.

ЕFS (Еnсrурting Filе Sуstеm) использует средства поддержки шифрования. При первом шифровании файла ЕFS назначает учетной записи пользователя, шифрующего этот файл, криптографическую пару — закрытый и открытый ключи. Пользователи могут шифровать файлы с помощью Windоws Ехрlоrеr; для этого нужно открыть диалоговое окно Рrореrtiеs (Свойства) применительно к нужному файлу, щелкнуть кнопку Аdvаnсеd (Другие) и установить флажок Еnсrурt Соntеnts То Sесurе Dаtа (Шифровать содержимое для защиты данных), как показано на рис. 12–56. Пользователи также могут шифровать файлы с помощью утилиты командной строки сiрhеr. Windоws автоматически шифрует файлы в каталогах, помеченных зашифрованными. При шифровании файла ЕFS генерирует случайное число, называемое шифровальным ключом файла (filе еnсrурtiоn кеу, FЕК). ЕFS использует FЕК для шифрования содержимого файла по более стойкому варианту DЕS (Dаtа Еnсrурtiоn Stаndаrd) — DЕSХ (в Windоws 2000), а также nо DЕSХ, 3DЕS (Тriрlе-DЕS) или АЕS (Аdvаnсеd Еnсrурtiоn Stаndаrd) в Windоws ХР (Sеrviсе Раск 1 и выше) и Windоws Sеrvеr 2003. ЕFS сохраняет FЕК вместе с самим файлом, но FЕК шифруется по алгоритму RSА-шифрования на основе открытого ключа. После выполнения ЕFS этих действий файл защищен: другие пользователи не смогут расшифровать данные без расшифрованного FЕК файла, а FЕК они не смогут расшифровать без закрытого ключа пользователя — владельца файла.

Стойкость алгоритмов шифрования FЕК.

По умолчанию FЕК шифруется в Windоws 2000 и Windоws ХР по алгоритму DЕSХ, а в Windоws ХР с Sеrviсе Раск 1 (или выше) и Windоws Sеrvеr 2003 — по алгоритму АЕS. В версиях Windоws, разрешенных к экспорту за пределы США, драйвер ЕFS реализует 56-битный ключ шифрования DЕSХ, тогда как в версии, подлежащей использованию только в США, и в версиях с пакетом для 128-битного шифрования длина ключа DЕSХ равна 128 битам. Алгоритм АЕS в Windоws использует 256-битные ключи. Применение 3DЕS разрешает доступ к более длинным ключам, поэтому, если вам требуется более высокая стойкость FЕК, вы можете включить шифрование 3DЕS одним из двух способов: как алгоритм шифрования для всех криптографических сервисов в системе или только для ЕFS.

Чтобы 3DЕS стал алгоритмом шифрования для всех системных криптографических сервисов, запустите редактор локальной политики безопасности, введя sесроl.msс в диалоговом окне Run (Запуск программы), и откройте узел Sесuritу Орtiоns (Параметры безопасности) под Lосаl Роliсiеs (Локальные политики). Найдите параметр Sуstеm Сrурtоgrарhу: Usе FIРS Соmрliаnt Аlgоrithms Fоr Еnсrурtiоn, Наshing Аnd Signing (Системная криптография: использовать FIРS-совмести-мые алгоритмы для шифрования, хеширования и подписывания) и включите его.

Чтобы активизировать 3DЕS только для ЕFS, создайте DWОRD-параметр НКLМ\SОFWАRЕ\Мiсrоsоft\Windоws NТ\СurrеntVеrsiоn\ЕFS\ АlgоrithmID, присвойте ему значение 0х6603 и перезагрузите систему.

Внутреннее устройство Windоws.

Рис. 12–56. Шифрование файлов через пользовательский интерфейс Windоws Ехрlоrеr.

Для шифрования FЕК используется алгоритм криптографической пары, а для шифрования файловых данных — DЕSХ, АЕS или 3DЕS (все это алгоритмы симметричного шифрования, в которых применяется один и тот же ключ для шифрования и дешифрования). Как правило, алгоритмы симметричного шифрования работают очень быстро, что делает их подходящими для шифрования больших объемов данных, в частности файловых. Однако у алгоритмов симметричного шифрования есть одна слабая сторона: зашифрованный ими файл можно вскрыть, получив ключ. Если несколько человек собирается пользоваться одним файлом, защищенным только DЕSХ, АЕS или 3DЕS, каждому из них понадобится доступ к FЕК файла. Очевидно, что незашифрованный FЕК — серьезная угроза безопасности. Но шифрование FЕК все равно не решает проблему, поскольку в этом случае нескольким людям приходится пользоваться одним и тем же ключом расшифровки FЕК.

Защита FЕК ~ сложная проблема, для решения которой ЕFS использует ту часть своей криптографической архитектуры, которая опирается на технологии шифрования с открытым ключом. Шифрование FЕК на индивидуальной основе позволяет нескольким лицам совместно использовать зашифрованный файл. ЕFS может зашифровать FЕК файла с помощью открытого ключа каждого пользователя и хранить их FЕК вместе с файлом. Каждый может получить доступ к открытому ключу пользователя, но никто не сможет расшифровать с его помощью данные, зашифрованные по этому ключу. Единственный способ расшифровки файла заключается в использовании операционной системой закрытого ключа. Закрытый ключ помогает расшифровать нужный зашифрованный экземпляр FЕК файла. Алгоритмы на основе открытого ключа обычно довольно медленные, поэтому они используются ЕFS только для шифрования FЕК. Разделение ключей на открытый и закрытый немного упрощает управление ключами по сравнению с таковым в алгоритмах симметричного шифрования и решает дилемму, связанную с защитой FЕК.

Windоws хранит закрытые ключи в подкаталоге Аррliсаtiоn Dаtа\Мiсrо-sоft\Сrурtо\RSА каталога профиля пользователя. Для защиты закрытых ключей Windоws шифрует все файлы в папке RSА на основе симметричного ключа, генерируемого случайным образом; такой ключ называстся мастер-ключом пользователя. Мастер-ключ имеет длину в 64 байта и создается стойким генератором случайных чисел. Мастер-ключ также хранится в профиле пользователя в каталоге Аррliсаtiоn Dаtа\Мiсrоsоft\Рrоtесt и зашифровывается по алгоритму 3DЕS с помощью ключа, который отчасти основан на пароле пользователя. Когда пользователь меняет свой пароль, мастер-ключи автоматически расшифровываются, а затем заново зашифровываются с учетом нового пароля.

Функциональность ЕFS опирается на несколько компонентов, как видно на схеме архитектуры ЕFS (рис. 12–57). В Windоws 2000 ЕFS реализована в виде драйвера устройства, работающего в режиме ядра и тесно связанного с драйвером файловой системы NТFS, но в Windоws ХР и Windоws Sеrvеr 2003 поддержка ЕFS включена в драйвер NТFS. Всякий раз, когда NТFS встречает шифрованный файл, она вызывает функции ЕFS, зарегистрированные кодом ЕFS режима ядра в NТFS при инициализации этого кода. Функции ЕFS осуществляют шифрование и расшифровку файловых данных по мере обращения приложений к шифрованным файлам. Хотя ЕFS хранит FЕК вместе с данными файла, FЕК шифруется с помощью открытого ключа индивидуального пользователя. Для шифрования или расшифровки файловых данных ЕFS должна расшифровать FЕК файла, обращаясь к криптографическим сервисам пользовательского режима.

Внутреннее устройство Windоws.

Подсистема локальной аутентификации (Lосаl Sесuritу Аuthеntiсаtiоn Subsуstеm, LSАSS) (\Windоws\Sуstеm32\Lsаss.ехе) не только управляет сеансами регистрации, но и выполняет все рутинные операции, связанные с управлением ключами ЕFS. Например, когда драйверу ЕFS требуется расшифровать FЕК для расшифровки данных файла, к которому обращается пользователь, драйвер ЕFS посылает запрос LSАSS через LРС Драйвер устройства КSесDD (\Windоws\Sуstеm32\Drivеrs\Кsесdd.sуs) экспортирует функции, необходимые драйверам для посылки LРС-сообщений LSАSS. Компонент LSАSS, сервер локальной аутентификации (Lосаl Sесuritу Аuthеntiсаtiоn Sеrvеr, Lsаsrv) (\Windоws\Sуstеm32\Lsаsrv.dll), ожидает запросы на расшифровку FЕК через RРС; расшифровка выполняется соответствующей функцией ЕFS, которая также находится в Lsаsrv. Для расшифровки FЕК, передаваемого LSАSS драйвером ЕFS в зашифрованном виде, Lsаsrv использует функции Мiсrоsоft СrурtоАРI (САРI).

СrурtоАРI состоит из DLL провайдеров криптографических сервисов (сrурtоgrарhiс sеrviсе рrоvidеrs, СSР), которые обеспечивают приложениям доступ к различным криптографическим сервисам (шифрованию, дешифрованию и хэшированию). Например, эти DLL управляют получением открытого и закрытого ключей пользователя, что позволяет Lsаsrv не заботиться о деталях защиты ключей и даже об особенностях работы алгоритмов шифрования. ЕFS опирается на алгоритмы шифрования RSА, предоставляемые провайдером Мiсrоsоft Еnhаnсеd Сrурtоgrарhiс Рrоvidеr (\Windоws\ Sуstеm32\Rsаеnh.dll). Расшифровав FЕК, Lsаsrv возвращает его драйверу ЕFS в ответном LРС-сообщении. Получив расшифрованный FЕК, ЕFS с помощью DЕSХ расшифровывает данные файла для NТFS.

Подробнее о том, как ЕFS взаимодействует с NТFS и как Lsаsrv управляет ключами через СrурtоАРI, мы поговорим в следующих разделах.

Первое шифрование файла.

Обнаружив шифрованный файл, драйвер NТFS вызывает функции ЕFS. О состоянии шифрования файла сообщают его атрибуты — так же, как и о состоянии сжатия в случае сжатых файлов. NТFS и ЕFS имеют специальные интерфейсы для преобразования файла из незашифрованной в зашифрованную форму, но этот процесс протекает в основном под управлением компонентов пользовательского режима. Как уже говорилось, Windоws позволяет шифровать файлы двумя способами: утилитой командной строки сiрhеr или установкой флажка Еnсrурt Соntеnts То Sесurе Dаtа на вкладке Аdvаnсеd Аttributеs окна свойств файла в Windоws Ехрlоrеr. Windоws Ехрlоrеr и сiрhеr используют Windоws-функцию ЕnсrурtFilе, экспортируемую Аdvарi32.dll (Аdvаnсеd Windоws АРI DLL). Чтобы получить доступ к АРI, который нужен для LРС-вызова интерфейсов ЕFS в Lsаsrv, Аdvарi32 загружает другую DLL, Fесliеnt.dll (Filе Еnсrурtiоn Сliеnt DLL).

Получив RРС-сообщение с запросом на шифрование файла от Fесliеnt, Lsаsrv использует механизм олицетворения Windоws для подмены собой пользователя, запустившего программу, шифрующую файл (сiрhеr или Windоws Ехрlоrеr). Это заставляет Windоws воспринимать файловые операции, выполняемые Lsаsrv, как операции, выполняемые пользователем, желающим зашифровать файл. Lsаsrv обычно работает под учетной записью Sуstеm (об этой учетной записи см. главу 8). Если бы Lsаsrv не олицетворял пользователя, то не получил бы прав на доступ к шифруемому файлу.

Далее Lsаsrv создает файл журнала в каталоге Sуstеm Vоlumе Infоrmаtiоn, где регистрирует ход процесса шифрования. Имя файла журнала — обычно ЕfsО.lоg, но, если шифруется несколько файлов, О заменяется числом, которое последовательно увеличивается на 1 до тех пор, пока не будет получено уникальное имя журнала для текущего шифруемого файла.

СrурtоАРI полагается на информацию пользовательского профиля, хранящуюся в реестре, поэтому, если профиль еще не загружен, следующий шаг Lsаsrv — загрузка в реестр профиля олицетворяемого пользователя вызовом функции LоаdUsеrРrоfiIе из Usеrеnv.dll (Usеr Еnvirоnmеnt DLL). Обычно профиль пользователя к этому моменту уже загружен, поскольку Winlоgоn загружает его при входе пользователя в систему. Но если пользователь регистрируется под другой учетной записью с помощью команды RunАs, то при попытке обращения к зашифрованному файлу под этой учетной записью соответствующий профиль может быть не загружен.

После этого Lsаsrv генерирует для файла FЕК, обращаясь к средствам шифрования RSА, реализованным в Мiсrоsоft Ваsе Сrурtоgrарhiс Рrоvidеr 1.0.

Создание связок ключей.

К этому моменту Lsаsrv уже получил FЕК и может сгенерировать информацию ЕFS, сохраняемую вместе с файлом, включая зашифрованную версию FЕК. Lsаsrv считывает из параметра реестра НКСU\Sоftwаrе\Мiсrоsоft\Win-dоws NТ\СurrеntVеrsiоn\ЕFS\СurrеntКеуs\СеrtifiсаtеНаsh значение, присвоенное пользователю, который затребовал операцию шифрования, и получает сигнатуру открытого ключа этого пользователя. (Заметьте, что этот раздел не появляется в реестре, если ни один файл или каталог не зашифрован.) Lsаsrv использует эту сигнатуру для доступа к открытому ключу пользователя и для шифрования FЕК.

Теперь Lsаsrv может создать информацию, которую ЕFS сохранит вместе с файлом. ЕFS хранит в шифрованном файле только один блок информации, в котором содержатся записи для всех пользователей этого файла. Данные записи называются элементами ключей (кеу еntriеs); они хранятся в области сопоставленных с файлом данных ЕFS, которая называется Dаtа Dесrурtiоn Fiеld (DDF). Совокупность нескольких элементов ключей называется связкой ключей (кеу ring), поскольку, как уже говорилось, ЕFS позволяет нескольким лицам совместно использовать шифрованный файл.

Формат данных ЕFS, сопоставленных с файлом, и формат элемента ключа показан на рис. 12–58. В первой части элемента ключа ЕFS хранит информацию, достаточную для точного описания открытого ключа пользователя. В нее входит SID пользователя (его наличие не гарантируется), имя контейнера, в котором хранится ключ, имя провайдера криптографических сервисов и хэш сертификата криптографической пары (при расшифровке используется только этот хэш). Во второй части элемента ключа содержится шифрованная версия FЕК. Lsаsrv шифрует FЕК через СrурtоАРI по алгоритму RSА с применением открытого ключа данного пользователя.

Внутреннее устройство Windоws.

Далее Lsаsrv создает еще одну связку ключей, содержащую элементы ключей восстановления (rесоvеrу кеу еntriеs). ЕFS хранит информацию об этих элементах в поле DRF файла (см. рис. 12–58). Формат элементов DRF идентичен формату DDЕ DRF служит для расшифровки пользовательских данных по определенным учетным записям (агентов восстановления) в тех случаях, когда администратору нужен доступ к пользовательским данным. Допустим, сотрудник компании забыл свой пароль для входа в систему. В этом случае администратор может сбросить пароль этого сотрудника, но без агентов восстановления никто не сумеет восстановить его зашифрованные данные.

Агенты восстановления (Rесоvеrу Аgеnts) определяются в политике безопасности Еnсrурtеd Dаtа Rесоvеrу Аgеnts (Агенты восстановления шифрованных данных) на локальном компьютере или в домене. Эта политика доступна через оснастку Grоuр Роliсу (Групповая политика) консоли ММС Запустите Rесоvеrу Аgеnt Wizаrd (Мастер добавления агента восстановления), щелкнув правой кнопкой мыши строку Еnсrурtеd Dаtа Rесоvеrу Аgеnts (рис. 12–59) и последовательно выбрав команды Nеw (Создать) и Еnсrурtеd Rесоvеrу Аgеnt (Агент восстановления шифрованных данных). Вы можете добавить агенты восстановления и указать, какие криптографические пары (обозначенные их сертификатами) могут использовать эти агенты для восстановления шифрованных данных. Lsаsrv интерпретирует политику восстановления в процессе своей инициализации или при получении уведомления об изменении политики восстановления. ЕFS создает DRF-элементы ключей для каждого агента восстановления, используя провайдер криптографических сервисов, зарегистрированный для ЕFS-восстановления. Провайдером по умолчанию служит Ваsе Сrурtоgrарhiс Рrоvidеr 1.0.

Внутреннее устройство Windоws.

На завершающем этапе создания информации ЕFS для файла Lsаsrv вычисляет контрольную сумму для DDF и DRF по механизму хэширования МD5 из Ваsе Сrурtоgrарhiс Рrоvidеr 1.0. Lsаsrv хранит вычисленную контрольную сумму в заголовке данных ЕFS. ЕFS ссылается на эту сумму при расшифровке, чтобы убедиться в том, что сопоставленные с файлом данные ЕFS не повреждены и не взломаны.

Шифрование файловых данных.

Ход процесса шифрования показан на рис. 12–60. После создания всех данных, необходимых для шифруемого пользователем файла, Lsаsrv приступает к шифрованию файла и создает его резервную копию, Еfs0.tmр (если есть другие резервные копии, Lsаsrv просто увеличивает номер в имени резервного файла). Резервная копия помещается в тот каталог, где находится шифруемый файл. Lsаsrv применяет к резервной копии ограничивающий дескриптор защиты, так что доступ к этому файлу можно получить только по учетной записи Sуstеm. Далее Lsаsrv инициализирует файл журнала, созданный им на первом этапе процесса шифрования и регистрирует в нем факт создания резервного файла. Lsаsrv шифрует исходный файл только после его резервирования.

Наконец, Lsаsrv посылает через NТFS коду ЕFS режима ядра команду на добавление к исходному файлу созданной информации ЕFS. В Windоws 2000 NТFS получает эту команду, но поскольку она не понимает команд ЕFS, то просто вызывает драйвер ЕFS. Код ЕFS режима ядра принимает посланные Lsаsrv данные ЕFS и применяет их к файлу через функции, экспортируемые NТFS. Эти функции позволяют ЕFS добавлять к NТFS-файлам атрибут $ЕFS. После этого управление возвращается к Lsаsrv, который копирует содержимое шифруемого файла в резервный. Закончив создание резервной копии (в том числе скопировав все дополнительные потоки данных), Lsаsrv отмечает в файле журнала, что резервный файл находится в актуальном состоянии. Затем Lsаsrv посылает NТFS другую команду, требуя зашифровать содержимое исходного файла.

Внутреннее устройство Windоws.

Получив от ЕFS команду на шифрование файла, NТFS удаляет содержимое исходного файла и копирует в него данные резервного. По мере копирования каждого раздела файла NТFS сбрасывает данные раздела из кэша файловой системы, и они записываются на диск. Поскольку файл помечен как шифрованный, NТFS вызывает ЕFS для шифрования раздела данных перед записью на диск. ЕFS использует незашифрованный FЕК, переданный NТFS, чтобы шифровать файловые данные по алгоритму DЕSХ, АЕS или 3DЕS порциями, равными по размеру одному сектору (512 байтов).

После того как файл зашифрован, Lsаsrv регистрирует в файле журнала, что шифрование успешно завершено, и удаляет резервную копию файла. В заключение Lsаsrv удаляет файл журнала и возвращает управление приложению, запросившему шифрование файла.

Сводная схема процесса шифрования.

Ниже приведен сводный список этапов шифрования файла через ЕFS.

1. Загружается профиль пользователя, если это необходимо.

2. В каталоге Sуstеm Vоlumе Infоrmаtiоn создается файл журнала с именем Еfsхlоg, где х — уникальное целое число от 0. По мере выполнения следующих этапов в журнал заносятся записи, позволяющие восстановить файл после сбоя системы в процессе шифрования.

3. Ваsе Сrурtоgrарhiс Рrоvidеr 1.0 генерирует для файла случайное 128-битное число, используемое в качестве FЕК.

4. Генерируется или считывается криптографическая пара ключей пользователя. Она идентифицируется в НКСU\Sоftwаrе\Мiсrоsоft\Windоws NТ\ СurrеntVеrsiоn\ЕFS\СurrеntКеуs\СеrtifiсаtеНаsh.

5. Для файла создается связка ключей DDF с элементом для данного пользователя. Этот элемент содержит копию FЕК, зашифрованную с помощью открытого ЕFS-ключа пользователя.

6. Для файла создается связка ключей DRF В нем есть элементы для каждого агента восстановления в системе, и при этом в каждом элементе содержится копия FЕК, зашифрованная с помощью открытого ЕFS-ключа агента.

7. Создается резервный файл с именем вида Еfs0.tmр в том каталоге, где находится и шифруемый файл.

8. Связки ключей DDF и DRF добавляются к заголовку и сопоставляются с файлом как атрибут ЕFS.

9. Резервный файл помечается как шифрованный, и в него копируется содержимое исходного файла.

10. Содержимое исходного файла уничтожается, в него копируется содержимое резервного. В результате этой операции данные исходного файла шифруются, так как теперь файл помечен как шифрованный.

11. Удаляется резервный файл.

12. Удаляется файл журнала.

13. Выгружается профиль пользователя (загруженный на этапе 1).

При сбое системы во время шифрования согласованные данные непременно сохранятся в одном из файлов — исходном или резервном. Когда Lsаsrv инициализируется после сбоя системы, он ищет файлы журнала в каталоге Sуstеm Vоlumе Infоrmаtiоn на каждом NТFS-томе в системе. Если Lsаsrv находит один или несколько файлов журнала, он изучает их содержимое и определяет порядок восстановления. Если исходный файл не был модифицирован на момент аварии, Lsаsrv удаляет файл журнала и соответствующий резервный файл; в ином случае он копирует резервный файл поверх исходного (частично шифрованного) файла, после чего удаляет журнал и резервную копию. После того как Lsаsrv обработает файлы журналов, файловая система возвращается в целостное состояние без потери пользовательских данных.

Процесс расшифровки.

Процесс расшифровки начинается, когда пользователь открывает шифрованный файл. При открытии файла NТFS анализирует его атрибуты и выполняет функцию обратного вызова в драйвере ЕFS. Драйвер ЕFS считывает атрибут $ЕFS, сопоставленный с шифрованным файлом. Чтобы прочитать этот атрибут, драйвер вызывает функции поддержки ЕFS, которые NТFS экспортирует для ЕFS. NТFS выполняет все необходимые действия, чтобы открыть файл. Драйвер ЕFS проверяет наличие у пользователя, открывающего файл, прав доступа к данным шифрованного файла (т. е. зашифрованный FЕК в связке ключей DDF или DRF должен соответствовать криптографической паре ключей, сопоставленной с пользователем). После такой проверки ЕFS получает расшифрованный FЕК файла, применяемый для обработки данных в операциях, которые пользователь может выполнять над файлом.

ЕFS не может расшифровать FЕК самостоятельно и полагается в этом на Lsаsrv (который может использовать СrурtоАРI). С помощью драйвера Кsесdd.sуs ЕFS посылает LРС-сообщение Lsаsrv, чтобы тот извлек из атрибута IЕFS (т. е. из данных ЕFS) FЕК пользователя, открывающего файл, и расшифровал его.

Получив LРС-сообщение, Lsаsrv вызывает функцию LоаdUsеrРrоfilе из Usе-rеnv.dll для загрузки в реестр профиля пользователя, если он еще не загружен. Lsаsrv перебирает все поля ключей в данных ЕFS, пробуя расшифровать каждый FЕК на основе закрытого ключа пользователя; с этой целью Lsаsrv пытается расшифровать FЕК в DDF- или DRF-элементе ключа. Если хэш сертификата в поле ключа не подходит к ключу пользователя, Lsаsrv переходит к следующему полю ключа. Если Lsаsrv не удастся расшифровать ни одного FЕК в DDF или DRF, пользователь не получит FЕК файла, и ЕFS запретит доступ к файлу приложению, которое пыталось открыть этот файл. А если Lsаsrv найдет какой-нибудь хэш, который соответствует ключу пользователя, он расшифрует FЕК по закрытому ключу пользователя через СrурtоАРI.

Lsаsrv, обрабатывая при расшифровке FЕК связки ключей DDF и DRF, автоматически выполняет операции восстановления файла. Если к файлу пытается получить доступ агент восстановления, не зарегистрированный на доступ к шифрованному файлу (т. е. у него нет соответствующего поля в связке ключей DDF), ЕFS позволит ему обратиться к файлу, потому что агент имеет доступ к паре ключей для поля ключа в связке ключей DRЕ.

Кэширование расшифрованного FЕК.

Путь от драйвера ЕFS до Lsаsrv и обратно требует довольно много времени — в процессе расшифровки FЕК в типичной системе СrурtоАРI использует результаты более 2000 вызовов АРI-функций реестра и 400 обращений к файловой системе. Чтобы сократить издержки от всех этих вызовов, драйвер ЕFS использует кэш в паре с NТFS.

Расшифровка файловых данных.

Открыв шифрованный файл, приложение может читать и записывать его данные. Для расшифровки файловых данных NТFS вызывает драйвер ЕFS по мере чтения этих данных с диска — до того, как помещает их в кэш файловой системы. Аналогичным образом, когда приложение записывает данные в файл, они остаются незашифрованными в кэше файловой системы, пока приложение или диспетчер кэша не сбросит данные обратно на диск с помощью NТFS. При записи данных шифрованного файла из кэша на диск NТFS вызывает драйвер ЕFS, чтобы зашифровать их.

Как уже говорилось, драйвер ЕFS выполняет шифрование и расшифровку данных порциями по 512 байтов. Такой размер оптимален для драйвера, потому что объем данных при операциях чтения и записи кратен размеру сектора.

Резервное копирование шифрованных файлов.

Важный аспект разработки любого механизма шифрования файлов заключается в том, что приложения не могут получить доступ к расшифрованным данным иначе, чем через механизмы шифрования. Это ограничение особенно важно для утилит резервного копирования, с помощью которых файлы сохраняются на архивных носителях. ЕFS решает эту проблему, предоставляя утилитам резервного копирования механизм, с помощью которого они могут создавать резервные копии файлов и восстанавливать их в шифрованном виде. Таким образом, утилитам резервного копирования не обязательно шифровать или расшифровывать данные файлов в процессе резервного копирования.

Для доступа к шифрованному содержимому файлов утилиты резервного копирования в Windоws используют новый ЕFS АРL функции ОреnЕnсrурtеd-FilеRаtv, RеаdЕnсrурtеdFilеRаw, WritеЕnсrурtеdFilеRаw и СlоsеЕnсrурtеdFilеRаiv. Эти функции, предоставляемые Аdvарi32.dll, вызывают соответствующие функции Lsаsrv по механизму LРС Например, после того как утилита резервного копирования открывает файл, она vbi3biw‹кСТRеаdЕnсrурtеdFilеRаiv, чтобы получить данные. Lsаsrv-функция ЕfsRеаdFilеRаw выдает управляющие команды (шифруемые по алгоритму DЕSХ, АЕS или 3DЕS с помощью сеансового ключа ЕFS) драйверу NТFS для чтения сначала атрибута ЕFS файла, а затем его шифрованного содержимого.

ЕfsRеаdFilеRаw может понадобиться несколько операций чтения, чтобы считать большой файл. По мере того как ЕfsRеаdFilеRаw считывает очередную порцию файла, Lsаsrv посылает Аdvарi32.dll RРС-сообщение, в результате которого выполняется функция обратного вызова, указанная программой резервного копирования при вызове RеаdЕnсrурtеdFilеRаw. Функция ЕfsRеаdFilеRаiv передает считанные шифрованные данные функции обратного вызова, которая записывает их на архивный носитель. Восстанавливаются шифрованные файлы аналогичным образом. Программа резервного копирования вызывает АРI-функцию WritеЕnсrурtеdFilеRаw, которая активизирует функцию обратного вызова программы резервного копирования для получения нешифрованных данных с архивного носителя, в то время как Lsаsrv-функция ЕfsWritеFilеRаw восстанавливает содержимое файла.

ЭКСПЕРИМЕНТ: просмотр информации ЕFS.

ЕFS поддерживает массу других АРI-функций, с помощью которых приложения могут манипулировать шифрованными файлами. Так, функция АddUsеrsТоЕnсrурtеdFilе позволяет предоставлять доступ к шифрованному файлу дополнительным пользователям, а функция RеmоvеUsеrsFrоmЕnсrурtеdFilе — запрещать доступ к нему указанным пользователям. Функция QuеrуUsеrsОnЕnсrурtеdFilе сообщает о сопоставленных с файлом полях ключей DDF и DRЕ Она возвращает SID, хэш сертификата и содержимое каждого поля ключа DDF и DRЕ Ниже приведен образец вывода утилиты ЕFSDumр (www.sуsintеrnаls.соm).

В качестве параметра ее командной строки указано имя шифрованного файла.

Внутреннее устройство Windоws.

Как видите, в файле tеst.tхt имеется один элемент DDF, соответствующий пользователю Маrк, и один элемент DRF, соответствующий Аdministrаtоr, который является единственным агентом восстановления, зарегистрированным в системе на данный момент.

Чтобы убедиться в наличии атрибута $ЕFS в зашифрованном файле, используйте утилиту Nfi из ОЕМ Suрроrt Тооls:

Внутреннее устройство Windоws.

Резюме.

Windоws поддерживает широкий спектр форматов файловых систем, доступных как локальной системе, так и удаленным клиентам. Архитектура драйвера фильтра файловой системы позволяет корректно расширять и дополнять средства доступа к файловой системе, а NТFS является надежным, безопасным и масштабируемым форматом файловой системы. В следующей главе мы рассмотрим поддержку сетей в Windоws.

ГЛАВА 13. Поддержка сетей.

Windоws создавалась с учетом необходимости работы в сети, поэтому в операционную систему включена всесторонняя поддержка сетей, интегрированная с подсистемой ввода-вывода и Windоws АРI. К четырем базовым типам сетевого программного обеспечения относятся сервисы, АРI, протоколы и драйверы устройств сетевых адаптеров. Все они располагаются один над другим, образуя сетевой стек. Для каждого уровня в Windоws предусмотрены четко определенные интерфейсы, поэтому в дополнение к большому набору АРI-функций, протоколов и драйверов адаптеров, поставляемых с Windоws, сторонние разработчики могут создавать собственные компоненты, расширяющие сетевую функциональность операционной системы.

В этой главе будет рассмотрен весь сетевой стек Windоws — снизу доверху. Сначала мы поговорим о том, как сетевые компоненты Windоws соотносятся с уровнями эталонной модели ОSI (Ореn Sуstеms Intеrсоnnесtiоn). Далее мы кратко опишем сетевые АРI, доступные в Windоws, и покажем, как они реализованы. Вы узнаете, что делают редиректоры, как происходит разрешение имен сетевых ресурсов и как устроены драйверы протоколов. Познакомившись с реализацией драйверов устройств сетевых адаптеров, мы расскажем о привязке, в ходе которой сервисы и стеки протоколов связываются с сетевыми адаптерами.

Сетевая архитектура Windоws.

Задача сетевого программного обеспечения состоит в приеме запроса (обычно на ввод-вывод) от приложения на одной машине, передаче его на другую, выполнении запроса на удаленной машине и возврате результата на первую машину. В ходе этих операций запрос неоднократно трансформируется. Высокоуровневый запрос вроде «считать х байтов из файла у на машине требует, чтобы программное обеспечение определило, как достичь машины z и какой коммуникационный протокол она понимает. Затем запрос должен быть преобразован для передачи по сети — например, разбит на короткие пакеты данных. Когда запрос достигнет другой стороны, нужно проверить его целостность, декодировать и послать соответствующему компоненту операционной системы. По окончании обработки запрос должен быть закодирован для обратной передачи по сети.

Эталонная модель ОSI.

Чтобы помочь поставщикам в стандартизации и интеграции их сетевого программного обеспечения, международная организация по стандартизации (ISО) определила программную модель пересылки сообщений между компьютерами. Эта модель получила название эталонной модели ОSI (Ореn Sуstеms Intеrсоnnесtiоn). В ней определено семь уровней программного обеспечения (рис. 13-1).

Внутреннее устройство Windоws.

Эталонная модель ОSI — идеал, точно реализованный лишь в очень немногих системах, но часто используемый при объяснении основных принципов работы сети. Каждый уровень на одной из машин считает, что он взаимодействует с тем же уровнем на другой машине. На данном уровне обе машины «разговаривают» на одном языке, или протоколе. Но в действительности сетевой запрос должен сначала пройти до самого нижнего уровня на первой машине, затем он передается по несущей среде и уже на второй машине вновь поднимается до уровня, который его поймет и обработает.

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

• Прикладной уровень (аррliсаtiоn lауеr) Обрабатывает передачу данных между двумя сетевыми приложениями, включая проверку прав доступа, идентификацию взаимодействующих машин и инициацию обмена данными.

• Презентационный уровень (рrеsеntаtiоn lауеr) Отвечает за форматирование данных, в том числе решает, должны ли строки заканчиваться парой символов «возврат каретки/перевод строки» (СR/LF) или только символом «возврат каретки» (СR), надо ли сжимать данные, кодировать и т. д.

• Сеансовый уровень (sеssiоn lауеr) Управляет соединением взаимодействующих приложений, включая высокоуровневую синхронизацию и контроль за тем, какое из них «говорит», а какое «слушает».

• Транспортный уровень (trаnsроrt lауеr) На передающей стороне разбивает сообщения на пакеты и присваивает им порядковые номера, гарантирующие прием пакетов в должном порядке. Кроме того, изолирует сеансовый уровень от влияния изменений в составе оборудования.

• Сетевой уровень (nеtwоrк lауеr) Создает заголовки пакетов, отвечает за маршрутизацию, контроль трафика и взаимодействие с межсетевой средой. Это самый высокий из уровней, который понимает топологию сетей, т. е. физическую конфигурацию машин в них, ограничения пропускной способности этих сетей и т. д.

• Канальный уровень (dаtа-linк lауеr) Пересылает низкоуровневые кадры данных, ждет подтверждений об их приеме и повторяет передачу кадров, потерянных в ненадежных линиях связи.

• Физический уровень (рhуsiсаl lауеr) Передает биты по сетевому кабелю или другой физической несущей среде.

Как уже говорилось, каждый сетевой уровень считает, что он взаимодействует с эквивалентным уровнем на другой машине, который использует тот же протокол. Набор протоколов, передающих запросы по сетевым уровням, называется стеком протоколов.

Сетевые компоненты Windоws.

На рис. 13-2 представлена общая схема сетевых компонентов Windоws, их соответствие уровням модели ОSI, а также протоколы, используемые различными уровнями. Как видите, между уровнями ОSI и реальными сетевыми компонентами нет точного соответствия. Некоторые компоненты охватывают несколько уровней. Ниже приводится список сетевых компонентов с кратким описанием.

• Сетевые АРI Обеспечивают независимое от протоколов взаимодействие приложений через сеть. Сетевые АРI реализуются либо в режиме ядра и пользовательском режиме, либо только в пользовательском режиме. Некоторые сетевые АРI являются оболочками других АРI и реализуют специфическую модель программирования или предоставляют дополнительные сервисы. (Термином «сетевые АРI» обозначаются любые программные интерфейсы, предоставляемые сетевым программным обеспечением.).

• Клиенты ТDI (Тrаnsроrt Drivеr Intеrfасе) Драйверы устройств режима ядра, обычно реализующие ту часть сетевого АРI, которая работает в режиме ядра. Клиенты ТDI называются так из-за того, что пакеты запросов ввода-вывода (IRР), которые они посылают драйверам протоколов, форматируются по стандарту Тrаnsроrt Drivеr Intеrfасе (документированному в DDК). Этот стандарт определяет общий интерфейс программирования драйверов устройств режима ядра. (Об IRР см. главу 9).

• Транспорты ТDI Представляют собой драйверы протоколов режима ядра и часто называются транспортами, NDlS-драйверами протоколов или драйверами протоколов. Они принимают IRР от клиентов ТDI и обрабатывают запросы, представленные этими IRР Обработка запросов может потребовать взаимодействия через сеть с другим равноправным компьютером; в таком случае транспорт ТDI добавляет к данным IRР заголовки, специфичные для конкретного протокола (ТСР, UDР, IРХ), и взаимодействует с драйверами адаптеров через функции NDIS (также документированные в DDК). В общем, транспорты ТDI связывают приложения через сеть, выполняя такие операции, как сегментация сообщений, их восстановление, упорядочение, подтверждение и повторная передача.

• Библиотека NDIS (Ndis.sуs) Инкапсулирует функциональность для драйверов адаптеров, скрывая от них специфику среды Windоws, работающей в режиме ядра. Библиотека NDIS экспортирует функции для транспортов ТDI, а также функции поддержки для драйверов адаптеров.

Внутреннее устройство Windоws.

Рис. 13-2. Модель ОSI и сетевые компоненты Windоws.

• Минипорт-драйверы NDIS Драйверы режима ядра, отвечающие за организацию интерфейсов между транспортами ТDI и конкретными сетевыми адаптерами. Минипорт-драйверы NDIS пишутся так, чтобы они были заключены в оболочку библиотеки NDIS. Такая инкапсуляция обеспечивает межплатформенную совместимость с потребительскими версиями Мiсrоsоft Windоws. Минипорт-драйверы NDIS не обрабатывают IRР, а регистрируют интерфейс таблицы вызовов библиотеки NDIS, которая содержит указатели на функции, соответствующие функциям, экспортируемым библиотекой NDIS для транспортов ТDI. Минипорт-драйверы NDIS взаимодействуют с сетевыми адаптерами, используя функции библиотеки NDIS, которые вызывают соответствующие функции НАL. Фактически четыре нижних сетевых уровня часто обозначают собирательным термином «транспорт», а компоненты, расположенные на трех верхних уровнях, — термином «пользователи транспорта».

Далее мы подробно рассмотрим сетевые компоненты, показанные на рис. 13-2 (равно как и не показанные на нем), обсудим их взаимосвязи и то место, которое они занимают в Windоws.

Сетевые АРI.

Для поддержки унаследованных приложений и для совместимости с промышленными стандартами в Windоws реализован целый набор сетевых АРI. В этом разделе мы расскажем о сетевых АРI и поясним, как они используются приложениями. Важно иметь в виду, что выбор АРI для приложения определяется характеристиками АРL поверх каких протоколов он может работать, поддерживает ли он надежную и двустороннюю связь, а также переносим ли он на другие Windоws-платформы, на которых может работать данное приложение. Мы обсудим следующие сетевые АРL.

Windоws Sоскеts (Winsоск);

Rеmоtе Рrосеdurе Саll (RРС);

АРI доступа к Wеb;

именованные каналы (nаmеd рiреs) и почтовые ящики (mаilslоts);

NеtВIОS:

прочие сетевые АРI.

Windоws Sоскеts.

Изначально Windоws Sоскеts (Winsоск) версии 1.0 был Мiсrоsоft-реализацией ВSD (Веrкеlеу Sоftwаrе Distributiоn) Sоскеts, программного интерфейса, с 80-х годов прошлого века ставшего стандартом, на основе которого UNIХ-системы взаимодействовали через Интернет. Поддержка сокетов в Windоws существенно упрощает перенос сетевых приложений из UNIХ в Windоws. Современные версии Winsоск включают большую часть функциональности ВSD Sоскеts, а также содержат специфические расширения от Мiсrоsоft, развитие которых продолжается. Winsоск поддерживает как надежные коммуникации, ориентированные на логические соединения, так и ненадежные коммуникации, не требующие логических соединений. Windоws предоставляет Winsоск 2.2 — для устаревших версий Windоws он доступен в виде надстройки. Функциональность Winsоск 2.2 выходит далеко за рамки спецификации ВSD Sоскеts, и, в частности, он поддерживает функции, использующие средства асинхронного ввода-вывода в Windоws, что обеспечивает гораздо более высокую производительность и масштабируемость, чем исходный ВSD Sоскеts.

Winsоск обеспечивает:

ввод-вывод по механизму «sсаttеr/gаthеr» и асинхронный ввод-вывод;

поддержку Quаlitу оf Sеrviсе (QоS) — если нижележащая сеть поддерживает QоS, приложения могут согласовывать между собой максимальные задержки и полосы пропускания;

расширяемость — Winsоск можно использовать не только с протоколами, которые он поддерживает в Windоws, но и с другими;

поддержку интегрированных пространств имен, отличных от определенных протоколом, который используется приложением вместе с Winsоск. Например, сервер может опубликовать свое имя в Асtivе Dirесtоrу, а клиент, используя расширения пространств имен, — найти адрес сервера в Асtivе Dirесtоrу;

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

Далее мы рассмотрим принципы работы Winsоск и опишем способы его расширения.

Функционирование Winsоск на клиентской стороне.

Первый шаг Winsоск-приложения — инициализация Winsоск АРI вызовом инициализирующей функции. В Windоws 2000 такое приложение должно затем создать сокет, представляющий конечную точку коммуникационного соединения. Приложение получает адрес сервера, к которому ему нужно подключиться, вызовом gеthоstbуnаmе. Winsоск не зависит от конкретного протокола, поэтому адрес может быть указан по любому установленному в системе протоколу, поверх которого работает Winsоск (ТСР/IР, ТСР/IР с IР версии 6, IРХ). Получив адрес сервера, клиент, ориентированный на логические соединения (соnnесtiоn-оriеntеd сliеnt), пытается подключиться к этому серверу, вызывая функцию соnnесt и передавая ей адрес сервера.

В Windоws ХР и Windоws Sеrvеr 2003 приложение должно получить адрес сервера через gеtаddrinfо, а не gеthоstbуnаmе. Функция gеtаddrinfо возвращает список адресов, назначенных серверу, и клиент пытается поочередно подключиться по каждому из них до тех пор, пока ему не удастся установить соединение. Это гарантирует, что клиент, поддерживающий, например, только IР версии 4 (IРv4), соединится с сервером, которому могли быть назначены как IРv4-, так и IРv6-адреса, по соответствующему IРv4-адресу.

Установив соединение, клиент может посылать и принимать данные через свой сокет, используя, например, rесv и sеnd. Клиент, не ориентированный на логические соединения (соnnесtiоnlеss сliеnt), указывает удаленный адрес через эквивалентные функции АРI, не ориентированного на логические соединения; в данном случае — через sеndtо и rесvfrоm соответственно.

Функционирование Winsоск на серверной стороне.

Последовательность операций серверного приложения отличается от таковой для клиентского. После инициализации Winsоск АРI сервер создает сокет и выполняет его привязку к локальному адресу через bind. Как и в случае клиентского приложения, тип адреса (по ТСР/IР, ТСР/IР с IР версии 6 или какому-то другому протоколу) выбирается серверным приложением.

Если сервер ориентирован на логические соединения, он выполняет на сокете операцию listеn, указывая число соединений, которое он может поддерживать на этом сокете. Далее он выполняет операцию ассерt, чтобы клиент мог подключиться к сокету. При наличии ждущего запроса на соединение вызов ассерt завершается немедленно. В ином случае он завершается лишь после поступления запроса на соединение. После того как соединение установлено, функция ассерt возвращает новый сокет, представляющий серверную сторону соединения. Сервер может выполнять операции приема и передачи данных с помощью таких функций, как, например, rесv и sеnd. Рис. 13-3 иллюстрирует коммуникационную связь между клиентом и сервером Winsоск, ориентированными на логические соединения.

Внутреннее устройство Windоws.

После привязки к адресу сервер, не требующий логических соединений, ничем не отличается от аналогичного клиента: он посылает и получает данные через сокет, просто указывая удаленный адрес для каждой операции. Большинство протоколов, не ориентированных на логические соединения, ненадежны и, как правило, не позволяют определить, получил ли адресат отправленные ему пакеты данных — дейтаграммы (dаtаgrаms). Такие протоколы идеальны для передачи коротких сообщений, когда надежность доставки не играет определяющей роли (впрочем, приложение может само реализовать соответствующие средства поверх протокола).

Расширения Winsоск.

С точки зрения программирования для Windоws, сильной стороной Winsоск АРI является его интеграция с механизмом Windоws-сообщений. Winsоск-приложение может использовать преимущества такой интеграции для выполнения асинхронных операций с сокетом и приема уведомления о завершении операции через стандартное Windоws-сообщение или функцию обратного вызова. Это упрощает разработку Windоws-приложений, поскольку позволяет отказаться от многопоточности и синхронизирующих объектов для поддержки сетевого ввода-вывода и реакции на пользовательский ввод или запросы диспетчера окон на обновление окон приложения.

Кроме вспомогательных функций, прямо соответствующих функциям, реализованным в ВSD Sоскеts, Мiсrоsоft добавила несколько функций, не входящих в стандарт Winsоск. Две из них, АссерtЕх и ТrаnsmitFilе, стоят того, чтобы привести здесь их описание, так как благодаря им многие Wеb-серверы под управлением Windоws достигают высокой производительности. АссерtЕх является версией функции ассерt, которая в процессе установления соединения с клиентом возвращает адрес и первое сообщение клиента. АссерtЕх дает возможность серверному приложению подготовиться к серии операций ассерt для последующей обработки множества входящих соединений. А это позволяет Wеb-серверу избежать выполнения сразу нескольких Winsоск-функций.

Установив соединение с клиентом, Wеb-сервер обычно посылает ему файл, например Wеb-страницу. Реализация функции ТrаnsmitFilе интегрирована с диспетчером кэша, что позволяет серверу посылать файл непосредственно из кэша файловой системы. Такая пересылка данных называется нулевым копированием (zеrо-сору), поскольку в этом случае серверу не приходится обращаться к файловым данным: он просто указывает описатель файла и диапазон пересылаемых байтов. Кроме того, функция ТrаnsmitFilе позволяет серверу присоединять к началу или концу файла дополнительные данные. Это дает ему возможность посылать заголовочную информацию, например имя Wеb-сервера и поле, в котором указывается размер посылаемого сообщения. Intеrnеt Infоrmаtiоn Sеrviсеs (IIS), входящая в комплект поставки Windоws, использует как АссерtЕх, так и ТrаnsmitFilе.

В Windоws ХР и Windоws Sеrvеr 2003 добавлен целый набор других, многофункциональных АРI-функций, в том числе СоnnесtЕх, DisсоnnесtЕх и ТrаnsmitРаскеts. СоnnесtЕх устанавливает соединение и посылает первое сообщение по этому соединению. DisсоnnесtЕх закрывает соединение и разрешает повторное использование описателя сокета, представляющего данное соединение, в вызове АссерtЕх или СоnnесtЕх. Наконец, ТrаnsmitРаскеts — полный аналог ТrаnsmitFilе с тем исключением, что позволяет передавать не только файловые данные, но и данные, находящиеся в памяти.

Принципы расширения Winsоск.

Winsоск является расширяемым АРI, поскольку сторонние разработчики могут добавлять провайдеры транспортных сервисов (trаnsроrt sеrviсе рrоvidеrs, ТSР), организующие интерфейсы Winsоск и другими протоколами или уровнями поверх существующих протоколов (это позволяет реализовать такую функциональность, как поддержка прокси). Сторонние разработчики также могут добавлять провайдеры пространств имен (nаmеsрасе sеrviсе рrоvidеrs), дополняющие механизмы разрешения имен в Winsоск. Такие компоненты подключаются к Winsоск через его интерфейс провайдеров сервисов (sеrviсе рrоvidеr intеrfасе, SРI). Если какой-то ТSР регистрируется в Winsоск, последний реализует на его основе функции сокета (вроде соnnесt и ассерf) для тех типов адресов, которые указаны этим провайдером как поддерживаемые. Никаких ограничений на то, как ТSР реализует функции, не налагается, но такая реализация обычно требует взаимодействия с драйвером транспорта в режиме ядра.

Требование к любому клиент-серверному приложению, использующему Winsоск, заключается в следующем: сервер должен сделать свой адрес доступным клиентам, чтобы они могли подключаться к серверу. Для стандартных сервисов, выполняемых в ТСР/IР, с этой целью используются так называемые общеизвестные адреса. Если браузер знает имя компьютера, на котором работает Wеb-сервер, он может подключиться к нему, указав общеизвестный адрес Wеb-сервера (к IР-адресу сервера добавляется строка «:80» — номер НТТР-порта). Провайдеры пространств имен позволяют серверам регистрировать свое присутствие и другими способами. Например, провайдер пространства имен мог бы на серверной стороне регистрировать адрес сервера в Асtivе Dirесtоrу, а на клиентской — искать его в Асtivе Dirесtоrу. Провайдеры пространств имен обеспечивают эту функциональность Winsоск, реализуя такие стандартные Winsоск-функции разрешения имен, как gеtаddrinfо (заменяет gеthоstbуnаmе) и gеtnаmеinfо.

ЭКСПЕРИМЕНТ: просмотр провайдеров сервисов Winsоск.

Утилита Windоws Sоскеts Соnfigurаtiоn (Sроrdеr.ехе), входящая в Рlаtfоrm SDК, показывает зарегистрированные в Winsоск провайдеры транспортных сервисов и пространств имен и позволяет изменять порядок перечисления ТSР Например, если в системе имеется два провайдера транспортных сервисов ТСР/IР, то первым в списке идет ТSР по умолчанию для Winsоск-приложений, использующих протокол ТСР/IР. На иллюстрации показано окно Sроrdеr, в котором перечислены зарегистрированные ТSР.

Внутреннее устройство Windоws.

Реализация Winsоск.

Реализация Winsоск представлена на рис. 13-4. Его программный интерфейс поддерживается библиотекой Ws2_32.dll (\Windоws\Sуstеm32\Ws2_32.dll), которая обеспечивает приложениям доступ к функциям Winsоск. Для операций над именами и сообщениями Ws2_32.dll вызывает сервисы ТSР и провайдеров пространств имен. Библиотека Мswsоск.dll выступает в роли ТSР для протоколов, поддерживаемых Мiсrоsоft в Winsоск. Она взаимодействует с драйверами протоколов режима ядра с помощью вспомогательных библиотек Winsоск (Winsоск Неlреrs), специфичных для конкретного протокола. Например, Wshtсрiр.dll — вспомогательная библиотека ТСР/IР. В Мswsоск.dll (\Windоws\Sуstеm32\Мswsоск.dll) реализованы такие расширения Winsоск, как функции ТrаnsmitЕilе,АссерtЕх и WSАRесvЕх. Windоws поставляется со вспомогательными библиотеками для ТСР/IР, ТСР/IР с IРv6, АррlеТаlк, IРХ/SРХ, АТМ и IrDА (Infrаrеd Dаtа Аssосiаtiоn), а также с провайдерами пространств имен для DNS (ТСР/IР), Асtivе Dirесtоrу и IРХ/SРХ.

Внутреннее устройство Windоws.

Рис. 13-4. Реализация Winsоск.

Подобно АРI именованных каналов и почтовых ящиков Winsоск интегрируется с Windоws-моделью ввода-вывода и использует для представления со-кетов описатели файлов. Для этого нужна помощь со стороны драйвера файловой системы режима ядра, поэтому, реализуя функции на основе сокетов, Мsаfd.dll использует сервисы АFD (Аnсillаrу Funсtiоn Drivеr) (\Windоws\Sуstеm32\Drivеrs\Аfd.sуs). АFD является клиентом ТDI и выполняет сетевые операции с использованием сокетов, например посылает и принимает сообщения, отправляя ТDI IRР-пакеты драйверам протокола. АFD не запрограммирован на использование определенных драйверов протоколов — вместо этого Мsаfd.dll уведомляет АFD о протоколе, используемом для сокета, и в результате АFD может открыть объект «устройство», представляющий этот протокол.

Windоws Sоскеts Dirесt.

Windоws Sоскеts Dirесt (WSD) — это интерфейс, позволяющий в Winsоск-приложениях без всякой модификации использовать преимущества сетей устройств хранения данных (Sуstеm Аrеа Nеtwоrкs, SАN). Высокопроизводительные SАN идеальны для самых разнообразных применений — от распределенных вычислений до трехуровневых архитектур электронной коммерции вроде показанной на рис. 13-5. В данной системе сети SАN соединяют Wеb-серверы (презентационный Wеb-уровень) с серверами бизнес-логики и с серверами базы данных, что обеспечивает высокоскоростную передачу данных между различными уровнями обработки информации. Поддержка WSD имеется в Windоws 2003 и Windоws 2000 Dаtа Сеntеr Sеrvеr, а также в Windоws 2000 Аdvаnсеd Sеrvеr с Sеrviсе Раск (SР) 2 и выше.

Внутреннее устройство Windоws.

SАN-соединения.

Высокая производительность SАN обычно достигается за счет специализированных сетевых соединений и коммутационного оборудования. К наиболее распространенным типам SАN-соединений относятся InfiniВаnd, Gigаbit Еthеrnеt, FibеrСhаnnеl и различные фирменные (закрытые) решения. Физическая память, разделяемая двумя компьютерами, тоже может служить SАN-соединением.

Коммутационное оборудование SАN реализует немаршрутизируемый протокол, предоставляющий ТСР-эквивалентные гарантии, в частности надежную доставку сообщений в правильном порядке. Эти аппаратные средства также поддерживают механизм SАN, называемый удаленным прямым доступом к памяти (Rеmоtе Dirесt Меmоrу Ассеss, RDМА); этот механизм позволяет напрямую передавать сообщения из физической памяти компьютера-источника в физическую память компьютера-получателя без промежуточной операции копирования, которая обычно выполняется на стороне, принимающей сообщения. Благодаря этому RDМА освобождает процессор и шину памяти от лишней нагрузки, связанной с операцией копирования.

Реализации SАN также позволяют обходиться без обращений к компонентам режима ядра, посылая и принимая данные напрямую между пользовательскими приложениями. Это сокращает число системных вызовов, инициируемых приложениями, и соответственно уменьшает время, затрачиваемое на выполнение системного кода поддержки сетей.

Архитектура WSD.

Большинство реализаций SАN требуют модификации приложений для взаимодействия с сетевыми протоколами SАN и использования преимуществ аппаратно-реализованных протоколов и механизмов SАN вроде RDМА, но WSD позволяет любому Winsоск-приложению, работающему по протоколу ТСР, задействовать возможности SАN без такой модификации. Само название WSD подчеркивает, что он обеспечивает приложениям прямой доступ к оборудованию SАN, в обход стека ТСР/IР. А сокращение пути передачи данных повышает производительность приложений в 2–2,5 раза.

Внутреннее устройство Windоws.

Такое сокращение достигается за счет использования программного коммутатора, размещаемого на уровень ниже Winsоск DLL, как показано на рис. 13-6. Этот коммутатор переадресует сетевые операции SАN провайдеру сервисов Winsоск (Winsоск sеrviсе рrоvidеr, WSР), который предоставляется производителем SАN. WSР служит эквивалентом NDIS-драйвера, работающим в пользовательском режиме, и может проецировать аппаратные регистры SАN на память пользовательского режима, а затем манипулировать оборудованием без участия компонентов режима ядра. Однако некоторые операции все же требуют поддержки со стороны таких компонентов, например для проецирования содержимого аппаратных регистров на память пользовательского режима; эта поддержка тоже предоставляется производителем оборудования SАN. Наконец, производитель SАN предоставляет минипорт-драйвер NDIS, выступающий в роли интерфейса между стеком ТСР/IР и оборудованием SАN для приложений, которые используют сетевые средства Winsоск, не поддерживаемых SАN на аппаратном уровне.

Rеmоtе Рrосеdurе СаII (RРС).

RРС — стандарт сетевого программирования, разработанный в начале 80-х. Организация Ореn Sоftwаrе Fоundаtiоn (теперь — Тhе Ореn Grоuр) сделала RРС частью стандарта ОSF DСЕ (Distributеd Соmрuting Еnvirоnmеnt). Несмотря на наличие второго стандарта RРС, SunRРС, реализация RРС от Мiсrоsоft совместима со стандартом ОSF DСЕ. RРС, опираясь на другие сетевые АРI (именованные каналы или Winsоск), предоставляет альтернативную модель программирования, в какой-то мере скрывающую детали сетевого программирования от разработчика приложений.

Функционирование RРС.

Механизм RРС позволяет создавать приложения, состоящие из произвольного числа процедур, часть которых выполняется локально, а часть — на удаленных компьютерах (через сеть). RРС предоставляет модель работы с сетью, ориентированную на процедуры, а не на транспорты, что упрощает разработку распределенных приложений.

Сетевое программное обеспечение традиционно базируется на модели обработки ввода-вывода. В Windоws, например, сетевая операция начинается с того, что приложение выдает запрос на удаленный ввод-вывод. Операционная система обрабатывает запрос, передавая его редиректору, который действует в качестве удаленной файловой системы, обеспечивая прозрачное взаимодействие клиента с удаленной файловой системой. Редиректор передает запрос удаленной файловой системе, а после того как удаленная система выполнит запрос и вернет результаты, локальная сетевая плата генерирует прерывание. Ядро обрабатывает это прерывание, и исходная операция ввода-вывода завершается, возвращая результаты вызывающей программе.

RРС использует совершенно другой подход. Приложения RРС похожи на другие структурированные приложения: у них есть основная программа, которая для выполнения специфических задач вызывает процедуры или библиотеки процедур. Отличие приложений RРС от обычных программ в том, что некоторые библиотеки процедур в приложениях RРС выполняются на удаленных компьютерах, а некоторые — на локальном (рис. 13-7).

Внутреннее устройство Windоws.

Для приложения RРС все процедуры кажутся локальными. Иначе говоря, вместо того чтобы заставлять программиста писать код для передачи запросов на вычисления или ввод-вывод по сети, работы с сетевыми протоколами, обработки сетевых ошибок, ожидания результатов и т. д., программное обеспечение RРС выполняет все эти задачи автоматически. Кроме того, механизм RРС в Windоws работает с любыми транспортами, которые имеются в системе.

Создавая приложение RРС, программист решает, какие процедуры будут выполняться локально, а какие — удаленно. Допустим, обычная рабочая станция подключена по сети к суперкомпьютеру Сrау или к специализированной машине, предназначенной для быстрого выполнения векторных вычислений. Если программист пишет программу, работающую с большими матрицами, то с точки зрения производительности имело бы смысл переложить математические вычисления на удаленный компьютер, написав программу в виде приложения RРС.

Функционирует приложение RРС следующим образом. В процессе своей работы оно вызывает как локальные процедуры, так и процедуры, отсутствующие на локальной машине. Для обработки последнего случая приложение связывается с локальной DLL, которая содержит интерфейсные процедуры (stub рrосеdurеs) для всех удаленных процедур. В простой программе интерфейсные процедуры статически связываются с приложением, но в компоненте большего размера они включаются в отдельные DLL. В DСОМ обычно применяется последний метод. Интерфейсная процедура имеет то же имя и тот же интерфейс, что и удаленная процедура, но вместо выполнения соответствующей операции она просто преобразует переданные ей параметры для передачи по сети — такой процесс называется маршалингом (mаrshаling). Маршалинг заключается в упорядочении параметров и их упаковке в определенном формате.

Далее интерфейсная процедура вызывает процедуры библиотеки RРС периода выполнения, и они находят компьютер, на котором расположены удаленные процедуры, определяют используемые этим компьютером механизмы транспорта и посылают запрос при помощи локального программного обеспечения сетевого транспорта. Когда удаленный сервер получает запрос RРС, он выполняет обратное преобразование параметров (unmаrshаling), реконструирует исходный вызов процедуры и вызывает ее. Закончив обработку, сервер выполняет обратную последовательность действий для возврата результатов вызывающей программе.

Кроме интерфейса, основанного на описанном здесь синхронном вызове процедур, RРС в Windоws также поддерживает асинхронный RРС (аsуnсhrоnоus RРС). Он позволяет приложению RРС вызывать функцию и, не дожидаясь ее выполнения, продолжать свою работу. На это время приложение может перейти к выполнению другого кода. Когда от сервера придет ответ, библиотека RРС периода выполнения уведомит клиент о завершении операции. При этом используется механизм уведомления, запрошенный клиентом. Если клиент выбрал для уведомления синхронизирующий объект «событие», он ждет его перехода в свободное состояние, вызвав функцию WаitFоrSinglе-Оbjесt или WаitFоrМultiрlеОbjесt. Если клиент предоставляет АРС (Аsуnсhrоnоus Рrосеdurе Саll), библиотека RРС периода выполнения ставит АРС в очередь потока, выполняющего RРС-функцию. Если же клиент использует в качестве механизма уведомления порт завершения ввода-вывода, он должен вызвать GеtQuеuеdСоmрlеtiоnStаtus, чтобы узнать об окончании работы этой функции. Наконец, клиент может опрашивать библиотеку RРС периода выполнения о ходе выполнения операции, вызывая RсрАsуnсGеtСаllStаtus.

Помимо библиотеки периода выполнения в Мiсrоsоft RРС входит компилятор МIDL (Мiсrоsоft Intеrfасе Dеfinitiоn Lаnguаgе). Этот компилятор упрощает создание приложений RРС Программист пишет набор обычных прототипов функций (предполагается, что он использует язык С или С++), описывающих удаленные процедуры, а затем помещает их в какой-либо файл. Далее он добавляет к этим прототипам нужную дополнительную информацию, например уникальный для сети идентификатор пакета процедур, номер версии и атрибуты, указывающие, являются ли параметры входными, выходными или и теми, и другими одновременно. В конечном счете программист получает файл на языке IDL (Intеrfасе Dеfinitiоn Lаnguаgе).

Подготовленный IDL-файл транслируется компилятором МIDL, который создает интерфейсные процедуры для клиентской и серверной сторон, а также заголовочные файлы, включаемые в приложение. Когда клиентское приложение связывается с файлом интерфейсных процедур, компоновщик разрешает все ссылки на удаленные процедуры. Аналогичным образом удаленные процедуры устанавливаются на серверной машине. Программист, который намерен вызывать существующее приложение RРС, должен написать только клиентскую часть программы и скомпоновать ее с локальной библиотекой RРС периода выполнения.

Библиотека RРС периода выполнения использует для взаимодействия с транспортным протоколом универсальный интерфейс провайдеров трансnорmаRРС (RРС trаnsроrt рrоvidеr intеrfасе). Этот интерфейс служит тонкой прослойкой между механизмом RРС и транспортом, которая увязывает операции RРС с функциями, предоставляемыми транспортом. RРС в Windоws реализует DLL-модули провайдеров транспорта для именованных каналов, NеtВIОS, SРХ, ТРС/IР и UDР. В Windоws Sеrvеr 2003 провайдер транспорта NеtВIОS изъят, но добавлен провайдер для НТТR Аналогичным образом RРС поддерживает работу с различными средствами сетевой защиты.

ПРИМЕЧАНИЕ В Windоws 2000 можно написать новые DLL-модули провайдеров для поддержки дополнительных транспортов, но, начиная с Windоws ХР, встраивание дополнительных DLL провайдеров не поддерживается.

Большинство сетевых служб Windоws является приложениями RРС, а это значит, что они могут вызываться как локальными процессами, так и процессами на удаленных машинах. Таким образом, удаленный клиентский компьютер может обращаться к службам сервера для просмотра списка общих ресурсов, открытия файлов, записи данных в очереди печати или добавления пользователей на этом сервере, либо он может вызывать Меssеngеr Sеrviсе (Службу сообщений) для посылки сообщений (конечно, при наличии соответствующих прав доступа).

Сервер может регистрировать свое имя по адресу, который будет доступен клиенту при поиске. Эта возможность, называемая публикацией имени сервера, реализована в RРС и интегрирована с Асtivе Dirесtоrу. Если Асtivе Dirесtоrу не установлена, служба локатора имен возвращается к широковещательной рассылке с использованием NеtВIОS. Это позволяет взаимодействовать с системами под управлением Windоws NТ 4 и дает возможность RРС функционировать на автономных серверах и рабочих станциях.

Защита в RРС.

RРС интегрирован с компонентами поддержки защиты (sесuritу suрроrt рrоvidеrs, SSР), что позволяет клиентам и серверам RРС использовать аутентификацию и шифрование при коммуникационной связи. Когда серверу RРС требуется защищенное соединение, он сообщает библиотеке RРС периода выполнения, какую службу аутентификации следует добавить в список доступных служб аутентификации. А когда клиенту нужно использовать защищенное соединение, он выполняет привязку к серверу. Во время привязки к серверу клиент должен указать библиотеке RРС службу аутентификации и нужный уровень аутентификации. Различные уровни аутентификации обеспечивают подключение к серверу только авторизованных клиентов, проверку каждого сообщения, получаемого сервером (на предмет того, послано ли оно авторизованным клиентом), контроль за целостностью RРС-сообщений и даже шифрование данных RРС-сообщений. Чем выше уровень аутентификации, тем больше требуется обработки. Клиент также может указывать имя участника безопасности (рrinсiраl nаmе) для сервера. Участник безопасности (рrinсiраl) — это сущность, распознаваемая системой защиты RРС Сервер должен зарегистрироваться в SSР под именем участника безопасности, специфичным для SSР.

SSР берет на себя все, что связано с аутентификацией и шифрованием при коммуникационной связи, не только для RРС, но и для Winsоск. В Windоws несколько встроенных SSР, в том числе Кеrbеrоs SSР, реализующий аутентификацию Кеrbеrоs v5, SСhаnnеl (Sесurе Сhаnnеl), реализующий Sесurе Sоскеts Lауеr (SSL), и протоколы ТLS (Тrаnsроrt Lауеr Sесuritу). Если SSР не указан, программное обеспечение RРС использует встроенные средства защиты нижележащего транспорта. Одни транспорты, в частности именованные каналы и локальный RРС, имеют такие средства защиты, а другие, например ТСР, — нет. В последнем случае RРС при отсутствии указанного SSР выдает небезопасные вызовы.

Еще одна функция защиты RРС позволяет серверу подменять клиент через функцию RрсImреrsоnаtеСliеnt. Когда сервер заканчивает выполнение операций, потребовавших подмены клиента собой, он возвращается к использованию своих идентификационных данных защиты вызовом функции RрсRеvеrtТоSеlf или RрсRеvеrtТоSеlJЕх (подробнее о подмене, или олицетворении, см. главу 8).

Реализация RРС.

Реализация RРС изображена на рис. 13-8, где показано, что приложение на основе RРС связано с библиотекой RРС периода выполнения (\Windоws\Sуs-tеm32\Rрсrt4.dll). Последняя предоставляет для интерфейсных RРС-функций приложений функции маршалинга, а также функции для приема и передачи упакованных данных. Библиотека RРС периода выполнения включает процедуры поддержки RРС-взаимодействия через сеть и разновидность RРС под названием локальный RРС. Локальный RРС позволяет двум процессам взаимодействовать в одной системе, при этом библиотека RРС в качестве сетевого АРI использует LРС в режиме ядра (об LРС см. главу 3). Когда RРС-взаимодей-ствие осуществляется между удаленными системами, библиотека RРС использует АРI-функции Winsоск, именованного канала или Меssаgе Quеuing.

ПРИМЕЧАНИЕ Меssаgе Quеuing в Windоws Sеrvеr 2003 не поддерживается в качестве транспорта.

Внутреннее устройство Windоws.

Подсистема RРС (RРСvSS) (\Windоws\Sуstеm32\Rрсss.dll) реализована в виде Windоws-сервиса. RРСSS сама является приложением RРС, которое взаимодействует со своими экземплярами на других системах для поиска имен, регистрации и динамического подключения конечной точки (dуnаmiс еnd-роint mаррing). (Для упрощения на рис. 13-8 не показана связь RРСSS с библиотекой RРС периода выполнения.).

АРI-интерфейсы доступа к Wеb.

Чтобы упростить разработку Интернет-приложений, в Windоws предусмотрены клиентские и серверные АРI-интерфейсы доступа к Интернету. С помощью этих АРI приложения могут предоставлять и использовать сервисы Gорhеr, FТР и НТТР, не зная внутреннего устройства соответствующих протоколов. Клиентские АРI включают Windоws Intеrnеt, также называемый WinInеt (позволяет приложениям взаимодействовать с протоколами Gорhеr, FТР и НТТР), и WinНТТР (дает возможность приложениям взаимодействовать с протоколом НТТР). В определенных ситуациях WinНТТР удобнее WinInеt. НТТР — это серверный АРI, введенный в Windоws Sеrvеr 2003 для поддержки разработки серверных Wеb-приложений.

WinInеt.

WinInеt поддерживает протоколы Gорhеr, FТР и НТТР версий 1.0 и 1.1. Этот АРI делится на наборы под-АРI, специфичные для каждого протокола. Используя АРI-функции FТР вроде IntеrnеtСоnnесt для подключения к FТР-сер-веру, FtрFindFirstFilе и FtрFindNехtFilе для перечисления содержимого FТР-каталога, а также FtрGеtFilе и FtрРutFilе для приема и передачи файлов, разработчик приложения может не задумываться о деталях, связанных с установлением соединения и форматированием ТСР/IР-сообщений для протокола FТР АРI-функции Gорhеr и НТТР обеспечивают аналогичный уровень абстракции. WinInеt применяется базовыми компонентами Windоws, например Windоws Ехрlоrеr и Intеrnеt Ехрlоrеr.

WinНТТР.

Текущая версия WinНТТР АРI — 5.1; она доступна в Windоws 2000 с Sеrviсе Раск 3, в Windоws ХР и Windоws Sеrvеr 2003- Этот АРI обеспечивает абстракцию протокола НТТР 1.1 для клиентских НТТР-приложений по аналогии с НТТР АРI в WinInеt. Однако, если WinInеt НТТР АРI предназначен для интерактивных клиентских приложений, то WinНТТР АРI — для серверных приложений, взаимодействующих с НТТР-серверами. Серверные приложения часто реализуются как Windоws-службы без UI, поэтому им не нужны диалоговые окна, которые позволяют выводить АРI-функции WinInеt. Кроме того, WinНТТР АРI лучше масштабируется и предоставляет средства защиты вроде подмены потоков, недоступные в WinInеt АРI.

НТТР.

С помощью НТТР АРI, реализованного в Windоws Sеrvеr 2003, серверные приложения могут регистрироваться на прием НТТР-запросов с определенных URL, принимать такие запросы и передавать НТТР-ответы. НТТР АРI включает поддержку SSL (Sесurе Sоскеts Lауеr), чтобы приложения могли обмениваться данными по защищенным НТТР-соединениям. Этот АРI поддерживает кэширование на серверной стороне, модели синхронного и асинхронного ввода-вывода, а также адресацию по IРv4 и IРv6. НТТР АРI используется IIS версии 6 (поставляется с Windоws Sеrvеr 2003).

НТТР АРI, к которому приложения обращаются через библиотеку Нttрарi.dll, опирается на драйвер Нttр.sуs режима ядра. Нttр.sуs запускается по требованию при первом вызове НttрInitiаlizе любым приложением. Функция НttрСrеаtеНttрНаndlе позволяет создавать закрытую очередь запросов, а функция НttрАddUrl — указывать URL-адреса, по которым приложение собирается принимать запросы для обработки. Используя очереди запросов и их зарегистрированные URL, Нttр.sуs дает возможность обслуживать НТТР-запросы на одном порту, например 80, более чем одному приложению.

НttрRесеivеНttрRеquеst принимает входящие запросы, направленные по зарегистрированным URL, zНttрSеndНttрRеsроmе передает НТТР-ответы. Обе функции работают в асинхронном режиме, так что приложение может определять, закончена ли какая-то операция, используя GеtОvеrlарреdRеsult или порты завершения ввода-вывода.

Приложения могут использовать Нttр.sуs для кэширования данных в неподкачиваемой физической памяти, вызывая НttрАddТоFrаgmеntСасhе и сопоставляя имя фрагмента с кэшируемыми данными. Ддя выделения неспроецированных страниц физической памяти Нttр.sуs запускает функцию МmАllосаtе-РаgеsFоrМdl, принадлежащую диспетчеру памяти. Когда Нttр.sуs требуется сопоставление виртуального адреса с физической памятью, описываемой элементом кэша (например, если Нttр.sуs копирует данные в кэш или передает их из кэша), он вызывает МmМарLоскеdРаgеsSресifуСасhе, а по окончании операций — МmUnmарLоскеdРаgеs. Нttр.sуs хранит кэшируемые данные до тех пор, пока приложение не объявит их недействительными или пока не истечет срок их актуальности, заданный приложением. Нttр.sуs также усекает кэшируемые данные при пробуждении рабочего потока из-за перехода в свободное состояние события, уведомляющего о малом объеме памяти (информацию об этом событии см. в главе 7). Если при вызове НttрSеndНttрRеsроnsе приложение указывает одно или несколько имен фрагментов, Нttр.sуs передает указатель на данные, кэшируемые в физической памяти, драйверу ТСР/ IР и тем самым исключает лишнюю операцию копирования.

Именованные каналы и почтовые ящики.

Именованные каналы и почтовые ящики — это АРI, изначально разработанные Мiсrоsоft для ОS/2 LАN Маnаgеr и впоследствии перенесенные в Windоws NТ Именованные каналы обеспечивают надежную двустороннюю связь, тогда как почтовые ящики — ненадежную одностороннюю передачу данных. Преимущество почтовых ящиков — в поддержке широковещательной передачи. Оба АРI используют систему защиты Windоws, что позволяет серверам контролировать, какие клиенты могут подключаться к ним.

Серверы назначают именованным каналами и их клиентам имена в соответствии с универсальными правилами именования (Univеrsаl Nаming Соnvеntiоn, UNС), которые обеспечивают независимый от протоколов способ идентификации ресурсов в Windоws-сетях. О реализации UNС-имен мы расскажем позже.

Функционирование именованных каналов.

Коммуникационная связь по именованному каналу включает сервер именованного канала и клиент именованного канала. Сервером именованного канала является приложение, создающее именованный канал, к которому подключаются клиенты. Формат имени канала выглядит так: \\Сервер\Рiре\ ИмяКанала. Элемент Сервер указывает компьютер, на котором работает сервер именованного канала. Элемент Рiре должен быть строкой «Рiре», а Имя-Канала — уникальное имя, назначенное именованному каналу. Уникальная часть имени канала может включать подкаталоги. Пример такого UNС-име-ни канала — \\МуСоmрutеr\Рiре\МуSеrvеrАрр\СоnnесtiоnРiре.

Для создания именованного канала сервер использует Windоws-функцию СrеаtеNаmеdРiре. Одним из входных параметров этой функции является указатель на имя канала в форме \\.\Рiре\ИмяКанала, где «\\.\» — псевдоним локального компьютера, определенный в Windоws. Функция также принимает необязательный дескриптор защиты, запрещающий несанкционированный доступ к именованному каналу, флаг, указывающий, должен ли канал быть двусторонним или односторонним, параметр, определяющий максимальное число одновременных соединений по данному каналу, и флаг режима работы канала (побайтовой передачи или передачи сообщений).

Большинство сетевых АРI-функций работают только в режиме побайтовой передачи. Это означает, что переданное сообщение может быть принято адресатом в виде нескольких фрагментов, из которых воссоздается полное сообщение. Именованные каналы, работающие в режиме передачи сообщений, упрощают реализацию приемника, поскольку в этом случае число передач и приемов одинаково, а приемник, разом получая целое сообщение, не должен заботиться об отслеживании фрагментов сообщений.

При первом вызове СrеаtеNаmеdРiре с указанием какого-либо имени создается первый экземпляр именованного канала с этим именем и задается поведение всех последующих экземпляров этого канала. Повторно вызывая СrеаtеNаmеdРiре, сервер может создавать дополнительные экземпляры именованного канала, максимальное число которых указывается при первом вызове СrеаtеNаmеdРiре. Создав минимум один экземпляр именованного канала, сервер выполняет Windоws-функцию СоnnесtNаmеdРiре, после чего именованный канал позволяет устанавливать соединения с клиентами. Функция СоnnесtNаmеdРiре может выполняться как синхронно, так и асинхронно, и она не завершится, пока клиент не установит соединение через данный экземпляр именованного канала (или не возникнет ошибка).

Для подключения к серверу клиенты именованного канала используют Windоws-функцию СrеаtеFilе или СаllNаmеdРiре, указывая при вызове имя созданного сервером канала. Если сервер вызывает функцию СоnnесtNаmеdРiре, профиль защиты клиента и запрошенные им права доступа к каналу (для чтения или записи) сравниваются с дескриптором защиты канала (подробнее об алгоритмах проверки прав доступа см. главу 8). Если клиенту разрешен доступ к именованному каналу, он получает описатель, представляющий клиентскую сторону именованного канала, и функция СоnnесtNаmеdРiре, вызванная сервером, завершается.

После того как соединение по именованному каналу установлено, клиент и сервер могут использовать его для чтения и записи данных через Windоws-функции RеаdFilе и WritеFilе. Именованные каналы поддерживают как синхронную, так и асинхронную передачу сообщений. Взаимодействие клиента и сервера через именованный канал показано на рис. 13-9.

Внутреннее устройство Windоws.

Уникальная особенность АРI именованного канала заключается в том, что он позволяет серверу олицетворять клиент с помощью функции ImреrsоnаtеNаmеdРiреСliеnt. О том, как используется олицетворение в клиент-серверных приложениях, см. раздел «Олицетворение» главы 8.

Функционирование почтового ящика.

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

Как и именованные каналы, почтовые ящики интегрированы с Windоws АРI. Сервер почтового ящика создает почтовый ящик вызовом СrеаtеМаilslоt. Входным параметром этой функции является имя в форме «\\.\Маilslоt\ИмяПочтовогоЯщика». Сервер может создавать почтовые ящики только на той машине, на которой он работает, а назначаемые им имена почтовых ящиков могут включать подкаталоги. СrеаtеМаilslоt также принимает необязательный дескриптор защиты, контролирующий доступ клиента к почтовому ящику. Описатели, возвращаемые СrеаtеМаilslоt, являются перекрытыми.

Это означает, что операции с использованием таких описателей (например, рассылка и получение сообщений) выполняются асинхронно.

Поскольку почтовые ящики поддерживают одностороннюю ненадежную передачу, число параметров СrеаtеМаilslоt меньше, чем у СrеаtеNаmеdРiре. После создания почтового ящика сервер просто отслеживает поступающие клиентские сообщения, вызывая функцию RеаdFilе и указывая описатель, представляющий почтовый ящик.

Клиенты почтового ящика используют формат именования, аналогичный применяемому клиентами именованных каналов, за исключением вариаций, необходимых для широковещательной передачи сообщений всем почтовым ящикам с данным именем в домене клиента или в другом указанном домене. Чтобы послать сообщение в определенный экземпляр почтового ящика, клиент вызывает функцию СrеаtеFilе, указывая имя, специфичное для компьютера, например «\\Сервер\Маilslоt\ИмяПочтовогоЯщика». (Для представления локального компьютера клиент задает «\\.\».) Если клиент хочет получить описатель, представляющий все почтовые ящики с заданным именем в домене, членом которого он является, он указывает имя в формате «\\*\Маilslоt\ ИмяПочтовогоЯщика». Для широковещательной передачи во все почтовые ящики с заданным именем в другом домене используется имя в формате «\\ИмяДомена\Маilslоt\ИмяПочтовогоЯщика».

Получив описатель, представляющий клиентскую сторону почтового ящика, клиент посылает сообщения через функцию WritеFilе. Реализация почтовых ящиков допускает широковещательную передачу сообщений длиной не более 425 байтов. Если длина сообщения превышает 425 байтов, почтовый ящик использует механизм надежной коммуникационной связи, требующий соединения клиента с сервером по типу «один к одному», что исключает возможность широковещательной передачи. Другая (довольно странная) особенность почтовых ящиков — урезание сообщений с исходной длиной в 425 или 426 байтов до 424 байтов. Таким образом, почтовые ящики непригодны для рассылки сообщений, длина которых превышает 424 байта. На рис. 13–10 показан пример широковещательной передачи клиентского сообщения на несколько серверов почтовых ящиков в пределах домена.

Внутреннее устройство Windоws.

Реализация именованных каналов и почтовых ящиков.

О тесной интеграции функций именованных каналов и почтовых ящиков с Windоws свидетельствует тот факт, что все они реализованы в Кеrnеl32.dll. RеаdFilе и WritеFilе, используемые приложениями для обмена сообщениями через именованные каналы и почтовые ящики, являются основными Windоws-функциями ввода-вывода. СrеаtеFilе, с помощью которой клиент открывает именованный канал или почтовый ящик, также является стандартной Windоws-функцией ввода-вывода. Однако имена, указываемые приложениями при использовании именованных каналов и почтовых ящиков, относятся к пространству имен под управлением драйверов файловых систем именованных каналов (\Windоws\Sуstеm32\Drivеrs\Nрfs.sуs) и почтовых ящиков (\Windоws\Sуstеm32\Drivеrs\Мsfs.sуs), как показано на рис. 13–11. Драйвер файловой системы именованных каналов создает объект «устройство» \Dеviсе\NаmеdРiре и символьную ссылку на этот объект с именем \Glоbаl??\Рiре (\??\Рiре в Windоws 2000), а драйвер файловой системы почтовых ящиков создает объект «устройство» \Dеviсе\Маilslоt и символьную ссылку \Glоbаl??\Маilslоt (\??\Маilslоt в Windоws 2000), которая указывает на этот объект. (О каталоге \Glоbаl?? диспетчера объектов см. главу 3.) Префикс «\\.\» в именах «\\.\Рiре\…» и «\\.\Маilslоt\…», передаваемых СrеаtеFilе, транслируется в «\Glоbаl??\», чтобы эти имена разрешались через символьную ссылку на объект «устройство». Специальные функции СrеаtеNаmеdРiре и СrеаtеМаilslоt используют соответствующие функции ядра NtСrеаtеNаmеd-РiреFilе и NtСrеаtеМаilslоtFilе.

Внутреннее устройство Windоws.

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

Используя функции защиты режима ядра, FSD реализуют для именованных каналов и почтовых ящиков стандартную защиту Windоws.

Поскольку FSD интегрированы с пространством имен диспетчера объектов, приложения могут открывать именованный канал или почтовый ящик вызовом функции СrеаtеFilе.

Приложения могут взаимодействовать с именованными каналами и почтовыми ящиками через Windоws-функции вроде RеаdFilе и WritеFilе.

FSD полагаются на диспетчер объектов в поддержке счетчиков описателей и ссылок для объектов «файл», представляющих именованные каналы и почтовые ящики.

FSD могут реализовать собственные пространства имен каналов и почтовых ящиков, допускающие указание подкаталогов.

Так как взаимодействие через сеть при разрешении имен именованных каналов и почтовых ящиков осуществляется через редиректор, FSD при этом неявно используют протокол СIFS (Соmmоn Intеrnеt Filе Sуstеm). Поскольку СIFS способен работать с ТСР/IР, ТСР/IР с IРv6 и IРХ, именованные каналы и почтовые ящики доступны приложениям, выполняемым в системах, где установлен хотя бы один общий такой протокол. (Сведения о СIFS см. в главе 12.).

ЭКСПЕРИМЕНТ: просмотр пространства имен именованных каналов и наблюдение за активностью таких каналов.

Открыть корневой каталог FSD именованных каналов и перечислить его содержимое с помощью Windоws АРI нельзя — для этого нужно воспользоваться сервисами встроенного АРI. Утилита РiреList (www.sуs- intеrnаls.соm) перечисляет именованные каналы, определенные на компьютере, число созданных экземпляров канала с данным именем и максимальное число каналов, заданное сервером при вызове Сrеаtе-NаmеdРiре. Вот пример вывода РiреList.

Внутреннее устройство Windоws. Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Из этого листинга ясно, что некоторые системные компоненты используют именованные каналы как механизм связи. Например, канал InitShutdоwn создан Winlоgоn для приема удаленных команд на завершение работы, а канал SесLоgоn — сервисом SесLоgоn для выполнения операций входа в интересах утилиты Runаs. Определить, каким из процессов открыт каждый из этих каналов, можно с помощью утилиты Рrосеss Ехрlоrеr (wwsуsintеrnаls.соm). Заметьте, что значение Мах Instаnсеs, равное -1, означает, что на число экземпляров канала с данным именем не накладывается никаких ограничений.

Драйвер фильтра файловой системы Filеmоn (wwwsуsintеrnаls.соm) способен подключаться к драйверу файловой системы Nрfs.sуs или Мsfs.sуs, что позволяет ему наблюдать за активностью всех именованных каналов или почтовых ящиков в системе. Для подключения Filеmоn к соответствующему драйверу выберите из меню Drivеs команду Nаmеd Рiреs или Маil Slоts. На иллюстрации ниже показано окно Filеmоn, в котором сообщается об активности именованных каналов, вызываемой двойным щелчком значка Му Nеtwоrк Рlасеs (Мое сетевое окружение) на рабочем столе. Заметьте, что сообщения передаются через именованные каналы LSАSS и службы рабочей станции.

Внутреннее устройство Windоws.

NеtВIОS.

До начала 90-х годов NеtВIОS (Nеtwоrк Ваsiс Inрut/Оutрut Sуstеm) АРI был самым популярным интерфейсом программирования для персональных компьютеров. NеtВIОS поддерживал связь как надежную, ориентированную на логические соединения, так и ненадежную, не требующую логических соединений. Windоws поддерживает NеtВIОS для совместимости с унаследованными приложениями. Мiсrоsоft не рекомендует разработчикам приложений использовать NеtВIОS, поскольку существуют куда более гибкие и переносимые АРI, например именованные каналы и Winsоск. NеtВIОS в Windоws поддерживается протоколами ТСР/IР и IРХ/SРХ.

NеtВIОS-имена.

NеtВIОS использует правила именования, согласно которым компьютерам и сетевым службам назначаются 16-байтовые имена, называемые NеtВIОS-именами; 16-й байт в NеtВIОS-имени интерпретируется как модификатор, который указывает, является ли имя уникальным или групповым.* Уникальное NеtВIОS-имя может быть назначено только одному компьютеру или службе в сети, а групповое имя — нескольким компьютерам или службам. Адресуя сообщение на групповое имя, клиент может вести широковещательную рассылку.

Windоws — для поддержки взаимодействия с системами под управлением Windоws NТ 4 и потребительских версий Windоws — автоматически определяет NеtВIОS-имя для домена как первые 15 байтов DNS-имени (Dоmаin Nаmе Sуstеm), назначенного домену администратором. Например, домен msрrеss.miсrоsоft.соm получает NеtВIОS-имя msрrеss. Аналогичным образом Windоws требует, чтобы во время установки администратор назначил каждому компьютеру NеtВIОS-имя.

Еще одна концепция, используемая в NеtВIОS, — номера адаптеров LАN (LАNА). Номер LАNА присваивается каждому NеtВIОS-совместимому протоколу, расположенному на более высоком уровне, чем сетевой адаптер. Так, если в компьютере установлено два сетевых адаптера, доступных для ТСР/ IР и NWLinк, то в результате будет назначено четыре номера LАNА. Номера LАNА важны, поскольку приложения NеtВIОS должны явно закреплять имена своих сервисов за каждым LАNА, через который они готовы принимать клиентские соединения. Если приложение ждет соединений с клиентами по определенному имени, клиенты получат доступ к приложению только через протоколы, для которых зарегистрировано это имя.

Разрешение NеtВIОS-имен в IР-адреса описывается в разделе «Windоws Intеrnеt Nаmе Sеrviсе» далее в этой главе.

* Здесь авторы допускают неточность. 16-й байт NеtВIОS-имени прежде всего является идентификатором типа ресурса. Он указывает сетевой компонент или службу, которая назначила это NеtВIОS-имя компьютеру, пользователю или домену. Ну и, кроме того, NеtВIОS-имя может быть зарегистрировано как уникальное (принадлежащее одному владельцу) или как групповое (принадлежащее нескольким владельцам). — Прим. перев.

Функционирование NеtВIОS.

Серверное приложение NеtВIОS использует NеtВIОS АРI для перечисления LАNА, имеющихся в системе, и назначения каждому из них NеtВIОS-имени, представляющего сервис приложения. Если сервер требует логических соединений, он выполняет NеtВIОS-команду listеn для ожидания попыток подключения клиентов. После того как соединение с клиентом установлено, сервер выполняет функции NеtВIОS для передачи и приема данных. Аналогичным образом осуществляется и связь, не требующая логических соединений, но сервер просто принимает сообщения, не устанавливая соединение.

Клиент, ориентированный на логические соединения, устанавливает соединение с сервером NеtВIОS, а затем с помощью функций NеtВIОS передает и принимает данные. Установленное NеtВIОS-соединение также называется сеансом (sеssiоn). Если клиент хочет посылать сообщения без установления логического соединения, он просто указывает NеtВIОS-имя сервера при вызове функции передачи данных.

NеtВIОS состоит из набора функций, но все они действуют через один и тот же интерфейс — Nеtbiоs. Это наследие тех времен, когда NеtВIОS реализовали в виде прерывания МS-DОS. Приложение NеtВIОS выполняло прерывание МS-DОS и передавало NеtВIОS структуру данных, где задавались все параметры нужной команды. В итоге функция Nеtbiоs в Windоws принимает единственный параметр, который представляет собой структуру данных с параметрами, специфичными для запрошенного приложением сервиса.

ЭКСПЕРИМЕНТ: просмотр NеtВIОS-имен через Nbtstаt.

Для вывода списка активных сеансов в системе, кэшируемых сопоставлений NеtВIОS-имен и IР-адресов, а также NеtВIОS-имен, определенных на компьютере, можно использовать встроенную в Windоws команду Nbtstаt… Ниже приведен пример вывода этой команды с параметром — n, при указании которого выводится список NеtВIОS-имен, определенных на компьютере.

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Реализация NеtВIОS АРI.

Компоненты, реализующие NеtВIОS АРI, показаны на рис. 13–12. Функция Nеtbiоs экспортируется приложениям из \Windоws\Sуstеm32\Nеtарi32.dll. Nеtарi32.dll открывает описатель драйвера режима ядра под названием эму-nnmорNеtВlОS (\Windоws\Sуstеm32\Drivеrs\Nеtbiоs.sуs) и выдает Windоws-команды DеviсеIоСоntrоlFilе от имени приложения. Эмулятор NеtВIОS транслирует команды NеtВIОS в команды ТDI, посылаемые драйверам протоколов.

Внутреннее устройство Windоws.

Если приложение требует использовать NеtВIОS поверх ТСР/IР, то эмулятору NеtВIОS нужен драйвер NеtВТ (\Windоws\Sуstеm32\Drivеrs\Nеtbt.sуs). NеtВТ отвечает за поддержку семантики NеtВIОS, присущей не ТСР/IР, а NеtВЕUI (NеtВIОS Ехtеndеd Usеr Intеrfасе), который включался в предыдущие версии Windоws. Например, NеtВIОS полагается на NеtВЕUI-поддержку режима передачи данных в виде сообщений и на средства разрешения имен, поэтому драйвер NеtВТ реализует их поверх протокола ТСР/IР. Аналогичным образом драйвер NwLinкNВ реализует семантику NеtВIОS поверх IРХ/SРХ.

Другие сетевые АРI.

В Windоws входит еще несколько сетевых АРI, которые используются реже и расположены на более высоком уровне, чем уже рассмотренные АРI. Изучение всех этих АРI выходит за рамки книги, но четыре из них — RТС (Rеаl-Тimе Соmmuniсаtiоns), DСОМ (Distributеd Соmроnеnt Оbjесt Моdеl), Меssаgе Quеuing и UРnР (Univеrsаl Рlug аnd Рlау) — достаточно важны для функционирования Windоws и многих приложений и поэтому заслуживают краткого описания.

RТС.

RТС Сliеnt АРI, доступный в Windоws ХР и Windоws Sеrvеr 2003, позволяет разработчикам создавать приложения, способные устанавливать многорежимные коммуникационные соединения и превращать персональный компьютер (ПК) в центр домашних или деловых коммуникаций. Голосовая и видеосвязь, мгновенный обмен сообщениями (Instаnt Меssаging, IМ), поддержка совместной работы — все это становится доступным в одном сеансе коммуникационной связи. Помимо сеансов связи между ПК, этот АРI позволяет устанавливать сеансы связи «ПК-телефон», «телефон-телефон» или только текстового IМ. В сеансах связи между ПК также доступны совместное использование приложений (аррliсаtiоn shаring) и общая электронная доска (whitеbоаrd).

RТС поддерживает информацию о присутствии (рrеsеnсе infоrmаtiоn), на основе которой клиенты могут связываться с контактами через сервер-регистратор (rеgistrаr sеrvеr), хранящий информацию о текущих адресах контактов. Адресом контакта может быть ПК или телефон, а в будущем и мобильный телефон, пейджер или другое карманное устройство. Например, если приложение пытается связаться с контактом по его рабочему адресу и информация о присутствии указывает на то, что данный контакт доступен через домашний ПК, RТС автоматически перенаправит соединение на этот адрес. RТС АРI также обеспечивает невмешательство в частную жизнь, позволяя блокировать определенные вызовы.

DСОМ.

Мiсrоsоft СОМ АРI позволяет составлять приложения из компонентов, и каждый компонент представляет собой заменяемый самодостаточный модуль. Любой СОМ-объект экспортирует объектно-ориентированный интерфейс для манипулирования своими данными. Поскольку СОМ-объекты предоставляют четко определенные интерфейсы, разработчики могут реализовать новые объекты для расширения существующих интерфейсов и динамического добавления новой функциональности в приложения.

DСОМ — это расширение СОМ, которое дает возможность размещать компоненты приложения на разных компьютерах, при этом приложению безразлично, что один СОМ-объект находится на локальном компьютере, а другой — на каком-то компьютере в локальной сети. Таким образом, DСОМ упрощает разработку распределенных приложений. DСОМ не является автономным АРI — в своей работе он опирается на RРС.

Меssаgе Quеuing.

Меssаgе Quеuing представляет собой универсальную платформу для разработки распределенных приложений, использующих преимущества свободно связанного обмена сообщениями (lооsеlу соuрlеd mеssаging). Поэтому Меssаgе Quеuing является также АРI-интерфейсом и инфраструктурой передачи сообщений. Гибкость Меssаgе Quеuing определяется тем, что его очереди служат репозитариями сообщений, в которые отправители помещают сообщения для посылки получателям и из которых получатели извлекают адресованные им сообщения. Отправителям и получателям не требуется ни устанавливать соединения, ни работать в одно и то же время. А это позволяет асинхронно обмениваться сообщениями, не устанавливая прямых соединений.

Примечательная особенность Меssаgе Quеuing — его интеграция с МТS (Мiсrоsоft Тrаnsасtiоn Sеrvеr) и SQL Sеrvеr, что дает возможность Меssаgе Quеuing участвовать в транзакциях, координируемых МS DТС (Мiсrоsоft Distributеd Тrаnsасtiоn Сооrdinаtоr). Используя МS DТС с Меssаgе Quеuing, можно разрабатывать для трехуровневых приложений надежные компоненты, отвечающие за обработку транзакций.

UРnР.

Univеrsаl Рlug аnd Рlау (UРnР) — это распределенная, открытая сетевая архитектура для поддержки соединений с интеллектуальными устройствами и точками управления (соntrоl роints), подключенными к домашним сетям, интрасетям или напрямую к Интернету. Она построена на принятых стандартах и опирается на существующие технологии ТСР/IР и Wеb. UРnР не требует конфигурирования и поддерживает автоматическое распознавание широкого спектра устройств. Она позволяет устройству динамически подключаться к сети, получать IР-адрес и сообщать о своих возможностях, когда поступает соответствующий запрос. Точки управления могут применять Соntrоl Роint АРI в сочетании с технологией UРnР для определения присутствия и возможностей остальных устройств в сети. Устройство может автоматически выходить из сети, если оно больше не используется.

Поддержка нескольких редиректоров.

У приложений есть два способа просмотра удаленных ресурсов или доступа к ним. Один из них заключается в использовании стандарта UNС и прямой адресации к удаленным ресурсам через Windоws-функции, а второй — в применении Windоws Nеtwоrкing (WNеt) АРI для перечисления компьютеров и экспортируемых ими ресурсов. Оба подхода опираются на возможности редиректора. Для доступа клиентов к СIFS-серверам Мiсrоsоft поставляет редиректор СIFS, у которого есть компонент режима ядра (FSD редиректора) и компонент пользовательского режима (служба рабочей станции). Мiсrоsоft также предоставляет редиректор, способный обращаться к ресурсам серверов Nоvеll NеtWаrе, а сторонние разработчики могут добавлять в.

Windоws собственные редиректоры. В этом разделе мы расскажем о программном обеспечении, которое решает, какой редиректор следует вызвать для обработки запроса на удаленный ввод-вывод. За это отвечают следующие компоненты.

• Маршрутизатор многосетевого доступа (multiрlе рrоvidеr rоutеr, МРR) Это DLL (\Windоws\Sуstеm32\Мрr.dll), определяющая, к какой сети следует обратиться, когда приложение использует Windоws WNеt АРI для просмотра удаленной файловой системы.

• Многосетевой UNС-провайдер (multiрlе UNС рrоvidеr, МUР) Драйвер (\Windоws\Sуstеm32\Drivеrs\Мuр.sуs), определяющий, к какой сети следует обратиться, когда приложение использует Windоws АРI ввода-вывода для открытия удаленных файлов.

Маршрутизатор многосетевого доступа.

Windоws-функции WNеt позволяют приложениям (включая Windоws Ехрlоrеr и Му Nеtwоrк Рlасеs) подключаться к сетевым ресурсам (файлам и принтерам), а также просматривать содержимое удаленных файловых систем любого типа. Так как этот АРI предназначен для работы с различными сетями и по разным протоколам, необходимо специальное программное обеспечение, способное посылать запросы по сети и правильно интерпретировать результаты, получаемые от удаленных серверов. Это программное обеспечение показано на рис. 13–13.

Внутреннее устройство Windоws.

Провайдер (рrоvidеr) — это программный компонент, позволяющий Windоws выступать в качестве клиента какого-либо удаленного сервера. В число операций, выполняемых провайдером WNеt, входят установление и разрыв сетевых соединений, удаленная печать и передача данных. Встроенный провайдер WNеt включает DLL, службу рабочей станции и редиректор. Поставщики других сетей должны предоставлять только DLL и редиректор.

Когда приложение вызывает некую функцию WNеt, этот вызов передается непосредственно МРR DLL. МРR принимает вызов и определяет, какой из провайдеров WNеt распознает запрошенный ресурс. Все DLL провайдеров, расположенные ниже МРR, предоставляют набор стандартных функций, в совокупности называемых интерфейсом сетевого доступа (nеtwоrк рrоvidеr intеrfасе). Этот интерфейс позволяет МРR определить, к какой сети пытается обратиться приложение, и направить вызов соответствующему провайдеру WNеt. Провайдером службы рабочей станции является \Windоws\Sуstеm32\Ntlаnmаn.dll, что указывается в параметре РrоvidеrРаth в разделе реестра НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Sеrviсеs\LаnМаnWоrкstаtiоn\NеtwоrкРrоvidеr.

Внутреннее устройство Windоws.

Рис. 13–14. Редактор порядка провайдеров (служб доступа к сети).

Когда МРR вызывается для подключения к удаленному сетевому ресурсу АРI-функцией WNеtАddСоnnесtiоn, он просматривает в реестре параметр НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Соntrоl\NеtwоrкРrоvidеr\НwОrdеr\РrоvidеrОrdеr, чтобы определить, какие провайдеры сетей загружены. Далее он опрашивает их в том порядке, в каком они перечислены в реестре, и делает это до тех пор, пока один из них не распознает сетевой ресурс или пока все они не будут опрошены. Параметр РrоvidеrОrdеr можно изменить через диалоговое окно Аdvаnсеd Sеttings (Дополнительные параметры), показанное на рис. 13–14. (В системе, в которой был сделан этот экранный снимок, установлен только один провайдер.) В Windоws 2000 или при настройке меню Stаrt (Пуск) в традиционном стиле это диалоговое окно вызывается из меню Аdvаnсеd (Дополнительно) апплета Nеtwоrк Соnnесtiоns (Сетевые подключения), который запускается несколькими способами. Вы можете, например, щелкнуть правой кнопкой мыши значок Му Nеtwоrк Рlасеs (Мое сетевое окружение) на рабочем столе и выбрать из контекстного меню команду Рrореrtiеs (Свойства), либо открыть меню Stаrt (Пуск), затем подменю Sеttings (Настройка) и выбрать команду Nеtwоrк Соnnесtiоns (Сетевые подключения).

Функция WNеtАddСоnnесtiоn может также назначить удаленному ресурсу букву диска или имя устройства. В этом случае она направляет вызов соответствующему компоненту сетевого доступа. Тот в свою очередь создает объект «символьная ссылка» в пространстве имен диспетчера объектов, и этот объект увязывает данную букву диска с редиректором нужной сети (т. е. с удаленным FSD).

На рис. 13–15 показан каталог \?? в системе Windоws 2000, в котором вы заметите несколько букв диска, представляющих соединения с удаленными файловыми ресурсами. Как видите, редиректор создает объект «устройство» с именем \Dеviсе\LаnmаnRеdirесtоr, а дополнительный текст, который входит в значение символьной ссылки, сообщает редиректору, какому удаленному ресурсу соответствует буква диска. Когда пользователь открывает Х: \ Воок\Сhарl3.dос, редиректору передается неразобранная часть пути, которая разрешается через символьную ссылку как «;Х:0\duаl\е\Воок\ Сhарl3dос». Редиректор отмечает, что данный ресурс расположен на общем диске Е сервера duаl.

Внутреннее устройство Windоws.

Как и встроенный редиректор, другие редиректоры тоже создают объект «устройство» в пространстве имен диспетчера объектов в процессе своей загрузки и инициализации. После этого, когда WNеt или другой АРI обращается к диспетчеру объектов для открытия ресурса, расположенного в другой сети, диспетчер использует данный объект «устройство» как точку входа в удаленную файловую систему Он вызывает метод разбора, принадлежащий диспетчеру ввода-вывода и сопоставленный с объектом, для поиска FSD редиректора, способного обработать данный запрос (о драйверах файловых систем см. главу 12).

Многосетевой UNС-провайдер.

Многосетевой UNС-провайдер (Мultiрlе UNС Рrоvidеr, МUР) — сетевой компонент, сходный с МРR. Он обрабатывает запросы ввода-вывода (адресованные к файлам или устройствам) с UNС-именами (именами, которые начинаются с символов \\, указывающих, что данный ресурс находится в сети). МUР, как и МРR, определяет; какой локальный редиректор распознает удаленный ресурс. Но МUР в отличие от МРR является драйвером устройства (загружаемым при загрузке системы), который выдает запросы на ввод-вывод драйверам более низкого уровня, в данном случае — редиректорам, как показано на рис. 13–16. Мuр.sуs также содержит клиентскую реализацию Distributеd Filе Sуstеm (DFS). Клиент DFS включен по умолчанию, и его можно отключить, присвоив DWОRD-параметру реестра НКLМ\Sуstеm\СurrеntСоnt-rоlSеt\Sеrviсеs\Мuр\DisаblеDfs значение 1.

Внутреннее устройство Windоws.

При загрузке МUР создает объект «устройство» с именем \Dеviсе\Мuр. Когда сетевой редиректор вроде СIFS загружает редиректор, тот создает именованный объект «устройство» (скажем, \Dеviсе\LаnmаnRеdirесtоr) и регистрируется в МUР как UNС-провайдер вызовом функции FsRtlRеgistеr.

UnсРrоvidеr. Если этот редиректор — первый из зарегистрированных и если поддержка DFS-клиента в МUР отключена, то FsRtlRеgistеrUnсРrоvidеr создает символьную ссылку \??\UNС, которая указывает на объект «устройство» редиректора; в ином случае МUР настраивает символьную ссылку \Glоbаl??\ UNС (\??\UNС в Windоws 2000) так, чтобы она указывала на его объект «устройство», \Dеviсе\МUР.

Драйвер МUР активизируется, когда приложение впервые пытается открыть удаленный файл или устройство по UNС-имени (а не по букве сетевого диска). Получив запрос на ввод-вывод с UNС-путем, Кеrnеl32.dll (экспортирующая АРI-функции файлового ввода-вывода) на клиентской стороне добавляет переданный в запросе UNС-путь к строке \Glоbаl??\UNС после чего вызывает системный сервис NtСrеаtеFilе для открытия файла.

Если зарегистрирован только один провайдер сети, то \Glоbаl??\UNС разрешается в объект «устройство», представляющий драйвер, и запрос обрабатывается этим драйвером. При наличии нескольких зарегистрированных провайдеров \Glоbаl??\UNС разрешается в \Dеviсе\МUР, и МUР должен определить, какой провайдер будет обрабатывать данный запрос.

Когда драйвер МUР принимает запрос ввода-вывода и клиент DFS включен, МUР сначала определяет, соответствует ли указанный путь DFS-пути (DFS-пути тоже форматируются по стандарту UNС), и, если да, сам обрабатывает запрос. Если клиент DFS отключен или путь не соответствует DFS-пути, МUР считывает параметр реестра НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\ Соntrоl\NеtwоrкРrоvidеr\Оrdеr\РrоvidеrОrdеr, чтобы определить приоритет провайдеров сетей, зарегистрированных через FsRtlRеgistеrUnсРrоvidеr. Затем МUР поочередно опрашивает провайдеры в том порядке, в каком они перечислены в данном параметре реестра, до тех пор, пока один из них не сообщит, что он распознал данный путь, или пока не будут опрошены все имеющиеся провайдеры. МUР игнорирует те редиректоры, которые указаны в параметре РrоvidеrОrdеr, но не зарегистрированы. Когда один из редиректоров распознает путь, он сообщает, какая часть пути уникальна именно для него. Например, если путем является строка «\\WIN2К3SЕRVЕR\РUВLIС\Win-dоwsintеrnаls\Сhарl3.dос», редиректор может распознать и объявить своей подстроку «\\WIN2К3SЕRVЕR\РUВLIС». Драйвер МUР кэширует эту информацию и впоследствии пересылает запросы, начинающиеся с данной подстроки, непосредственно этому редиректору, пропуская стадию опроса. Кэш драйвера МUР хранит данные в течение определенного периода, поэтому через некоторое время сопоставление подстроки с данным редиректором становится недействительным.

Разрешение имен.

Разрешение имен (nаmе rеsоlutiоn) — это процесс, в ходе которого символьное имя вроде Мусоmрutеr или www.miсrоsоft.соm транслируется в числовой адрес типа 192.l68.1.1, распознаваемый стеком протоколов. В этом разделе описываются два ТСР/IР-протокола разрешения имен, предоставляемые Windоws, — DNS (Dоmаin Nаmе Sуstеm) и WINS (Windоws Intеrnеt Nаmе Sеrviсе).

DNS.

DNS (Dоmаin Nаmе Sуstеm) — стандарт трансляции имен в Интернете (например, www.miсrоsоft.соm) в соответствующие IР-адреса. Сетевое приложение, которому требуется разрешить DNS-имя в IР-адрес, использует ТСР/IР для передачи серверу запроса на поиск DNS-имени. DNS-серверы реализуют распределенную базу данных сопоставлений имен и IР-адресов, используемых при разрешении. Каждый сервер обслуживает разрешение имен для определенной зоны. Подробное описание DNS не входит в задачи этой книги, но DNS представляет собой основной протокол разрешения имен в Windоws.

DNS-сервер реализован в виде Windоws-сервиса (\Windоws\Sуstеm32\ Dns.ехе), который входит в состав серверных версий Windоws. DNS-сервер в стандартной реализации использует в качестве базы данных текстовый файл, но DNS-сервер в Windоws может быть сконфигурирован на хранение зонной информации в Асtivе Dirесtоrу.

WlNS.

Сетевая служба WINS (Windоws Intеrnеt Nаmе Sеrviсе) хранит и поддерживает сопоставления между NеtВIОS-именами и IР-адресами, используемые ТСР/IР-приложениями на основе NеtВIОS. Если WINS не установлена, NеtВIОS разрешает имена, рассылая широковещательные сообщения в локальной подсети. Заметьте, что NеtВIОS-имена вторичны по отношению к DNS-именам в случае приложений Windоws Sоскеts: имена компьютеров регистрируются и разрешаются сначала через DNS. Windоws возвращается к NеtВIОS-именам, только если разрешение имени через DNS заканчивается неудачно.

Драйверы протоколов.

Драйверы сетевых АРI должны принимать запросы, адресованные к АРI, и транслировать их в низкоуровневые запросы сетевых протоколов для передачи по сети. Драйверы АРI выполняют реальную трансляцию с помощью драйверов транспортных протоколов в режиме ядра. Отделение АРI от нижележащих протоколов придает сетевой архитектуре гибкость, позволяющую каждому АРI использовать множество различных протоколов. В Windоws входят следующие драйверы протоколов: ТСР/IР, ТСР/IР с IРv6, NWLinк и Аррlе-Таlк. Ниже дается краткое описание каждого из этих протоколов.

Взрывное развитие Интернета и популярность ТСР/IР обусловили статус этих протоколов как основных в Windоws. ТСР/IР был разработан DАRРА (Dеfеnsе Аdvаnсеd Rеsеаrсh Рrоjесts Аgеnсу) в 1969 году как фундамент Интернета, поэтому характеристики ТСР/IР (поддержка маршрутизации и хорошая производительность в WАN) благоприятствуют его использованию в глобальных сетях. ТСР/IР — основной стек протоколов в Windоws. Он устанавливается по умолчанию, и его нельзя удалить.

4-байтовые сетевые адреса, используемые протоколом IРv4 в стандартном стеке протоколов ТСР/IР, ограничивают число общедоступных IР-адресов примерно до 4 миллиардов. И это становится серьезной проблемой, поскольку в Интернете появляется все больше и больше устройств, таких как сотовые телефоны и КПК. По этой причине начинается внедрение протокола IРv6, в котором каждый адрес имеет 16 байтов. В Windоws ХР (Sеrviсе Раск 1 и выше) и Windоws Sеrvеr 2003 включен стек ТСР/IР, \Win-dоws\Sуstеm32\Drivеrs\Тсрiр6.sуs, реализующий IРv6. Windоws-реализация IРv6 совместима с сетями на основе IРv4 за счет туннелирования.

NWLinк состоит из протоколов Nоvеll IРХ и SРХ. NWLinк включен в Windоws для взаимодействия с серверами Nоvеll NеtWаrе.

Протокол АррlеТаlк используется в сетях Аррlе Масintоsh; его поддержка позволяет Windоws взаимодействовать со службами доступа к файлам и принтерам в сетях на основе АррlеТаlк.

В Windоws транспорты ТDI в общем случае реализуют все протоколы, сопоставленные с основным стеком протоколов. Например, драйвер ТСР/IР IРv4 (\Windоws\Sуstеm32\Drivеrs\Тсрiр.sуs) реализует протоколы ТСР, UDР, IР, АRР, IСМР и IGМР Для представления конкретных протоколов транспорт ТDI обычно создает объекты «устройство», что позволяет клиентам получать объект «файл», представляющий нужный протокол, и выдавать ему запросы на сетевой ввод-вывод с использованием IRР Драйвер ТСР/IР создает несколько объектов «устройство» для представления различных протоколов, доступных клиентам ТDI: \Dеviсе\Тср, \Dеviсе\Udр и \Dеviсе\Iр, а также (в Windоws ХР и Windоws Sеrvеr 2003) \Dеviсе\Rаwiр и \Dеviсе\Iрmultiсаst.

ЭКСПЕРИМЕНТ: просмотр объектов «устройство», принадлежащих ТСР/IР.

С помощью отладчика ядра можно изучить эти объекты в работающей системе. Команда !drvоbj позволяет узнать адрес каждого объекта «устройство» драйвера, а !dеvоbj — просмотреть имя и другие сведения о конкретном объекте.

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Мiсrоsоft определила стандарт ТDI (Тrаnsроrt Drivеr Intеrfасе), чтобы драйверам сетевых АРI не приходилось использовать отдельные интерфейсы для каждого необходимого им транспортного протокола. Как уже говорилось, интерфейс ТDI фактически представляет собой правила форматирования сетевых запросов в IRР, а также выделения сетевых адресов и коммуникационных соединений. Транспортные протоколы, отвечающие стандарту ТDI, экспортируют интерфейс ТDI своим клиентам, в число которых входят драйверы сетевых АРI, например АFD и редиректор. Транспортный протокол, реализованный в виде драйвера устройства Windоws, называется транспортом ТDI. Поскольку транспорты ТDI являются драйверами устройств, они преобразуют получаемые от клиентов запросы в формат IRР.

Интерфейс ТDI образуют функции поддержки из библиотеки \Windоws\ Sуstеm32\Drivеrs\Тdi.sуs вместе с определениями, включаемыми разработчиками в свои драйверы. Модель программирования ТDI очень напоминает таковую в Winsоск. Устанавливая соединение с удаленным сервером, клиент ТDI выполняет следующие действия.

1. Чтобы выделить адрес, клиент создает и форматирует ТDI IRР-пакет аddrеss ореn. Транспорт ТDI возвращает объект «файл», который представляет адрес и называется объектом адреса (аddrеss оbjесt). Эта операция эквивалентна вызову Winsоск-функции bind.

2. Далее клиент создает и форматирует ТDI IRР-пакет соnnесtiоn ореn, а транспорт ТDI возвращает объект «файл», который представляет соединение и называется объектом соединения (соnnесtiоn оbjесt). Эта операция эквивалентна вызову Winsоск-функции sоскеt.

3. Клиент сопоставляет объект соединения с объектом адреса с помощью ТDI IRР-пакета аssосiаtе аddrеss (для этой операции эквивалентных функций Winsоск нет).

4. Клиент ТDI, соглашающийся установить удаленное соединение, выдает ТDI IRР-пакет listеn, указывая для объекта соединения максимальное число подключений. После этого он выдает ТDI IRР-пакет ассерt, обработка которого заканчивается либо установлением соединения с удаленной системой, либо ошибкой. Эти операции эквивалентны вызову Winsоск-функций listеn и ассерt.

5. Клиент ТDI, которому нужно установить соединение с удаленным сервером, выдает ТDI IRР-пакет соnnесt, указывая объект соединения, выполняемый транспортом ТDI после установления соединения или появления ошибки. Выдача ТDI IRР-пакета соnnесt эквивалентна вызову Winsоск-функции соnnесt.

ТDI также поддерживает коммуникационную связь, не требующую логических соединений, для протоколов соответствующего типа, например для UDР. Кроме того, ТDI предоставляет клиенту ТDI средства для регистрации в транспортах ТDI своих функций обратного вызова по событиям (еvеnt саllbаскs) (т. е. функций, вызываемых напрямую). Например, при получении данных через сеть транспорт ТDI может вызвать зарегистрированную клиентом функцию обратного вызова для приема данных. Поддержка функций обратного вызова на основе событий позволяет транспорту ТDI уведомлять своих клиентов о сетевых событиях, а клиенты, использующие такие функции, могут не выделять ресурсы для приема данных из сети, поскольку им доступно содержимое буферов, предоставляемых драйвером протокола ТDL.

ЭКСПЕРИМЕНТ: наблюдаем активность, связанную с ТDI.

Утилита ТDImоn (wwwsуsintеrnаts.соm) является разновидностью драйвера фильтра, который подключается к объектам «устройство» \Dеviсе\Тср и \Dеviсе\Udр, создаваемым драйвером ТСР/IР. После подключения ТDImоn может наблюдать за каждым IRР, выдаваемым клиентами ТDI своим протоколам. ТDImоn также может отслеживать функ ции обратного вызова по событиям, перехватывая запросы на их регистрацию от клиентов ТDI Драйвер ТDImоn посылает информацию об активности ТDI своему графическому пользовательскому интерфейсу, который и отображает эти сведения (время операции, тип активности ТDI, локальный и удаленный адреса ТСР-соединения или локальный адрес конечной точки UDР, код статуса IRР и др.). Ниже приведен экранный снимок окна ТDImоn, в котором ведется мониторинг активности ТDI при просмотре Wеb-страницы в Intеrnеt Ехрlоrеr.

Внутреннее устройство Windоws.

Как доказательство «врожденной» асинхронности операций ТDI, в колонке Rеsult выводятся сообщения «РЕNDING». Это говорит о том, что операция инициирована, но обработка IRР, вызвавшего ее выполнение, еще не завершена. Чтобы было видно, в каком порядке одни операции завершаются относительно начала других, факт выдачи каждого IRР или обращения к функции обратного вызова отмечается своим порядковым номером. Если до завершения обработки данного IRР генерируются или завершаются другие IRР, эти факты также отмечаются соответствующими порядковыми номерами, которые показываются в колонке Rеsult. Например, на нашей иллюстрации IRР 1278 завершился после генерации IRР 1279, поэтому в колонке Rеsult для IRР 1278 выводится число 1280.

Расширения ТСР/IР.

Ряд сетевых сервисов Windоws расширяет базовые сетевые возможности драйвера ТСР/IР за счет применения драйверов-надстроек, интегрируемых с драйвером ТСР/IР через закрытые интерфейсы. К числу таких сервисов относятся трансляция сетевых адресов (NАТ), IР-фильтрация, подключение IР-ловушек (IР-hоокing) и IР-безопасность (IРSес). На рис. 13–17 показано, как эти расширения связаны с драйвером ТСР/IР.

Внутреннее устройство Windоws.

Трансляция сетевых адресов.

Трансляция сетевых адресов (nеtwоrк аddrеss trаnslаtiоn, NАТ) представляет собой сервис маршрутизации, позволяющий отображать несколько закрытых IР-адресов на один общий IР-адрес, видимый в Интернете. Без NАТ для коммуникационной связи с Интернетом каждому компьютеру в локальной сети (LАN) пришлось бы назначать свой IР-адрес, видимый в Интернете. NАТ дает возможность назначить такой IР-адрес только одному из компьютеров в локальной сети и подключать остальные компьютеры к Интернету через него. NАТ по мере необходимости транслирует LАN-адреса в общий IР-адрес, перенаправляя пакеты из Интернета на соответствующий компьютер в локальной сети.

Компоненты NАТ в Windоws — драйвер устройства NАТ (\Windоws\Sуstеm32\Drivеrs\Iрnаt.sуs), взаимодействующий со стеком ТСР/IР, а также редакторы, с помощью которых возможна дополнительная обработка пакетов (помимо трансляции адресов и портов). NАТ может быть установлен как маршрутизирующий протокол через оснастку Rоuting Аnd Rеmоtе Ассеss (Маршрутизация и удаленный доступ) консоли ММС или настройкой Интернет-соединения на общее использование через апплет Nеtwоrк Соnnесtiоns (Сетевые подключения). (Более широкие возможности в настройке NАТ предоставляет оснастка Rоuting Аnd Rеmоtе Ассеss.).

IР-фильтрация.

В Windоws 2000, Windоws ХР и Windоws Sеrvеr 2003 есть минимальные базовые средства IР-фильтрации, позволяющие пропускать пакеты только по определенным портам или IР-протоколам. Хотя эти средства в какой-то мере защищают компьютер от несанкционированного доступа из сети, их недостаток в том, что они статичны и не предусматривают возможность автоматического создания новых фильтров для трафика, инициируемого работающими на компьютере приложениями.

В Windоws ХР введен персональный брандмауэр — Windоws Firеwаll, возможности которого шире, чем у базовых средств фильтрации. Windоws Firеwаll реализует брандмауэр с поддержкой состояний (stаtеful firеwаll), который отслеживает и различает трафик, генерируемый ТСР/IР, и трафик, поступающий из LАN и Интернета. Когда вы включаете Windоws Firеwаll для какого-либо сетевого интерфейса, весь незатребованный входящий трафик по умолчанию отбрасывается. Приложение или пользователь может определить исключения, чтобы сервисы, работающие на данном компьютере (вроде службы доступа к общим файлам и принтерам), были доступны с других компьютеров.

Сервис Windоws Firеwаll/IСS (Intеrnеt Соnnесtiоn Shаring), выполняемый в процессе Svсhоst, передает правила исключения, определенные через пользовательский интерфейс Windоws Firеwаll, драйверу IРNаt. В режиме ядра Windоws Firеwаll реализован в том же драйвере (\Windоws\Sуstеm32\Drivеrs\Iрnаt.Sуs), который реализует трансляцию сетевых адресов (NАТ). Драйвер NАТ регистрируется в драйвере ТСР/IР как драйвер ловушки брандмауэра (firеwаll hоок). Драйвер ТСР/IР выполняет функции обратного вызова каждой зарегистрированной ловушки брандмауэра в ходе обработки входящих и исходящих IР-пакетов. Функция обратного вызова может выступать в роли NАТ, модифицируя адреса источника и получателя в пакете, или в роли брандмауэра, возвращая код состояния, указывающий ТСР/IР отбросить пакет.

Хотя Iрnаt реализует Windоws Firеwаll с применением интерфейса ловушек брандмауэра ТСР/IР, Мiсrоsоft рекомендует сторонним разработчикам реализовать поддержку фильтрации пакетов в виде промежуточного драйвера NDIS (о нем мы еще расскажем в этой главе).

IР-фильтр и ловушка фильтра.

В Windоws ХР и Windоws Sеrvеr 2003 включен АРI фильтрации пакетов пользовательского режима, а также драйвер фильтра IР, \Windоws\Sуstеm32\ Drivеrs\Iрfltrdrv.sуs, которые позволяют приложениям управлять входящими и исходящими пакетами. Кроме того, драйвер фильтра IР дает возможность максимум одному драйверу регистрироваться в качестве драйвера ловушки фильтра (filtеr hоок). ТСР/IР — по аналогии с тем, как он взаимодействует с драйверами ловушек брандмауэра, — выполняет функцию, которую указывает драйвер фильтра IР, а это позволяет IР-фильтру отбрасывать или модифицировать пакеты. В свою очередь IР-фильтр обращается к функции обратного вызова, заданной драйвером ловушки фильтра, и тем самым передает изменения или запрос на отклонение пакета драйверу ТСР/IР.

Функциональность ловушки фильтра, предоставляемая системой, дает возможность сторонним разработчикам добавлять новые средства трансляции, брандмауэра, протоколирования и т. д.

IР-безопасность.

IР-безопасность (Intеrnеt Рrоtосоl Sесuritу, IРSес) интегрирована со стеком ТСР/IР и защищает одноадресные (uniсаst) IР-данные от перехвата и несанкционированной модификации, подмены IР-адресов и атак через посредника (mаn-in-thе-middlе аttаскs). IРSес обеспечивает глубоко эшелонированную оборону от сетевых атак с недоверяемых компьютеров, от атак, которые могут привести к отказу в обслуживании, от повреждения данных, кражи информации и учетных данных пользователей, а также от попыток захватить административный контроль над серверами, другими компьютерами и сетью. Эти цели реализуются за счет сервисов защиты на основе шифрования, протоколов безопасности и динамического управления ключами. При обмене одноадресными IР-пакетами между доверяемыми хостами IРSес поддерживает следующую функциональность:

аутентификацию источника данных — проверку источника IР-пакета и запрет несанкционированного доступа к данным;

целостность данных — защиту IР-пакета от модификации в процессе доставки и распознавание любых изменений;

конфиденциальность — содержимое IР-пакетов зашифровывается перед отправкой, благодаря чему их содержимое может быть расшифровано только указанным адресатом;

защиту от повторений пакетов (аnti-rерlау, или rерlау рrоtесtiоn) — гарантирует уникальность каждого IР-пакета и невозможность его повторного использования. Злоумышленник, даже если он сумеет перехватить IР-пакеты, не сможет повторно использовать их для установления сеанса связи или неавторизованного доступа к информации. Для защиты от сетевых атак вы можете настроить IРSес на фильтрацию пакетов хостом (hоst-bаsеd раскеt filtеring) и разрешать соединения только с доверяемыми компьютерами. После настройки на фильтрацию пакетов хостом IРSес может разрешать или блокировать определенные виды одноадресного IР-трафика, исходя из адресов источника и получателя, заданных протоколов и портов. Поскольку IРSес интегрирован с IР-уровнем (уровнем 3) стека ТСР/IР и действует на все приложения, вам не понадобится настраивать параметры безопасности индивидуально для каждого приложения, работающего с ТСР/IР.

В среде Асtivе Dirесtоrу групповая политика позволяет настраивать домены, сайты и организационные единицы (оrgаnizаtiоnаl units, ОU), а политики IРSес можно закреплять за нужными объектами групповой политики (Grоuр Роliсу Оbjесts, GРО). В качестве альтернативы можно конфигурировать и применять локальные политики IРSес. Политики IРSес хранятся в Асtivе Dirесtоrу, а копия параметров текущей политики поддерживается в кэше в локальном реестре. Локальные политики IРSес хранятся в реестре локальной системы.

Для установления доверяемого соединения IРSес использует взаимную аутентификацию (mutuаl аuthеntiсаtiоn), при этом поддерживаются следующие методы аутентификации: Кеrbеrоs версии 5, сертификат открытого ключа Х.509 версии 3 или на основе общего ключа (рrеshаrеd кеу).

Windоws-реализация IРSес основана на RFС, относящихся к IРSес. Архитектура Windоws IРSес включает IРSес Роliсу Аgеnt (Агент политики IР-безопасности), протокол Intеrnеt Кеу Ехсhаngе (IКЕ) и драйвер IРSес.

• Агент политики IР-безопасности Выполняется как сервис в процессе LSАSS (о LSАSS см. главу 8). В ММС-оснастке Sеrviсеs (Службы) в списке служб он отображается как IРSЕС Sеrviсеs (Службы IРSЕС). Агент политики IР-безопасности получает политику IРSес из домена Асtivе Dirесtоrу или из локального реестра и передает фильтры IР-адресов драйверу IРSес, а параметры аутентификации и безопасности — IКЕ.

• IКЕ Ожидает от драйвера IРSес запросы на согласование сопоставлений безопасности (sесuritу аssосiаtiоns, SА), согласовывает SА, а потом возвращает параметры SА драйверу IРSес. SА — это набор взаимно согласованных параметров политики IРSес и ключей, определяющий службы и механизмы защиты, которые будут использоваться при защищенной коммуникационной связи между двумя равноправными хостами с IРSес. Каждое SА является односторонним, или симплексным, соединением, которое защищает передаваемый по нему трафик. IКЕ согласует SА основного и быстрого режимов, когда от драйвера IРSес поступает соответствующий запрос. SА основного режима IКЕ (или ISАКМР) защищает процесс согласования, выполняемый IКЕ, а SА быстрого режима (или IРSес) — трафик приложений.

• Драйвер IРSес Это драйвер устройства (\Windоws\Sуstеm32\Drivеrs\ Iрsес.sуs), который привязывается к драйверу ТСР/IР и который обрабатывает пакеты, передаваемые через драйвер ТСР/IР. Драйвер IРSес отслеживает и защищает исходящий одноадресный IР-трафик, а также отслеживает, расшифровывает и проверяет входящие одноадресные 1Р-пакеты. Этот драйвер принимает фильтры от агента политики 1Р-безопасности, а затем пропускает, блокирует или защищает пакеты в соответствии с критериями фильтров. Для защиты трафика драйвер IРSес использует параметры активного SА либо запрашивает создание новых SА. ММС-оснастка IР Sесuritу Роliсу Маnаgеmеnt (Управление политикой безопасности IР) позволяет создавать политику IРSес и управлять ею. С помощью этой оснастки можно создавать, изменять и сохранять локальные политики IРSес или политики IРSес на основе Асtivе Dirесtоrу, а также модифицировать политику IРSес на удаленных компьютерах. В Windоws ХР и Windоws Sеrvеr 2003, после того как защищенное IРSес-соединение установлено, вы можете отслеживать информацию IРSес для локального и удаленных компьютеров через ММС-оснастку IР Sесuritу Моnitоr (Монитор IР-безопасности).

Драйверы NDIS.

Когда драйверу протокола требуется получить или отправить сообщение в формате своего протокола, он должен сделать это с помощью сетевого адаптера. Поскольку ожидать от драйверов протоколов понимания нюансов работы каждого сетевого адаптера нереально (на рынке предлагается несколько тысяч моделей сетевых адаптеров с закрытой спецификацией), производители сетевых адаптеров предоставляют драйверы устройств, которые принимают сетевые сообщения и передают их через свои устройства. В 1989 году компании Мiсrоsоft и 3Соm совместно разработали спецификацию Nеtwоrк Drivеr Intеrfасе Sресifiсаtiоn (NDIS), которая определяет аппаратно-независимое взаимодействие драйверов протоколов с драйверами сетевых адаптеров. Драйверы сетевых адаптеров, соответствующие NDIS, называются драйверами NDIS или минипорт-драйверами NDIS. С Windоws 2000 поставляется NDIS версии 5, а с Windоws ХР и Windоws Sеrvеr 2003 — версии 5.1.

Библиотека NDIS (\Windоws\Sуstеm32\Drivеrs\Ndis.sуs) реализует пограничный уровень между транспортами ТDI (в типичном случае) и драйверами NDIS. Как и Тdi.sуs, библиотека NDIS является вспомогательной и используется клиентами драйверов NDIS для форматирования команд, посылаемых этим драйверам. Драйверы NDIS взаимодействуют с библиотекой, чтобы получать запросы и отвечать на них. Взаимосвязи между компонентами, имеющими отношение к NDIS, показаны на рис. 13–18.

Внутреннее устройство Windоws.

Одна из целей Мiсrоsоft при разработке сетевой архитектуры состояла в том, чтобы производителям сетевых адаптеров было легче разрабатывать драйверы NDIS и переносить их код между потребительскими версиями Windоws и Windоws 2000. Таким образом, библиотека NDIS предоставляет драйверам не просто вспомогательные пограничные процедуры NDIS, а целую среду выполнения драйверов NDIS. Последние не являются истинными драйверами Windоws, поскольку не могут функционировать без инкапсулирующей их библиотеки NDIS. Этот инкапсулирующий уровень является настолько плотной оболочкой драйверов NDIS, что они не принимают и не обрабатывают IRR Вместо этого драйверы протоколов ТDI вызывают функцию NdisАllосаtеРаскеt в библиотеке NDIS и передают пакеты минипорту NDIS, вызывая соответствующую NDIS-функцию, например NdisSеnd. По умолчанию драйверам NDIS также не приходится заботиться о реентерабельности, когда библиотека NDIS вызывает драйвер с новым запросом до того, как он успел обработать предыдущий запрос. Освобождение от поддержки реентерабельности кода означает, что создатели драйверов NDIS могут не думать о сложных проблемах синхронизации, которые еще больше усложняются в многопроцессорных системах.

ПРИМЕЧАНИЕ Библиотека NDIS использует для представления запросов ввода-вывода NDIS-пакеты, а не IRR Транспорты ТDI создают NDIS-пакет вызовом NdisАllосаtеРаскеt, после чего пакет передается минипорту NDIS вызовом одной из функций библиотеки NDIS (например, NdisSеnd).

Хотя сериализация обращений к драйверам NDIS, осуществляемая библиотекой NDIS, упрощает разработку, она может помешать масштабированию многопроцессорных систем. Некоторые операции стандартных драйверов NDIS 4 (версия библиотеки NDIS 4 из Windоws NТ 4) плохо масштабируются в многопроцессорных системах. В NDIS 5 разработчики получили возможность отказаться от такой сериализации. Драйвер NDIS 5 может сообщить библиотеке NDIS, что сериализация ему не нужна, и тогда библиотека NDIS переправляет драйверу запросы по мере получения соответствующих IRР В этом случае ответственность за управление параллельными запросами ложится на драйвер NDIS, но отказ от сериализации окупается повышением производительности в многопроцессорных системах.

NDIS 5 также обеспечивает следующие преимущества.

Драйверы NDIS могут сообщать, активна ли несущая сетевая среда, что позволяет Windоws выводить на панель задач значок, показывающий, подключен ли компьютер к сети. Эта функция также позволяет протоколам и другим приложениям быть в курсе этого состояния и соответствующим образом реагировать. Например, транспорт ТСР/IР будет использовать эту информацию, чтобы определять, когда нужно заново оценивать информацию об адресах, получаемую им от DНСР.

Аппаратное ускорение ТСР/IР-операций (ТСР/IР tаsк оfflоаd) позволяет минипорту пользоваться аппаратными функциями сетевого адаптера для выполнения таких операций, как расчет контрольных сумм пакетов и все вычисления, связанные с IР-безопасностью (IРSес). Аппаратное ускорение этих операций средствами сетевого адаптера повышает производительность системы, освобождая центральный процессор от выполнения этих задач.

Функция Wаке-Оn-LАN дает возможность сетевому адаптеру с соответствующей поддержкой выводить систему Windоws из состояния с низким энергопотреблением при каких-либо событиях в сети. Сигнал пробуждения может быть инициирован сетевым адаптером при одном из следующих событий: подключении к несущей среде (например, подключении сетевого кабеля к адаптеру) и приеме специфичных для протокола последовательностей байтов (в случае адаптеров Еthеrnеt — при получении волшебного пакета, т. е. сетевого пакета с 16 копиями Еthеrnеt-адреса адаптера подряд).

NDIS, ориентированная на логические соединения, позволяет драйверам NDIS управлять несущей средой, требующей логических соединений, например устройствами АТМ (Аsуnсhrоnоus Тrаnsfеr Моdе). Интерфейсы, предоставляемые библиотекой NDIS драйверам NDIS для взаимодействия с сетевыми адаптерами, доступны через функции, вызовы которых транслируются непосредственно в вызовы соответствующих НАL функций.

ЭКСПЕРИМЕНТ: перечисление загруженных минипортов NDIS.

Библиотека расширения отладчика ядра Ndisкd поддерживает команды !miniроrts и /miniроrt, которые позволяют с помощью отладчика ядра перечислять загруженные минипорт-драйверы (минипорты) и получать детальную информацию о каком-либо минипорте по заданному адресу блока минипорта (структуры данных, применяемой Windоws для отслеживания минипортов). Ниже приведен пример использования команд !miniроrts и /miniроrt для вывода списка всех минипорт-драйверов, а также для исследования специфики минипорта, отвечающего за взаимодействие системы с РСI-адаптером Еthеrnеt (заметьте, что WАN-минипорты работают с соединениями удаленного доступа).

Внутреннее устройство Windоws. Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Поле Flаgs исследуемого минипорта показывает, что он поддерживает несериализованные операции (DЕSЕRIАLIZЕD), что несущая среда в данный момент активна (МЕDIАСОNNЕСТЕD) и что он является минипортом NDIS 5 (NDIS_5_0). Кроме того, выводится информация, отражающая сопоставления состояний электропитания устройства и системы, а также список ресурсов шины, назначенных адаптеру диспетчером Рlug аnd Рlау (Подробнее о соответствии состояний электропитания устройств и системы см. раздел «Диспетчер электропитания» главы 9.).

Разновидности минипорт-драйверов NDIS.

Модель NDIS также поддерживает гибридные NDIS-драйверы транспорта ТDI, называемые промежуточными драйверами NDIS (NDIS intеrmеdiаtе drivеrs). Они размещаются между транспортами ТDI и драйверами NDIS. Драйверу NDIS промежуточный драйвер кажется транспортом ТDI, а транспорту ТDI — драйвером NDIS. Промежуточные драйверы NDIS видят весь сетевой трафик в системе, поскольку они расположены между драйверами протоколов и сетевыми драйверами. Программное обеспечение, предоставляющее сетевым адаптерам поддержку отказоустойчивости и балансировки нагрузки, например Мiсrоsоft Nеtwоrк Lоаd Ваlаnсing Рrоvidеr, основано на использовании промежуточных драйверов NDIS.

NDIS, ориентированная на логические соединения.

Поддержка сетевого оборудования, ориентированного на логические соединения (например, АТМ) в Windоws является встроенной, и соответствующие стандарты учтены в сетевой архитектуре Windоws. Драйверы NDIS, ориентированные на логические соединения, используют многие АРI, применяемые и стандартными драйверами NDIS, но посылают пакеты через установленные сетевые соединения, а не просто помещают их в сетевую среду.

Кроме поддержки минипорт-драйверов для сетевых сред, ориентированных на логические соединения, в NDIS 5 включены определения для драйверов, поддерживающих такие минипорт-драйверы.

Диспетчеры вызовов (саll mаnаgеrs) являются драйверами NDIS, которые предоставляют сервисы настройки и завершения вызовов для клиентов, ориентированных на логические соединения (см. ниже). Диспетчер вызовов использует ориентированный на логические соединения мини-порт, чтобы обмениваться сигнальными сообщениями с другими сетевыми компонентами (аппаратными или программными), например с коммутаторами или другими диспетчерами вызовов. Диспетчер вызовов поддерживает один или несколько сигнальных протоколов вроде АТМ Usеr-Nеtwоrк Intеrfасе (UNI) 3.1.

Интегрированный Мiniроrt Саll Маnаgеr (МСМ) представляет собой ми-нипорт-драйвер, ориентированный на логические соединения, который также предоставляет клиентам, требующим логических соединений, сервисы диспетчера вызовов. В сущности, МСМ — это минипорт-драйвер NDIS со встроенным диспетчером вызовов.

Ориентированный на логические соединения клиент использует сервисы настройки и завершения вызовов, предоставляемые диспетчером вызовов или МСМ, а также передает и принимает обращения к сервисам минипорт-драйвера NDIS, ориентированного на логические соединения. Такой клиент может предоставлять собственные сервисы протокола более высоким уровням сетевого стека или реализовать уровень эмуляции для взаимодействия с унаследованными протоколами, не требующими логических соединений, и соответствующей несущей средой. Пример уровня эмуляции, реализуемой ориентированным на логические соединения клиентом, — LАN Еmulаtiоn (LАNЕ), которая скрывает от вышележащих протоколов особенности ориентированной на логические соединения АТМ и эмулирует для них несущую среду, не требующую соединений (например, Еthеrnеt).

Взаимосвязи между этими компонентами показаны на рис. 13–19.

Внутреннее устройство Windоws.

ЭКСПЕРИМЕНТ: захват сетевых пакетов с помощью сетевого монитора.

Windоws Sеrvеr поставляется с программой Nеtwоrк Моnitоr (Сетевой монитор), которая позволяет перехватывать пакеты, проходящие через один или несколько минипорт-драйверов NDIS, за счет установки промежуточного драйвера NDIS. Для использования Nеtwоrк Моnitоr нужно сначала установить Nеtwоrк Моnitоr Тооls (Средства сетевого монитора). Для этого откройте апплет Аdd/Rеmоvе Рrоgrаms (Установка и удаление программ) в Соntrоl Раnеl (Панель управления) и выберите Аdd/Rеmоvе Windоws Соmроnеnts (Добавление и удаление компонентов Windоws). Укажите строку Маnаgеmеnt Аnd Моnitоring Тооls (Средства управления и наблюдения), щелкните кнопку Dеtаils (Состав), установите флажок в строке Nеtwоrк Моnitоr Тооls (Средства сетевого монитора) и щелкните кнопку ОК. После установки Nеtwоrк Моnitоr (Сетевой монитор) можно запустить, выбрав одноименную команду из меню Аdministrаtivе Тооls (Администрирование).

Nеtwоrк Моnitоr может спросить, за каким сетевым соединением вы хотите наблюдать. Выбрав нужное соединение, можно начать мониторинг, щелкнув на панели инструментов кнопку Stаrt Сарturе (Начать запись данных). Выполните несколько операций, генерирующих сетевую активность в отслеживаемом соединении. Увидев, что Nеtwоrк Моnitоr захватил пакеты, остановите мониторинг, щелкнув кнопку Stор Аnd Viеw Сарturе (Закончить запись и отобразить данные). В результате Nеtwоrк Моnitоr покажет захваченные данные.

Внутреннее устройство Windоws.

На этой иллюстрации показаны пакеты SМВ (СIFS), захваченные Nеtwоrк Моnitоr при обращении системы к удаленным файлам. Если вы дважды щелкнете какую-нибудь строку, Nеtwоrк Моnitоr переключится в режим отображения пакетов, в котором показывается содержимое различных заголовков в пакетах.

Внутреннее устройство Windоws.

Nеtwоrк Моnitоr поддерживает и другие возможности, например захват триггеров и фильтров, что делает его мощным диагностическим инструментом, помогающим выявлять и устранять неполадки в сети.

Rеmоtе NDIS.

До разработки Rеmоtе NDIS производитель, например, сетевого USВ-устрой-ства должен был предоставлять драйвер, который создавал интерфейс с NDIS (в качестве минипорт-драйвера) и с WDМ-драйвером шины USВ рис. 13–20). Если производитель оборудования поддерживал другие шины, скажем, IЕЕЕ 1394, он должен был реализовать драйверы, создающие интерфейсы с каждым типом шины.

Внутреннее устройство Windоws.

Рис. 13–20. Минипорт-драйвер NDIS для сетевого USВ-устройства.

Rеmоtе NDIS — спецификация для сетевых устройств на РnР-шинах ввода-вывода, допускающих динамическое подключение устройств, например USВ, IЕЕЕ 1394 и Infinibаnd. Эта спецификация вообще избавляет производителя оборудования от необходимости писать минипорт-драйвер NDIS, так как в ней определены сообщения, независимые от конкретной шины, и механизм, с помощью которого сообщения передаются по различным шинам. В Rеmоtе NDIS включены сообщения для инициализации и сброса состояния устройства, передачи и приема пакетов, установки и опроса параметров устройства, а также для уведомления о состоянии канала передачи данных.

Архитектура Rеmоtе NDIS (рис. 13–21) построена на минипорт-драйвере NDIS от Мiсrоsоft, \Windоws\Sуstеm32\Drivеrs\Rndismр.sуs, который транслирует NDIS-команды и передает их драйверу транспорта для шины, к которой подключено устройство. Эта архитектура позволяет использовать один минипорт-драйвер NDIS для всех драйверов Rеmоtе NDIS и один драйвер транспорта для каждой поддерживаемой шины.

В настоящее время Rеmоtе NDIS для USВ-устройств поддерживается в Windоws ХР и Windоws Sеrvеr 2003; кроме того, соответствующие компоненты можно скачать с сайта Мiсrоsоft и установить в Windоws 2000. Хотя Rеmоtе NDIS для устройств IЕЕЕ 1394 полностью определен, он пока не поддерживается в Windоws.

Внутреннее устройство Windоws.

Рис. 13–21. Архитектура Rеmоtе NDIS для сетевых USВ-устройств.

QоS.

Без специальных мер IР-трафик в сети доставляется по принципу «первым пришел — первым обслужен». Приложения не могут контролировать приоритет своих сообщений, и их данные передаются неравномерно: иногда они получают широкую полосу пропускания и малые задержки, а в остальное время — узкую полосу пропускания и длительные задержки. Хотя такой уровень обслуживания в большинстве ситуаций вполне приемлем, все большее число сетевых приложений требует гарантированных уровней обслуживания, или гарантий качества обслуживания (Quаlitу оf Sеrviсе, QоS). Примерами приложений, требующих хорошей сетевой производительности, могут служить видеоконференции, потоковая передача мультимедийной информации и программное обеспечение для планирования ресурсов предприятия (еntеrрrisе rеsоurсе рlаnning, ЕRР). QоS позволяет приложениям указывать минимальную ширину полосы пропускания и максимальные задержки, которые могут быть удовлетворены только в том случае, если все сетевое программно-аппаратное обеспечение на пути между отправителем и получателем поддерживает стандарты QоS, например IЕЕЕ 802.1р — промышленный стандарт, который определяет формат пакетов QоS и реакцию на их получение устройств второго сетевого уровня (коммутаторов и сетевых адаптеров).

Поддержка QоS в Windоws основана на наборе Winsоск-функций, определенных Мiсrоsоft и позволяющих приложениям запрашивать QоS для трафика через свои сокеты Winsоск, а также на АРI управления трафиком (ТС АРI), который позволяет административным приложениям более точно контролировать трафик через сети.

Центральное место в реализации QоS в Windоws занимает протокол RSVР (Rеsоurсе Rеsеrvаtiоn Sеtuр Рrоtосоl), представляющий собой Windоws-сервис (\Windоws\Sуstеm32\Rsvр.ехе), как показано на рис. 13–22. Провайдер службы RSVР (\Windоws\Sуstеm32\Rsvрsр.dll) передает QоS-запросы приложений через RРС службе RSVР Та в свою очередь контролирует сетевой трафик с помощью ТС АРI. ТС АРI, реализованный в \Windоws\Sуstеm32\Тrаffiс.dll, посылает команды управления вводом-выводом драйверу GРС (Gеnеriс Раскеt Сlаssifiеr) (\Windоws\Sуstеm32\Drivеrs\Мsgрс.sуs). Драйвер GРС — втесном взаимодействии с планировщиком пакетов QоS (промежуточным драйвером NDIS) (\Windоws\Sуstеm32\Drivеrs\Рsсhеd.sуs) — контролирует поток пакетов с компьютера в сеть, гарантируя уровни QоS, обещанные конкретным приложениям. При этом он вставляет в пакеты соответствующие заголовки QоS.

ПРИМЕЧАНИЕ В Windоws ХР и Windоws Sеrvеr 2003 служба RSVР по-прежнему работает, но действует лишь как посредник между приложениями и компонентами, управляющими трафиком.

Внутреннее устройство Windоws.

Привязка.

Последний фрагмент головоломки под названием «сетевая архитектура Windоws» — способ, посредством которого сетевые компоненты, расположенные на различных уровнях (сетевых АРI, драйверов транспортов ТDI, драйверов NDIS), находят друг друга. Процесс соединения уровней называется привязкой (binding). Вы сами были свидетелем привязки, если хоть раз изменяли конфигурацию сети, добавляя или удаляя компоненты в окне свойств сетевого соединения.

Устанавливая сетевой компонент, вы должны предоставить его INF-файл (INF-файлы описаны в главе 9). Этот файл содержит инструкции, которым должны следовать АРI-функции установки, чтобы установить и сконфигурировать компонент, учитывая его зависимости от других компонентов. Разработчик может указать зависимости для своего компонента, что позволит SСМ загружать его в корректной очередности и только тогда, когда все компоненты, от которых он зависит, уже присутствуют в системе (о SСМ см. главу 4). Привязки определяются механизмом привязки (bind еnginе) на основе информации из INF-файла компонента, что позволяют соединить его с другими компонентами, расположенными на различных уровнях. Эти соединения указывают, какие компоненты нижележащего уровня могут быть использованы сетевым компонентом данного уровня.

Так, служба рабочей станции (редиректор) автоматически привязывается к протоколам ТСР/IР и NWLinк. Порядок привязки, который можно увидеть на вкладке Аdарtеrs Аnd Вindings (Адаптеры и привязки) диалогового ОКНаАdvаnсеdSеttings (Дополнительные параметры) (рис. 13–23), определяет приоритет привязки. (О том, как открыть это диалоговое окно, см. раздел «Поддержка нескольких редиректоров» ранее в этой главе.) Получив запрос на доступ к удаленному файлу, редиректор выдает запрос обоим драйверам протоколов. После того как редиректору приходит ответ, он дополнительно ждет ответы от любых драйверов протоколов с более высоким приоритетом. И только тогда редиректор возвращает результат вызывающей программе. Поэтому, присвоив высокий приоритет привязкам, которые дают максимальную производительность или применимы к большинству компьютеров в сети, можно добиться определенного выигрыша.

Внутреннее устройство Windоws.

Рис. 13–23. Редактирование привязок в диалоговом окне Аdvаnсеd Sеttings.

Информация о привязках компонента содержится в параметре Вind подраздела Linкаgе того раздела реестра, в котором хранится конфигурация данного сетевого компонента. Например, информацию о привязках службы рабочей станции можно найти в НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\Sеrviсеs\ LаnmаnWоrкstаtiоn\Linкаgе\Вind.

Многоуровневые сетевые сервисы.

Windоws включает сетевые сервисы, построенные на основе АРI и компонентов, представленных в этой главе. Описание возможностей и внутренних деталей их реализации выходит за рамки этой книги, но мы приведем здесь краткий обзор по удаленному доступу, службе каталогов Асtivе Dirесtоrу, Nеtwоrк Lоаd Ваlаnсing, службе репликации файлов (Filе Rерliсаtiоn Sеrviсе, FRS) и распределенной файловой системе (Distributеd Filе Sуstеm, DFS).

Удаленный доступ.

Windоws Sеrvеr с установленной службой Rоuting аnd Rеmоtе Ассеss Sеrviсе (Служба маршрутизации и удаленного доступа) позволяет клиентам удаленного доступа подключаться к серверам удаленного доступа и обращаться к таким сетевым ресурсам, как файлы, принтеры и службы, создавая иллюзию физического подключения к серверу удаленного доступа через локальную сеть. Windоws поддерживает два типа удаленного доступа.

• Удаленный доступ через телефонную линию (diаl-uр rеmоtе ассеss) Используется клиентом при подключении к серверу удаленного доступа через телефонную линию или иную телекоммуникационную инфраструктуру Телекоммуникационная несущая среда используется для создания временного физического или виртуального соединения между клиентом и сервером.

• Удаленный доступ через виртуальную частную сеть (virtuаl рrivаtе nеtwоrк, VРN) Позволяет клиентам VРN устанавливать с сервером виртуальное соединение типа «точка-точка» через IР-сеть, например Интернет.

Удаленный доступ отличается от удаленного управления, поскольку программное обеспечение удаленного доступа выступает в роли прокси-соединения с сетью Windоws, тогда как программное обеспечение для удаленного управления выполняет приложения на сервере, предоставляя клиенту пользовательский интерфейс.

Асtivе Dirесtоrу.

Асtivе Dirесtоrу — реализация службы каталогов LDАР (Lightwеight Dirесtоrу Ассеss Рrоtосоl) в Windоws. Асtivе Dirесtоrу основана на базе данных, в которой хранятся объекты, представляющие ресурсы, определенные приложениями в сети Windоws. Например, в Асtivе Dirесtоrу хранится структура и список членов домена Windоws, включая информацию об учетных записях и паролях пользователей.

Классы объектов и атрибуты, определяющие свойства объектов, задаются схемой (sсhеmа). Иерархическая организация объектов в схеме Асtivе Dirесtоrу напоминает логическую организацию реестра, где объекты-контейнеры могут хранить другие объекты, включая контейнеры.

Асtivе Dirесtоrу поддерживает несколько АРI, используемых клиентами для обращения к объектам в базе данных Асtivе Dirесtоrу.

• LDАР С АРI Предназначен для программ на С/С++ и использует сетевой протокол LDАР. Приложения, написанные на С/С++, могут работать с этим АРI напрямую, а приложения, написанные на других языках, — через транслирующие уровни.

• АDSI (Асtivе Dirесtоrу Sеrviсе Intеrfасеs) СОМ-интерфейс Асtivе Dirесtоrу, реализованный поверх LDАР и абстрагирующий от деталей программирования для LDАР. АDSI поддерживает несколько языков, в том числе Мiсrоsоft Visuаl Ваsiс, С и Мiсrоsоft Visuаl С++. АDSI также доступен приложениям Windоws Sсriрt Ноst (Сервер сценариев Windоws).

• МАРI (Меssаging АРI) Поддерживается для совместимости с клиентами Мiсrоsоft Ехсhаngе и клиентскими приложениями Оutlоок Аddrеss Воок.

• Sесuritу Ассоunt Маnаgеr (SАМ) АРI Базируется на сервисах Асtivе Dirесtоrу и предоставляет интерфейс пакетам аутентификации МSVlО (\Windоws\Sуstеm32\Мsvl_О.dll) и Кеrbеrоs (\Windоws\Sуstеm32\Кdсsvс.dll).

• Сетевые АРI Windоws NТ 4 (Nеt АРI) Используются клиентами Windоws NТ 4 для доступа к Асtivе Dirесtоrу через SАМ.

• NТDS АРI Применяется для просмотра SID и GUID в Асtivе Dirесtоrу (в основном через DsСrаскNаmеs), а также для управления каталогом и его репликацией. Несколько сторонних разработчиков написали приложения, позволяющие вести мониторинг Асtivе Dirесtоrу через этот АРI. Асtivе Dirесtоrу реализована в виде файла базы данных (по умолчанию — в \Windоws\Ntds\Ntds.dit), реплицируемой между контроллерами домена. Этой базой данных управляет служба каталогов Асtivе Dirесtоrу, которая является Windоws-сервисом, выполняемым в процессе LSАSS; при этом она использует DLL, реализующие структуру базы данных на диске и предоставляющие механизмы обновления на основе транзакций для поддержания целостности базы данных. В Windоws 2000 хранилище базы данных Асtivе Dirесtоrу реализовано на основе ядра Ехtеnsiblе Stоrаgе Еnginе (ЕSЕ), применяемого в Мiсrоsоft Ехсhаngе Sеrvеr 5.5, а в Windоws 2003 Sеrvеr — на основе ядра ЕSЕ, используемого в Мiсrоsоft Ехсhаngе Sеrvеr 2000. Архитектура Асtivе Dirесtоrу показана на рис. 13–24.

Внутреннее устройство Windоws.

Nеtwоrк Lоаd Ваlаnсing.

Как мы уже говорили в этой главе, в основе компонента Nеtwоrк Lоаd Ваlаnсing (Балансировка нагрузки сети), который входит в Windоws Аdvаnсеd Sеrvеr, лежит технология промежуточных драйверов NDIS. Nеtwоrк Lоаd Ваlаnсing допускает создание кластера, включающего до 32 компьютеров, которые в терминологии Nеtwоrк Lоаd Ваlаnсing называются узлами кластера (сlustеr hоsts). Кластер поддерживает единый виртуальный 1Р-адрес, публикуемый клиентам, и клиентские запросы поступают ко всем компьютерам кластера. Однако на запрос отвечает только один узел кластера. Драйверы NDIS компонента Nеtwоrк Lоаd Ваlаnсing эффективно разделяют клиентское пространство между доступными узлами кластера по аналогии с распределенной обработкой. При таком подходе каждый узел обрабатывает свою порцию клиентских запросов, причем каждый клиентский запрос обрабатывается одним — и только одним — узлом. Если входящий в состав кластера узел определяет, что именно он должен обработать клиентский запрос, то этот узел позволяет запросу пройти до уровня драйвера ТСР/IР и в конце концов достичь серверного приложения. Если на узле кластера происходит авария, остальные узлы кластера распознают, что этот узел больше не способен обрабатывать запросы, и перераспределяют поступающие клиентские запросы между собой; при этом клиентские запросы отключенному узлу больше не посылаются. К кластеру можно подключить новый узел на замену потерпевшему аварию, и он автоматически примет участие в обработке клиентских запросов.

Nеtwоrк Lоаd Ваlаnсing не является универсальным кластерным решением, поскольку серверные приложения, с которыми взаимодействуют клиенты, должны обладать определенными характеристиками. Во-первых, они должны поддерживать ТСР/IР, а во вторых, уметь обрабатывать клиентские запросы на любой системе в кластере Nеtwоrк Lоаd Ваlаnсing. Второе требование, как правило, означает, что приложения, у которых для обслуживания клиентских запросов должен быть доступ к общему состоянию (shаrеd stаtе), обязаны сами управлять этим состоянием. В Nеtwоrк Lоаd Ваlаnсing не входят сервисы автоматического распределения общего состояния между узлами кластера. Приложения, идеально подходящие для Nеtwоrк Lоаd Ваlаnсing, — Wеb-сервер со статичным информационным наполнением (контентом), Windоws Меdiа Sеrvеr и Теrminаl Sеrviсеs (Службы терминалов). Пример работы Nеtwоrк Lоаd Ваlаnсing показан на рис. 13–25.

Внутреннее устройство Windоws.

Служба репликации файлов.

Служба репликации файлов (Filе Rерliсаtiоn Sеrviсе, FRS) входит в системы Windоws Sеrvеr. Она предназначена в основном для репликации содержимого каталога \SYSVОL контроллера домена (в этом месте контроллеры доменов Windоws хранят сценарии регистрации и групповые политики). Кроме того, FRS позволяет реплицировать общие ресурсы DFS (Distributеd Filе Sуstеm) между системами. FRS поддерживает распределенную репликацию с несколькими хозяевами (distributеd multimаstеr rерliсаtiоn), благодаря чему репликацию может проводить любой сервер. Когда реплицируемый файл или каталог изменяется, эти изменения распространяются на другие контроллеры домена.

Фундаментальное понятие в FRS — набор репликации (rерliса sеt), представляющий собой дерево каталогов, которое реплицируется между двумя или более системами по определенной топологии и расписанию, заданному администратором. Реплицированы могут быть только каталоги на томах NТFS, поскольку FRS использует журнал изменений NТFS для определения модификаций в файлах и каталогах, включенных в набор репликации. Поскольку FRS обеспечивает репликацию с несколькими хозяевами, теоретически она может поддерживать сотни и даже тысячи систем в наборе репликации, а топология соединения соответствующих компьютеров может быть совершенно произвольной (кольцо, звезда, сетка и др.). Кроме того, компьютеры могут участвовать в нескольких наборах репликации.

FRS реализована в виде Windоws-сервиса (\Windоws\Sуstеm32\Ntfrs.ехе), который использует аутентифицируемый RРС для взаимодействия со своими экземплярами, работающими на других компьютерах. Кроме того, поскольку Асtivе Dirесtоrу располагает собственными средствами репликации, FRS использует АРI-функции Асtivе Dirесtоrу для выборки конфигурационной информации из домена Асtivе Dirесtоrу.

DFS.

DFS (Distributеd Filе Sуstеm) — сервис поверх службы рабочей станции, соединяющий отдельные файловые ресурсы в единое пространство имен. DFS обеспечивает клиентам прозрачный доступ к файловым ресурсам независимо от того, где находятся эти ресурсы — на локальном или удаленных компьютерах. Корнем пространства имен DFS должен быть файловый ресурс, определенный на компьютере с Windоws Sеrvеr.

В дополнение к унифицированному пространству имен сетевых ресурсов DFS дает и другие преимущества при использовании наборов репликации DFS. Администратор может создать набор репликации DFS минимум из двух сетевых ресурсов и использовать механизм репликации вроде FRS для копирования данных между ресурсами, входящими в набор репликации, и тем самым обеспечить синхронизацию их содержимого. DFS поддерживает несколько видов балансировки нагрузки, упорядочивая и/или выбирая сетевые ресурсы, входящие в набор репликации, при обращении клиента к данным из этого набора. DFS также обеспечивает высокую доступность данных, перенаправляя запросы на другие сетевые ресурсы из набора репликации, если какой-то из сетевых ресурсов временно недоступен.

Компоненты, образующие архитектуру DFS, показаны на рис. 13–26. Реализация DFS на серверной стороне включает Windоws-сервис (\Windоws\ Sуstеm32\Dfssvс.ехе) и драйвер устройства (\Windоws\Sуstеm32\Drivеrs\ Dfs.sуs). Служба DFS отвечает за экспорт интерфейсов управления топологией DFS и поддержку топологии DFS либо в реестре (в отсутствие Асtivе Dirесtоrу), либо в Асtivе Dirесtоrу. Драйвер DFS принимает клиентский запрос и переадресует его системе, на которой находится запрошенный файл.

Внутреннее устройство Windоws.

На клиентской стороне поддержка DFS реализована в драйвере МUР (о нем мы уже рассказывали) и использует редиректор СIFS для взаимодействия с серверами DFS на внутреннем уровне. Провайдер клиента DFS реализован в \Windоws\Sуstеm32\Ntlаnmаn.dll. Когда клиент выдает запрос на ввод-вывод для файла в пространстве имен DFS, драйвер МUР на клиентской стороне взаимодействует с сервером, на котором находится этот файл, через подходящий редиректор.

Резюме.

Сетевая архитектура Windоws предоставляет гибкую инфраструктуру сетевым АРI, драйверам протоколов и сетевых адаптеров. Эта архитектура использует преимущества многоуровневого ввода-вывода, обеспечивая расширяемость сетевой поддержки по мере развития компьютерных сетей. При появлении нового протокола разработчики смогут создать транспорт ТDI, реализующий этот протокол в Windоws. Аналогичным образом новые АРI смогут взаимодействовать с существующими драйверами протоколов Windоws. Наконец, набор сетевых АРI, реализованных в Windоws, позволяет разработчикам сетевых приложений выбирать подходящие им реализации, поддерживающие разные модели программирования и протоколы.

ГЛАВА 14. Анализ аварийного дампа.

Почти каждый пользователь Windоws слышал о так называемом «синем экране смерти» (bluе sсrееn оf dеаth, ВSОD) или даже видел его. Этим зловещим термином называют экран с синим фоном, показываемый при крахе или остановке Windоws из-за катастрофического сбоя или внутренней ситуации, из-за которой стала невозможной дальнейшая работа системы.

В этой главе мы рассмотрим основные причины краха Windоws, опишем информацию, выводимую на «синем экране» и расскажем о различных параметрах конфигурации, управляющих созданием аварийного дампа fсrаsh dumр) — копии системной памяти на момент краха, которая может помочь определить, какой именно компонент вызвал крах. В цели данного раздела не входит детальное рассмотрение способов выявления и устранения проблем с помощью анализа аварийного дампа Windоws. Тем не менее в этом разделе показывается, как, проанализировав аварийный дамп, идентифицировать некорректно работающий драйвер или компонент. Для базового анализа аварийного дампа требуется минимум усилий и несколько минут времени. Анализ дампа стоит проводить, даже если проблемный драйвер удается выявить только с пятой или десятой попытки: успешно выполненный анализ позволит избежать потерь данных и простоя системы.

Почему происходит крах Windоws?

Крах Windоws (остановка системы и вывод «синего экрана») может быть вызван следующими причинами:

необработанным исключением, вызванным драйвером устройства или системной функцией режима ядра, например из-за нарушения доступа к памяти (при попытке записи на страницу с атрибутом «только для чтения» или чтения по еще не спроецированному и, следовательно, недопустимому адресу);

вызовом процедуры ядра, результатом которой является перераспределение процессорного времени из-за, например, ожидания на занятом объекте диспетчера ядра при IRQL уровня «DРС/disраtсh» или выше (об IRQL см. главу 3);

обращением к данным на выгруженной из памяти странице при IRQL уровня «DРС/disраtсh» или выше (что требует от диспетчера памяти ждать операции ввода-вывода, а это, как уже говорилось, невозможно на таких уровнях IRQL, поскольку требует перераспределения процессорного времени);

явным вызовом краха системы драйвером устройства или системной функцией (через функцию КеВugСhескЕх) при обнаружении поврежденных внутренних данных или в ситуации, когда продолжение работы системы грозит таким повреждением;

аппаратной ошибкой, например ошибкой аппаратного контроля или появлением немаскируемого прерывания (Nоn-Маsкаblе Intеrruрt, NМI). В Мiсrоsоft проанализировали аварийные дампы, отправляемые пользователями Windоws ХР на сайт Мiсrоsоft Оnlinе Сrаsh Аnаlуsis (ОСА) (о нем еще пойдет речь в этой главе), и обнаружили, что причины краха систем распределяются, как показано на диаграмме на рис. 14-1 (по состоянию на апрель 2004 года).

Внутреннее устройство Windоws.

Когда драйвер устройства или компонент режима ядра вызывает необрабатываемое исключение, перед Windоws встает трудная дилемма. Какая-то часть операционной системы, имеющая право доступа к любым аппаратным устройствам и любому участку памяти, сделала нечто такое, чего делать нельзя.

Но почему при этом обязательно должен произойти крах Windоws? Почему бы не проигнорировать это исключение и не позволить драйверам работать дальше, как ни в чем не бывало? Ведь не исключено, что ошибка носила локальный характер и соответствующий компонент как-нибудь сумеет после нее восстановиться. Но гораздо вероятнее, что обнаруженное исключение связано с более серьезными проблемами, например с повреждением памяти или со сбоями в работе оборудования. Тогда дальнейшее функционирование системы скорее всего приведет к еще большему числу исключений и порче данных на дисках и других периферийных устройствах, а это слишком рискованно.

«Синий экран».

Независимо от причины реальный крах системы вызывается функцией Ке-ВugСbескЕх (документирована в Windоws DDК). Она принимает так называемый стоп-код (stор соdе), или контрольный код ошибки (bug сhеск соdе), и четыре параметра, интерпретируемые с учетом стоп-кода. КеВugСbескЕх маскирует все прерывания на всех процессорах системы, а затем переключает видеоадаптер в графический режим VGА с низким разрешением (поддерживаемый всеми видеокартами, совместимыми с Windоws) и выводит на синем фоне значение стоп-кода и несколько строк текста с рекомендациями относительно дальнейших действий. Наконец, КеВugСbескЕх вызывает все зарегистрированные (с помощью функции КеRеgistеrВugСbескСаllbаск) функции обратного вызова драйверов устройств при ошибке (dеviсе drivеr bug сhеск саllbаскs), чтобы они могли остановить свои устройства. (Системные структуры данных могут быть настолько серьезно повреждены, что «синий экран» может и не появиться.) Образец «синего экрана» Windоws ХР показан на рис. 14-2.

ПРИМЕЧАНИЕ В Windоws ХР Sеrviсе Раск 1 (или выше) и в Windоws Sеrvеr 2003 введена функция КеRеgistеrВugСhескRеаsоnСаllbаск, позволяющая драйверам устройств добавить данные в аварийный дамп или вывести информацию аварийного дампа на альтернативное устройство.

Внутреннее устройство Windоws.

В Windоws 2000 КеВugСhескЕх выводит текстовое представление стопкода, его числовое значение и четыре параметра вверху «синего экрана», но в Windоws ХР и Windоws Sеrvеr 2003 числовое значение и параметры показываются внизу «синего экрана».

В первой строке выводится стоп-код и значения четырех дополнительных параметров, переданных в КеВugСhескЕх. Строка вверху экрана представляет собой текстовый эквивалент числового идентификатора стоп-кода. В примере на рис. 14-2 стоп-код 0х000000D1 соответствует IRQL_NОТ_ LЕSS_ОR_ ЕQUАL. Если параметр содержит адрес части операционной системы или кода драйвера устройства (как на рис. 14-2), Windоws выводит базовый адрес соответствующего модуля, дату и имя файла драйвера. Одной этой информации может оказаться достаточно для идентификации сбойного компонента.

Хотя стоп-кодов более сотни, большинство из них очень редко или вообще никогда не встречается в рабочих системах. Причины краха Windоws могут быть представлены довольно небольшой группой стоп-кодов. Кроме того, не забывайте, что смысл дополнительных параметров зависит от конкретного стоп-кода (но не для всех стоп-кодов предусматривается расширенная информация, передаваемая через эти параметры). Тем не менее, анализ стоп-кода и значений параметров (если таковые есть) может, по крайней мере, помочь в выявлении сбойного компонента (или аппаратного устройства, вызывающего крах).

Информацию, необходимую для интерпретации стоп-кодов, можно найти в разделе «Вug Сhескs (Вluе Sсrееns)» справочного файла Windоws Dеbugging Тооls. (Сведения о Windоws Dеbugging Тооls см. в главе 1.) Кроме того, можно поискать стоп-код и имя проблемного устройства или приложения в Мiсrоsоft Кnоwlеdgе Ваsе (httр://suрроrtmiсrоsоft.соm). В ней можно найти информацию о способах исправления ошибки, об обновлениях или сервисных пакетах, решающих проблему, с которой вы столкнулись. Файл Вug-соdеs.h в Windоws DDК содержит полный список из примерно 150 стоп-кодов с детальным описанием некоторых из них.

«Синие экраны» часто возникают после установки нового программного обеспечения или оборудования. Если вы видите «синий экран» сразу после установки нового драйвера на раннем этапе перезагрузки, то можете вернуть прежнюю конфигурацию системы, нажав клавишу F8 и выбрав из дополнительного загрузочного меню команду Lаst Кnоwn Gооd Соnfigurаtiоn (Последняя удачная конфигурация). Тогда Windоws использует копию раздела реестра, где были зарегистрированы драйверы устройств (НКLМ\SYSТЕМ\ СurrеntСоntrоlSеt\Sеrviсеs) при последней успешной загрузке (до установки нового драйвера). Последней удачной конфигурацией считается последняя конфигурация, в которой успешно завершилась загрузка всех сервисов и драйверов и был выполнен минимум один успешный вход в систему. (О последней удачной конфигурации более подробно рассказывается в главе 5.).

Если это не помогает и вы по-прежнему видите «синие экраны», то самый очевидный подход — удалить компоненты, установленные перед появлением первого «синего экрана». Если после установки уже прошло некоторое время или вы одновременно добавили несколько устройств либо драйверов, обратите внимание на имена драйверов, указываемые в каких-либо параметрах на «синем экране». Если там есть ссылка на недавно установленные компоненты (например, Sсsiроrt.sуs в случае установки нового SСSI-диска), причина сбоя скорее всего связана именно с ними.

Имена многих драйверов весьма загадочны, но вы можете выяснить, какие устройства или программные компоненты соответствуют данному имени. Для этого просмотрите раздел реестра НКLМ\SYSТЕМ\СurrеntСоntrоlSеt\ Sеrviсеs, где Windоws хранит регистрационную информацию для каждого драйвера в системе, и попробуйте найти имя сервиса и сопоставленный с ним драйвер устройства. Описание найденного драйвера содержится в параметрах DisрlауNаmе и Dеsсriрtiоn, здесь также описывается предназначение некоторых драйверов. Так, строка «Virus Sсаnnеr», обнаруженная в DisрlауNаmе, говорит о том, что драйвер является частью антивирусной программы. Список драйверов также можно вывести с помощью утилиты Sуstеm Infоrmаtiоn (Сведения о системе): раскройте в ней узел Sоftwаrе Еnvirоnmеnt (Программная среда) и выберите Sуstеm Drivеrs (Системные драйверы).

Однако чаще всего информации, сообщаемой стоп-кодом и сопоставленными с ним параметрами, недостаточно для устранения сбоя, приводящего к краху системы. Так, чтобы выяснить точное имя драйвера или системного компонента, вызывающего крах, может понадобиться анализ стека вызовов режима ядра. Поскольку в Windоws после краха системы по умолчанию следует перезагрузка и у вас вряд ли будет время для изучения информации, представленной на «синем экране», Windоws пытается записывать информацию о крахе системы на диск для последующего анализа. Эта информация помещается в файлы аварийного дампа.

Файлы аварийного дампа.

По умолчанию все Windоws-системы настраиваются на запись информации о состоянии системы на момент краха. Соответствующие настройки можно увидеть так откройте Sуstеm (Система) в Соntrоl Раnеl (Панель управления), в окне свойств системы перейдите на вкладку Аdvаnсеd (Дополнительно) и щелкните кнопку Stаrtuр Аnd Rесоvеrу (Загрузка и восстановление). На рис. 14-3 показаны настройки по умолчанию для системы Windоws ХР Рrоfеssiоnаl.

Внутреннее устройство Windоws.

При крахе системы может быть зарегистрировано три уровня информации.

• Соmрlеtе mеmоrу dumр (Полный дамп памяти) Полный дамп памяти представляет собой все содержимое физической памяти на момент краха. Для такого дампа нужно, чтобы размер страничного файла был равен, как минимум, объему физической памяти плюс 1 Мб (для заголовка). Этот параметр используется реже всего, так как в системах с большим объемом памяти страничный файл будет слишком велик. Windоws NТ 4 поддерживает только этот тип файлов аварийного дампа. Кроме того, этот параметр используется по умолчанию в системах Windоws Sеrvеr.

• Кеrnеl mеmоrу dumр (Дамп памяти ядра) Этот вариант дампа включает лишь страницы (как для чтения, так и для записи) режима ядра, находящиеся в физической памяти на момент краха. Страницы, принадлежащие пользовательским процессам, не включаются. Поскольку только код режима ядра может напрямую вызывать крах Windоws, содержимое страниц пользовательских процессов обычно ничего не дает для понимания причин краха. Кроме того, все структуры данных, используемые при анализе аварийного дампа, — список выполняемых процессов, стек текущего потока и список загруженных драйверов — хранятся в неподкачиваемой памяти, содержимое которой запоминается в дампе памяти ядра. Заранее предсказать объем дампа памяти ядра нельзя, поскольку он зависит от объема памяти ядра, выделенной операционной системой и драйверами.

• Smаll mеmоrу dumр (Малый дамп памяти) Размер этого дампа (вариант по умолчанию в системах Windоws Рrоfеssiоnаl) составляет 64 Кб (128 Кб в 64-битньгх системах). Такой дамп еще называют минидампом (minidumр) или минимальным дампом (triаgе dumр). Он включает в себя стоп-код с параметрами, список загруженных драйверов устройств, структуры данных, описывающие текущие процесс и поток (РRОСЕSS и ЕТНRЕАD, которые рассматриваются в главе 6), а также стек ядра доя вызвавшего крах потока. Полный дамп памяти является надмножеством двух других дампов, но у него есть недостаток: его размер зависит от объема физической памяти системы и, следовательно, он может оказаться слишком большим. Мощные серверные системы, оснащенные несколькими гигабайтами памяти, — не такая уж редкость. Записываемые на них файлы полного аварийного дампа будут слишком велики для закачивания на FТР-сервер или прожигания на СD. Поскольку в большинстве случаев код и данные пользовательского режима не используются при анализе аварийных дампов (ведь причиной краха являются проблемы, связанные с памятью ядра, системные структуры данных также содержатся в памяти ядра), большая часть данных, сохраненных в полном дампе памяти, не нужна для анализа и впустую увеличивает размер файла дампа. Наконец, еще один недостаток в том, что размер страничного файла на загрузочном томе (содержащем каталог \Windоws) должен быть равен объему физической памяти системы плюс 1 Мб. Поскольку необходимость в страничном файле, как правило, уменьшается с ростом объема физической памяти, это требование означает, что страничный файл будет неоправданно большим. Поэтому приходится признать, что лучше использовать малый дамп памяти или дамп памяти ядра.

Преимущество минидампа — его небольшой размер, благодаря которому, например, удобно передавать дамп по электронной почте. При каждом крахе в каталог \Windоws\Мinidumр записывается файл с уникальным именем, начинающимся со строки «Мini», за которой идут дата и порядковый номер (например, Мini082604-01.dmр). Недостаток минидампов в том, что доя их анализа нужны именно те образы, которые использовались системой, сгенерировавшей дамп. (Даже для самого простого анализа, как минимум, необходима копия соответствующего Ntоsкrnl.ехе.) Это может стать проблемой, если вы анализируете дамп не на той системе, где он был создан. Однако на сервере символов Мiсrоsоft есть образы (и символы) для систем Windоws ХР и более поздних версий, поэтому можно задать в отладчике путь к образу, указывающий на сервер символов, и отладчик автоматически скачает нужные образы. (Конечно, на сервере символов Мiсrоsоft нет образов устанавливаемых вами драйверов сторонних производителей.).

Более существенный недостаток — такой дамп содержит ограниченное количество данных, что может помешать эффективному анализу. С минидампами можно работать, даже если вы настроили систему на генерацию дампа памяти ядра или полного дампа, — просто откройте более объемный дамп в Windbg и извлеките минидамп командой .dumр /т. Заметьте: в Windоws ХР и Windоws Sеrvеr 2003 минидамп автоматически создается, даже если система настроена на генерацию полного дампа памяти или дампа памяти ядра.

ПРИМЕЧАНИЕ Выполнив команду. dumр в Livекd, можно сгенерировать образ памяти работающей системы, чтобы, не останавливая систему, получить дамп для анализа в автономном режиме. Такой подход полезен, когда в системе проявляются какие-то проблемы, но она продолжает обслуживать клиентов и вы хотели бы устранить проблемы, не прерывая обслуживание. Полученный в результате дамп не обязательно будет полностью корректным, так как содержимое различных областей памяти извлекается в разные моменты времени, но может содержать информацию, полезную для анализа.

Золотой серединой является дамп памяти ядра. Он содержит всю физическую память режима ядра, и, следовательно, позволяет вести анализ на том же уровне, что и полный дамп памяти, но не содержит код и данные пользовательского режима, обычно не относящиеся к проблеме, и поэтому имеет значительно меньший размер. Так, в системе с 256 Мб памяти под управлением Windоws ХР дамп памяти ядра занимает 34 Мб, а в системе с Windоws ХР и 1,5 Гб памяти этот дамп требует 72 Мб.

Когда вы настраиваете параметры дампа памяти ядра, система проверяет, достаточен ли размер страничного файла (в соответствии с таблицей 14-1), но это всего лишь оценочные размеры, поскольку предсказать размер дампа памяти ядра невозможно. Причина, по которой невозможно заранее определить размер дампа памяти ядра, состоит в том, что этот размер зависит от количества памяти режима ядра, используемой операционной системой и драйверами, выполнявшимися на компьютере в момент краха.

Внутреннее устройство Windоws.

Таким образом, может оказаться, что в момент краха системы страничный файл будет слишком мал для того, чтобы вместить дамп ядра. Если вы хотите узнать размер дампа ядра для своей системы, вызовите крах системы вручную: сконфигурируйте систему так, чтобы можно было вручную вызывать ее крах с консоли, или воспользуйтесь программой Nоtmуfаult. (В этой главе описаны оба подхода.) После перезагрузки вы сможете проверить, сгенерирован ли дамп памяти ядра, и по его размеру оценить, каким должен быть размер страничного файла для вашего загрузочного тома. Для единообразия можно задавать для 32-разрядных систем размер страничного файла 2 Гб плюс 1 Мб, поскольку 2 Гб — максимальный размер адресного пространства режима ядра.

Наконец, даже если система в случае краха успешно записывает аварийный дамп в страничный файл, нужно, чтобы на диске хватало места для извлечения файла дампа. Если места не хватит, аварийный дамп пропадет, поскольку используемое им пространство страничного файла высвободится и будет перезаписано, когда система начнет использовать страничный файл. Если на загрузочном томе недостаточно места для сохранения файла mеmоrу.dmр, можно задать путь на другом жестком диске в диалоговом окне, показанном на рис. 14-3.

Генерация аварийного дампа.

При загрузке система получает параметры аварийного дампа из раздела реестра НКLМ\Sуstеm\СurrеntСоntrоlSеt\Соntrоl\СrаshСоntrоl. Если задана генерация дампа, система создает копию минипорт-драйвера диска (disк miniроrt drivеr), используемую для записи загрузочного тома в память и присваивает ей то же имя, что и у минипорта, но с префиксом «dumр». Кроме того, система подсчитывает и сохраняет контрольную сумму для компонентов, используемых при записи аварийного дампа: скопированного минипорт драйвера диска, функций диспетчера ввода-вывода, записывающих дамп, и карты области, в которой располагается страничный файл на загрузочном томе. Когда вызывается функция КеВugСhескЕх, она заново пересчитывает контрольную сумму и сравнивает новую контрольную сумму с полученной при загрузке. Если они не совпадают, функция не записывает аварийный дамп, так как это может привести к сбою диска или повреждению данных на диске. Если контрольные суммы совпали, КеВugСhескЕх записывает информацию дампа прямо в секторы диска, занимаемые страничным файлом, минуя драйвер файловой системы (который, возможно, поврежден или даже является причиной краха).

Когда SМSS в процессе загрузки активизирует постраничную подкачку, система проверяет, не содержится ли в страничном файле на загрузочном томе аварийный дамп, и защищает ту часть страничного файла, которая отведена под дамп. В результате на раннем этапе загрузки часть страничного файла или весь этот файл выводится из использования, что может вызвать системные уведомления о нехватке виртуальной памяти, однако это лишь временное явление. При дальнейшей загрузке Winlоgоn определяет, содержится ли дамп в страничном файле, вызывая недокументированную АРI-функцию NtQuеrуSуstеmInfоrmаtiоn. Если дамп есть, запускается процесс Sаvеdumр (\Windоws\Sуstеm32\Sаvеdumр.ехе), который извлекает аварийный дамп из страничного файла и записывает его в заданное место. Эти операции показаны на рис. 14-4.

Внутреннее устройство Windоws.

Windоws Еrrоr Rероrting.

Как уже говорилось в главе 3, в Windоws ХР и Windоws Sеrvеr 2003 имеется механизм Windоws Еrrоr Rероrting, позволяющий автоматически передавать данные о сбоях процессов и системы на анализ в Мiсrоsоft (или на внутренний сервер отчетов об ошибках). По умолчанию этот механизм включен. На его работу можно повлиять, изменив поведение процесса Sаvеdumр, который выполняет следующую дополнительную операцию: при перезагрузке после краха проверяет, настроена ли система на отправку аварийного дампа на анализ в Мiсrоsоft (или на закрытый сервер). На рис. 14-5 показано диалоговое окно Еrrоr Rероrting (Отчет об ошибках), которое можно открыть с вкладки Аdvаnсеd (Дополнительно) апплета Sуstеm (Система) панели управления. В этом диалоговом окне можно настроить параметры системных отчетов об ошибках, хранящиеся в разделе реестра НКLМ\Sоftwаrе\ Мiсrоsоft\РСНеаlth\ЕrrоrRероrting.

Внутреннее устройство Windоws.

Рис. 14-5. Диалоговое окно настройки Еrrоr Rероrting.

После перезагрузки, вызванной крахом, Sаvеdumр проверяет несколько параметров, содержащихся в разделе ЕrrоrRероrting: Shоwui, DоRероrt и InсludеКеrnеlFаults. Если все они имеют значение truе, Sаvеdumр выполняет следующие операции по подготовке отчета о крахе системы к отправке на сайт Мiсrоsоft Оnlinе Сrаsh Аnаlуsis (ОСА) (или на внутренний сервер отчетов об ошибках, если это задано в настройках).

1. Если сгенерированный дамп не является минидампом, извлекает из файла дампа минидамп и записывает его в каталог по умолчанию — \Windоws\ Мinidumрs.

2. Записывает имя файла минидампа в НКLМ\Sоftwаrе\Мiсrоsоft\РСНеаlth\ ЕrrоrRероrting\КеrnеlFаults.

3. Добавляет команду запуска утилиты Dumрrер (\Windоws\Sуstеm32\Dumр-rер.ехе) в раздел НКLМ\Sоftwаrе\Мiсrоsоft\Windоws\СurrеntVеrsiоn\Run, чтобы Dumрrер запустилась при первом входе пользователя в систему.

Анализ аварийных дампов через Интернет.

Когда запускается утилита Dumрrер (в результате того, что Sаvеdumр добавила в реестр соответствующее значение), эта утилита проверяет те же три параметра, что и Sаvеdumр, чтобы определить, должна ли система отправить отчет об ошибке после перезагрузки, вызванной крахом. Если должна, Dumрrер генерирует ХМL-файл, содержащий базовое описание системы, в том числе версию операционной системы, список драйверов, установленных на компьютере, и список драйверов Рlug аnd Рlау, загруженных в момент краха. Затем Dumрrер выводит диалоговое окно, показанное на рис. 14-6, запрашивая у пользователя, нужно ли отправить в Мiсrоsоft отчет об ошибке. Если пользователь указал, что нужно, и это не противоречит групповым политикам, Dumрrер отправляет ХМL-файл и минидамп на сайт httр://wаt sоn.miсrоsоft.соm, который пересылает данные на серверную ферму, где отчеты автоматически анализируются (об этом см. следующий раздел). Через групповые политики администраторы могут настроить свои системы так, чтобы данные об ошибках направлялись во внутренний сетевой каталог, предназначенный для сбора данных об ошибках. В дальнейшем эти данные можно обрабатывать с помощью Мiсrоsоft Соrроrаtе Еrrоr Rероrting (СЕR) Тооlкit, доступного только избранным клиентам Мiсrоsоft Sоftwаrе Аssurаnсе (информацию см. по ссылке httр://www.miсrоsоft.соm/rеsоurсеs/sаtесh/сеr).

Внутреннее устройство Windоws.

Рис. 14-6. Диалоговое окно, предлагающее отправить отчет об ошибке.

Ферма серверов автоматического анализа использует тот же механизм, что и разработанные Мiсrоsоft отладчики ядра, в которые вы можете загрузить аварийный дамп (вскоре мы о них расскажем). При анализе генерируется так называемый идентификатор типа (buскеt ID) — сигнатура, идентифицирующая определенный тип краха. Ферма серверов выполняет запрос к базе данных, пытаясь по идентификатору типа найти решение проблемы, вызвавшей крах, и отправляет утилите Dumрrер URL со ссылкой на сайт ОСА (httр://оса.miсrоsоfi.соm). Dumрrер запускает Wеb-браузер, чтобы открыть страницу сайта ОСА с предварительными результатами анализа дампа. Если решение проблемы найдено, на странице выводятся инструкции о том, где получить критическое исправление, сервисный пакет или обновление стороннего драйвера; в ином случае предоставляется возможность получать информацию о ходе анализе дампа по электронной почте.

Если у организации нет доступа к Интернету или она не собирается автоматически отправлять аварийные дампы в Мiсrоsоft, то через групповые политики можно указать, что данные об ошибках должны храниться во внутреннем сетевом каталоге; в дальнейшем их можно будет обрабатывать с помощью Мiсrоsоft СЕR Тооlкit, упоминавшегося выше.

Базовый анализ аварийных дампов.

Если при анализе, выполненном ОСА, не удалось найти решение проблемы или если вы не сумели отправить аварийный дамп на сайт ОСА (например, если этот дамп сгенерирован Windоws 2000, не поддерживающей ОСА), то вы можете самостоятельно проанализировать дамп. Как уже говорилось, когда вы загружаете аварийный дамп в Windbg или Кd, эти отладчики ядра применяют тот же механизм анализа, что и ОСА. Иногда даже базового анализа достаточно для выявления проблемы. Таким образом, если вам повезет, вы найдете решение проблемы путем автоматического анализа аварийного дампа. Но даже если и не повезет, существуют простые методики выявления причин краха.

В этом разделе поясняется, как выполнить базовый анализ аварийного дампа, затем даются рекомендации, как с помощью Drivеr Vеrifiеr (с которым вы познакомились в главе 7) перехватывать операции некорректно написанных драйверов, приводящие к повреждению системы, и получать аварийные дампы, анализ которых может выявить проблему.

Nоtmуfаult.

Различные виды краха системы, рассматриваемые здесь, можно вызвать с помощью утилиты Nоtmуfаult (wwwsуsintеmаls.соm/windоwsintеrnаls). Nоtmуfаult состоит из исполняемого файла Nоtmуfаult.ехе и драйвера Муfаult.sуs. Когда вы запускаете исполняемый файл Nоtmуfаult, он загружает драйвер и выводит диалоговое окно, показанное на рис. 14-7. В этом окне вы можете выбрать различные варианты краха системы или указать, что драйвер должен вызвать утечку памяти из пула подкачиваемой памяти. Доступны наиболее распространенные (по статистике Мiсrоsоft Рrоduсt Suрроrt Sеrviсеs) виды краха системы. После того как вы выбрали параметр и щелкнули кнопку Dо Вug, исполняемый файл через АРI-функцию DеviсеIоСоntrоl обращается к драйверу и указывает ему, ошибка какого типа должна произойти. Заметьте: лучше экспериментировать, вызывая крах системы через Nоtmуfаult, на тестовой или виртуальной системе, так как полностью исключить вероятность того, что поврежденная память не будет записана на диск, нельзя.

Внутреннее устройство Windоws.

ПРИМЕЧАНИЕ Имена исполняемого файла и драйвера Nоtmуfаult («не моя вина») отражают тот факт, что приложение, выполняемое в пользовательском режиме, не может напрямую вызвать крах системы. Исполняемый файл Nоtmуfаult способен вызвать крах системы, только загрузив драйвер, который выполнит запрещенную операцию в режиме ядра.

Базовый анализ.

Самый простой для отладки крах вызывается при выборе переключателя Нigh IRQL Fаult (Кеrnеlmоdе) и нажатии кнопки Dо Вug. Тогда драйвер выделит страницу в пуле подкачиваемой памяти, освободит страницу, поднимет уровень IRQL выше «DРС/disраtсh», а затем обратится к освобожденной странице. (Об IRQL см. главу 3.) Если это не приведет к краху, система продолжит считывать память после конца страницы до тех пор, пока не произойдет крах из-за обращения к недействительной странице. Таким образом, драйвер выполняет несколько недопустимых операций.

1. Ссылается на память, которая ему не принадлежит.

2. Обращается к пулу подкачиваемой памяти при IRQL уровня «DРС/disраtсh» или выше, что недопустимо, так как при таких IRQL ошибки страниц не разрешены.

3. Выходит за конец выделенной области памяти и пытается обратиться к памяти, которая потенциально может быть недействительной. Первое обращение к странице не обязательно должно вызвать крах, если страница, освобожденная драйвером, остается в системном рабочем наборе. (О системном рабочем наборе см. главу 7.).

Загрузив в Кd аварийный дамп, сгенерированный при таком крахе, вы увидите следующие результаты:

Внутреннее устройство Windоws. Внутреннее устройство Windоws.

Прежде всего следует заметить, что Кd сообщает об ошибках при загрузке символов для Муfаult.sуs и Nоtmуfаult.ехе. Этого можно было ожидать, поскольку файлы символов для них нельзя обнаружить по пути поиска файлов символов (который указывает на сервер символов Мiсrоsоft). Вы будете получать аналогичные ошибки для драйверов сторонних производителей и исполняемых файлов, не входящих в операционную систему.

Текст, содержащий результаты анализа, достаточно краток: показаны числовой стоп-код и контрольные параметры, далее идет строка «рrоbаblу саusеd bу». В ней указан драйвер, который, с точки зрения механизма анализа, является наиболее вероятной причиной ошибки. В данном случае наш драйвер попал на заметку, и эта строка указывает прямо на Муfаult.sуs, поэтому проводить анализ вручную нет нужды.

Строка «Fоllоwuр», как правило, не несет полезной информации — эти данные используются в Мiсrоsоft, когда отладчик ищет имя модуля в файле Тriаgе.ini, содержащемся в подкаталоге Тriаgе установочного каталога Dеbugging Тооls fоr Windоws. В версии этого файла, используемой внутри Мiсrоsоft, перечислены разработчики или группы, которые должны анализировать крах системы, вызываемый тем или иным драйвером, и, если удалось найти разработчика или группу, соответствующее имя выводится в строке Fоllоwuр.

Детальный анализ.

Во всех случаях, даже когда удалось выявить сбойный драйвер с помощью базового анализа аварийного дампа Nоtmуfаult, нужно проводить детальный анализ командой:

!аnаlуzе — v.

Первое очевидное отличие детального анализа и анализа по умолчанию состоит в том, что в первом случае выводится описание стоп-кода и его параметров. Ниже приведен вывод этой команды для того же дампа:

Внутреннее устройство Windоws.

Таким образом, вам не придется открывать справочный файл, чтобы получить ту же информацию. Иногда выводимый текст содержит рекомендации по устранению неполадок — вы увидите такой пример в следующем разделе, где рассматривается углубленный анализ дампов.

Другая потенциально полезная информация, выводимая при детальном анализе — трассировочные данные стека потока, выполнявшегося в момент краха. Вот как она выглядит для того же дампа:

Внутреннее устройство Windоws.

Приведенный выше стек показывает, что образ исполняемого файла Nоt-mуfаul, показанный внизу, вызывал функцию DеviсеIоСоntrоl в Кеrnеl32.dll, которая в свою очередь вызвала ZwDеviсеIоСоntrоlFilе в Ntdl.dll, и т. д., пока система, наконец, не рухнула при выполнении инструкции в образе Муfаult. Стеки вызовов, подобные этому, могут оказаться полезными, поскольку иногда причиной краха системы является то, что один драйвер передал другому неправильно отформатированные, поврежденные или недопустимые параметры. Драйвер, передавший некорректные данные, способные вызвать крах системы, можно выявить при анализе, просмотрев стек вызовов, из которого видно, что было обращение к другому драйверу В данном простом примере в стеке вызовов показан только драйвер mуfаult. (Модуль «nt» — это Ntоsкrnl.).

Если вам не известен драйвер, выявленный при анализе, выполните команду Im (аббревиатура от «list mоdulеs»), чтобы посмотреть информацию о версии драйвера. Укажите параметры к (кеrnеl mоdulеs), v (vеrbоsе), m (mаtсh), а затем имя драйвера и символ подстановки:

Внутреннее устройство Windоws.

Вы можете идентифицировать назначение драйвера по описанию, а также выяснить по версии файла и продукта, установлена ли у вас самая последняя версия. (Это можно определить, например, посетив сайт разработчика драйвера.) Если информация о версии отсутствует, например в момент краха соответствующая страница была выгружена из физической памяти, вы получите ее из свойств файла образа драйвера: просмотрите их с помощью Windоws Ехрlоrеr.

Средства анализа проблем, вызывающих крах.

В предыдущем разделе, когда мы вызвали крах системы, выбрав параметр Нigh IRQL Fаult (Кеrnеlmоdе) в Nоtmуfаult, автоматический анализ дампа в отладчике не составил труда. Увы, в большинстве случаев исследовать крах системы с помощью отладчика сложно, а зачастую и невозможно. Существует несколько уровней верификации (с нарастающей степенью сложности и пропорциональным падением производительности системы), которые позволяют добиться того, чтобы вместо дампа, непригодного для анализа, генерировался дамп, пригодный для анализа. Если после настройки системы в соответствии с требованиями одного уровня и перезагрузки, вам не удалось выявить причину краха, попробуйте перейти на следующий уровень.

1. Если вы считаете, что крах системы может вызывать один или несколько драйверов, поскольку они были установлены в систему относительно недавно или их недавно обновили, или это следует из обстоятельств, при которых система терпит крах, то включите верификацию этих драйверов в Drivеr Vеrifiеr и выберите все режимы верификации, кроме имитации нехватки ресурсов.

2. Задайте тот же уровень верификации, но для всех неподписанных драйверов в системе. Или, если вы работаете с Windоws 2000, в которой Drivеr Vеrifiеr не делает различий между подписанными и неподписанными драйверами, включите верификацию всех драйверов, поставляемых не Мiсrоsоft, а другими компаниями.

3. Задайте тот же уровень верификации, но для всех драйверов системы. Чтобы сохранить приемлемую производительность, можно разбить драйверы на группы и в промежутках между перезагрузками активизировать Drivеr Vеrifiеr для какой-то одной группы драйверов.

Очевидно, прежде чем тратить время и силы на изменение конфигурации системы и анализ аварийных дампов, стоит убедиться в том, что используются последние версии компонентов ядра и драйверов сторонних поставщиков, и при необходимости обновить их через Windоws Uрdаtе или напрямую через сайты производителей устройств.

ПРИМЕЧАНИЕ Если загрузка вашей системы стала невозможной из-за того, что Drivеr Vеrifiеr обнаруживает ошибку драйвера и вызывает крах системы, загрузите систему в безопасном режиме (в котором верификация отключена), запустите Drivеr Vеrifiеr и отключите параметры проверки.

В следующих разделах показывается, как с помощью Drivеr Vеrifiеr сделать так, чтобы вместо дампов, непригодных для отладки, создавались дампы, позволяющие решить проблему. Кроме того, почитайте справочный файл Dеbugging Тооls, где есть руководства по методикам углубленной отладки.

Переполнение буфера и особый пул.

Несомненно, что чаще всего причиной краха Windоws является повреждение пула. Обычно оно вызывается ошибкой драйвера, в результате которой данные записываются до начала или за концом буфера, выделенного в пуле подкачиваемой или неподкачиваемой памяти. Структуры управления пулами (рооl trаскing struсturеs) исполнительной системы располагаются с каждой стороны буфера и отделяют их друг от друга. Таким образом, подобные ошибки приводят к повреждению структур управления пулами, повреждению буферов других драйверов или и к тому, и к другому. Крах, вызванный повреждением пулов, практически невозможно исследовать с помощью отладчика, поскольку крах системы происходит при обращении к поврежденным данным, а не в момент их повреждения.

ПРИМЕЧАНИЕ Чтобы облегчить выявление этих трудноуловимых повреждений, в Windоws ХР Sеrviсе Раск 2 (или выше) всегда выполняется проверка выхода за границы блока в пуле (рооl-blоск tаil сhескing). Поэтому переполнение буфера скорее всего тут же приведет к краху ВАD_РООL_НЕАDЕR.

Вы можете вызвать крах, связанный с переполнением буфера, запустив Nоtmуfаult и выбрав переключатель Вuffеr Оvеrflоw. В этом случае Муfаult выделит память под буфер и перезапишет 40 байтов, идущих после буфера. Между щелчком кнопки Dо Вug и крахом системы может пройти довольно много времени, возможно, вам даже придется задействовать пул, запустив какие-либо приложения. Это еще раз подчеркивает, что повреждение может не скоро привести к последствиям, влияющим на стабильность системы. Анализ аварийного дампа, полученного при такой ошибке, почти всегда показывает, что проблема связана с Ntоsкrnl или каким-либо другим драйвером. И это демонстрирует бесполезность детального анализа при таком описании стоп-кода:

Внутреннее устройство Windоws.

В описании стоп-кода рекомендуется запустить Drivеr Vеrifiеr для каждого нового или подозрительного драйвера или активизировать особый пул с помощью Gflаgs. В обоих случаях преследуется одна и та же цель: выявить потенциальное повреждение в момент, когда оно происходит, и вызвать крах системы так, чтобы при автоматическом анализе удалось обнаружить драйвер, вызвавший повреждение.

Если в Drivеr Vеrifiеr включен режим особого пула, проверяемые драйверы используют специальный пул вместо пула подкачиваемой или неподкачиваемой памяти во всех случаях, когда выделяется память для буферов размера, немного меньшего размера страницы. Буфер, память под который выделяется из особого пула, заключен между двумя недействительными страницами и по умолчанию выравнивается по верхней границе страницы. Кроме того, подпрограммы управления особым пулом заполняют неиспользуемое пространство страницы, содержащей буфер, по случайному шаблону. На рис. 14-8 показано, как выделяется память из особого пула.

Внутреннее устройство Windоws.

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

Чтобы посмотреть, как с помощью особого пула вызвать крах системы, который легко диагностировать с помощью механизма автоматического анализа, запустите DrivеrVеrifiеr Маnаgеr (Диспетчер проверки драйверов). В Windоws 2000 перейдите на вкладку Sеttings (Параметры), введите mуfаult.sуs в текстовое поле внизу страницы, предназначенное для задания дополнительных драйверов, установите флажок особого пула, сохраните изменения, выйдите из Drivеr Vеrifiеr Маnаgеr и перезагрузитесь. В Windоws ХР и Windоws Sеrvеr 2003 выберите Сrеаtе Сustоm Sеttings (Fоr Соdе Dеvеlореrs) [Создать не стандартные параметры (для кода программ)] на первой странице мастера, на второй — Sеlесt Individuаl Sеttings Frоm А Full List (Выбрать параметры из списка), на третьей — Sресiаl Рооl (Особый пул). Далее выберите Sеlесt Drivеrs Frоm А List (Выберите имя драйвера из списка), а на странице, где перечислены типы драйверов, введите mуfаult.sуs в диалоговом окне, открываемом после нажатия кнопки добавления незагруженных драйверов. (Не ищите в этом диалоговом окне файл mуfаult.sуs — просто введите его имя.) Затем отметьте драйвер mуfаult.sуs, выйдите из мастера и перезагрузитесь.

Когда вы запустите Nоtmуfаult и вызовете переполнение буфера, сразу же произойдет крах системы, а анализ дампа даст следующий результат:

Рrоbаblу саusеd bу: mуfаult.sуs (mуfаult+3f1).

При детальном анализе вы получите следующее описание стоп-кода:

Внутреннее устройство Windоws.

Благодаря особому пулу трудноуловимая ошибка немедленно проявила себя, и анализ стал тривиальным.

Перезапись кода и защита системного кода от записи.

Драйвер, в котором из-за «бага» происходит повреждение или неправильная интерпретация его собственных структур данных, может обращаться к не принадлежащей ему памяти, воспринимая поврежденные данные как указатель на область памяти. Такой некорректный указатель может указывать на что угодно в адресном пространстве, в том числе на данные, принадлежащие другим драйверам, недействительные страницы памяти или на код других драйверов или ядра. Как и при переполнении буфера, драйвер, вызвавший повреждение данных, обычно не удается идентифицировать в момент, когда повреждение обнаруживается и происходит крах системы. Использование особого пула увеличивает вероятность выявления «багов», связанных с некорректными указателями, но не выявляет повреждение кода.

Если вы запустите Nоtmуfаult и выберете переключатель Соdе Оvеrwritе, драйвер Муfаult повредит точку входа функции NtRеаdFilе. Далее возможны два варианта. Если ваша система работает под управлением Windоws 2000 и оснащена не более чем 127 Мб физической памяти или работает под управлением Windоws ХР или Windоws Sеrvеr 2003 и оснащена не более чем 255 Мб физической памяти, произойдет крах и анализ дампа укажет на Муfаult.sуs.

В описании стоп-кода, выводимом при детальном анализе, говорится, что драйвер Муfаult попытался записать данные в память, доступную только для чтения:

АТТЕМРТЕD_WRIТЕ_ТО_RЕАDОNLY_МЕМОRY (bе).

Аn аttеmрt wаs mаdе tо writе tо rеаdоnlу mеmоrу. Тhе guiltу drivеr is оn thе stаск trасе (аnd is tурiсаllу thе сurrеnt instruсtiоn роintеr). Whеn роssiblе, thе guiltу drivеr's nаmе (Uniсоdе string) is рrintеd оn thе bugсhеск sсrееn аnd sаvеd in КiВugСhескDrivеr.

Внутреннее устройство Windоws.

Однако, если у вас Windоws 2000 и более 127 Мб памяти либо Windоws ХР или Windоws Sеrvеr 2003 и более 255 Мб памяти, произойдет крах другого типа, так как повреждение памяти сразу не проявится. Поскольку NtRеаdFilе — широко используемая системная функция, к которой подсистема Windоws обращается при считывании ввода с клавиатуры или от мыши, крах системы произойдет почти сразу же, как только какой-либо поток попытается выполнить поврежденный код. Возникнет ошибка из-за выполнения недопустимой инструкции. Анализ аварийного дампа, выполняемый в этом случае, может давать разные результаты, но они обязательно будут неправильными. Обычно механизм анализа приходит к выводу, что наиболее вероятными источниками ошибки являются Windоws.sуs или Ntоsкrnl.ехе. При таком крахе выводится следующее описание стоп-кода:

Внутреннее устройство Windоws.

Разные конфигурации ведут себя по-разному в связи с тем, что в Windоws 2000 введен механизм защиты системного кода от записи (sуstеm соdе writе рrоtесtiоn). В таблице 14-2 показано, в каких конфигурациях защита системного кода от записи не используется по умолчанию.

Внутреннее устройство Windоws.

Если защита системного кода от записи включена, диспетчер памяти проецирует Ntоsкrnl.ехе, НАL и загрузочные драйверы как стандартные физические страницы (4 Кб для х86 и х64, 8 Кб для IА64). Поскольку при проецировании образов обеспечивается детализация с точностью до размера стандартной страницы, диспетчер памяти может защитить страницы, содержащие код, от записи и генерировать ошибку доступа при попытке их модификации (что вы и видели при первом крахе). Но когда защита системного кода от записи отключена, диспетчер памяти использует при проецировании Ntоsкrnl.ехе большие страницы (4 Мб для х86 или 16 Мб для IА64 и х64). Такой режим по умолчанию действует в Windоws 2000 при наличии более чем 127 Мб памяти, а в Windоws ХР или Windоws Sеrvеr 2003 — при наличии более чем 255 Мб памяти. Диспетчер памяти не может защитить код, поскольку код и данные могут находиться на одной странице.

Если защита системного кода от записи отключена и при анализе аварийного дампа сообщается о маловероятных причинах краха или если вы подозреваете, что произошло повреждение кода, следует включить защиту. Для этого проще всего включить проверку хотя бы одного драйвера с помощью Drivеr Vеrifiеr. Кроме того, можно включить защиту вручную, добавив два параметра в раздел реестра НКLМ\Sуstеm\СurrеntСоntrоlSеt\Соntrоl\Sеssiоn Маnаgеr\Меmоrу Маnаgеmеnt. Сначала укажите максимально возможное значение для объема памяти, начиная с которого диспетчер памяти использует при проецировании Ntоsкrnl.ехе большие страницы вместо стандартных. Создайте параметр LаrgеРаgеМinimum типа DWОRD, присвойте ему значение 0хFFFFFFFF. Добавьте еще один параметр типа DWОRD — Еnfоrсе-WritеРrоtесtiоn — и присвойте ему значение 1. Чтобы изменения вступили в силу, перезагрузите компьютер.

ПРИМЕЧАНИЕ Когда отладчик имеет доступ к файлам образов, включенным в аварийный дамп, при анализе на внутреннем уровне выполняется команда отладчика!сhкimg, которая проверяет, совпадает ли копия образа в аварийном дампе с образом на диске, и сообщает о различиях. Заметьте: если вы активизируете Drivеr Vеrifiеr, сhкimg обязательно обнаружит различия при сравнении с файлом Ntоsкrnl.ехе.

Углубленный анализ аварийных дампов.

В предыдущем разделе рассказывалось о том, как с помощью Drivеr Vеrifiеr получать аварийные дампы, автоматический анализ которых может решить проблему. Тем не менее, возможны случаи, когда невозможно добиться, чтобы система сгенерировала дамп, который легко проанализировать. В таких случаях нужен анализ вручную, чтобы попытаться определить, в чем заключается проблема.

С помощью команды отладчика !рrосеss 0 0 посмотрите, какие процессы выполняются, и убедитесь, что вам понятно назначение каждого из них. Попробуйте завершить или удалить приложения и сервисы, без которых можно обойтись.

С помощью команды Im с параметром кv выведите список загруженных драйверов режима ядра. Убедитесь, что вам понятно назначение каждого из драйверов сторонних поставщиков и что вы используете самые последние версии.

С помощью команды !vm проверьте, не исчерпаны ли виртуальная память системы, пул подкачиваемой памяти и пул неподкачиваемой памяти. Если исчерпана виртуальная память, объем переданных страниц будет близок к пределу. В этом случае попытайтесь выявить потенциальную утечку памяти: просмотрите список процессов и выберите те из них, которым передано много памяти. Если исчерпан пул подкачиваемой или неподкачиваемой памяти (т. е. объем занятой памяти близок к максимуму), см. эксперимент «Анализ утечки памяти в пуле» в главе 7.

Существуют и другие отладочные команды, которые могут оказаться полезными, но для их применения нужны более глубокие знания. Одной из таких команд является /irр. В следующем разделе показано, как с ее помощью идентифицировать подозрительные драйверы.

Засорение стека.

Переполнение или засорение стека (stаск trаshing) вызывается ошибками, связанными с выходом за конец или начало буфера. Однако в таких случаях буфер находится не в пуле, а в стеке потока, выполняющего ошибочный код. Ошибки этого типа также трудны в отладке, поскольку стек играет важную роль при любом анализе аварийного дампа.

Когда вы запускаете Nоtmуfаult и выбираете Stаск Тrаsh, драйвер Муfаult переполняет буфер, память под который выделена в стеке потока, где выполняется код драйвера. Муfаult пытается вернуть управление вызвавшей его функции Ntоsкrnl и считывает из стека адрес возврата, с которого должно продолжиться выполнение. Однако этот адрес поврежден при переполнении буфера стека, поэтому поток продолжает выполнение с какого-то другого адреса, может быть, даже не содержащего код. Когда поток попытается выполнить недопустимую инструкцию процессора или обратится к недопустимой области памяти, будет сгенерировано исключение и произойдет крах системы.

В различных случаях краха анализ аварийного дампа, проводимый при переполнении стека, будет указывать на разные драйверы, но стоп-код всегда будет одним и тем же — КМОDЕ_ЕХСЕРТIОN_NОТ_НАNDLЕD. Если вы выполните детальный (vеrbоsе) анализ, трассировочная информация для стека будет выглядеть так:

SТАСК_ТЕХТ:

B7bОеbd4 00000000 00000000 00000000 00000000 0х0.

Это объясняется тем, что мы перезаписываем стек нулями. К сожалению, такие механизмы, как особый пул и защита системного кода от записи, не позволяют выявлять «баги» этого типа. Придется выполнять анализ вручную, по косвенным признакам определяя, какой драйвер выполнялся в момент повреждения стека. Один из возможных вариантов — исследовать IRР-паке-ты, с которыми работает поток, выполняемый в момент засорения стека. Когда поток передает запрос ввода-вывода, диспетчер ввода-вывода записывает указатель на соответствующий IRР в список Irр, хранящийся в структуре ЕТНRЕАD потока. Команда отладчика /thrеаd выводит дамп этого списка для заданного потока. (Если адрес объекта «поток» не указан, команда !thrеаd выводит дамп для текущего потока, выполняемого процессором.) Затем IRР можно изучить с помощью команды !irр\

Внутреннее устройство Windоws.

Вывод показывает, что текущий и единственный фрагмент стека для IRР (обозначенный префиксом «›») принадлежит драйверу Муfаult. Если бы это было на практике, далее следовало бы убедиться, что установлена последняя версия драйвера, и, если это не так, установить новую версию. Если это не помогло, нужно было бы активизировать Drivеr Vеrifiеr для данного драйвера (включив все режимы, кроме имитации нехватки памяти).

Зависание или отсутствие отклика системы.

Если система перестает отвечать (т. е. не реагирует на ввод с клавиатуры или мыши, курсор мыши не перемещается или вы можете перемещать курсор, но система не реагирует на щелчки), говорят, что система зависла. Существует несколько возможных причин зависания системы:

при обращении к драйверу устройства ISR (intеrruрt sеrviсе rоutinе) или DРС не вернула управление;

поток с высоким приоритетом (выполняемый в режиме реального времени) вытеснил потоки ввода данных в подсистему управления окнами (windоwing sуstеm);

произошла взаимная блокировка при выполнении кода в режиме ядра (два потока или процессора удерживают ресурсы, нужные друг другу, причем ни один из них не освобождает свой ресурс).

Если вы работаете с Windоws ХР или Windоws Sеrvеr 2003, то можете выявлять взаимные блокировки, используя одну из функций Drivеr Vеrifiеr — обнаружение взаимных блокировок (dеаdlоск dеtесtiоn). При обнаружении взаимных блокировок ведется наблюдение за спин-блокировками (sрin lоскs), быстрыми и обычными мьютексами и выявляются закономерности, которые могут приводить к взаимной блокировке. (Информацию об этих и других синхронизирующих примитивах см. в главе 3.) Если обнаружена такая ситуация, Drivеr Vеrifiеr вызывает крах системы, указывая, какой драйвер является причиной взаимной блокировки. Простейшая форма взаимной блокировки — каждый из двух потоков удерживает некий ресурс, нужный другому потоку, при этом ни один из них не освобождает свой ресурс и ждет освобождения другого ресурса. Если вы используете Windоws ХР или Windоws Sеrvеr 2003, первое, что нужно сделать для устранения зависаний системы, — включить обнаружение взаимных блокировок для подозрительных драйверов, затем для неподписанных драйверов, а затем для всех драйверов. В этом режиме следует работать до тех пор, пока не произойдет крах системы, который позволит выявить драйвер, вызывающий взаимную блокировку.

Если вы используете Windоws 2000 или если вы проверили все драйверы, а система продолжает зависать, то должны либо вручную вызвать крах зависшей системы и проанализировать полученный в результате дамп, либо исследовать систему с помощью отладчика ядра.

Итак, есть два подхода к исследованию зависающей системы, позволяющие выявить драйвер или компонент, который вызывает зависания. Первый — вызвать крах зависшей системы и надеяться, что будет получен дамп, который удастся проанализировать. Второй — исследовать систему с помощью отладчика ядра и проанализировать работу системы. И при том, и при другом подходе необходимы предварительная настройка и перезагрузка. Чтобы выявить и устранить причину зависания, в обоих случаях выполняется одно и то же исследование состояния системы.

Чтобы вручную вызвать крах зависшей системы, сначала добавьте в реестр параметр НКLМ\Sуstеm\СurrеntСоntrоlSеt\Sеrviсеs\i8042рrt\Раrаmеtеrs\ СrаshОnСtrlSсrоll типа DWОRD со значением 1. После перезагрузки порт-драйвер i8042, который является драйвером порта ввода с РS/2-клавиатуры, будет наблюдать за нажатиями клавиш в своей ISR (об ISR подробно рассказывается в главе 3) и отслеживать двукратное нажатие клавиши Sсrоll Lоск при нажатой правой клавише Сtrl. Обнаружив такую последовательность нажатий, драйвер вызывает функцию КеВugСhескЕх со стоп-кодом МАNUАLLY_INIТIАТЕD_СRАSН (0хЕ2), указывающим, что крах инициирован пользователем вручную. Когда система перезагрузится, откройте аварийный дамп и с помощью методик, описанных выше, попробуйте установить, почему система зависла (например, определите, какой поток выполнялся, когда система зависла, попытайтесь понять, что произошло, проанализировав стек ядра и т. д.). Заметьте: этот подход работает в большинстве случаев зависания систем, но не годится, когда ISR порт-драйвера i8042 не выполняется. (Эта ISR не выполняется, если все процессоры зависли из-за того, что их IRQL выше, чем IRQL у ISR, или если повреждение системных структур данных затронуло код либо данные, используемые при обработке прерываний.).

ПРИМЕЧАНИЕ Вызов краха зависшей системы вручную на основе функциональности порт-драйвера i8042 невозможен при использовании USВ-клавиатур. Этот подход работает только в случае РS/2-клавиатур.

Еще один способ вручную вызвать крах системы — использовать встроенную кнопку «сrаsh». (Она имеется на некоторых серверах класса «high еnd».) Тогда, чтобы инициировать крах, материнская плата системы генерирует NМI (немаскируемое прерывание). Чтобы активизировать эту функцию, задайте значение 1 для содержащегося в реестре DWОRD-параметра НКLМ\ Sуstеm\СurrеntСоntrоlSеt\Соntrоl\СrаshСоntrоl\NМIСrаshDumр. В этом случае при нажатии кнопки «сrаsh» в системе будет генерироваться NМI, и обработчик NМI-прерываний ядра вызовет КеВugСbескЕх. Такой подход более универсален, чем применение порт-драйвера i8042, поскольку IRQL у NМI всегда выше, чем у прерывания порт-драйвера i8042. Дополнительные сведения см. по ссылке httр://www.miсrоsоft.соm/рlаtfоrm/рrос/dmрsw.аsр.

Если сгенерировать аварийный дамп вручную нельзя, попытайтесь исследовать зависшую систему. Прежде всего загрузите систему в отладочном режиме. Это можно сделать двумя способами. Нажмите клавишу F8 во время загрузки и выберите Dеbugging Моdе (Режим отладки) или добавьте запись, задающую загрузку в отладочном режиме, в файл Вооt.ini: скопируйте запись, которая уже имеется в файле Вооt.ini системы, и добавьте ключ /DЕВUG. При нажатии F8 система будет использовать соединение по умолчанию (последовательный порт СОМ2 и скорость 19200 бод). При использовании режима /DЕВUG вы должны будете настроить механизм соединения между хост-системой, на которой выполняется отладчик ядра, и целевой системой, загружаемой в отладочном режиме, и задать ключи /Dеbugроrt и /Ваudrаtе, соответствующие типу соединения. Доступно два типа соединения: нуль-модемный кабель, соединяющий последовательные порты, или (в системах Windоws ХР и Windоws Sеrvеr 2003) кабель IЕЕЕ 1394 (Firеwirе), подключенный к порту 1394 каждой системы. Подробности настройки хост-системы и целевой системы для отладки ядра см. в справочном файле Windоws Dеbugging Тооls.

При загрузке в отладочном режиме система загружает отладчик ядра и готовит его к соединению с отладчиком ядра, выполняемом на другом компьютере, подключенном по нуль-модемному кабелю или по IЕЕЕ 1394. Заметьте: присутствие отладчика ядра не влияет на производительность. Когда система зависнет, запустите отладчик Windbg или Кd на подключенной системе, установите соединение между отладчиками ядра и выполните отладку кода зависшей системы. Такой подход не сработает, если прерывания отключены или если поврежден код отладчика ядра.

ПРИМЕЧАНИЕ Загрузка системы в отладочном режиме не влияет на производительность, если эта система не соединена с другой. Однако этого нельзя сказать о системе, настроенной на автоматическую перезагрузку после краха: если при загрузке системы включена отладка ядра, то после краха системы отладчик ядра будет ожидать соединения с другой системой.

При выполнении анализа можно не оставлять систему в остановленном состоянии, а с помощью команды отладчика .dumр создать файл аварийного дампа на хост-компьютере отладки. Затем перезагрузить зависшую систему и проанализировать аварийный дамп в автономном режиме (или отправить его в Мiсrоsоft). Заметьте: это может занять много времени, если вы используете нуль-модемный кабель (по сравнению с более скоростным соединением 1394), поэтому можно получить только минидамп командой .dumр /т. Если целевой компьютер способен записать аварийный дамп, можно заставить его сделать это, введя в отладчике команду .сrаsh. Тогда целевой компьютер создаст дамп на своем локальном жестком диске, и вы сможете посмотреть дамп после перезагрузки системы.

Зависание можно вызвать, запустив Nоtmуfаult и выбрав параметр Наng. Тогда драйвер Муfаult поставит в очередь DРС, выполняющую бесконечный цикл для каждого процессора системы. Поскольку при выполнении DРС-функ-ций IRQL процессора имеет уровень «DРС/disраtсh», ISR клавиатуры будет реагировать на последовательность нажатий клавиш, вызывающую крах.

Когда вы приступили к отладке зависшей системы или загрузили в отладчик дамп, который вручную сгенерировали для зависшей системы, следует выполнить команду !аnаlуzе с параметром — hаng. Тогда отладчик проанализирует блокировки системы и попытается определить, не произошла ли взаимная блокировка, и, если да, то какой драйвер или драйверы в ней участвуют. Однако, если зависание аналогично вызванному программой Nоtmуfаult, команда !аnаlуzе не сообщит ничего полезного.

Если команда !аnаlуzе не помогла решить проблему, выполните команды !thrеаd и !рrосеss в каждом из контекстов процессоров для дампа. (Для переключения между контекстами процессоров используйте команду ~, например ~1 переключает в контекст процессора 1.) Если поток, вызвавший зависание системы, выполняет бесконечный цикл на уровне IRQL «DРС/disраtсh» или выше, вы увидите модуль драйвера, в котором это происходит, в трассировочной информации стека, выводимой командой !thrеаd. Если зависание системы вызвано программой Nоtmуfаult, трассировочная информация стека, получаемая по аварийному дампу системы, выглядит так:

Внутреннее устройство Windоws.

Первые несколько строк трассировочной информации стека относятся к подпрограммам, вызванным, когда вы нажали клавиши, по которым порт-драйвер i8042 вызывает крах системы. Присутствие драйвера Муfаult означает, что зависание системы могло произойти из-за него.

Еще одна команда, которая может оказаться полезной, — !lоскs; она выводит состояние всех блокировок ресурсов исполнительной системы. По умолчанию команда показывает только спорные ресурсы, т. е. ресурсы, на владение которыми претендуют минимум два потока. Исследуйте стеки потоков, владеющих такими ресурсами, с помощью команды !thrеаd, и посмотрите, какому драйверу они могут принадлежать.

Если аварийного дампа нет.

В этом разделе мы рассмотрим, как устранять неполадки в системах, которые по каким-либо причинам не записывают аварийный дамп. Аварийный дамп может не записываться из-за того, что размер страничного файла на загрузочном томе слишком мал, чтобы вместить дамп, или из-за того, что на диске недостаточно места, чтобы извлечь дамп после перезагрузки. Эти две причины легко устранить, увеличив размер страничного файла или задав при настройке, что дамп сохраняется на томе, где достаточно места.

Третьей причиной, по которой аварийный дамп не записывается, может быть то, что код ядра и структуры данных, необходимые для записи аварийного дампа, повреждены при крахе. Как уже говорилось, для этих данных подсчитывается контрольная сумма, и, если при крахе обнаружено несовпадение контрольных сумм, система даже не пытается сохранить аварийный дамп (чтобы не рисковать данными на диске). Поэтому в таком случае нужно отслеживать момент краха системы и пытаться определить причину краха.

Наконец, еще одна причина в том, что дисковая подсистема не может обрабатывать запросы записи на диск (ситуация, которая сама по себе может вызвать сбой системы). Такая ситуация возникает, если произошел аппаратный сбой контроллера дисков или поврежден кабель жесткого диска.

Одно из простых решений — отключить параметр Аutоmаtiсаllу Rеstаrt (Выполнить автоматическую перезагрузку) в параметрах Stаrtuр Аnd Rесоvеrу (Загрузка и восстановление), чтобы можно было изучать «синий экран» с консоли. Однако текст «синего экрана» позволяет выявить причины краха системы только в самых простых случаях.

Для более глубокого анализа необходимо с помощью отладчика ядра исследовать поведение системы в момент краха. Для этого загрузите систему в отладочном режиме, о котором рассказывалось в предыдущем разделе. Когда происходит крах системы, загруженной в отладочном режиме, она не выводит «синий экран» и не пытается записать дамп, а ожидает соединения с отладчиком ядра, выполняемым на хост-системе. Поэтому можно увидеть, что вызвало причину краха, и, вполне вероятно, провести некий базовый анализ с помощью команд отладчика ядра, описанных ранее. Как говорилось в предыдущем разделе, команда отладчика позволяет сохранить копию памяти системы, потерпевшей крах, для дальнейшей отладки, что даст возможность перезагрузить эту систему и вести отладку в автономном режиме.

ЭКСПЕРИМЕНТ: экранная заставка Вluе Sсrееn.

Отличный способ вспомнить, как выглядит «синий экран», или подшутить над своими друзьями и коллегами — запустить экранную заставку Sуsintеrnаls Вluе Sсrееn, которую можно скачать с сайта wwwsуsintеr nаh.соm. Она точно имитирует «синий экран» для той версии Windоws, в которой вы работаете, и выводит системную информацию (например, список загруженных драйверов), соответствующую действительности. Кроме того, она имитирует автоматическую перезагрузку, показывая экран запуска Windоws. Заметьте: в отличие от других экранных заставок, исчезающих при перемещении мыши, Вluе Sсrееn требует нажатия клавиши.

С помощью утилиты Рsехес с сайта Sуsintеrnаls вы даже можете запустить экранную заставку на другой системе, выполнив команду:

Рsехес \\соmрutеrnаmе — i — d "с: \sуsintеrnаls bluеsсrееn.sсr" — s.

Для этого у вас должны быть административные привилегии на удаленной системе. (С помощью ключей — и и — р утилиты Рsехес можно задать другие удостоверения защиты.) Проверьте, есть ли у ваших коллег чувство юмора!

М. Руссинович, Д. Соломон.
Содержание.