Введение во внутреннее устройство Windows
Котельников Евгений Вячеславович

Содержание


Лекция 1. Введение в операционные системы

Функции операционной системы. Структура операционной системы. Классификация операционных систем. Требования к операционным системам.

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

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

Для более полного понимания роли операционной системы рассмотрим составные компоненты любой вычислительной системы (рис.1.1).

Компоненты вычислительной системы


Рис. 1.1.  Компоненты вычислительной системы

Все компоненты можно разделить на два больших класса – программы или программное обеспечение (ПО, software) и оборудование или аппаратное обеспечение (hardware). Программное обеспечение делится на прикладное, инструментальное и системное. Рассмотрим кратко каждый вид ПО.

Цель создания вычислительной системы – решение задач пользователя. Для решения определенного круга задач создается прикладная программа (приложение, application). Примерами прикладных программ являются текстовые редакторы и процессоры (Блокнот, Microsoft Word), графические редакторы (Paint, Microsoft Visio), электронные таблицы (Microsoft Excel), системы управления базами данных (Microsoft Access, Microsoft SQL Server), браузеры (Internet Explorer) и т. п. Все множество прикладных программ называется прикладным программным обеспечением (application software).

Создается программное обеспечение при помощи разнообразных средств программирования (среды разработки, компиляторы, отладчики и т. д.), совокупность которых называется инструментальным программным обеспечением. Представителем инструментального ПО является среда разработки Microsoft Visual Studio.

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

Взаимодействие всех программ с операционной системой осуществляется при помощи системных вызовов (system calls) – запросов программ на выполнение операционной системой необходимых действий. Набор системных вызовов образует API – Application Programming Interface (интерфейс прикладного программирования).

Далее рассмотрим, какие функции должны выполнять современные операционные системы.

Функции операционной системы

К основным функциям, выполняемым операционными системами, можно отнести:

Структура операционной системы

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

Современные процессоры имеют минимум два режима работы – привилегированный (supervisor mode) и пользовательский (user mode).

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

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

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

Основным компонентом операционной системы является ядро (kernel). Функции ядра могут существенно отличаться в разных системах; но во всех системах ядро работает в привилегированном режиме (который часто называется режим ядра, kernel mode).

Термин "ядро" также используется в разных смыслах. Например, в Windows термин "ядро" (NTOS kernel) обозначает совокупность двух компонентов – исполнительной системы (executive layer) и собственно ядра (kernel layer) [12].

Существует два основных вида ядер – монолитные ядра (monolithic kernel) и микроядра (microkernel). В монолитном ядре реализуются все основные функции операционной системы, и оно является, по сути, единой программой, представляющей собой совокупность процедур [6]. В микроядре остается лишь минимум функций, который должен быть реализован в привилегированном режиме: планирование потоков, обработка прерываний, межпроцессное взаимодействие. Остальные функции операционной системы по управлению приложениями, памятью, безопасностью и пр. реализуются в виде отдельных модулей в пользовательском режиме.

Ядра, которые занимают промежуточные положение между монолитными и микроядрами, называют гибридными (hybrid kernel).

Примеры различных типов ядер:

Обсуждение того, к какому типу относится ядро Windows NT, приведено в [5; 2]. В [2] говорится о том, что Windows NT имеет монолитное ядро, однако, поскольку в Windows NT имеется несколько ключевых компонентов, работающих в пользовательском режиме (например, подсистемы окружения и системные процессы – см. Лекцию 4 "Архитектура Windows"), то относить Windows NT к истинно монолитным ядрам нельзя, скорее к гибридным.

Кроме ядра в привилегированном режиме (в большинстве операционных систем) работают драйверы (driver) – программные модули, управляющие устройствами.

В состав операционной системы также входят:

Пользовательские оболочки реализуют один из двух основных видов пользовательского интерфейса:

Пример реализации текстового интерфейса в Windows – интерпретатор командной строки cmd.exe; пример графического интерфейса – Проводник Windows (explorer.exe).

Классификация операционных систем

Классификацию операционных систем можно осуществлять несколькими способами.

  1. По способу организации вычислений:
    • системы пакетной обработки (batch processing operating systems) – целью является выполнение максимального количества вычислительных задач за единицу времени; при этом из нескольких задач формируется пакет, который обрабатывается системой;
    • системы разделения времени (time-sharing operating systems) – целью является возможность одновременного использования одного компьютера несколькими пользователями; реализуется посредством поочередного предоставления каждому пользователю интервала процессорного времени;
    • системы реального времени (real-time operating systems) – целью является выполнение каждой задачи за строго определённый для данной задачи интервал времени.
  2. По типу ядра:
    • системы с монолитным ядром (monolithic operating systems);
    • системы с микроядром (microkernel operating systems);
    • системы с гибридным ядром (hybrid operating systems).
  3. По количеству одновременно решаемых задач:
    • однозадачные (single-tasking operating systems);
    • многозадачные (multitasking operating systems).
  4. По количеству одновременно работающих пользователей:
    • однопользовательские (single-user operating systems);
    • многопользовательские (multi-user operating systems).
  5. По количеству поддерживаемых процессоров:
    • однопроцессорные (uniprocessor operating systems);
    • многопроцессорные (multiprocessor operating systems).
  6. По поддержке сети:
    • локальные (local operating systems) – автономные системы, не предназначенные для работы в компьютерной сети;
    • сетевые (network operating systems) – системы, имеющие компоненты, позволяющие работать с компьютерными сетями.
  7. По роли в сетевом взаимодействии:
    • серверные (server operating systems) – операционные системы, предоставляющие доступ к ресурсам сети и управляющие сетевой инфраструктурой;
    • клиентские (client operating systems) – операционные системы, которые могут получать доступ к ресурсам сети.
  8. По типу лицензии:
    • открытые (open-source operating systems) – операционные системы с открытым исходным кодом, доступным для изучения и изменения;
    • проприетарные (proprietary operating systems) – операционные системы, которые имеют конкретного правообладателя; обычно поставляются с закрытым исходным кодом.
  9. По области применения:
    • операционные системы мэйнфреймов – больших компьютеров (mainframe operating systems);
    • операционные системы серверов (server operating systems);
    • операционные системы персональных компьютеров (personal computer operating systems);
    • операционные системы мобильных устройств (mobile operating systems);
    • встроенные операционные системы (embedded operating systems);
    • операционные системы маршрутизаторов (router operating systems).

Требования к операционным системам

Основное требование, предъявляемое к современным операционным системам – выполнение функций, перечисленных выше в параграфе "Функции операционных систем". Кроме этого очевидного требования существуют другие, часто не менее важные [3]:

Резюме

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

В следующей лекции будет представлен обзор операционных систем Microsoft Windows.

Контрольные вопросы

  1. Дайте определение понятию "операционная система".
  2. Назовите примеры прикладного, инструментального и системного программного обеспечения.
  3. Дайте определение понятий "системный вызов", "API", "драйвер", "ядро".
  4. Какие виды ядер вы знаете? К каким видам относятся ядра известных вам операционных систем?
  5. Чем ядро отличается от операционной системы?
  6. Приведите несколько способов классификации операционных систем.
  7. Назовите требования к современным операционным системам и объясните, что они означают.

Лекция 2. Обзор операционных систем Windows

16-разрядные Windows. Windows 9x. Windows NT. Windows CE. Windows Mobile и Windows Phone.

Microsoft Windows – операционные системы корпорации Microsoft, различные версии которых предназначены для широкого класса устройств – от суперкомпьютеров до встроенных систем. В настоящее время Microsoft Windows установлена на большинстве персональных компьютеров: по данным сайта анализа веб трафика StatCounter (http://gs.statcounter.com) операционные системы Windows (версий XP, Vista, 7) в августе 2012 года были установлены на 88% компьютеров в мире; в то же время по данным компании веб-аналитики Net Applications (http://marketshare.hitslink.com) Windows занимает 92% рынка настольных компьютеров и ноутбуков.

В настоящее время существует несколько семейств (family) операционных систем Windows, предназначенных для использования на разных типах компьютеров:

Кроме того, в прошлом выпускались 16 разрядные операционные системы (Windows 1.0, Windows 2.х, Windows 3.х) и семейство операционных систем Windows 9x (Windows 95, Windows 98, Windows Me).

В данной лекции представлен краткий обзор семейств операционных систем Microsoft Windows (рис.2.1).

История развития семейств операционных систем Windows


увеличить изображение

Рис. 2.1.  История развития семейств операционных систем Windows

16 разрядные Windows

Первой Windows была Windows 1.0, выпущенная в ноябре 1985 года. Это была не полноценная операционная система, а надстройка над операционной системой MS-DOS. Windows 1.0 предоставляла пользователю графический оконный интерфейс и возможность запускать несколько приложений одновременно (и то и другое отсутствовало в MS DOS). Сначала эту программу хотели назвать Interface Manager, но затем склонились к названию Windows ("окна"), как более точно отражающему суть работы с новой программой [7]. Минимальные системные требования к памяти ограничивались 256 КБ.

В Windows 2.0 (декабрь 1987 года) были введены некоторые улучшения графического интерфейса (в частности поддержка перекрывающихся окон) и работы с памятью. Также для большего удобства стали использоваться комбинации клавиш. В мае 1988 года и в марте 1989 года появляются соответственно Windows 2.10 и Windows 2.11, поддерживающие новые на то время процессоры Intel 80286 и Intel 80386 [16].

В мае 1990 года выходит Windows 3.0 с улучшенной графикой и поддержкой виртуальной памяти. В 1992 1993 гг. появляются версии Windows for Workgroups 3.1 и 3.11, в которых имеется поддержка работы в одноранговых сетях и сетях под управлением сервера. Это были последние версии 16 разрядных Windows.

Windows 9x

В августе 1995 года выпускается Windows 95 – 32 разрядная клиентская операционная система, в которой была встроенная поддержка работы с Интернетом (браузер Internet Explorer) и модемными сетями, а также технология Plug-and-Play ("подключи и работай"), позволяющая быстро подключать к компьютеру различные устройства. Впервые появилась кнопка Пуск (Start) и Панель задач (Taskbar). Windows 95 требовала минимум 4 МБ оперативной памяти [7].

На смену Windows 95 в июне 1998 года приходит Windows 98 с множеством программ для работы с Интернетом (Internet Explorer 4, Outlook Express и др.), поддержкой DVD и USB, первым появлением Панели быстрого запуска программ (Quick Launch bar). Windows 98 была последней операционной системой, основанной на MS DOS [7].

Последней версией в семействе 9x стала Windows Me (Millennium Edition, сентябрь 2000 года). Эта система была нацелена на домашних пользователей, и, следовательно, имела широкую поддержку работы с мультимедиа (Windows Media Player 7, Windows Movie Maker), Интернетом и домашними сетями.

Другим направлением развития операционных систем Windows в 90 е годы стало семейство NT.

Windows NT

В июле 1993 года была выпущена первая операционная система семейства NT – Windows NT 3.1. Есть разные варианты объяснения названия NT, самый распространенный вариант – это аббревиатура от New Technology ("новая технология").

Разработка системы, основанной на новом ядре (не MS DOS), началась в 1989 году. К новой операционной системе предъявлялись следующие основные требования [5]:

Windows NT 3.1 соответствовала всем этим требованиям, а на ядре этой системы (конечно, с изменениями) основаны все современные версии Windows, включая Windows 8.

Windows NT 3.1 поддерживала процессоры Intel 80386, Intel 80486, MIPS R4000 и DEC Alpha [5]. Существовали клиентская и серверная версии системы – Windows NT и Windows NT Advanced Server. Windows NT, помимо других файловых систем, поддерживала специально разработанную в Microsoft файловую систему NTFS (New Technology File System).

В 1994 1996 годах последовательно выходят операционные системы Windows NT 3.5, Windows NT 3.51 и Windows NT 4.0. Целями разработки Windows NT 3.5 были повышение производительности и надежности, а также уменьшение размера системы. В Windows NT 3.51 была включена поддержка процессора IBM PowerPC. Windows NT 4.0 обладала таким же графическим интерфейсом как и система Windows 95 [5].

Windows 2000, вышедшая в декабре 1999 года, разрабатывалась в качестве системы для профессиональных пользователей, объединяющей два направления – Windows 9x и Windows NT [7]. Система Windows 2000 включала Active Directory (служба и базу данных ресурсов для управления большими сетями) и поддержку значительного числа Plug-and Play устройств, в том числе беспроводных сетей, USB, IEEE 1394 и др. Существовало 4 версии Windows 2000 – одна клиентская (Professional) и три серверных (Server, Advanced Server и Datacenter Server). Windows 2000 была последней системой, для которой выпускались одновременно клиентские и серверные версии.

Следующим шагом стало объединение обоих направлений клиентских систем: и систем для профессиональных пользователей (Windows 2000 Professional), и систем для домашних пользователей (Windows Me). Результатом такого объединения стала операционная система Windows XP (август 2001 года). Благодаря своей стабильности, скорости и удобному интерфейсу, Windows XP стала (и до сих пор является) одной из самых распространенных операционных систем в мире. Важным шагом явилось появление 64 разрядных версий Windows XP (Windows XP 64-bit Edition). Количество строк кода в Windows XP – 45 миллионов [7].

В марте 2003 года выходит серверная операционная система Windows Server 2003, имеющая большую производительность и поддерживающая более мощное оборудование, чем Windows 2000. Система имеет 4 основные версии: Web, Standard, Enterprise и Datacenter. Например, версия Datacenter поддерживает 64 процессора и до 64 ГБ оперативной памяти (до 512 ГБ на 64 разрядных платформах).

Клиентская операционная система Windows Vista вышла в ноябре 2006 года. Акцент при разработке этой системы был сделан на безопасность – контроль учетных записей пользователей (User Account Control), шифрование дисков (BitLocker Drive Encryption), антишпионское программное обеспечение (Windows Defender) и др. В Windows Vista был также изменен пользовательский интерфейс, в частности поменяла вид кнопка Пуск (Start).

В феврале 2008 года появилась операционная система Windows Server 2008, основанная на коде Windows Vista – поэтому большая часть нововведений Windows Vista перешла и в Windows Server 2008.

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

В 2012 году Microsoft выпускает новейшие версии операционных систем – клиентскую Windows 8 (октябрь 2012 года) и серверную Windows Server 2012 (сентябрь 2012 года). Windows 8 – операционная система, одинаково рассчитанная как на обычные настольные компьютеры и ноутбуки, так и на планшетные компьютеры, завоевавшие в последнее время существенную долю всего рынка персональных компьютеров (см. лекцию 3 "Windows 8").

Windows CE

Windows CE – операционная система реального времени для встраиваемых систем. Символы "CE", по утверждению Microsoft, обозначают "Compact, Connectable, Compatible, Companion, Efficient"1) . В настоящее время эта система имеет официальное название Windows Embedded Compact (http://www.microsoft.com/windowsembedded).

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

Первая версия Windows CE 1.0 появилась в 1996 году и была разработана как урезанная версия Windows 95. В дальнейшем команда разработчиков Windows CE сотрудничала с командой Windows 2000, затем Windows CE развивалась как независимая система.

На сентябрь 2012 года последней версией является Windows CE 7.0.

Windows Mobile и Windows Phone

Windows Mobile – операционная система для смартфонов и карманных персональных компьютеров (КПК, Personal Digital Assistant – PDA), основанная на Windows CE.

Первые версии операционных систем этого семейства назывались Pocket PC (2000 год). С 2003 года утвердилось наименование Windows Mobile – были выпущены операционные системы Windows Mobile 2003, Windows Mobile 5, Windows Mobile 6. Последней версией с таким названием стала система Windows Mobile 6.5 (2009 год).

С октября 2010 года Microsoft выпустила новую операционную систему для мобильных устройств – Windows Phone 7, несовместимую с Windows Mobile, хотя и основанную также на Windows CE. В Windows Phone 7 появился новый пользовательский интерфейс, в настоящее время называемый Modern UI.

В октябре 2012 года ожидается выход Windows Phone 8, основанной на ядре Windows NT.

Резюме

В лекции представлен обзор операционных систем Windows с 1985 года до 2012 года. Рассмотрены основные семейства и их ключевые представители – 16 разрядные Windows, Windows 9x, Windows NT, Windows NT Server, Windows Mobile/Windows Phone и Windows CE.

В следующей лекции приводится обзор новейшей операционной системы от Microsoft – Windows 8.

Контрольные вопросы

Лекция 3. Windows 8

Основные особенности. Версии Windows 8. Разработка приложений для Windows 8.

Windows 8 – новейшая операционная система от корпорации Microsoft, предназначенная для использования на персональных компьютерах, в том числе с сенсорными дисплеями.

Разработка Windows 8 началась в 2009 году и впервые система была анонсирована в январе 2011 года, а в сентябре того же года представлена предварительная версия для разработчиков Windows 8 Developer Preview. В феврале 2012 года выпускается предварительная версия Windows 8 Consumer Preview, в мае – Windows 8 Release Preview. В августе 2012 становится доступной окончательная версия Windows 8 для подписчиков MSDN и TechNet. Официальная дата начала продаж назначена на 26 октября 2012 года.

Ядро Windows 8 имеет номер версии 6.2 и его код основан на коде ядра Windows 7 (имеющего номер версии 6.1) с небольшими изменениями.

Основные особенности

Интерфейс

Самым заметным отличием новой системы от Windows 7 является, конечно, интерфейс Modern UI, который используется при старте системы вместо привычного рабочего стола (рис.3.1).

Интерфейс Modern UI


Рис. 3.1.  Интерфейс Modern UI

Впервые Modern UI появился в Windows Phone 7 в 2010 году. Принцип, используемый в этом интерфейсе, – на первом месте содержание, а не графическое оформление. Поэтому в Modern UI минимизировано использование элементов интерфейса – кнопок и меню; вместо иконок используются плитки (tiles), внутри которых текст выводится при помощи легко читаемых шрифтов, а для динамичного отображения информации широко используется анимация.

Традиционный рабочий стол также присутствует – его можно вызвать, щелкнув на плитку Desktop. Обратно к интерфейсу Modern UI можно вернуться, подведя указатель мыши в левый нижний угол экрана (один из четырех "активных углов") или нажав кнопку Windows на клавиатуре.

Другим изменением в интерфейсе стало использование Ribbon Interface (Ленточный интерфейс) в Проводнике Windows (рис.3.2).

Проводник Windows


Рис. 3.2.  Проводник Windows

Учетные записи

На компьютер под управлением Windows 8 можно войти, используя учетную запись Microsoft (Live ID). При этом становятся доступны все связанные с учетной записью сервисы – SkyDrive, Outlook.com, Microsoft Messenger, Facebook, LinkedIn, Twitter и др.

С помощью использования Live ID доступна функция семейной безопасности и родительского контроля (Microsoft Family Safety).

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

Программа Защитник Windows (Windows Defender), которая ранее обладала только антишпионскими функциями, теперь стала ещё и антивирусом.

Поддерживается механизм безопасной загрузки на системах с UEFI (Unified Extensible Firmware Interface – унифицированный расширенный интерфейс для встроенного программного обеспечения; стандарт, предназначенный для замены BIOS), путем проверки целостности загрузчика Windows. Таки образом, предотвращаются попытки вредоносных программ перехватить управление до загрузки системы.

Диспетчер задач

Диспетчер задач (Task Manager) существенно изменен по сравнению с предыдущими версиями: добавлены подробности по текущему использованию ресурсов, добавлена вкладка Автозапуск (Startup), добавлена вкладка истории использования приложениями различных ресурсов (App history) (рис.3.3).

Диспетчер задач (Task Manager)


Рис. 3.3.  Диспетчер задач (Task Manager)

История файлов

Функция История файлов (File history) автоматически сохраняет копии изменяемых файлов, так что при необходимости можно откатить изменения и вернуться к старым версиям файлов.

Восстановление системы

Добавлены две функции по восстановлению системы без использования носителей с дистрибутивом – Обновление (Refresh) и Сброс (Reset). При Обновлении система переустанавливается с сохранением пользовательских файлов и настроек; при Сбросе диск форматируется и система устанавливается с нуля.

Storage Spaces

Функция Storage Spaces позволяет объединять физические диски, построенные по разным технологиям (SATA, USB, SAS), в единый виртуальный диск с автоматическим резервированием информации.

Версии Windows 8

Планируется выпуск четырех версий Windows 8:

Минимальные системные требования для Windows 8 практически совпадают с требованиями для Windows 7:

Разработка приложений для Windows 8

Для Windows 8 стала возможной разработка нового типа Windows приложений – приложений в стиле Modern UI (см. раздел на MSDN [MSDN Apps]).

Особенности приложений в стиле Modern UI

У приложений в стиле Modern UI есть ряд особенностей, которые отличают их от традиционных Windows-приложений:

Пример строки команд (App bar) для музыкального проигрывателя


Рис. 3.4.  Пример строки команд (App bar) для музыкального проигрывателя

Панель Charms


Рис. 3.5.  Панель Charms

Инструменты

Для написания приложений в стиле Modern UI можно использовать среду разработки Visual Studio 2012, средство для создания пользовательского интерфейса Blend, шаблоны проектов Visual Studio (http://msdn.microsoft.com/ru-RU/windows/apps/br229516.aspx).

Поддерживаемые языки программирования – C#, С++, Visual Basic, JavaScript. Для разработки приложений, требующих эффективной работы с графикой, можно использовать Microsoft DirectX 11.

Для интеграции приложения с сервисами Hotmail, Windows Live Messenger, Microsoft SkyDrive и др. применяется Live SDK – набор специализированных API для доступа к информации пользователя этих сервисов.

Для более подробной информации см. [MSDN Apps; Лутай и др.; Techdays].

Резюме

Рассмотрены ключевые особенности и версии новейшей операционной системы Microsoft Windows 8. Приводится также информация о разработке приложений в стиле нового интерфейса Modern UI.

В следующей лекции мы переходим к изучению внутреннего устройства Windows и начинаем с рассмотрения архитектуры системы.

Контрольные вопросы

  1. Расскажите об истории разработки Windows 8.
  2. Перечислите основные особенности Windows 8.
  3. Назовите версии Windows 8 и отличия между ними.
  4. Каковы минимальные системные требования для установки Windows 8?
  5. Что такое "контракты" при разработке приложений в стиле Modern UI?
  6. Чем отличается Modern UI от традиционных интерфейсов?
  7. Какие новые элементы интерфейса появились в Modern UI?

Лекция 4. Архитектура Windows

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

Общая схема архитектуры

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

Архитектура Windows представлена на рис.4.1 [5; 2].

Архитектура Windows


Рис. 4.1.  Архитектура Windows

Компоненты пользовательского режима

В пользовательском режиме работают следующие виды процессов:

Все перечисленные процессы пользовательского режима (кроме подсистемы POSIX1)) для взаимодействия с модулями режима ядра используют библиотеки Windows DLL (Dynamic Link Library – динамически подключаемая библиотека). Каждая DLL экспортирует набор Windows API функций, которые может вызывать процесс.

Windows API (Windows Application Programming Interface, WinAPI) – это способ взаимодействия процессов пользовательского режима с модулями режима ядра. WinAPI включает тысячи функций и хорошо документирован [10].

Основные Windows DLL следующие:

Библиотека Ntdll.dll экспортирует в большинстве своем недокументированные системные функции, реализованные, в основном, в Ntoskrnl.exe. Набор таких функций называется Native API ("родной" API).

Библиотеки Windows DLL преобразуют вызовы документированных WinAPI функций в вызовы функций Native API и переключают процессор на режим ядра.

Компоненты режима ядра

Диспетчер системных сервисов (System Service Dispatcher) работает в режиме ядра, перехватывает вызовы функций от Ntdll.dll, проверяет их параметры и вызывает соответствующие функции из Ntoskrnl.exe.

Исполнительная система и ядро содержатся в Ntoskrnl.exe (NT Operating System Kernel – ядро операционной системы NT) (по поводу использования термина "ядро" в Windows см. лекцию 1 "Введение в операционные системы").

Исполнительная система (Executive) представляет собой совокупность компонентов (называемых диспетчерами – manager), которые реализуют основные задачи операционной системы:

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

Компонент Windows USER и GDI отвечает за пользовательский графический интерфейс (окна, элементы управления в окнах – меню, кнопки и т. п., рисование), является частью подсистемы Windows и реализован в драйвере Win32k.sys.

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

Однако, и драйверы, и ядро не взаимодействуют с физическими устройствами напрямую – посредником между программными компонентами режима ядра и аппаратурой является HAL (Hardware Abstraction Layer) – уровень абстрагирования от оборудования, реализованный в Hal.dll. HAL позволяет скрыть от всех программных компонентов особенности аппаратной платформы (например, различия между материнскими платами), на которой установлена операционная система.

Резюме

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

В следующей лекции рассматривается исследовательское ядро Windows (Windows Research Kernel) – вариант Ntoskrnl.exe с исходным кодом, доступным академическим организациям.

Контрольные вопросы

  1. К какому типу ядер в большей степени относится Windows NT, к монолитным или микроядрам? Ответ обоснуйте.
  2. Перечислите основные компоненты пользовательского режима.
  3. Перечислите основные компоненты режима ядра.
  4. Что такое Windows API? Где можно найти информацию по этому вопросу?
  5. Каковы основные функции исполнительной системы, входящей в состав Ntoskrnl.exe?
  6. Каковы основные функции ядра, входящего в состав Ntoskrnl.exe?
  7. Что такое HAL?

Лекция 5. Исследовательское ядро Windows

Windows Academic Program. Структура Windows Research Kernel. HTML‑документация по WRK.

Windows Academic Program

В 2006 году корпорация Microsoft в рамках академической программы Windows (Windows Academic Program) сделала доступной для академических организаций исходный код исследовательского ядра Windows (Windows Research Kernel, WRK) [15]. WRK основано на коде операционных систем Windows Server 2003 SP1 и Windows XP x64 [13; 11].

Кроме WRK в академическую программу Microsoft входят следующие компоненты [15]:

Все компоненты Windows Academic Program, кроме WRK и материалов для преподавателей (Instructor Supplement), доступны любому желающему. WRK и Instructor Supplement можно получить, подтвердив свой статус преподавателя или по подписке Microsoft Developer Network Academic Alliance (MSDN AA).

Microsoft, предоставляя академическому сообществу исходные коды ядра Windows, преследовало следующие цели [11]:

Исследовательское ядро Windows включает более 800 000 строк исходного кода, в основном на языке программирования C, но есть файлы и на ассемблере. В процессе подготовки к опубликованию исходный код в некоторых местах был упрощен, а комментарии улучшены [11].

На рис.5.1 представлена схема, отражающая покрытие исходным кодом WRK компонентов ядра [13].

Покрытие исходным кодом WRK компонентов ядра (выделено серым цветом)


Рис. 5.1.  Покрытие исходным кодом WRK компонентов ядра (выделено серым цветом)

Как видно из рисунка, исходные коды практически всех компонентов исполнительной системы (кроме диспетчера Plug-and-Play и диспетчера электропитания) и ядра представлены в WRK.

Структура Windows Research Kernel

В состав WRK, кроме собственно исходных кодов ядра Windows, входят руководство по ядру Windows NT (NT OS/2 Design Workbook) и решение (solution) Visual Studio 2008 (WRK.sln) (которое можно преобразовать для более новых версий Visual Studio).

Руководство по ядру Windows NT было составлено в конце 1980 х – начале 1990 х гг., когда в Microsoft велась разработка новой операционной системы Windows NT с рабочим названием "NT OS/2" сначала совместно с IBM, затем самостоятельно. Руководство содержит ценную информацию по структуре и функциям ядра Windows, а также раскрывает соображения, которые привели разработчиков к тем или иным архитектурным решениям.

Главные компоненты WRK находятся в папке WRK-v1.2\base\ntos и включают, в основном описания и определения функций и структур данных. В ядре Windows при именовании функций используются определенные соглашения [5; 2]. Название функции обычно строится по следующей схеме:

<Префикс><Операция><Объект>

где <Префикс> обозначает модуль, которому принадлежит функция, <Операция> – действие, совершаемое над <Объектом>.

Например, рассмотрим функцию KeStartThread:

В таблице 5.1 приведены основные компоненты WRK (см. соответствие с компонентами на рис.5.1) с указанием префиксов входящих в их состав функций.

Таблица 5.1. Компоненты WRK и префиксы функций
Компонент WRKПрефикс функцийНазвание компонента на англ. языкеНазвание компонента на русском языке
cacheCcCache managerдиспетчер кэша
config CmConfiguration managerдиспетчер конфигурации
dbgk DbgkDebugging Frameworkподсистема отладки
exExExecutive support routinesфункции поддержки исполнительной системы – синхронизация, таймеры, структуры данных исполнительной системы, системная информация
fsrtlFsRtlFile system driver run-time libraryбиблиотека функций поддержки файловой системы времени выполнения
ioIoInput/Output managerдиспетчер ввода-вывода
keKeKernelядро
lpcLpcLocal Procedure Callмеханизм вызова локальных процедур
mmMmMemory managerдиспетчер памяти
obObObject managerдиспетчер объектов
perfPerfPerformanceфункции для сбора информации о производительности системы
psPsProcess managerдиспетчер процессов
rawRawRaw File Systemфункции для Raw File System1)
rtlRtlRun-Time Libraryбиблиотека функций времени выполнения
seSe Security managerдиспетчер безопасности
wmiWmiWindows Management Instrumentationподдержка WMI – инструментальные средства управления Windows

Кроме перечисленных в таблице, в WRK есть ещё два важных компонента:

Приведем ещё один префикс часто встречающихся в WRK функций – Nt. Функции ядра с этим префиксом входят в Native API, они экспортируются Ntdll.dll, их можно вызывать из пользовательского режима. Часто функции с префиксом Nt соответствует WinAPI функция, и, например, при вызове WinAPI функции CreateProcess происходит вызов функции NtCreateProcess.

HTML документация по WRK

В Институте программной инженерии Хассо Платтнера Университета г. Потсдама (Hasso-Plattner-Institute for Software Engineering at University Potsdam) Александром Шмидтом (Alexander Schmidt) и Михаэлем Шёбелем (Michael Schobel) была создана HTML документация по WRK с использованием генератора документации Phoenix Cross Reference (PXR)2) . Данная документация доступна для преподавателей по следующей ссылке:

http://www.facultyresourcecenter.com/curriculum/pfv.aspx?ID=8668

HTML документация по WRK включает 4 раздела: функции (functions), типы данных (types), синонимы (typedefs) и макросы (macros) (рис.5.2).

HTML документация по Windows Research Kernel


Рис. 5.2.  HTML документация по Windows Research Kernel

По информации, предоставляемой HTML документацией, WRK содержит 4167 функций и 1957 типов данных.

Резюме

В данной лекции представлен обзор исследовательского ядра Windows (Windows Research Kernel, WRK). Перечислены компоненты WRK. Предложено использование HTML документации по WRK.

В следующей лекции будут рассмотрены основные объекты, отвечающие за работу приложений – процессы и потоки.

Контрольные вопросы

  1. Расскажите о составе академической программы Microsoft.
  2. Что такое Windows Research Kernel?
  3. Какие компоненты ядра присутствуют в Windows Research Kernel?
  4. Перечислите основные компоненты Windows Research Kernel.
  5. Какие префиксы используются в названиях функций WRK?
  6. Какая информация имеется в HTML документации по WRK?

Лекция 6. Сборка исследовательского ядра Windows и работа с отладчиком

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

Задание 1. Установить операционную систему Microsoft Windows Server 2003 SP1 на виртуальную машину Microsoft Virtual PC 2007 SP1.

Указания к выполнению.

1. Скачайте и установите программу виртуализации Microsoft Virtual PC 2007 SP1, доступную по адресу:

http://www.microsoft.com/en-us/download/details.aspx?id=24439

2. Создайте в программе Microsoft Virtual PC 2007 SP1 виртуальную машину со следующими параметрами:

Информация по созданию виртуальной машины и установке операционной системы на виртуальную машину доступна по адресу:

http://windows.microsoft.com/ru-RU/windows-vista/Virtual-PC-2007-Run-multiple-operating-systems-without-multibooting

3. Установите на виртуальную машину операционную систему Microsoft Windows Server 2003 SP1.

Замечание. Обратите внимание, в операционной системе обязательно должен быть установлен Service Pack 1 (SP1), поскольку исследовательское ядро Windows WRK поставляется именно для этой версии Windows.

В дальнейшем компьютер, на котором установлена программа Microsoft Virtual PC 2007 SP1, будем называть "физический компьютер", а виртуальный компьютер с операционной системой Microsoft Windows Server 2003 SP1 будем называть "виртуальной машиной".

Задание 2. Осуществить сборку исследовательского ядра Windows (Windows Research Kernel, WRK).

Указания к выполнению.

1. Загрузите с сайта Windows Academic Program исходные коды Windows Research Kernel:

http://www.microsoft.com/WindowsAcademic

Для загрузки потребуется наличие подписки Microsoft Developer Network Academic Alliance (MSDN AA) или подтверждение статуса преподавателя.

2. Сборку ядра можно осуществить двумя способами: при помощи командного файла Build.bat и в среде Microsoft Visual Studio. Рассмотрим оба способа.

3. Сборка при помощи Build.bat.

В папке WRK-v1.2 находится файл Build.bat. Запустите его – должно открыться консольное окно и начаться сборка ядра:



Сборка продолжается несколько минут. В результате в папке WRK v1.2\base\ntos\BUILD\EXE появится файл ядра wrkx86.exe, а также файл с отладочной информацией wrkx86.pdb. Файл wrkx86.exe – это аналог файла ntoskrnl.exe для WRK.

4. Сборка при помощи Microsoft Visual Studio.

В папке WRK-v1.2 содержится файл решения WRK.sln – файл решения (solution) Microsoft Visual Studio 2008. Если на вашем компьютере установлена Microsoft Visual Studio версии не ниже 2008, вы можете открыть данный файл. При этом если установленная версия Visual Studio выше 2008, будет предложено сконвертировать решение в более новый формат, и после конвертации с решением можно будет работать.

Выберите конфигурацию x86 и платформу Win32:



В меню Построение выберите Построить решение (Build – Build Solution). Результат построения должен быть такой же, как при использовании Build.bat – в папке WRK v1.2\base\ntos\BUILD\EXE появится файл ядра wrkx86.exe.

Замечание. Рекомендуется при изучении лекций и выполнении лабораторных работ для просмотра исходного кода WRK использовать либо Visual Studio, либо HTML документацию по WRK (см. лекцию 5 "Исследовательское ядро Windows").

Задание 3. Запустить операционную систему Windows Server 2003 SP1 с исследовательским ядром WRK.

Указания к выполнению.

1. Запустите виртуальную машину с операционной системой Windows Server 2003 SP1.

2. Откройте в ней папку C:\Windows\System32.

3. Скопируйте в эту папку два файла:

Копирование можно осуществлять двумя способами. Для обоих способов нужно установить в Microsoft Virtual PC 2007 компонент Virtual Machine Additions (пункт меню Action – Install or Update Virtual Machine Additions).

Способ 1. Компонент Virtual Machine Additions позволяет копировать файлы простым перетаскиванием из окна физического компьютера в окно виртуальной машины.

Способ 2. Откройте окно настроек виртуальной машины: пункт меню Edit – Settings. Выберите в левой панели Shared Folders (Папки общего доступа) и нажмите в правой панели кнопку Share Folder:



Выберите папку или диск, который будет использоваться для общего доступа. Нажмите кнопку ОК.

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



4. На виртуальной машине откройте файл boot.ini, расположенный в корневом каталоге системного диска (C:).

Если файл boot.ini не отображается в корневом каталоге, значит в настройках Проводника Windows выключен показ скрытых и/или системных файлов. На виртуальной машине откройте My Computer, в меню Tools выберите пункт Folder Options… При этом должно открыться окно Folder Options. Перейдите на вкладку View и найдите в списке два параметра:

Первый параметр отметьте, а у второго уберите флажок:



Нажмите кнопку OK и проверьте, что файл boot.ini отображается в корневом каталоге:



Откройте файл boot.ini и замените его содержимое следующим текстом:

[boot loader]
timeout=30
default=multi(0)disk(0)rdisk(0)partition(1)\WINDOWS
[operating systems]
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Windows Server 2003, 
Enterprise" /fastdetect /NoExecute=OptOut
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Windows Research Kernel
  " /noexecute=optout /fastdetect /kernel=wrkx86.exe /hal=halacpim.dll /debug /debugport=com1
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Windows Research Kernel [no debugger]
  " /noexecute=optout /fastdetect /kernel=wrkx86.exe /hal=halacpim.dll

В файле boot.ini хранятся параметры загрузки Windows. В данном случае, указаны три варианта загрузки:

Обратите внимание, мы использовали следующие опции в файле boot.ini:

5. Перезагрузите виртуальную машину. Когда она начнет загружаться, должно отобразиться следующее окно:



Выберите загрузку в режиме отладки (debugger enabled) и нажмите Enter.

6. После загрузки операционной системы проверьте её версию.

Нажмите кнопку Start – Run (Пуск – Выполнить) и введите команду winver:





Появится окно с информацией о версии операционной системы:



Выключите виртуальную машину до окончания установки и настройки отладчика.

Задание 4. Установить отладчик Windows – WinDbg (Windows Debugger).

Указания к выполнению.

1. Скачайте Windows Software Development Kit (SDK) for Windows 8 по следующему адресу:

http://msdn.microsoft.com/en-US/windows/hardware/hh852363

Замечание. Данный дистрибутив предназначен для работы на операционных системах Windows 7 и Windows 8.

Загрузится программа инсталлятор (sdksetup.exe) размером около 1 Мб, после чего следует её запустить. Откроется следующее окно:



Выберите пункт Install the Windows Software Development Kit to this computer, нажмите кнопку Next.

При желании можете поучаствовать в программе улучшения продуктов Microsoft на основе опыта пользователей (Customer Experience Improvement Program) или отказаться от участия в ней:



На следующем шаге, если на вашем компьютере не установлен .NET Framework 4.5, программа инсталлятор предупредит, что без его наличия компонент .NET Framework 4.5 SDK не установится. Нам он не нужен, поэтому можно пропустить этот шаг (нажмите Next):



Прочитайте лицензионное соглашение и, если согласны с ним, нажмите Accept.

В следующем окне уберите все галочки, кроме Debugging Tools for Windows и нажмите Install:



Во время установки должно работать соединение с Интернетом.

После завершения установки программу WinDbg можно запустить (в Windows 7) из меню Пуск – Все программы – Windows Kits – Debugging Tools for Windows (X86) – WinDbg (X86).

Задание 5. Настроить отладчик WinDbg для отладки ядра.

Указания к выполнению.

1. Сначала необходимо задать настройки виртуальной машины.

В меню Action выберите пункт Settings. В левой панели выберите пункт COM1, в поле Named pipe введите следующий текст:

\\.\pipe\debugPipe



Нажмите кнопку ОК.

2. Установите пути к отладочным символам в настройках отладчика WinDbg.

Запустите отладчик WinDbg. В меню File выберите пункт Symbol File Path (или просто нажмите Ctrl+S). Откроется окно Symbol Search Path, в которое нужно ввести пути к файлам с отладочными символами. Введем два пути:

В окне Symbol Search Path введите следующий текст:

c:\WRK-v1.2\base\ntos\BUILD\EXE;

SRV*c:\symbols*http://msdl.microsoft.com/download/symbols

Обратите внимание, что текст должен идти без пробелов и переносов строк:



Нажмите ОК.

3. Установить путь к исходному коду WRK в настройках отладчика WinDbg.

В меню File выберите пункт Source File Path (или нажмите Ctrl+P). Откроется окно Source Search Path, в которое нужно ввести путь к исходным файлам WRK, например, C:\WRK-v1.2\base:



Нажмите ОК.

4. Установить соединение отладчика WinDbg с виртуальной машиной.

В меню File выберите пункт Kernel Debug (или нажмите Ctrl+K). Откроется окно Kernel Debugging.

Установите следующие значения на вкладке COM в этом окне:



Нажмите кнопку OK, при этом начнется процесс подключения к виртуальной машине по COM порту. На вопрос о сохранении информации (Save information for workspace) можете ответить Yes.

Запустите виртуальную машину. В меню загрузки выберите Windows Research Kernel [debugger enabled]. В процессе загрузки в окне Command отладчика должна появиться следующая информация:



5. Узнать информацию о загруженных модулях.

Остановите процесс выполнения виртуальной машины, нажав кнопку Break на панели инструментов отладчика, или выбрав в меню Debug пункт Break, или нажав Ctrl+Break.

Отладчик перейдет в режим ввода команд (внизу окна Command сделается активной строка для текстового ввода с подсказкой kd>):



При этом виртуальная машина будет недоступна для любого взаимодействия с пользователем.

В строке команд отладчика введите команду:

lmD

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

В окне Command выведется список всех загруженных модулей (на рисунке показана только часть вывода):



Обратите внимание на информацию об отладочных символах справа от модулей ntdll и nt.

Если щелкнуть по названию модуля nt (или ввести команду lmDvmnt), то отобразится более подробная информация о данном модуле:



Чтобы продолжить работу виртуальной машины нажмите F5, или нажмите кнопку g на панели инструментов отладчика, или введите команду g в окне команд.

Задание 6. Научиться пользоваться справкой отладчика WinDbg.

Указания к выполнению.

1. В отладчике WinDbg в меню Help выберите пункт Contents – откроется окно справочной системы отладчика, которая предоставляет подробную информацию по всем его командам.



2. Например, найдем информацию о команде lm.

Способ 1. Перейдите на вкладку Указатель и введите название команды:



Нажав кнопку Вывести можно перейти на страницу с описанием команды:



Способ 2. На вкладке Содержание найдите следующий раздел: Debugger Reference – Debugger Commands – Commands:



В указанном разделе найдите команду lm:



Задание 7. Изучить способы представления чисел в WinDbg.

Указания к выполнению.

1. В WinDbg существуют два основных способа представления десятичных и шестнадцатеричных чисел – в стиле ассемблера MASM и в стиле C++. Этим способам посвящен раздел Numerical Expression Syntax в справке WinDbg:



2. В лабораторных работах будут использоваться обозначения в стиле C++:

3. Если не указывается префикс, число в WinDbg считается записанным в текущей системе счисления, которую можно узнать и, при необходимости, изменить при помощи команды n (Set Number Base):



В данном примере сначала проверяется текущая система счисления (команда n), она оказывается шестнадцатеричной (base is 16), затем устанавливается десятичная система счисления (команда n 10) и возвращается шестнадцатеричная (команда n 16).

4. Для перевода чисел из одной системы счисления в другую можно воспользоваться командой ? (Evaluate Expression):



В примере на рисунке сначала проверяется, что текущая система счисления – шестнадцатеричная (команда n), затем выполняется команда перевода числа 10, которое, в соответствии с текущей системой счисления рассматривается как шестнадцатеричное (команда ? 10), далее явно указывается, что переводимое число десятичное (команда ? 0n10), и, наконец, явно указывается, что переводимое число шестнадцатеричное (команда ? 0x10).

Задания для самостоятельного выполнения

Задание 1. Найдите информацию о следующих командах отладчика:

Задание 2. Найдите информацию о следующих командах расширений отладчика:

Указания к выполнению.

1. Описание команд расширений содержится в разделе справки Debugger Reference – Debugger Commands – General Extension Commands.

Задание 3. Найдите информацию о следующих командах расширений режима ядра:

Указания к выполнению.

1. Описание команд расширений содержится в разделе справки Debugger Reference – Debugger Commands – Kernel Mode Extension Commands.

Задание 4. Изучить пример хорошего комментирования кода.

Указания к выполнению.

  1. Откройте в Visual Studio файл WRK: base\ntos\mm\addrsup.c.
  2. В этом файле комментирование построено на основе связи с книгой Дональда Кнута (Donald Knuth) "Искусство программирования. Том 3. Сортировка и поиск" ("The Art of Computer Programming, Volume 3, Sorting and Searching").
  3. Просмотрите комментарии к алгоритмам, приведенным в файле.

Лекция 7. Процессы и потоки

Основные понятия. Структуры данных для процессов и потоков. Создание процесса.

Основные понятия

Программа (program) – это последовательность команд, реализующая алгоритм решения задачи. Программа может быть записана на языке программирования (например, на Pascal, С++, BASIC); в этом случае она хранится на диске в виде текстового файла с расширением, соответствующим языку программирования (например, .PAS, .CPP, .VB). Также программа может быть представлена при помощи машинных команд; тогда она хранится на диске в виде двоичного исполняемого файла (executable file), чаще всего с расширением .EXE. Исполняемый файл генерируется из текста программы при компиляции.

Процесс (process) – это программа (пользовательская или системная) в ходе выполнения.

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

Если операционная система умеет запускать в одно и то же время несколько процессов, она называется многозадачной (multitasking) (пример – Windows), иначе – однозадачной (пример – MS DOS).

Процесс может содержать один или несколько потоков (thread) – объектов, которым операционная система предоставляет процессорное время. Сам по себе процесс не выполняется – выполняются его потоки. Таким образом, машинные команды, записанные в исполняемом файле, выполняются на процессоре в составе потока. Если потоков несколько, они могут выполняться одновременно.

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

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

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

Многопоточность – это средство распараллеливания действий внутри процесса.

Примеры.

  1. В современных браузерах каждой вкладке соответствует свой поток.
  2. В текстовом редакторе один поток может управлять вводом текста, второй – проверять орфографию, третий – выводить документ на принтер.
  3. В компьютерной игре-стратегии за обработку действий каждого игрока отвечает отдельный поток.

Каждый процесс имеет свое собственное виртуальное адресное пространство (см. лекцию 11 "Управление памятью"), в котором независимо друг от друга работают все потоки процесса. Например, поток 1 может записывать данные в ячейку с адресом 100, а поток 2 читать данные из ячейки с адресом 101.

Замечание. Конечно, если два (или более) потоков захотят записать что-то свое в одну и ту же ячейку, возникнет неопределенность – кто раньше? Это одна из подзадач обширной проблемы синхронизации.

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

Структуры данных для процессов и потоков

В WRK за управление процессами отвечает диспетчер процессов (base\ntos\ps), а многие важные структуры данных описаны в заголовочных файлах base\ntos\inc\ps.h и base\ntos\inc\ke.h.

Процесс в Windows описывается структурой данных EPROCESS [5]. Эта структура в WRK содержится в файле base\ntos\inc\ps.h (строка 238). Рассмотрим некоторые её поля.

Структура для потока в Windows называется ETHREAD и описывается в файле base\ntos\inc\ps.h (строка 545). Её основные поля следующие:

Создание процесса

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

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

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

В Windows для создания процессов применяется одна из следующих WinAPI-функций: CreateProcess, CreateProcessAsUser, CreateProcessWithTokenW, CreateProcessWithLogonW [10]. Далее при описании будем использовать функцию CreateProcess.

Создание процессов в Windows включает 7 этапов [5; 2].

1. Проверка и преобразование параметров.

Параметры функции CreateProcess проверяются на корректность и преобразуются к внутреннему формату системы.

2. Открытие исполняемого файла.

Происходит поиск файла, который содержит запускаемую программу. Обычно это файл с расширением .EXE, но могут быть также расширения .COM, .PIF, .BAT, .CMD. Определяется тип исполняемого файла:

3. Создание объекта "Процесс".

Формируются структуры данных EPROCESS, KPROCESS, PEB, инициализируется адресное пространство процесса. Для этого вызывается системная функция NtCreateProcess (файл base\ntos\ps\create.c, строка 826), которая затем вызывает функцию NtCreateProcessEx (тот же файл, строка 884), а та, в свою очередь, функцию PspCreateProcess (тот же файл, строка 1016).

Замечание. Начиная с Windows Vista при создании процесса вызов нескольких функций (NtCreateProcess, NtWriteVirtualMemory, NtCreateThread) заменен вызовом одной функции NtCreateUserProcess.

Рассмотрим некоторые важные действия, выполняемые функцией PspCreateProcess.

4. Создание основного потока.

Формируется структура данных ETHREAD, стек и контекст потока, генерируется идентификатор потока. Поток создается при помощи функции NtCreateThread, определенной в файле base\ntos\ps\create.c, (строка 117), которая вызывает функцию PspCreateThread (тот же файл, строка 295). При этом выполняются следующие действия:

5. Уведомление подсистемы Windows.

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

6. Запуск основного потока.

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

7. Инициализация процесса.

Резюме

В этой лекции введены понятия "процесса" и "потока". Рассмотрены структуры данных, представляющие в операционной системе процесс (EPROCESS) и поток (ETHREAD). Описан ход создания процесса с использованием структур данных и функций Windows Research Kernel.

Следующая лекция посвящена алгоритмам планирования потоков и реализации этих алгоритмов в Windows.

Контрольные вопросы

  1. Приведите определение понятий "программа", "процесс", "поток", "стек".
  2. Опишите основные поля структуры EPROCESS.
  3. Какой структурой является поле Pcb структуры EPROCESS? Опишите поля этой структуры.
  4. Опишите основные поля структуры ETHREAD.
  5. Перечислите этапы создания процесса.
  6. Опишите этапы создания объекта "процесс".
  7. Опишите этапы создания основного потока.

Лекция 8. Создание и управление процессами и потоками

Цель работы: исследовать структуры данных и функции WRK, используемые при создании и управлении процессами и потоками.

Задание 1. Исследовать структуру данных EPROCESS.

Указания к выполнению.

1. Откройте файл WRK, содержащий описание структуры EPROCESS: base\ntos\inc\ps.h, строка 238.

Замечание. Открыть описание структуры EPROCESS можно в проекте Visual Studio или в HTML документации по WRK (см. лекцию 5 "Исследовательское ядро Windows"):



Замечание. Обратите внимание, в WRK часто названия типов (раздел Types в HTML документации) начинаются с подчеркивания (_EPROCESS), при этом для типа имеется синоним (раздел Typedefs) без подчеркивания (EPROCESS). Использовать можно одинаково как типы, так и их синонимы.

2. Откройте отладчик WinDbg. Запустите процесс отладки ядра.

3. Запустите виртуальную машину Windows Server 2003 SP1 в режиме отладки.

4. Остановите выполнение виртуальной машины в отладчике, нажав Ctrl+Break или выбрав пункт меню Debug – Break или щелкнув кнопку Break на панели инструментов.

Внизу окна Command отладчика (View – Command) должно появиться приглашение для ввода команд kd>:



5. Введите команду: dt eprocess.

dt (display type) – команда отображения информации о типах данных и переменных.

При этом в окне команд должна отобразиться информация обо всех полях типа EPROCESS:



Слева в окне вывода указывается шестнадцатеричное смещение в байтах для поля относительно начала расположения структуры в памяти. Например, поле Pcb – первое поле (смещение 0x000 байт), представляющее собой структуру _KRPOCESS и занимающее в памяти 0x078 = 120 байт.

Замечание. Обратите внимание, что в качестве параметра команды dt мы указываем EPROCESS – синоним типа _EPROCESS. Ввод команды dt _eprocess приведет к аналогичному результату.

6. Чтобы отобразить значения полей структуры EPROCESS для какого-либо конкретного процесса, мы должны узнать адрес этой структуры в памяти. Для этого в отладчике в поле команд введите: !process 0 0.

Команда !process отображает информацию обо всех или нескольких процессах. Первый ноль в параметрах команды означает, что нужно выводить информацию о всех процессах. Если на месте первого параметра указать ID процесса или адрес в памяти его структуры EPROCESS, будет выводиться информация только о данном процессе.

Второй ноль в параметрах команды !process определяет количество информации о процессе: 0 – минимум информации, 7 – максимум информации.

На экране отобразится краткая информация о всех процессах в системе, в том числе их идентификаторы (на рисунке обведено красным) и адреса структур EPROCESS (обведено синим):



Из рисунка видно, что в данном случае ID процесса explorer.exe равен 7EC (в шестнадцатеричном виде) или 2028 (в десятичном виде), а адрес структуры EPROCESS = 81F24BD0. Имейте в виду, что при следующем запуске системы эти значения скорее всего изменятся.

Сейчас можно отображать значения полей структуры EPROCESS для процесса explorer.exe, воспользовавшись адресом структуры:

dt eprocess 81F24BD0.



Обратите внимание на идентификатор процесса – поле UniqueProcessId (его значение должно совпадать с полученным ранее идентификатором), и на файл образа процесса – поле ImageFileName (explorer.exe).

При выводе информации с помощью команды dt не отображаются значения полей внутри вложенных структур, например, поле Pcb (структура KPROCESS). Чтобы отобразить их следует использовать ключ рекурсии –rN, где N – число уровней рекурсии.

Например, команда dt eprocess 81F24BD0 -r1 позволяет получить следующий вывод (приведен не полностью):



Задание 2. Получить информацию о процессе и его потоках.

Указания к выполнению.

1. Как уже отмечалось, для получения информации о процессах используется команда !process. У неё есть следующие варианты:

!process 0 0 – отображение краткой информации о всех процессах в системе;

!process id 7 – отображение полной информации о процессе с идентификатором id;

!process id 0 – отображение краткой информации о процессе с идентификатором id;

!process address 7 – отображение полной информации о процессе с адресом address структуры EPROCESS;

!process address 0 – отображение краткой информации о процессе с адресом address структуры EPROCESS.

2. Чтобы узнать идентификатор процесса, можно воспользоваться либо командой отладчика !process (этот способ уже демонстрировался), либо специальными утилитами, например Process Explorer или Task Manager (Диспетчер задач).

Продемонстрируем второй способ на примере процесса explorer.exe.

Возобновите выполнение виртуальной машины: нажмите F5 или выберите пункт меню Debug – Go или щелкните кнопку Go на панели инструментов.

На виртуальной машине запустите утилиту Process Explorer от Sysinternals.

Замечание. Утилиту Process Explorer можно скачать на сайте Sysinternals: http://technet.microsoft.com/ru-ru/sysinternals. Информацию о ней можно получить либо на том же сайте, либо в литературе [5; 2].

Идентификатор процесса указывается в поле PID (Process Identifier):



В примере на рисунке PID = 2028 (в десятичном виде) или 7EC (в шестнадцатеричном виде).

3. Остановите выполнение виртуальной машины (нажмите Ctrl+Break в отладчике).

В окне команд введите

!process 7EC 0

или

!process 81F24BD0 0

Результаты вывода почти одинаковые, за тем исключением, что в первом случае дополнительно выводится адрес таблицы дескрипторов процесса:



4. Чтобы вывести информацию о потоках процесса, наберите команду:

!process 7EC 4



Для каждого потока указывается адрес его структуры ETHREAD (на рисунке выделено красным цветом), идентификатор потока (синий цвет) и текущее состояние потока (зеленый цвет).

Для отображения полной информации о потоках процесса введите команду:

!process 7EC 7

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

Указания к выполнению.

1. Создайте пустой проект С/С++ в Visual Studio (Файл – Создать – Проект…) с названием CreateProcess. Выберите расположение проекта (кнопка Обзор…), например, C:\Programs:



2. Добавьте в проект файл исходного кода с расширением CPP.

Для этого щелкните правой кнопкой мыши на папке Файлы исходного кода в Обозревателе решений, выберите Добавить, затем Создать элемент…



В открывшемся окне выберите пункт Файл C++ (.cpp) и введите его название, например, main:



3. Откройте добавленный файл и вставьте в него следующий код (взят с сайта MSDN):

#include <windows.h>
#include <stdio.h>
#include <tchar.h>

void _tmain( int argc, TCHAR *argv[] )
{
    STARTUPINFO si;
    PROCESS_INFORMATION pi;

    ZeroMemory( &si, sizeof(si) );
    si.cb = sizeof(si);
    ZeroMemory( &pi, sizeof(pi) );

    // Запуск дочернего процесса
    if( !CreateProcess( NULL,
        "notepad.exe",  // Имя образа запускаемого процесса
        NULL,          // Дескриптор процесса не наследуется
        NULL,          // Дескриптор потока не наследуется
        FALSE,         // Дескрипторы не наследуются
        0,             // Флагов создания нет
        NULL,          // Родительский environment block
        NULL,          // Родительский текущий каталог 
        &si,           // Указатель на структуру STARTUPINFO
        &pi )          // Указатель на PROCESS_INFORMATION
    ) 
    {
        printf( "CreateProcess failed (%d).\n", GetLastError() );
        return;
    }

    // Ожидаем, пока созданный процесс не завершится
    WaitForSingleObject( pi.hProcess, INFINITE );

    // Закрываем дескрипторы процесса и потока 
    CloseHandle( pi.hProcess );
    CloseHandle( pi.hThread );
}

Приведенная программа будет запускать другой процесс (в данном случае стандартное приложение Windows "Блокнот") и ждать, пока он не завершится.

4. Установите требуемые свойства проекта.

Для этого выберите конфигурацию Release на панели инструментов:



В конфигурации Release проект будет компилироваться без отладочной информации и с оптимизацией кода.

Затем щелкните правой кнопкой мыши на заголовке проекта в окне Обозреватель решений и выберите пункт Свойства (или щелкните левой кнопкой мыши на заголовке проекта и нажмите Alt+Enter):



В свойствах проекта найдите опцию Свойства конфигурации – C/C++ – Создание кода – Библиотека времени выполнения. Выберите параметр Многопоточная (/MT). Если этого не сделать, то приложение при запуске в Windows Server 2003, возможно, будет требовать библиотеку msvcr100.dll (для Visual Studio 2010).



5. Проверьте работоспособность созданного приложения.

Нажмите кнопку Play на панели инструментов или нажмите F5. При появлении окна с вопросом о выполнении построения проекта выберите Да.

Если все сделано правильно, должно запуститься созданное приложение (консольное окно), а затем приложение "Блокнот".



Закройте "Блокнот" – при этом должно завершиться ваше приложение.

Задание 4. Исследовать в отладчике ход создания процесса.

Указания к выполнению.

1. Исполняемый файл созданного в предыдущем пункте приложения CreateProcess.exe после компиляции проекта должен находиться в следующей папке:

c:\Programs\CreateProcess\Release.

Скопируйте исполняемый файл на виртуальную машину.

2. Проверьте работоспособность исполняемого файла на виртуальной машине. При его запуске должен запускаться "Блокнот".

3. Установите в отладчике точку останова на функции NtCreateProcessEx.

Для этого прервите выполнение виртуальной машины (Ctrl+Break в отладчике). В меню Edit выберите пункт Breakpoints…

В открывшемся окне введите команду:

bp nt!NtCreateProcessEx

и нажмите Enter.



Данная команда устанавливает точку останова на функции NtCreateProcessEx. Указание nt! означает модуль, в котором отладчику следует искать функцию. Если в данном случае не указать модуль nt (ядро), то отладчик автоматически поставит точку останова на функции NtCreateProcessEx, экспортируемой ntdll.dll.

4. Возобновите выполнение виртуальной машины (нажмите F5 в отладчике). Запустите в виртуальной машине приложение CreateProcess.

Сработает точка останова для функции NtCreateProcessEx, которая запускает приложение CreateProcess. Нас интересует запуск "Блокнота", поэтому продолжите выполнение, нажав F5 в отладчике.

Точка останова сработает второй раз – произошел вызов функции NtCreateProcessEx для запуска "Блокнота". Перейдите в отладчик. В нем должно открыться окно с исходным кодом функции NtCreateProcessEx (файл base\ntos\ps\create.c).

Проследите ход создания объекта процесс (см. лекцию 6 "Процессы и потоки", раздел "Создание процесса", шаг 3). Для этого используйте клавиши F11 – выполнение следующей команды с заходом в процедуры (Step Into) и F10 – то же самое, но процедуры выполняются за один шаг (без захода, Step Over).

Значения переменных можно отслеживать в окне Locals (Alt+3), окне Watch (Alt+2, нужно вводить интересующие переменные) или просто наводя курсор мыши на переменную в окне исходного кода.

Задания для самостоятельного выполнения

Задание 1. Исследуйте структуру данных ETHREAD.

Указания к выполнению.

1. Используйте HTML документацию по WRK или проект Visual Studio.

2. Для просмотра полей структуры ETHREAD используйте команду:

dt ethread.

3. Для получения информации об адресах структур ETHREAD потоков процесса используйте команду:

!process id 4.

4. Для получения информации о потоке используйте команду:

!thread address,

где address – адрес структуры ETHREAD потока.

5. Для просмотра значений полей структуры ETHREAD для конкретного потока используйте команду:

dt ethread address.

Задание 2. Исследовать в отладчике ход создания потока.

Указания к выполнению.

bp nt! NtCreateThread

Лекция 9. Планирование потоков

Алгоритмы планирования. Состояния потоков. Кванты. Приоритеты. Алгоритм планирования в Windows. Динамическое повышение приоритета.

Если операционная система поддерживает многопоточность, она может распределять процессорное время либо между процессами, либо между потоками. В операционной системе Windows процессор предоставляется потокам, иначе говоря, осуществляется планирование на уровне потоков.

Таким образом, если один процесс имеет пять потоков, а второй – десять, то первый процесс будет занимать процессор в два раза больше времени, чем второй (при условии, конечно, что все потоки имеют равный приоритет и выполняют примерно одинаковую работу).

Алгоритмы планирования

Существуют разные алгоритмы планирования. Рассмотрим основные виды.

1. Вытесняющие/невытесняющие алгоритмы.

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

2. Алгоритмы с квантованием.

Каждому потоку предоставляется квант времени, в течение которого поток может выполняться на процессоре. По истечении кванта операционная система переключает процессор на следующий поток в очереди. Квант обычно равен целому числу интервалов системного таймера1).

3. Алгоритмы с приоритетами.

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

В Windows реализован смешанный алгоритм планирования – вытесняющий, на основе квантования и приоритетов.

Состояния потоков

За время своего существования поток может находиться в нескольких состояниях. Перечислим основные состояния:

  1. Готовность (Ready) – поток готов к выполнению и ждет своей очереди занять процессор.
  2. Выполнение (Running) – поток выполняется на процессоре.
  3. Ожидание (Waiting) – поток не может выполняться, поскольку ждет наступление некоторого события (например, завершения операции ввода-вывода или сообщения от другого потока)

Кроме основных существует ещё несколько состояний – Инициализация (Init), Завершение (Terminate), Состояние простоя (Standby), Переходное состояние (Transition), Состояние отложенной готовности (Deferred ready). Подробнее о них можно узнать в [5; 2].

На рис.9.1 показаны основные состояния потока, возможные переходы между состояниями и условия переходов.

Состояния потока


Рис. 9.1.  Состояния потока

Кванты

В Windows имеется два базовых размера кванта – 2 интервала системного таймера и 12 интервалов. Если квант времени короткий, то потоки будут переключаться быстрее и "отзывчивость" (responsiveness) системы улучшится – это важное свойство для пользователя, поэтому в клиентских системах Windows по умолчанию используются короткие кванты. При этом производительность системы в целом снижается, поскольку потоки не будут успевать выполнять свои задачи в течение выделенного кванта, а частые переключения создадут высокие накладные расходы (служебные операции системы при смене потоков). Вследствие этого в серверных версиях Windows по умолчанию применяются длинные кванты.

Длительность интервала системного таймера (в сотнях наносекунд) хранится в переменной KeMaximumIncrement (для x86 – файл base\ntos\ex\i386\splocks.asm, строка 140; для x64 – файл base\ntos\ex\amd64\hifreqlk.asm, строка 147) и устанавливается функцией KeSetTimeIncrement (файл base\ntos\ke\miscc.c, строка 711 на основе значения, предоставляемого HAL.

Каждый процесс хранит величину кванта в поле QuantumReset структуры KPROCESS (файл base\ntos\inc\ke.h, строка 1029). Значение в этом поле равно количеству интервалов таймера, умноженному на 3. Например, для длинных квантов (12 интервалов) значение QuantumReset будет равно 36. Таким образом, при каждом срабатывании таймера (возникает прерывание) система уменьшает квант выполняющегося потока на 3 единицы.

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

Значение кванта может быть изменено пользователем. Например, на Windows 7 нужно проделать следующее: Компьютер – Свойства – Дополнительные параметры системы – вкладка "Дополнительно" – раздел "Быстродействие" – Параметры – вкладка "Дополнительно" – раздел "Распределение времени процессора". Можно выбрать короткие кванты ("Оптимизировать работу программ") или длинные ("Оптимизировать работу служб, работающих в фоновом режиме") (рис.9.2).

Изменение величины кванта в Windows 7number


Рис. 9.2.  Изменение величины кванта в Windows 7number

За изменение величины кванта отвечает функция KeSetQuantumProcess (файл base\ntos\ke\procobj.c, строка 1393).

Кроме длинных и коротких квантов в Windows реализовано динамическое увеличение размера кванта для потоков активного процесса (т.е. того процесса, окно которого в настоящий момент активно). За повышение кванта (и приоритета) отвечает функция PspComputeQuantumAndPriority (файл base\ntos\ps\psquery.c, строка 4415). Более подробную информацию о динамическом увеличении кванта см. [5, стр. 361].

Приоритеты

В операционной системе Windows имеется 32 уровня приоритета – от 0 до 31 (рис.9.3).

Приоритеты в Windows


Рис. 9.3.  Приоритеты в Windows

Приоритеты назначаются процессам и потокам. У процесса имеется единственный приоритет, который называется базовым. Значение этого приоритета хранится в поле BasePriority структуры KPROCESS (файл base\ntos\inc\ke.h, строка 1028). В WinAPI для работы с базовым приоритетом процесса используются классы приоритета (например, REALTIME, NORMAL и т. д.); соответствие классов приоритета числовым значениям показано на рис.9.3. Например, при создании процесса можно указать класс приоритета в качестве параметра WinAPI-функции CreateProcess, иначе будет установлен приоритет по умолчанию (см. лекцию 6 "Процессы и потоки", раздел "Создание процесса"). В дальнейшем класс приоритета процесса можно изменить при помощи WinAPI-функции SetPriorityClass.

В WRK структура PROCESS_PRIORITY_CLASS и значения соответствующих констант (заметьте, что эти значения не совпадают с числовыми значениями приоритетов) определены в файле public\sdk\inc\ntpsapi.h (строка 399). Класс приоритета процесса хранится в поле PriorityClass структуры EPROCESS (см. лекцию 7 "Процессы и потоки", раздел "Структуры данных для процессов и потоков"). Таким образом, если, например, процессу назначен класс приоритета High, то в поле PriorityClass запишется число 3 (значение константы PROCESS_PRIORITY_CLASS_HIGH), в поле BasePriority – значение 13 (соответствующее числовое значение приоритета).

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

Таблица 9.1. Влияние относительных приоритетов
Относительный приоритетСмещение для динамических приоритетовСмещение для приоритетов реального времени
Time CriticalБазовый приоритет = 15Базовый приоритет = 31
Highest+2+2
Above Normal+1+1
Normal00
Below Normal–1–1
Lowest–2–2
IdleБазовый приоритет = 1Базовый приоритет = 16

Пример. Имеется процесс с базовым приоритетом Below Normal (6). Поток, принадлежащий этому процессу, имеет такой же базовый приоритет. Вызов функции SetThreadPriority с параметром Highest сделает базовый приоритет потока равным 8, а с параметром Time Critical – равным 15.

Текущий приоритет потока при создании потока равен базовому, но в дальнейшем может динамически повышаться и понижаться операционной системой (эта процедура будет рассмотрена далее). Заметим, что для потоков с базовым приоритетом Real Time текущий приоритет не изменяется и всегда равен базовому.

Базовый приоритет потока хранится в поле BasePriority, а текущий – в поле Priority структуры KTHREAD (файл base\ntos\inc\ke.h, строки 1123 и 1237).

Алгоритм планирования в Windows

В Windows отсутствует единый модуль, отвечающий за планирование потоков. Алгоритм планирования реализуется несколькими процедурами ядра, совокупность которых называется диспетчером ядра (kernel’s dispatcher).

Для хранения данных, необходимых для планирования, предназначена база данных диспетчера ядра, которая является частью структуры KPRCB (Kernel Processor Control Block), описанной в файле base\ntos\inc\i386.h (строка 1073). Эта структура создается для каждого процессора, присутствующего в системе. Структура KPRCB содержит следующие поля, требуемые для планирования:

Выбор потока с максимальным приоритетом из массива DispatcherReadyListHead с использованием поля ReadySummary осуществляется функцией KiSelectReadyThread (файл base\ntos\ke\ki.h, строка 3550).

Рассмотрим основные ситуации, возникающие при планировании потоков.

1. Выбор потока на выполнение.

Просматривается очередь готовых к выполнению потоков (сначала поле ReadySummary, затем, когда определена непустая очередь с максимальным приоритетом, поле DispatcherReadyListHead) и выбирается первый поток в очереди с наибольшим приоритетом, которому для выполнения предоставляется квант времени (рис.9.4).

Выбор потока для выполнения (квадратами обозначены потоки, числами – их приоритеты)


Рис. 9.4.  Выбор потока для выполнения (квадратами обозначены потоки, числами – их приоритеты)

2. Переход выполняющегося потока в состояние ожидания.

Выполняющийся поток вызывает одну из функций ожидания (см. MSDN – Wait Functions [10]) и освобождает процессор. Его квант времени не истек и сохраняется за потоком, но при выходе из состояния ожидания уменьшается на единицу (см. параграф "Кванты" этой лекции).

Диспетчер ядра выбирает на выполнение первый поток из очереди с наибольшим приоритетом (рис.9.5).

Переход потока в состояние ожидания


Рис. 9.5.  Переход потока в состояние ожидания

3. Вытеснение потоком с большим приоритетом.

Во время выполнения поток может быть вытеснен при появлении потока с большим приоритетом. Такая ситуация может возникнуть по следующим причинам:

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

Вытеснение потока


Рис. 9.6.  Вытеснение потока

4. Завершение кванта времени

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

Завершение кванта


Рис. 9.7.  Завершение кванта

Динамическое повышение приоритета

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

Чтобы дать всем потокам шанс на выполнение операционная система применяет механизм динамического повышения приоритета (Priority Boosts), который работает в следующих случаях:

Замечание. Никогда не повышаются приоритеты потоков реального времени (16–31).

Резюме

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

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

Контрольные вопросы

  1. Перечислите виды алгоритмов планирования.
  2. Нарисуйте схему состояний потоков, переходов и условий переходов между состояниями.
  3. Какие кванты используются в Windows? Каким образом можно изменить величину кванта?
  4. Нарисуйте схему уровней приоритетов в Windows. Укажите соответствие уровней и классов приоритета.
  5. Рассмотрите основные ситуации, возникающие при планировании потоков и действия диспетчера ядра Windows в этих ситуациях.
  6. Возможна ли в Windows ситуация, когда какой-либо созданный поток вообще не получит процессорного времени?

Лекция 10. Задания по планированию потоков

Цель работы: исследовать структуры данных и функции WRK, используемые при планировании потоков.

Задание 1. Определить величину интервала системного таймера на виртуальной машине.

Указания к выполнению.

1. Откройте отладчик ядра WinDbg.

2. Запустите виртуальную машину Windows Server 2003 SP1 в режиме отладки.

3. Остановите выполнение виртуальной машины в отладчике, нажав Ctrl+Break.

4. Определим значение переменной KeMaximumIncrement, в которой содержится длительность интервала системного таймера.

В командной строке отладчика введите команду:

dd KeMaximumIncrement

В окне команд должно быть выведено содержимое памяти:



Команда dd (Display Double word) отображает содержимое памяти как набор 4 байтовых значений.

Значение 4 байтовой переменной KeMaximumIncrement в данном примере равно 0x18730 = 100 144 в десятичном виде. В этой переменной хранится длительность интервала системного таймера в сотнях наносекунд (1 нс = 10–9 с). Переведем полученное значение в миллисекунды (1 мс = 10–3 с):

100 144 сотен нс = 100 144 * 100 * 10–9 с = 0, 0100144 с = 10, 0144 мс.

Проверить полученное значение длительности интервала системного таймера можно при помощи утилиты clockres от Sysinternals (http://technet.microsoft.com/ru-ru/sysinternals). Запустите эту утилиту на виртуальной машине из командной строки:



Задание 2. Определить величину кванта, предоставляемого потокам.

Указания к выполнению.

1. Узнайте адрес структуры EPROCESS какого-либо процесса в системе, например, explorer.exe (см. Лабораторную работу 2 "Процессы и потоки").

Для этого в отладчике ядра введите команду:

!process 0 0

и найдите адрес структуры EPROCESS для процесса explorer.exe:



В данном примере адрес равен 0x81EBCBD8.

2. Величина кванта для потоков процесса хранится в поле QuantumReset структуры KPROCESS. Структура KPROCESS содержится в поле Pcb структуры EPROCESS, причем это поле находится в самом начале структуры (смещение равно нулю). Поэтому можно отобразить структуру KPROCESS с того же адреса, по которому располагается EPROCESS:

dt kprocess 81EBCBD8



Значение поля QuantumReset равно 36 единицам, что составляет 12 интервалов системного таймера (см. лекцию 9 "Планирование потоков") – значение кванта по умолчанию для серверных операционных систем Windows.

Задание 3. Изменить величину кванта.

Указания к выполнению.

1. В виртуальной машине с операционной системой Windows Server 2003 SP1 щелкните правой кнопкой мыши на ярлыке My Computer – выберите пункт Properties – перейдите на вкладку Advanced – щелкните на кнопку Settings в разделе Performance:



В появившемся окне Performance Options перейдите на вкладку Advanced и в разделе Processor scheduling выберите пункт Programs:



2. Проверьте значение поля QuantumReset для процесса explorer.exe. Оно должно быть равно 6:



Задание 4. Определить значения класса приоритета и базового приоритета процесса.

Указания к выполнению.

1. В отладчике WinDbg определите адрес процесса explorer.exe.

2. Для процесса explorer.exe выведите на экран значения полей структуры EPROCESS. Класс приоритета процесса хранится в поле PriorityClass.

3. Определите символьное значение класса приоритета – воспользуйтесь проектом Visual Studio для WRK и найдите там структуру _PROCESS_PRIORITY_CLASS. В том же файле выше описания этой структуры содержатся определения констант для классов приоритета. Установите соответствие полученного числового значения и класса приоритета для процесса explorer.exe.

4. Для процесса explorer.exe выведите на экран значения полей структуры KPROCESS. Базовый приоритет процесса хранится в поле BasePriority. Значение базового приоритета должно соответствовать классу приоритета процесса (см. рис. 3 в лекции 9 "Планирование потоков").

Задание 5. Изменить базовый приоритет процесса.

Указания к выполнению.

1. В виртуальной машине запустите утилиту Process Explorer.

2. Найдите процесс explorer.exe, щелкните на нем правой кнопкой мыши, выберите пункт Set Priority и установите значение приоритета High: 13:



3. Проверьте в отладчике, что значения полей PriorityClass и BasePriority для процесса explorer.exe изменились.

Задание 6. Исследовать структуру KPRCB (Kernel Processor Control Block).

Указания к выполнению.

1. Выведите описание полей структуры KPRCB при помощи команды:

dt nt!_kprcb

Обратите внимание, что в команде нужно указать модуль – nt (ядро), иначе выведется информация из ntdll.dll, а также указать знак подчеркивания, иначе информация будет неполной.

Часть вывода данной команды приведена на рисунке:



2. Определите адрес структуры KPRCB.

В отладчике введите команду:

!prcb



В данном примере адрес структуры KPRCB равен FFDFF120.

3. Выведите значение полей структуры KPRCB.

В отладчике введите команду:

dt nt!_kprcb FFDFF120

Часть вывода для этой команды приведена на рисунке:



Обратите внимание на указатели текущего потока (CurrentThread), следующего потока (NextThread) и потока простоя (IdleThread). В примере на рисунке видно, что указатели текущего потока и потока простоя совпадают, т. е. в настоящий момент времени процессор занят потоком простоя.

Задание 7. Исследовать очередь потоков для выполнения.

Указания к выполнению.

1. Скачайте утилиту CPUSTRES от Sysinternals по адресу: http://live.sysinternals.com/WindowsInternals/.

2. В виртуальной машине запустите утилиту CPUSTRES.

Выберите значения полей утилиты так, как показано на рисунке:



Важно обеспечить загрузку системы несколькими потоками, желательно с разными приоритетами.

3. Прервите выполнение виртуальной машины в отладчике (Ctrl+Break).

4. Определите адрес структуры KPRCB (см. предыдущее задание). Предположим, адрес структуры KPRCB равен FFDFF120.

5. Выведите на экран значения полей структуры KPRCB при помощи команды:

dt nt!_kprcb FFDFF120

6. Найдите поля ReadySummary и DispatcherReadyListHead (см. лекцию 9 "Планирование потоков", раздел "Алгоритмы планирования в Windows"):

7.



8. Поле ReadySummary показывает приоритеты, для которых имеются готовые к выполнению потоки.

Предположим, поле ReadySummary = 0x381. Переведем это значение как 32 разрядное число в двоичный вид:



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

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

9. Поле DispatcherReadyListHead указывает на очереди готовых потоков.

Данное поле представляет собой массив элементов типа LIST_ENTRY (см. файл public\sdk\inc\ntdef.h, строка 1084). Размерность массива совпадает с количеством приоритетов в системе – 32.

Чтобы просмотреть содержимое массива, введите в отладчике следующую команду:

dd FFDFF120+9F0

Адрес получается путем прибавления смещения поля DispatcherReadyListHead (9F0) к стартовому адресу структуры KPRCB (FFDFF120).



Тип LIST_ENTRY описывает двунаправленный список и представляет собой структуру, состоящую из двух полей: Flink (Forward Link) – указатель на следующий элемент списка и Blink (Backward Link) – указатель на предыдущий элемент списка.

На рисунке показаны первые 16 элементов массива DispatcherReadyListHead и обведены разные структуры LIST_ENTRY, представляющие элементы массива и состоящие из двух адресов – Flink и Blink.

Большинство элементов массива описывают пустые списки – это ситуация, когда адреса в обоих полях структуры LIST_ENTRY совпадают и указывают на одно и то же поле – Flink. Например, на рисунке первый элемент массива представляет собой структуру LIST_ENTRY, располагающуюся по адресу FFDFFB18, оба поля которой содержат тот же самый адрес.

Нас интересуют непустые списки – это нулевой, седьмой, восьмой и девятый элементы массива (см. единичные биты в поле ReadySummary). Рассмотрим девятый элемент массива, описывающий очередь готовых потоков с максимальным в данный момент приоритетом. Заметим, что он расположен по адресу FFDFFB58.

Для девятого элемента поле Flink = 81F454C0, поле Blink = 82258570.

Посмотрим, что располагается в памяти по адресу, на который указывает Flink:



По этому адресу располагается структура LIST_ENTRY, первое поле которой (Flink) указывает на следующий элемент списка, а второе поле (Blink) – на предыдущий элемент.

Пройдем дальше, к следующему элементу списка:



Очевидно, это последний элемент списка, поскольку первое поле Flink указывает на адрес FFDFFB58 – начало списка.

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



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

В списке три элемента: первый элемент по адресу FFDFFB58 является частью структуры KPRCB, второй и третий элементы представляют собой поле WaitListEntry структуры KTHREAD (см. файл base\ntos\inc\ke.h, строка 1128). Данное поле располагается по смещению 0x060 относительно начала структуры KTHREAD, это можно узнать, введя команду:

dt kthread



Таким образом, чтобы узнать адрес начала структуры KTHREAD потока в очереди готовых потоков, нужно из адреса, по которому располагается элемент списка очереди, вычесть 0x060.

Выведем на экран структуру KTHREAD для первого потока в очереди потоков. Адрес соответствующего элемента списка равен 81F454C0, поэтому используем команду:

dt kthread 81F454C0–60



Чтобы узнать процесс, к которому принадлежит данный поток, найдем поле Process структуры KTHREAD:



Обратите внимание на поле BasePriority – там указан приоритет 9, который совпадает с приоритетом очереди потоков.

В поле Process указан адрес структуры KPROCESS (поля Pcb) процесса, которому принадлежит данный поток. Поскольку поле Pcb является первым в структуре EPROCESS (нулевое смещение), то адрес структуры KPROCESS совпадает с адресом структуры EPROCESS процесса.

Выведем на экран структуру EPROCESS по найденному адресу и узнаем имя исполняемого образа по полю ImageFileName:



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

Аналогичным образом можно определить процессы-владельцы остальных потоков в очереди.

Задания для самостоятельного выполнения

Задание 1. Исследуйте функцию KeSetQuantumProcess.

Указания к выполнению.

1. Установите в отладчике точку останова на функции KeSetQuantumProcess:

bp nt!KeSetQuantumProcess

2. Измените величину кванта в системе Windows Server 2003 SP1 так, как это описано в задании 3 основной части лабораторной работы.

Задание 2. Определить базовый приоритет какого-либо потока, принадлежащего процессу explorer.exe.

Указания к выполнению.

  1. Базовый приоритет потока хранится в поле BasePriority, а текущий – в поле Priority структуры KTHREAD.
  2. Также приоритет потока отображается при выполнении команды !thread.
  3. Ещё один способ узнать приоритет потока – с помощью утилиты Process Explorer.

Задание 3. Изменить базовый приоритет потока.

Указания к выполнению.

1. Приоритет потока можно изменить программным путем. Например, утилита CPUSTRES от Sysinternals предоставляет возможность запустить несколько потоков с разными приоритетами.

2. Запустите утилиту CPUSTRES. Посмотрите в отладчике приоритеты её потоков. Измените приоритет потока в CPUSTRES и проверьте, что в отладчике значение также изменилось.

3. Более сложный вариант задания – написать программу, изменяющую приоритет собственных потоков. См. WinAPI функцию SetThreadPriority.

Задание 4. Исследовать функцию KiQuantumEnd.

Указания к выполнению.

1. Установите в отладчике точку останова на функции KiQuantumEnd (см. лабораторную работу 2 "Процессы и потоки", задание 4).

Для этого прервите выполнение виртуальной машины (Ctrl+Break) и воспользуйтесь следующей командой:

bp nt!KiQuantumEnd

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

3. Выполните трассировку функции KiQuantumEnd.

Лекция 11. Управление памятью

Виртуальная память. Реализация виртуальной памяти в Windows. Структура виртуального адресного пространства. Выделение памяти процессам. Дескрипторы виртуальных адресов. Трансляция адресов. Ошибки страниц. Пределы памяти.

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

Всем процессам в операционной системе Windows предоставляется важнейший ресурс – виртуальная память (virtual memory). Все данные, с которыми процессы непосредственно работают, хранятся именно в виртуальной памяти.

Название "виртуальная" произошло из-за того что процессу неизвестно реальное (физическое) расположение памяти – она может находиться как в оперативной памяти (ОЗУ), так и на диске. Операционная система предоставляет процессу виртуальное адресное пространство (ВАП, virtual address space) определенного размера и процесс может работать с ячейками памяти по любым виртуальным адресам этого пространства, не "задумываясь" о том, где реально хранятся данные.

Размер виртуальной памяти теоретически ограничивается разрядностью операционной системы. На практике в конкретной реализации операционной системы устанавливаются ограничения ниже теоретического предела. Например, для 32-разрядных систем (x86), которые используют для адресации 32 разрядные регистры и переменные, теоретический максимум составляет 4 ГБ (232 байт = 4 294 967 296 байт = 4 ГБ). Однако для процессов доступна только половина этой памяти – 2 ГБ, другая половина отдается системным компонентам. В 64 разрядных системах (x64) теоретический предел равен 16 экзабайт (264 байт = 16 777 216 ТБ = 16 ЭБ). При этом процессам выделяется 8 ТБ, ещё столько же отдается системе, остальное адресное пространство в нынешних версиях Windows не используется.

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

Реализация виртуальной памяти в Windows

Схема реализации виртуальной памяти в 32-разрядной операционной системе Windows представлена на рис.11.1. Как уже отмечалось, процессу предоставляется виртуальное адресное пространство размером 4 ГБ, из которых 2 ГБ, расположенных по младшим адресам (0000 0000 – 7FFF FFFF), процесс может использовать по своему усмотрению (пользовательское ВАП), а оставшиеся два гигабайта (8000 0000 – FFFF FFFF) выделяются под системные структуры данных и компоненты (системное ВАП)1). Отметим, что каждый процесс имеет свое собственное пользовательское ВАП, а системное ВАП для всех процессов одно и то же.

Реализация виртуальной памяти в 32-разрядных Windows


Рис. 11.1.  Реализация виртуальной памяти в 32-разрядных Windows

Виртуальная память делится на блоки одинакового размера – виртуальные страницы. В Windows страницы бывают большие (x86 – 4 МБ, x64 – 2 МБ) и малые (4 КБ). Физическая память (ОЗУ) также делится на страницы точно такого же размера, как и виртуальная память. Общее количество малых виртуальных страниц процесса в 32 разрядных системах равно 1 048 576 (4 ГБ / 4 КБ = 1 048 576).

Обычно процессы задействуют не весь объем виртуальной памяти, а только небольшую его часть. Соответственно, не имеет смысла (и, часто, возможности) выделять страницу в физической памяти для каждой виртуальной страницы всех процессов. Вместо этого в ОЗУ (говорят, "резидентно") находится ограниченное количество страниц, которые непосредственно необходимы процессу. Такое подмножество виртуальных страниц процесса, расположенных в физической памяти, называется рабочим набором процесса (working set).

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

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

Структура виртуального адресного пространства

Рассмотрим, из каких элементов состоит виртуальное адресное пространство процесса в 32 разрядных Windows (рис.11.2).

В пользовательском ВАП располагаются исполняемый образ процесса, динамически подключаемые библиотеки (DLL, dynamic-link library), куча процесса и стеки потоков.

При запуске программы создается процесс (см. лекцию 6 "Процессы и потоки"), при этом в память загружаются код и данные программы (исполняемый образ, executable image), а также необходимые программе динамически подключаемые библиотеки (DLL). Формируется куча (heap) – область, в которой процесс может выделять память динамическим структурам данных (т. е. структурам, размер которых заранее неизвестен, а определяется в ходе выполнения программы). По умолчанию размер кучи составляет 1 МБ, но при компиляции приложения или в ходе выполнения процесса может быть изменен. Кроме того, каждому потоку предоставляется стек (stack) для хранения локальных переменных и параметров функций, также по умолчанию размером 1 МБ.

Структура виртуального адресного пространства


Рис. 11.2.  Структура виртуального адресного пространства

В системном ВАП расположены:

Переменные, в которых хранятся границы разделов в системном ВАП, приведены в [5, стр. 442]. Вычисляются эти переменные в функции MmInitSystem (файл base\ntos\mm\mminit.c, строка 373), отвечающей за инициализацию подсистемы памяти. В файле base\ntos\mm\i386\mi386.h приведена структура ВАП и определены константы, связанные с управлением памятью (например, стартовый адрес системного кэша MM_SYSTEM_CACHE_START, строка 199).

Выделение памяти процессам

Существует несколько способов выделения виртуальной памяти процессам при помощи Windows API2). Рассмотрим два основных способа – с помощью функции VirtualAlloc и с использованием кучи.

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

Отметим, что резервируются участки виртуальной памяти по адресам, кратным значению константы гранулярности выделения памяти MM_ALLOCATION_GRANULARITY (файл base\ntos\inc\mm.h, строка 54). Это значение равно 64 КБ. Кроме того, размер резервируемой области должен быть кратен размеру страницы (4 КБ).

WinAPI функция VirtualAlloc для выделения памяти использует функцию ядра NtAllocateVirtualMemory (файл base\ntos\mm\allocvm.c, строка 173).

2. Для более гибкого распределения памяти существует куча процесса, которая управляется диспетчером кучи (heap manager). Кучу используют WinAPI функция HeapAlloc, а также оператор языка C malloc и оператор C++ new. Диспетчер кучи предоставляет возможность процессу выделять память с гранулярностью 8 байтов (в 32-разрядных системах), а для обслуживания этих запросов использует те же функции ядра, что и VirtualAlloc.

Дескрипторы виртуальных адресов

Для хранения информации о зарезервированных страницах памяти используются дескрипторы виртуальных адресов (Virtual Address Descriptors, VAD). Каждый дескриптор содержит данные об одной зарезервированной области памяти и описывается структурой MMVAD (файл base\ntos\mm\mi.h, строка 3976).

Границы области определяются двумя полями – StartingVpn (начальный VPN) и EndingVpn (конечный VPN). VPN (Virtual Page Number) – это номер виртуальной страницы; страницы просто нумеруются, начиная с нулевой. Если размер страницы 4 КБ (212 байт), то VPN получается из виртуального адреса начала страницы отбрасыванием младших 12 бит (или 3 шестнадцатеричных цифр). Например, если виртуальная страница начинается с адреса 0x340000, то VPN такой страницы равен 0x340.

Дескрипторы виртуальных адресов для каждого процесса организованы в сбалансированное двоичное АВЛ дерево3) (AVL tree). Для этого в структуре MMVAD имеются поля указатели на левого и правого потомков: LeftChild и RightChild.

Для хранения информации о состоянии области памяти, за которую отвечает дескриптор, в структуре MMVAD содержится поле флагов VadFlags.

Трансляция адресов

Трансляция виртуального адреса – это определение реального (физического) расположение ячейки памяти с данным виртуальным адресом, т. е. преобразование виртуального адреса в физический. Принцип трансляции показан на рис.11.1, здесь мы рассмотрим подробности трансляции и детали реализации в WRK.

Из рис.11.1 видно, что информация о соответствии виртуальных адресов физическим хранится в таблицах страниц. В системе для каждого процесса поддерживается множество записей о страницах: если размер страницы 4 КБ, то чтобы хранить информацию обо всех виртуальных страницах в 32 разрядной системе требуется более миллиона записей (4 ГБ / 4 КБ = 1 048 576). Эти записи о страницах сгруппированы в таблицы страниц (Page Table), запись называется PTE (Page Table Entry). В каждой таблице содержится 1024 записи, таким образом, максимальное количество таблиц страниц для процесса – 1024 (1 048 576 / 1024 = 1024). Половина от общего количества – 512 таблиц – отвечают за пользовательское ВАП, другая половина – за системное ВАП.

Таблицы страниц хранятся в виртуальной памяти (см. рис.11.2). Информация о расположении каждой из таблиц страниц находится в каталоге страниц (Page Directory), единственном для процесса. Записи этого каталога называются PDE (Page Directory Entry). Таким образом, процесс трансляции является двухступенчатым: сначала по виртуальному адресу определяется запись PDE в каталоге страниц, затем по этой записи находится соответствующая таблица страниц, запись PTE которой указывает на требуемую страницу в физической памяти.

Откуда процесс знает, где в памяти хранится каталог страниц? За это отвечает поле DirectoryTableBase структуры KPROCESS (файл base\ntos\inc\ke.h, строка 958, первый элемент массива). Схема трансляции адресов показана на рис.11.3.

Трансляция адресов


Рис. 11.3.  Трансляция адресов

Записи PDE и PTE представлены структурой MMPTE_HARDWARE (base\ntos\mm\i386\mi386.h, строка 2508), содержащей следующие основные поля:

В поле PageFrameNumber хранится номер записи в базе данных PFN – системной структуре, отвечающей за информацию о страницах физической памяти. Запись PFN представлена структурой MMPFN (файл base\ntos\mm\mi.h, строка 1710) и подробно описана в [5, стр. 502].

Ошибки страниц

Страница может находиться либо в физической памяти (ОЗУ), либо на диске в файле подкачки.

Если в записи PTE флаг Valid = 1, то страница находится в физической памяти и к ней можно обращаться. Иначе (Valid = 0) – страница недоступна процессу. При попытке доступа к такой странице возникает страничная ошибка (page fault) и вызывается функция MmAccessFault (файл base\ntos\mm\mmfault.c, строка 101).

Причин страничных ошибок существует множество (см. [Руссинович и др., 2008, стр. 463]), мы рассмотрим только одну – страница выгружена в страничный файл (файл подкачки). В этом случае запись PTE имеет тип MMPTE_SOFTWARE (файл base\ntos\mm\i386\mi386.h, строка 2446) и вместо поля PageFrameNumber имеет 20 разрядное поле PageFileHigh, отвечающее за расположение страницы в страничном файле.

Страничные файлы описываются структурой MMPAGING_FILE (base\ntos\mm\mi.h, строка 4239), имеющей следующие поля:

В 32 разрядных Windows поддерживается до 16 файлов подкачки размером до 4095 МБ каждый. Список файлов подкачки находится в ключе реестра HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\PagingFiles. Соответствующий системный массив MmPagingFile[MAX_PAGE_FILES] типа PMMPAGING_FILE описывается в файле base\ntos\mm\mi.h (строка 8045).

Пределы памяти

В таблицах 8.1, 8.2 и 8.3 приведены ограничения на виртуальную и физическую память в 32 разрядных и 64 разрядных операционных системах Windows.

Таблица 11.1. Ограничения на виртуальную память
Тип памяти32-разрядные Windows64-разрядные Windows
Виртуальное адресное пространство4 ГБ16 ТБ (16 000 ГБ)
Пользовательское ВАП2 ГБ; до 3 ГБ в случае использования специальных ключей при загрузке8 ТБ
Системное ВАП2 ГБ; от 1 до 2 ГБ в случае использования специальных ключей при загрузке8 ТБ
Таблица 11.2. Ограничения на физическую память в клиентских версиях
Версия Windows32-разрядные64-разрядные
Windows XPОт 512 МБ (Starter) до 4 ГБ (Professional)128 ГБ (Professional)
Windows Vistaот 1 ГБ (Starter) до 4 ГБ (Ultimate)от 8 ГБ (Home Basic) до 128 ГБ (Ultimate)
Windows 7от 2 ГБ (Starter) до 4 ГБ (Ultimate)от 8 ГБ (Home Basic) до 192 ГБ (Ultimate)
Таблица 11.3. Ограничения на физическую память в серверных версиях
Версия Windows32-разрядные64-разрядные
Windows Server 2003 R2 От 4 ГБ (Standard) до 64 ГБ (Datacenter)От 32 ГБ (Standard) до 1 ТБ (Datacenter)
Windows Server 2008 От 4 ГБ (Web Server) до 64 ГБ (Datacenter)От 32 ГБ (Web Server) до 1 ТБ (Datacenter)
Windows Server 2008 R2 нет 32 разрядных версийот 8 ГБ (Foundation) до 2 ТБ (Datacenter)

Резюме

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

Следующая лекция посвящена принципам обеспечения безопасности в Windows.

Контрольные вопросы

Лекция 12. Функции по управлению памятью

Цель работы: исследовать структуры данных и функции WRK, используемые при управлении памятью.

Задание 1. Определить значения системных переменных, отвечающих за границы областей виртуального адресного пространства (ВАП).

Указания к выполнению.

1. Узнаем значения 4 х системных переменных:

2. Чтобы узнать значение переменной MmHighestUserAddress, введите следующую команду:

dd MmHighestUserAddress L1

Команда dd означает отображение 4 байтовых значений, а L1 указывает, что нужно отобразить одно такое значение.



Таким образом, переменная MmHighestUserAddress = 7FFE FFFF.

Заметьте, что верхняя граница пользовательского ВАП не достигает стартового адреса системного ВАП 8000 0000. Область 64 КБ (8000 0000 – 7FFE FFFF = 64 КБ) зарезервирована системой и недоступна пользовательским процессам.

3. Аналогичным образом можно посмотреть значения других системных переменных.

Задание 2. Создать программу, которая выделяет область памяти.

Указания к выполнению.

1. Создайте пустой проект с названием, например, MemoryAlloc, в Visual Studio и добавьте файл исходного кода main.cpp (см. лабораторную работу 2 "Процессы и потоки", задание 3).

Сохраните проект в папке c:\Programs\MemoryAlloc.

2. Вставьте в main.cpp следующий исходный код:

#include <windows.h>
#include <tchar.h>
#include <stdio.h>

// Функция пытается записать по адресу lpvPointer 1 байт
VOID WriteCharToMemory(LPVOID lpvPointer, char Symbol)
{
  __try
  {
    *(LPTSTR)lpvPointer = Symbol;
    _tprintf ("First byte in memory area = '%c' (hex code = %x)\n\n", Symbol, Symbol);
  }
  __except(EXCEPTION_EXECUTE_HANDLER)
  {
    _tprintf ("Write to memory area failed\n\n");
  }
}

// Вывод сообщения об ошибке
VOID ErrorExit(LPTSTR lpMsg)
{
_tprintf("Error! %s with error code of %ld\n", lpMsg, GetLastError ());
exit (0);
}

VOID _tmain(VOID)
{
LPVOID lpvReserved;  // Адрес зарезервированной области памяти
  LPVOID lpvCommit;    // Адрес переданной области памяти
BOOL bSuccess;    // Признак успешного освобождения памяти
SYSTEM_INFO sSysInfo;  // Информация о системе
  DWORD dwPageSize;    // Размер страницы

GetSystemInfo(&sSysInfo);    // Получаем информацию о системе

_tprintf ("This computer has page size %d\n\n", sSysInfo.dwPageSize);

dwPageSize = sSysInfo.dwPageSize;  // Определяем размер страницы

// Точка останова
//_asm int 3

// Резервируем 1 страницу в памяти
lpvReserved = VirtualAlloc(
                     NULL,      // Адрес размещения выбирает система
                     dwPageSize,    // Размер области памяти
                     MEM_RESERVE,    // Резервируем, не передаем
                     PAGE_READWRITE);  // Память доступна для чтения-записи
if (lpvReserved == NULL )
ErrorExit("VirtualAlloc reserve failed");

  _tprintf ("Reserve succeeded, address = %x\n\n", lpvReserved);

  // Пытаемся записывать в память
WriteCharToMemory(lpvReserved, 'a');
    
// Точка останова
//_asm int 3

  // Передаем зарезервированную страницу
  lpvCommit = VirtualAlloc(
        lpvReserved,  // Адрес зарезервированной области 
        dwPageSize,    // Размер области памяти
        MEM_COMMIT,    // Передаем память
        PAGE_READWRITE);  // Память доступна для чтения-записи
  if (lpvCommit == NULL )
ErrorExit(TEXT("VirtualAlloc commit failed"));

  _tprintf ("Commit succeeded, address = %x\n\n", lpvCommit);

  // Пытаемся записывать в память
WriteCharToMemory(lpvCommit, 'a');

// Точка останова
//_asm int 3

  // Освобождаем область памяти
bSuccess = VirtualFree(
                       lpvReserved,    // Адрес освобождаемой области
                       0,      // Параметр должен быть равен 0
                       MEM_RELEASE);  // Освобождаем память

  if (bSuccess)
    _tprintf ("Release succeeded\n\n");
  else 
    _tprintf ("Release failed\n\n");

  system("pause");
}

Данная программа сначала резервирует (но не передает) страницу в памяти, пытается записать в эту область данные, затем передает (commit) зарезервированную область и снова пытается записать в неё данные.

3. Установите свойства проекта (см. лабораторную работу 2 "Процессы и потоки", задание 3):

4. Проверьте работоспособность созданного приложения (клавиша F5):



Задание 3. В приложении расставить точки останова.

Указания к выполнению.

1. В созданном проекте уберите комментарии (//) перед операторами:

_asm int 3

Требуется убрать 3 комментария (т. е. установить 3 точки останова).

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

int 3 – команда Ассемблера, обозначающая точку останова.

2. Запустите проект. Программа должна прерваться в первой точке останова, причем её выполнение можно продолжить, нажав кнопку Продолжить:



Задание 4. Исследовать функцию NtAllocateVirtualMemory.

Указания к выполнению.

1. Исполняемый файл созданного в предыдущем пункте приложения MemoryAlloc.exe после компиляции проекта должен находиться в следующей папке:

c:\Programs\MemoryAlloc\Release.

Скопируйте исполняемый файл на виртуальную машину.

2. Запустите исполняемый файл. Выполнение программы должно прерываться, а управление перейти к отладчику WinDbg.

3. Установите в отладчике точку останова на функции NtAllocateVirtualMemory. Для этого введите команду:

bp NtAllocateVirtualMemory

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

4. Продолжите выполнение, нажав F5 в отладчике. Должна сработать точка останова на функции NtAllocateVirtualMemory и откроется исходный код этой функции.

Замечание. В отладчике WinDbg номер текущей строки исходного кода отображается в окне отладчика внизу в информационной строке (Ln – Line):



5. Выполните трассировку функции NtAllocateVirtualMemory, обращая внимание на следующие участки кода.

Строка 344 – формирование маски защиты ProtectionMask. В нашем приложении указано значение PAGE_READWRITE = 0*04 (см. MSDN1)), поэтому маска защиты также будет равна 0*04.

Строка 353 – определение текущего процесса CurrentProcess. Проверьте, что имя исполняемого файла процесса MemoryAlloc.exe (воспользуйтесь окном Locals отладчика, нажав Alt+3; проверьте значение поля ImageFileName переменной CurrentProcess).

Строка 380 – определение размера области памяти (CapturedRegionSize). В нашем случае эта переменная должна быть равна 0x1000 (4096 байт, 1 страница).

Строка 491 – определение величины выравнивания (гранулярности памяти) Alignment. Как указывалось в лекции 8 "Управление памятью", это значение составляет 64 КБ.

Строка 504 – округление размера области памяти CapturedRegionSize до целого числа страниц.

Строка 545 – определение числа страниц NumberOfPages.

Строка 647 – выделение памяти под новый дескриптор виртуального адреса (VAD) при помощи функции ExAllocatePoolWithTag.

Строки 660–668 – заполнение поля VadFlags в дескрипторе виртуального адреса.

Строка 853 – вычисление стартового адреса StartingAddress области памяти при помощи функции MiFindEmptyAddressRange. Эта функция определяет свободные места подходящего размера в памяти, просматривая АВЛ дерево дескрипторов виртуальных адресов.

Строка 867 – вычисление конечного адреса EndingAddress области памяти по простой формуле с округлением до последнего адреса последней страницы, входящей в область.

Строки 899 и 900 – вычисление полей StartingVpn и EndingVpn дескриптора VAD (структура MMVAD) – начального и конечного номеров виртуальных страниц.

Строка 930 – вставка сформированного дескриптора виртуального адреса в АВЛ дерево и его балансировка при помощи функции MiInsertVad.

Строка 1049 – вычисление реального размера зарезервированной области памяти (CapturedRegionSize).

Строка 1050 – увеличение размера виртуальной памяти процесса на величину зарезервированной области (Process->VirtualSize).

Строка 1052 – изменение при необходимости пикового размера виртуальной памяти процесса (Process->PeakVirtualSize).

Строки 1078 и 1079 – возвращение указателя на область памяти (BaseAddress) и реального размера области памяти (RegionSize).

Задание 5. Просмотреть содержимое памяти, изменяемое приложением.

Указания к выполнению.

1. Удалите точку останова с функции NtAllocateVirtualMemory: в меню Edit отладчика выберите Breakpoints… и нажмите кнопку Remove All.

2. Возобновите выполнение виртуальной машины, пройдя все точки останова приложение MemoryAlloc.exe (несколько раз нажмите в отладчике F5).

3. Запустите приложение MemoryAlloc.exe ещё раз.

4. Когда сработает первая точка останова, продолжите в отладчике выполнение (нажмите F5 один раз).

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



6. Перейдите в отладчик и просмотрите содержимое памяти по этому адресу, введя команду:

db 340000 L1

Команда db отображает однобайтовое значение, параметр L1 указывает, что необходимо показать одно такое значение.

Как и подсказывает сообщение нашего приложения ("Write to memory area failed"), попытка записи в зарезервированную область память оказалась неудачна, значение в данной ячейке не определено:



Чтобы работать с памятью, её нужно передать (commit).

7. Нажмите в отладчике F5 – программа дойдет до третьей точки останова (после передачи памяти):



Снова просмотрите содержимое памяти по адресу 340000. Сейчас в этой ячейке должен оказаться символ ‘a’ (с шестнадцатеричным кодом 61):



Задание 6. Исследовать дескрипторы виртуальных адресов VAD.

Указания к выполнению.

1. Запустите приложение MemoryAlloc.exe. Когда сработает первая точка останова и управление перейдет к отладчику, требуется узнать адрес процесса MemoryAlloc.exe в памяти. Для этого введите команду:

!process 0 0

и найдите процесс с именем MemoryAlloc.exe:



В данном примере адрес процесса в памяти (адрес объекта EPROCESS): 81F9E320.

2. Определите адрес корня АВЛ дерева дескрипторов виртуальных адресов VadRoot.

Для этого введите команду:

!process 81F9E320 1



Адрес корня VadRoot равен 82251498.

3. Выведите на экран дерево дескрипторов виртуальных адресов процесса MemoryAlloc.exe.

Введите команду:

!vad 82251498



На экран выводится таблица со следующими столбцами:

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

4. Продолжите выполнение процесса MemoryAlloc.exe, нажав F5 в отладчике. Должна сработать вторая точка останова.

Снова выведите на экран дерево дескрипторов виртуальных адресов:



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

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



Как и отмечалось в лекции 8 "Управление памятью" номер виртуальной страницы в VAD получается путем отбрасыванием младших 12 бит (или 3 шестнадцатеричных цифр).

5. Продолжите выполнение приложения (F5). Сработает третья точка останова.

Снова выведите дерево дескрипторов виртуальных адресов: на экран:



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

Задания для самостоятельного выполнения

Задание 1. Исследуйте действие различных значений параметров защиты памяти функции VirtualAlloc на примере приложения MemoryAlloc.

Указания к выполнению.

1. На странице MSDN "Memory Protection Constants" приведено описание значений параметра flProtect функции VirtualAlloc. Попробуйте запустить приложение MemoryAlloc с разными значениями данного параметра и оцените результат.

Желательно также исследовать влияние параметра защиты на дескрипторы виртуальных адресов VAD.

Задание 2. Продолжить исследование функции NtAllocateVirtualMemory.

Указания к выполнению.

1. В основной части лабораторной работы (задание 4) осуществлялась трассировка функции NtAllocateVirtualMemory при резервировании области памяти в приложении MemoryAlloc.

Выполните трассировку данной функции при передаче (commit) области памяти (поставьте точку останова на функции NtAllocateVirtualMemory после срабатывания второй точки останова в программе MemoryAlloc.

Задание 3. Исследовать процесс трансляции виртуальных адресов.

Указания к выполнению.

1. Выполните процесс трансляции виртуального адреса из диапазона адресов области памяти, выделенной в приложении MemoryAlloc. Воспользуйтесь описанием трансляции из лекции 8 "Управление памятью" и примером из [5, стр. 456].

Задание 4. Исследовать утилиту для работы с виртуальной памятью VMMap от Sysinternals.

Указания к выполнению.

1. Скачайте утилиту VMMap по адресу:

http://technet.microsoft.com/en-us/sysinternals/dd535533

2. Исследуйте возможности утилиты на примере приложения MemoryAlloc.

Лекция 13. Безопасность в Windows

Требования к безопасности. Организация управляемого доступа к объектам. Права и привилегии.

Требования к безопасности

Требования к безопасности компьютерных систем определяются на основе международных стандартов по оценке защищенности. В настоящее время основным таким стандартом является ISO/IEC 15408 Common Criteria for Information Technology Security Evaluation (Общие критерии оценки безопасности информационных технологий), сокращенно Common Criteria (Общие критерии)1). В этом стандарте определены семь уровней безопасности – от EAL1 (минимальная оценка безопасности) до EAL7, причем требования к высшим уровням EAL5–EAL7 устанавливаются каждой страной индивидуально. Большинство современных операционных систем (в том числе семейство клиентских и серверных систем Microsoft: от Windows 2000 до Windows 7 и Windows Server 2008) сертифицировано на уровень EAL4+ (плюс означает Flaw Remediation – исправление ошибок, постоянный выпуск обновлений)2).

Основными требованиями к безопасности являются следующие3).

1. Обязательная идентификация и аутентификация.

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

В Windows за идентификацию и аутентификация пользователей отвечают процессы Winlogon.exe и Lsass.exe.

2. Управляемый доступ к объектам.

Пользователь-владелец объекта должен иметь возможность предоставлять доступ к объекту определенным пользователям и/или группам пользователей.

Безопасный доступ реализуется в Windows компонентом Security Reference Monitor (SRM, монитор контроля безопасности) исполнительной системы Ntoskrnl.exe.

3. Аудит.

Система должна уметь отслеживать и записывать все события, связанные с доступом к объектам.

В Windows аудит поддерживается SRM и Lsass.exe.

4. Защита при повторном использовании объектов.

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

В Windows освобожденная память очищается системным потоком обнуления страниц, работающим во время простоя системы (с нулевым приоритетом).

Далее в лекции будет рассмотрена организация управляемого доступа к объектам в SRM, а также права и привилегии пользователей.

Организация управляемого доступа к объектам

Принцип организации доступа

Принцип организации управляемого безопасного доступа к объектам выглядит следующим образом. У каждого пользователя в системе имеется свой маркер доступа (access token), в котором указан уникальный идентификатор пользователя. Процессы, создаваемые пользователем, наследуют его маркер.

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

При попытке доступа процесса к объекту идентификатор из маркера доступа процесса сравнивается с идентификаторами, содержащимися в дескрипторе защиты объекта, и на основании результатов сравнения доступ разрешается или запрещается.

Рассмотрим структуры данных и функции, отвечающие за реализацию этого принципа в ядре Windows.

Идентификаторы защиты

Для однозначного определения пользователя в системе используются идентификаторы защиты (SID – Security Identifier). Кроме пользователей, SID имеется у групп пользователей, компьютеров, доменов4) и членов доменов.

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

В WRK структура SID описывается в файле public\sdk\inc\ntseapi.h (строка 251). SID состоит из следующих частей:

В текстовом виде SID записывается следующим образом:

Текстовое представление SID


Рис. 13.1.  Текстовое представление SID

На рис.13.1 последний код субагента называется относительным идентификатором (relative identifier, RID), поскольку все учетные записи пользователей на компьютере могут иметь одинаковые коды, кроме RID. RID, который равен 500, обозначает локального администратора.

Существует множество предопределенных SID (см., WRK, файл public\sdk\inc\ntseapi.h, строки 296–568 и, например, статью базы знаний Microsoft – http://support.microsoft.com/kb/243330).

Маркер доступа

Идентификаторы безопасности пользователей хранятся в маркерах доступа (access token). Во время входа пользователя в систему процесс Lsass.exe создает для него маркер доступа, который назначается первому пользовательскому процессу UserInit.exe, остальные процессы, запущенные пользователем, наследуют этот маркер (рис.13.2). Маркер доступа процесса хранится в поле Token структуры EPROCESS (см. лекцию 6 "Процессы и потоки").

Маркер доступа представлен структурой TOKEN, описанной в файле base\ntos\se\tokenp.h (строка 235) и имеющей следующие основные поля:

Создание и наследование маркера доступа


Рис. 13.2.  Создание и наследование маркера доступа

Дескриптор защиты

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

Структура данных SECURITY_DESCRIPTOR, представляющая дескриптор защиты, описана в файле public\sdk\inc\ntseapi.h (строка 1173) и включает следующие основные поля:

Списки управления доступом (ACL, Access-Control List) в системе представлены заголовком (ACL Header) и последовательностью элементов списка (ACE, Access-Control Entry) (рис.13.3).

Внутреннее представление списка управления доступом ACL


Рис. 13.3.  Внутреннее представление списка управления доступом ACL

Заголовок списка описывается структурой ACL (файл public\sdk\inc\ntseapi.h, строка 658), в которой хранятся количество элементов списка ACL (поле AceCount) и общий размер списка без заголовка (поле AclSize).

Элементы ACE имеют заголовок (ACE Header), описываемый структурой ACE_HEADER (тот же файл, строка 687), маску доступа и идентификатор безопасности SID. В заголовке указывается тип ACE (поле AceType); множество значений этого поля приведены в строках 699–728. Основными являются ACCESS_ALLOWED_ACE_TYPE (доступ разрешен) и ACCESS_DENIED_ACE_TYPE (доступ запрещен).

Маска доступа (Access Mask) описывает разнообразные виды доступа к объектам (строки 72–166). В маске выделяются стандартные права доступа (Standard Access Rights), применимые к большинству объектов, и специфичные для объектов права доступа (Object-Specific Access Rights).

Стандартными являются следующие права доступа:

Списки управления доступом бывают двух видов: DACL и SACL. Список управления избирательным доступом (DACL, Discretionary Access-Control List) определяет пользователей, которые могут получать доступ к объекту, а также указывает тип доступа. В системном списке управления доступом (SACL, System Access-control List) перечислены пользователи и операции, которые должны учитываться в журнале аудита безопасности (security audit log).

Схема получения доступа процесса к объекту показана на рис.13.4.

Схема получения доступа процесса к объекту


Рис. 13.4.  Схема получения доступа процесса к объекту

За проверку возможности доступа процесса к объекту отвечает функция SeAccessCheck (файл base\ntos\se\accessck.c, строка 3391). На вход функции поступают следующие параметры:

Функция возвращает TRUE, если процессу возможно предоставить доступ к объекту, а также маску предоставленного доступа (GrantedAccess). Если доступ запрещен, функция возвращает FALSE.

Функция SeAccessCheck осуществляет следующие действия:

Права и привилегии

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

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

Право учетной записи (account right) – разрешение или запрет на определенный вид входа в систему.

Различают следующие виды входа:

Проверка прав учетных записей осуществляется не в ядре, а в процессах Winlogon.exe и Lsass.exe.

Привилегия (privilege) – разрешение или запрет определенных действий в системе, например, включение/выключение компьютера или загрузка драйверов. Привилегии хранятся в поле Privileges структуры маркера доступа TOKEN (см. выше).

Список всех привилегий в системе можно посмотреть в оснастке MMC "Локальная политика безопасности" (Local Security Policy), раздел "Локальные политики" – "Назначение прав пользователей" (Local Policies – User Rights Assignment) (см. рис.13.5). Вызывается оснастка через Панель управления – Администрирование. (Control Panel – Administrative Tools).

Оснастка "Локальная политика безопасности"


Рис. 13.5.  Оснастка "Локальная политика безопасности"

Следует отметить, что в оснастке не различаются права учетных записей и привилегии, но это легко можно сделать самостоятельно: право учетной записи всегда содержит слово "вход" (например, "Вход в качестве пакетного задания").

Резюме

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

Следующая лекция посвящена вопросам взаимодействия Windows с внешними устройствами.

Контрольные вопросы

  1. Какие требования к безопасности предъявляются к Windows?
  2. Что такое идентификатор защиты (SID)? Как его узнать?
  3. Что такое дескриптор защиты (security descriptor)? Где он хранится?
  4. Что такое маркер доступа (access token)? Где он хранится?
  5. Опишите схему получения доступа процесса к объекту.
  6. Что такое права и привилегии учетных записей? Чем они отличаются?

Лекция 14. Обеспечение безопасности в Windows

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

Задание 1. Определить идентификатор защиты SID текущего пользователя.

Указания к выполнению.

1. Узнать SID текущего пользователя можно несколькими способами.

Способ 1. При помощи утилиты Process Explorer.

Запустите в виртуальной машине утилиту Process Explorer. Сделайте двойной щелчок на процессе explorer.exe (или любом другом процессе, запущенном текущим пользователем). Когда откроется окно свойств процесса, перейдите на вкладку Security:



На рисунке вверху красным цветом выделен SID пользователя – владельца процесса. В данном случае, процесс запущен пользователем с именем Administrator, который является администратором системы, о чем говорит, во первых, RID = 500 (последнее число в SID, см. статью базы знаний Microsoft "Хорошо известные идентификаторы безопасности в операционных системах Windows"1)), во вторых, то, что пользователь входит в группу администраторов (BUILTIN\Administrators).

Способ 2. При помощи утилиты PsGetSid.

Утилита PsGetSid специально предназначена для получения SID разных учетных записей. Данная утилита входит в набор PsTools и её можно скачать с сайта Sysinternals.

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



Удостоверьтесь, что SID полностью совпадают в первом и втором способах.

Задание 2. Определить тип файловой системы.

Указания к выполнению.

1. Следующие задания в данной лабораторной работе основываются на предположении, что системный раздел (логический диск) на виртуальной машине отформатирован с использованием файловой системы NTFS. Проверить, так ли это, можно следующим образом. Дважды щелкните на ярлыке My Computer – щелкните правой кнопкой на локальном диске C – выберите пункт Properties. На рисунке ниже обведен тип файловой системы логического диска:



Задание 3. Создать программу, которая открывает и читает файл.

Указания к выполнению.

1. Создайте пустой проект с названием, например, ReadFile, в Visual Studio и добавьте файл исходного кода main.cpp (см. лабораторную работу 2 "Процессы и потоки", задание 3).

Сохраните проект в папку c:\Programs\ReadFile.

2. Вставьте в main.cpp следующий исходный код (модифицированный пример из MSDN):

#include <windows.h>
#include <tchar.h>
#include <stdio.h>

#define BUFFERSIZE 81

void __cdecl _tmain(int argc, TCHAR *argv[])
{
HANDLE hFile;					// Дескриптор файла
DWORD  dwBytesRead = 0;			// Количество прочитанных байт
char   ReadBuffer[BUFFERSIZE] = {0};	// Буфер для чтения
char   FileName[] = "input.txt";	// Имя файла

// Точка останова
//__asm int 3

hFile = CreateFile(FileName,	// Имя файла для открытия
GENERIC_READ,		// Открываем для чтения
FILE_SHARE_READ,		// Файл можно открывать другим процессам
NULL,				// Атрибуты безопасности по умолчанию
OPEN_EXISTING,		// Открытие существующего файла
FILE_ATTRIBUTE_NORMAL,	// Атрибуты файла - обычные
NULL);			// Без шаблона

if (hFile == INVALID_HANDLE_VALUE) 
{ 
_tprintf("Terminal failure: unable to open file \"%s\" for read\n", FileName);
system("pause");
return; 
}

// Читаем на один символ меньше, чем помещается в буфер,
// чтобы сохранить место под завершающий нулевой символ

if( ReadFile(hFile, ReadBuffer, BUFFERSIZE-1, &dwBytesRead, NULL) == FALSE)
{
printf("Terminal failure: Unable to read from file\n");
CloseHandle(hFile);
system("pause");
return;
}

if (dwBytesRead > 0 && dwBytesRead <= BUFFERSIZE-1)
{
ReadBuffer[dwBytesRead]='\0'; // символ с кодом "0"
_tprintf("Data read from %s (%d bytes): \n\n", FileName, dwBytesRead);
printf("%s\n\n", ReadBuffer);
}
else if (dwBytesRead == 0)
{
_tprintf(TEXT("No data read from file %s\n"), FileName);
}
else
{
printf("\n ** Unexpected value for dwBytesRead ** \n");
}

CloseHandle(hFile);

system("pause");
}

Приведенная программа открывает файл input.txt при помощи функции CreateFile, пытается прочитать из него строку символов при помощи функции ReadFile (не более 80 символов) и выводит её на экран.

3. Создайте текстовый файл input.txt в папке проекта, в которой расположен файл main.cpp (например, c:\Programs\ReadFile\ReadFile).

Наберите в нем какой нибудь текст латиницей и сохраните файл.

4. Установите свойства проекта (см. лабораторную работу 2 "Процессы и потоки", задание 3):

5. Проверьте работоспособность созданного приложения (клавиша F5):



Задание 4. В приложении поставить точку останова.

Указания к выполнению.

1. В созданном проекте уберите комментарии (//) перед оператором, обозначающим точку останова:

_asm int 3

2. Запустите проект. Программа должна прерваться в точке останова. Её выполнение можно продолжить, нажав кнопку Продолжить:



Задание 5. Запустить приложение ReadFile на виртуальной машине, установить точку останова на функции SeAccessCheck.

Указания к выполнению.

1. Исполняемый файл созданного в предыдущем пункте приложения ReadFile.exe после компиляции проекта должен находиться в следующей папке:

c:\Programs\ReadFile\Release.

Скопируйте исполняемый файл на виртуальную машину.

Скопируйте текстовый файл input.txt на виртуальную машину в ту же папку, что и исполняемый файл.

2. Запустите исполняемый файл. Выполнение программы должно прерываться, а управление перейти к отладчику WinDbg.

3. Установите в отладчике точку останова на функции SeAccessCheck для процесса ReadFile.exe. Для этого сначала следует определить адрес объекта EPROCESS для процесса ReadFile.exe при помощи команды:

!process 0 0 ReadFile.exe



В данном случае адрес равен 0x8222F630.

Установить точку останова для функции SeAccessCheck для процесса ReadFile.exe можно при помощи следующей команды:

bp /p 8222F630 nt!SeAccessCheck

Проверьте, что команда выполнена верно: в меню Edit отладчика выберите пункт Breakpoints…:



Замечание. После перезапуска процесса ReadFile.exe его адрес может поменяться. В этом случае придется удалить старую точку останова и повторить нахождение адреса объекта EPROCESS и установки новой точки останова.

4. Продолжите выполнение, нажав F5 в отладчике. Должна сработать точка останова на функции SeAccessCheck и откроется исходный код этой функции.

Задание 6. Исследовать маркер доступа (access token).

Указания к выполнению.

1. Функция SeAccessCheck проверяет возможность доступа процесса с заданным маркером доступа к объекту с определенным дескриптором защиты (см. лекцию 13 "Безопасность в Windows").

2. Адрес маркера доступа процесса содержится в поле PrimaryToken параметра SubjectSecurityContext. Чтобы узнать этот адрес, откройте в отладчике окно Locals (Alt+3) и раскройте параметр SubjectSecurityContext (нажмите "плюс" слева от имени параметра):



На рисунке видно, что адрес в поле PrimaryToken равен 0xE1249870.

3. Просмотрите содержимое структуры _TOKEN, по адресу, определенному в предыдущем пункте. Введите команду:

dt _token 0xE1249870



В этой структуре содержится вся информация о маркере. Например, ID маркера хранится в поле TokenID типа _LUID по смещению 0x010 от начала структуры. Чтобы узнать ID маркера, введите следующую команду:

dt _LUID 0xE1249870+0x010



В данном примере TokenID равен 0x963D2.

SID учетной записи пользователя владельца маркера и групп, в которые он входит, хранятся по адресу в поле UserAndGroups:

dt _SID_AND_ATTRIBUTES 0xe1249a00



В первом поле структуры _SID_AND_ATTRIBUTES хранится адрес SID. Чтобы узнать какой SID расположен по данному адресу, можно воспользоваться следующей командой:

!sid 0xe1249a58



Очевидно, что это SID учетной записи с именем Administrator, ведь процесс ReadFile запускался из под неё.

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

dd 0xe1249a00



Структура _SID_AND_ATTRIBUTES для учетной записи Administrator выделена на рисунке красным, для первой группы, в которую входит Administrator – синим.

Чтобы узнать SID групп, можно снова воспользоваться командой !sid:



Этот SID соответствует группе Users (см. статью MSDN "Хорошо известные идентификаторы безопасности в операционных системах Windows").

4. В отладчике WinDbg имеется специальная команда для удобного отображения содержимого маркера доступа – !token.

Введите эту команду, указав адрес маркера доступа:

!token 0xE1249870



Сравните информацию, выводимую командой !token, с данными, полученными в предыдущем пункте путем анализа полей структуры TOKEN.

Задание 7. Исследовать дескриптор защиты (security descriptor).

Указания к выполнению.

1. Адрес дескриптора защиты объекта (в данном случае – файла) содержится в параметре SecurityDescriptor функции SeAccessCheck:



2. Дескриптор защиты, зная его адрес, можно посмотреть при помощи команды !sd:

!sd 0xE186ABC0 1

Параметр 1 в конце указывает, что нужно по возможности указывать имена для SID.



Уровень доступа к объекту определяется в списке DACL маской доступа (поля Mask выделены на рисунке красным). В маске отдельные биты отвечают за определенные виды доступа.

Выделяют стандартные права доступа (Standard Access Rights), применимые к большинству объектов, и специфичные для объектов права доступа (Object-Specific Access Rights) (см. лекцию 9 "Безопасность в Windows"). Описание стандартных прав доступа и соответствующих значений масок приведено в файле public\sdk\inc\ntseapi.h (строки 72–166), а также описаны в статье MSDN "Access Mask Format"1). Описание прав доступа для файлов и каталогов имеется в файле public\sdk\inc\ntioapi.h (строки 41–108), а также в статье MSDN "Access Mask"2).

Для примера рассмотрим две маски, используемые для файла input.txt (см. рисунок выше): 0x001F01FF и 0x001200A9.

Представим маску 0x001F01FF в двоичном виде и укажем, за что отвечает каждая единица:



Как видно из рисунка, маска 0x001F01FF обозначает полный доступ к файлу. Такой доступ имеют члены группы Administrators и системная учетная запись Система (System).

Рассмотрим вторую маску доступа 0x001200A9:



Таким образом, члены группы Пользователи (Users) имеют доступ только на чтение и исполнение.

Задания для самостоятельного выполнения

Задание 1. Изменить права доступа на файл и зафиксировать изменения в DACL дескриптора защиты файла.

Указания к выполнению.

1. В виртуальной машине измените права доступа на файл input.txt. Для этого в свойствах файла перейдите на вкладку Security и сделайте необходимые изменения (добавьте/удалите пользователей и/или группы, измените их права):



Замечание. Для осуществления некоторых действий (например, удаления пользователей) может потребоваться отказаться от наследования прав доступа объекта родителя (папки). Для этого нажмите кнопку Advanced – снимите флажок Allow inheritable permissions… – в появившемся окне нажмите кнопку Copy:



2. Просмотрите дескриптор безопасности файла, повторив действия из задания 7 основной части работы.

Задание 2. Исследовать функцию SeAccessCheck.

Указания к выполнению.

1. Выполните трассировку функции SeAccessCheck, пользуясь материалом лекции 9 "Безопасность в Windows" и документацией WRK.

Задание 3. Исследовать расположение в памяти структуры SECURITY_DESCRIPTOR.

Указания к выполнению.

1. Выясните расположение в памяти структуры SECURITY_DESCRIPTOR, пользуясь информацией, изложенной в задании 7 данной лабораторной работы, материалом лекции 9 "Безопасность в Windows", документацией WRK и статьей MSDN "SECURITY_DESCRIPTOR"3).

Лекция 15. Управление устройствами

Подсистема ввода-вывода. Принцип управления устройствами. Структуры данных для ввода-вывода. Пример ввода-вывода.

Подсистема ввода-вывода

В компьютерных системах кроме процессора и оперативной памяти присутствует множество разнообразных устройств (device) – жесткие диски, приводы оптических дисков (CD, DVD, Blu-Ray Disk), устройства флеш-памяти, принтеры, сканеры, звуковые и видеокарты, модемы, сетевые карты и т. п.

Операционная система должна обеспечивать управление всеми этими устройствами, т. е. предоставлять способы обмена информацией между приложениями и устройствами.

Управление устройствами в Windows осуществляется подсистемой ввода вывода, включающей несколько компонентов (см. рис.4.1 в лекции 4 "Архитектура Windows"):

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

Принцип управления устройствами

Рассмотрим схематично принцип управления внешними устройствами, а затем перейдем к изучению соответствующих структур и функций WRK.

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

Файл (file) – совокупность данных, имеющих имя и допускающих операции чтения-записи. Типичная последовательность работы с файлом: открытие файла, выполнение команд чтения-записи, закрытие файла.

При открытии файла создается файловый объект типа FILE_OBJECT, который связан с объектом, представляющим конкретное устройство (DEVICE_OBJECT). В объекте-устройстве содержится информация о драйвере, который управляет этим устройством. Драйвер в системе описывается объектом типа DRIVER_OBJECT. Объекты DRIVER_OBJECT создаются при загрузке в систему нового драйвера. Затем объект DRIVER_OBJECT может создать несколько объектов DEVICE_OBJECT – по количеству управляемых драйвером устройств (рис.15.1).

Объекты для управления вводом-выводом


Рис. 15.1.  Объекты для управления вводом-выводом

Как видно из рис.15.1, в объекте DRIVER_OBJECT содержится указатель на список объектов-устройств, а в каждом из этих объектов хранится ссылка на управляющий драйвер. Таким образом, имея информацию об объекте DRIVER_OBJECT, можно найти все устройства, которыми он управляет и, наоборот, по объекту DEVICE_OBJECT легко определяется драйвер устройства.

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

Операция, которая запрашивается приложением, представляется в системе объектом типа IRP (I/O Request Packet – пакет запроса на ввод/вывод). В этом объекте хранится информация о типе операции ввода/вывода (создание, чтение, запись и т. п.), а также необходимые параметры для данной операции. Пакет IRP передается диспетчером ввода-вывода в очередь IRP потока, который запросил операцию ввода-вывода, после чего вызывается соответствующий драйвер, непосредственно выполняющий запрошенную операцию.

Структуры данных для ввода-вывода

Драйвер в системе описывается объектом типа DRIVER_OBJECT (файл base\ntos\inc\io.h, строка 1603), имеющим следующие основные поля:

Устройства представлены объектами типа DEVICE_OBJECT, который включает следующие главные поля (файл base\ntos\inc\io.h, строка 1397):

Пакеты запроса на ввод-вывод описываются типом IRP (I/O Request Packet), состоящим из двух частей – заголовка фиксированной длины (тело IRP) и одного или нескольких блоков стека. В заголовке описывается информация, общая для запроса. Каждый блок стека содержит данные об одной операции ввода-вывода.

Заголовок включает следующие основные поля:

Структура блока стека IO_STACK_LOCATION описана в файле base\ntos\inc\io.h, строка 2303) и имеет следующие главные поля:

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

Пример ввода-вывода

Для ввода-вывода используются следующие основные функции:

Рассмотрим пример чтения с устройства, используя изученные структуры данных и функцию NtReadFile (рис.15.2).

Последовательность операций и структуры данных при чтении с устройства


Рис. 15.2.  Последовательность операций и структуры данных при чтении с устройства

Предположим, некоторому приложению требуется прочитать данные с устройства, например, из файла на жестком диске. Предварительно приложение должно получить дескриптор объекта FILE_OBJECT, например, при помощи WinAPI функции CreateFile.

Для чтения из файла приложение вызывает WinAPI-функцию ReadFile, которая обращается к функции диспетчера ввода-вывода NtReadFile и передает ей дескриптор объекта FILE_OBJECT.

Функция NtReadFile определена в файле base\ntos\io\iomgr\read.c (строка 90) и выполняет две основные задачи – создает объект IRP (строка 517) и вызывает функцию IopSynchronousServiceTail (строка 725). При создании объекта IRP в блок стека заносится номер основной функции (Major Function), в случае операции чтения этот код равен константе IRP_MJ_READ (строка 558) и указывает на функцию чтения в массиве MajorFunction структуры DRIVER_OBJECT.

Функция IopSynchronousServiceTail определена в файле base\ntos\io\iomgr\internal.c (строка 7458). Эта функция помещает переданный ей объект IRP в очередь потока (функция IopQueueThreadIrp, строка 7468). Указатель на очередь IRP потока хранится в поле IrpList структуры ETHREAD (файл base\ntos\inc\ps.h, строка 623). Кроме этого, функция IopQueueThreadIrp вызывает соответствующий драйвер (функция IoCallDriver, строка 7494).

Драйвер выполняет определенную кодом IRP функцию и возвращает статус операции.

Резюме

В лекции представлены компоненты подсистемы ввода вывода в Windows, рассмотрен принцип управления устройствами, а также реализация этого принципа на основе структур данных и функций Windows Research Kernel. Разобран пример ввода вывода для операции чтения из файла.

В следующей лекции подробно рассматривается структура основной файловой системы Windows – NTFS.

Контрольные вопросы

  1. Перечислите компоненты подсистемы ввода вывода в Windows.
  2. Дайте определение понятия "файл".
  3. Опишите основные структуры данных, участвующие в процессе ввода вывода.
  4. Расскажите о взаимодействии объектов FILE_OBJECT, DEVICE_OBJECT, DRIVER_OBJECT, IRP в процессе ввода вывода.
  5. Какую роль играет массив MajorFunction в структуре DRIVER_OBJECT?
  6. Приведите пример ввода вывода с описанием участвующих в нем структур данных и функций Windows Research Kernel.

Лекция 16. Функции для управления устройствами

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

Задание 1. Запустить приложение ReadFile.exe, установить точку останова на функции NtReadFile.

Указания к выполнению.

1. В виртуальной машине запустите приложение ReadFile.exe, разработанное в лабораторной работе 5 "Безопасность в Windows".

2. Когда сработает точка останова и управление перейдет к отладчику WinDbg, нужно установить точку останова на функции NtReadFile для процесса ReadFile.exe.

Для этого сначала следует определить адрес объекта EPROCESS для процесса ReadFile.exe при помощи команды:

!process 0 0 ReadFile.exe



В данном случае адрес равен 0x8222F630.

Установить точку останова для функции NtReadFile для процесса ReadFile.exe можно при помощи следующей команды:

bp /p 8222F630 nt!NtReadFile

Проверьте, что команда выполнена верно: в меню Edit отладчика выберите пункт Breakpoints…:



Замечание. После перезапуска процесса ReadFile.exe его адрес может поменяться. В этом случае придется удалить старую точку останова и повторить нахождение адреса объекта EPROCESS и установки новой точки останова.

3. Продолжите выполнение приложения ReadFile.exe (нажмите в отладчике F5). Должна сработать точка останова и открыться исходный код функции NtReadFile.

4. Удостоверьтесь, что поток, вызвавший функцию NtReadFile, действительно принадлежит процессу ReadFile.exe: используя клавишу F10 дойдите в отладчике до строки 112. В этом месте вызывается функция PsGetCurrentThread – получение указателя на текущий поток (посмотрите, как эта функция реализована). Чтобы произошел её вызов ещё раз нажмите F10.

После вызова функции PsGetCurrentThread в переменной CurrentThread содержится адрес объекта ETHREAD текущего потока. Этот адрес можно узнать либо наведя указатель мыши на переменную CurrentThread, либо посмотрев её значение в окне Locals (Alt+3):



В данном примере адрес структуры ETHREAD равен 0x81FB6A10.

Чтобы найти процесс, которому принадлежит текущий поток, найдите поле ThreadsProcess структуры ETHREAD, расположенной по адресу 0x81FB6A10. Это можно сделать либо введя команду:

dt ethread 81FB6A10

либо развернув переменную CurrentThread в окне Locals. На рисунке ниже показан первый способ:



Адрес структуры EPROCESS процесса-владельца текущего потока равен 0x8222F630.

Далее введите команду:

dt eprocess 8222F630

и в поле ImageFileName будет записано имя исполняемого файла:



Ту же информацию можно получить последовательно раскрывая поля структур в окне Locals.

5. Обратите внимание на функцию ObReferenceObjectByHandle в строке 121. Эта функция по заданному дескриптору (handle) возвращает указатель на объект. В данном случае функция ObReferenceObjectByHandle по известному дескриптору файла (параметр FileHandle) возвращает указатель на объект FILE_OBJECT (параметр fileObject).

Кроме того, данная функция проверяет права доступа потока на чтение файла. Если прав недостаточно, функция сообщает об этом, возвращая соответствующее значение (STATUS_ACCESS_DENIED) переменной status.

Задание 2. Исследовать параметры функции NtReadFile.

Указания к выполнению.

1. Функция NtReadFile имеет следующие параметры:

Значения параметров можно посмотреть в окне Locals:



Дескриптор файла равен 1C, адрес буфера равен 0x0012FF14, размер буфера – 80 байт (0x50), смещение в файле равно нулю.

Посмотрим, что находится в данный момент в буфере:

dd 12FF14



Пока данные не прочитаны, буфер пуст.

По дескриптору файла можно узнать, что это за файл. Воспользуемся следующей командой:

!handle 1C f 8222F630

где 1C – дескриптор файла;

f – отображение полной информации об объекте;

8222F630 – адрес объекта EPROCESS процесса ReadFile.exe.



Как видно из рисунка, дескриптор 1C описывает файл input.txt.

Задание 3. Исследовать структуру FILE_OBJECT.

Указания к выполнению.

1. Как уже отмечалось, в строке 121 вызывается функция ObReferenceObjectByHandle, которая в переменной fileObject возвращает указатель на объект FILE_OBJECT для файла input.txt.

Вследствие оптимизации программного кода ядра значение переменной fileObject невозможно напрямую посмотреть в отладчике (например, окно Locals не показывает эту переменную, наведение указателя мыши также не дает результата). Можно определить адрес объекта FILE_OBJECT, исследуя дизассемблированный код (окно Disassembly отладчика), но мы поступим проще.

В предыдущем задании при помощи команды

!handle 1C f 8222F630

нам удалось по дескриптору определить имя файла. В результате этой же команды выводится и адрес объекта FILE_OBJECT:



Таким образом, адрес объекта FILE_OBJECT для файла input.txt равен 0x81FFF2F8.

2. Изучим информацию, содержащуюся в объекте FILE_OBJECT.

Для этого можно воспользоваться двумя способами.

Способ 1. При помощи команды:

dt FILE_OBJECT 81FFF2F8



Поле Type равно 5 – согласно константам, определенным в файле base\ntos\inc\io.h (строка 35), это тип IO_TYPE_FILE.

В структуре присутствует адрес объекта DEVICE_OBJECT, с которым связан данный файл (см. лекцию 15 "Управление устройствами", рис. 15.1 и рис. 15.2).

Способ 2. При помощи команды:

!fileobj 81FFF2F8



Здесь присутствует та же информация, что и в первом случае.

Задание 4. Исследовать структуру DEVICE_OBJECT.

Указания к выполнению.

1. В предыдущем задании в информации, выводимой об объекте FILE_OBJECT, присутствует адрес объекта DEVICE_OBJECT, на котором находится файл. Этот адрес равен 0x823ADE00.

Чтобы посмотреть информацию об объекте DEVICE_OBJECT по этому адресу, можно опять воспользоваться либо общей командой отображения типов данных dt, либо специальной командой !devobj. Продемонстрируем результат второго способа:.

!devobj 823ADE00



Данная команда сообщает имя драйвера, который управляет устройством (\Driver\Ftdisk – диспетчер томов) и адрес объекта драйвера DRIVER_OBJECT (0x82373690). Кроме того, из рисунка видно, что данное устройство отвечает за том HarddiskVolume1 (диск C).

Задание 5. Исследовать структуру DRIVER_OBJECT.

Указания к выполнению.

1. В предыдущем задании мы узнали адрес объекта драйвера, который отвечает за устройство HarddiskVolume1. Информацию об этом драйвере можно получить либо при помощи команды:

dt DRIVER_OBJECT 82373690

либо при помощи команды:

!drvobj 82373690

Воспользуемся вторым способом:



На рисунке показаны имя драйвера и объекты-устройства, которыми данный драйвер управляет. В частности, кроме устройства HarddiskVolume1, драйвер управляет устройством, которое описывается объектом DEVICE_OBJECT, расположенным по адресу 0x82373278.

2. Дополнительную информацию о драйверах можно получить, воспользовавшись утилитой Process Explorer. В ней следует выбрать процесс System и отобразить для него DLL (меню View – пункт Lower Pane View – DLL):



Задание 6. Исследовать структуру IRP.

Указания к выполнению.

1. Продолжим трассировку функции NtReadFile (мы остановились на строке 121 – вызов функции ObReferenceObjectByHandle) – клавиша F10.

Обратите внимание на переменную deviceObject (строка 135). Значение, которое в ней оказывается после вызова функции IoGetRelatedDeviceObject не совпадает с полученным нами в задании 3. Дело в том, что запрос к файлу проходит несколько драйверов на разных уровнях, и в переменную deviceObject помещается ссылка на драйвер верхнего уровня.

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

2. Найдите строку 517 – здесь происходит вызов функции IopAllocateIrp, которая выделяет память (но не заполняет) под структуру IRP. Поставьте в этой строке точку останова (нажмите F9):



И продолжите выполнение кода функции – нажмите F5. Управление должно перейти в точку останова на строку 517.

3. После выполнения функции IopAllocateIrp (нажмите клавишу F10) узнайте адрес переменной irp и просмотрите её содержимое либо при помощи команды:

dt irp address

либо при помощи команды:

!irp address

Предположим, адрес переменной irp равен 0x81efc008:



Из рисунка видно, что структура IRP пустая, и в ней 9 блоков стека (структур типа IO_STACK_LOCATION), 10-й блок, не заполненный, является текущим. Количество блоков стека указывается в поле StackSize структуры DEVICE_OBJECT, а определяется это количество системой; причем различаются малые IRP с одним блоком стека и большие IRP, количество блоков стека которых варьируется (подробнее см. [5, стр. 595]). В нашем случае мы имеем дело с большим IRP.

4. Далее по коду функции происходит заполнение структуры IRP (просмотрите заполнение полей самостоятельно).

Обратите внимание на поля UserIosb (блок статуса, строка 542), majorFunction (номер основной функции; в случае чтения он равен константе IRP_MJ_READ = 3, строка 558), UserBuffer (буфер чтения, строка 697).

5. В строке 725 происходит вызов функции IopSynchronousServiceTail, которая помещает сформированный IRP в очередь потока.

Перед вызовом этой функции просмотрите структуру IRP:

!irp 0x81efc008



Из рисунка видно, что последний блок стека сейчас заполнен:

Чтобы посмотреть структуру IO_STACK_LOCATION для данного блока стека, нужно из адреса текущего блока стека (выделен на рисунке) вычесть 0x24 (размер блока стека):

dt IO_STACK_LOCATION 0x81EFC1BC–0x24



Задание 7. Исследовать результаты операции чтения.

Указания к выполнению.

1. Выполните вызов функции IopSynchronousServiceTail (нажмите один раз F10).

Когда управление вернется к отладчику, операция чтения будет выполнена.

2. Просмотрите содержимое буфера чтения.

Адрес буфера равен 0x0012FF14 (переменная Buffer, см. Задание 2 данной лабораторной работы). Просмотреть его содержимое (в ASCII кодах) можно командой:

da 0x0012FF14

или (в байтах) командой:

db 0x0012FF14



3. Просмотрите содержимое блока статуса – переменную IoStatusBlock (тип IO_STATUS_BLOCK). Найти её адрес можно в окне Locals.



Поле Status, равное нулю, говорит о том, что операция выполнена успешно. В поле Information содержится количество прочитанных байт (0x20 = 32 байта).

Задания для самостоятельного выполнения

Задание 1. Выполнить трассировку функции NtWriteFile и найдите отличия от функции NtReadFile.

Указания к выполнению.

1. Для исследования функции ядра NtWriteFile создайте проект на основе примера из MSDN для WinAPI функции WriteFile.

Задание 2. Исследовать параметры безопасности объектов устройств.

Указания к выполнению.

1. В объекте DEVICE_OBJECT имеется поле SecurityDescriptor. Требуется по методике, изложенной в лабораторной работе 5 "Безопасность в Windows", исследовать дескриптор безопасности объекта устройства.

Лекция 17. Файловая система NTFS

Основные понятия. Возможности NTFS. Структура NTFS. Файлы NTFS. Структуры данных для управления файлами.

Основные понятия

Файловая система (file system) – способ организации данных в виде файлов на устройствах внешней памяти (жестких и оптических дисках, устройствах флеш-памяти и т. п.).

Файловая система должна обеспечивать:

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

Windows поддерживает несколько файловых систем для различных внешних устройств:

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

Диск (disk) – устройство внешней памяти, например, жесткий диск или оптический диск (CD, DVD, Blu ray).

Раздел (partition) – непрерывная часть жесткого диска. Диск может содержать несколько разделов.

Том (volume) или логический диск (logical disk) – область внешней памяти, с которой операционная система работает как с единым целым. Тома бывают простые и составные.

Простой том (simple volume) – том, состоящий из одного раздела.

Составной том (multipartition volume) – том, состоящий из нескольких разделов (необязательно на одном диске).

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

Сектор (sector) – блок данных фиксированного размера на диске; наименьшая единица информации для диска. Типичный размер сектора для жестких дисков равен 512 байтам, для оптических дисков – 2048 байт. Деление диска на секторы происходит один раз при создании диска в процессе низкоуровневого форматирования и обычно не может быть изменено.

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

При записи на диск файл всегда будет занимать целое число кластеров. Например, файл размером 100 байт в файловой системе с размером кластера 4 КБ будет занимать ровно 4 КБ.

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

Возможности NTFS

Файловая система NTFS (New Technology File System) разрабатывалась Microsoft в начале 1990 х гг. как основная файловая система для серверных версий операционных систем Windows. NTFS была представлена в 1993 году в операционной системе Windows NT 3.1.

В настоящее время NTFS рассматривается в качестве предпочтительной файловой системы как для серверных, так и для клиентских версий Windows.

В NTFS используются 64 разрядные идентификаторы кластеров, поэтому теоретически том NTFS может содержать 264 кластеров (16 ЭБ3) ). Однако текущие реализации в Windows поддерживают только 32 разрядную адресацию кластеров, что при размере кластера максимум 64 КБ (216 байт) позволяет NTFS тому достигать размера до 256 ТБ:

232 * 216 байт = 248 байт = 28 * 240 байт = 256 ТБ.

Для томов, больших 4 ГБ, при форматировании Windows предлагает размер кластера по умолчанию 4 КБ.

Перечислим некоторые возможности NTFS [5, стр. 761]:

Структура NTFS

Структура тома NTFS представлена на рис.17.1.

Структура NTFS тома


Рис. 17.1.  Структура NTFS тома

В начале тома находится загрузочная запись тома (Volume Boot Record), в которой содержится код загрузки Windows, информация о томе (в частности, тип файловой системы), адреса системных файлов ($Mft и $MftMirr – см. далее). Загрузочная запись занимает обычно 8 КБ (16 первых секторов).

В определенной области тома (адрес начала этой области указывается в загрузочной записи) расположена основная системная структура NTFS – главная таблица файлов (Master File Table, MFT). В записях этой таблицы содержится вся информация о расположении файлов на томе, а небольшие файлы хранятся прямо в записях MFT.

Важной особенностью NTFS является то, что вся информация, как пользовательская, так и системная, хранится в виде файлов. Имена системных файлов начинаются со знака "$". Например, загрузочная запись тома содержится в файле $Boot, а главная таблица файлов – в файле $Mft. Такая организация информации позволяет единообразно работать как с пользовательскими, так и с системными данными на томе.

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

Остальное место на томе NTFS отводится под файлы – системные и пользовательские.

Рассмотрим более подробно структуру MFT (рис.17.2).

Главная таблица файлов MFT


увеличить изображение

Рис. 17.2.  Главная таблица файлов MFT

Главная таблица файлов MFT состоит из множества записей о файлах (файловых записей), расположенных на томе. Размер одной записи – 1 КБ (2 сектора). Самая первая запись в MFT – это запись о самом файле $Mft. Во второй записи содержится информация о файле $MftMirr – зеркальной копии MFT. В этом файле дублируются первые 4 записи таблицы MFT, в том числе запись о $Mft. В случае возникновения сбоя, если MFT окажется недоступной, информация о системных файлах будет считываться из $MftMirr (в загрузочной записи имеется адрес $MftMirr).

Перечислим следующие несколько записей в таблице MFT и кратко опишем назначение соответствующих системных файлов:

Кроме перечисленных, имеются и другие системные файлы NTFS, а в новых версиях появляются новые системные файлы.

Далее рассмотрим, что представляет собой файл в системе NTFS.

Файлы NTFS

Как уже обсуждалось, основная информация о файле содержится в файловой записи (File Record) размером 1 КБ таблицы MFT, а небольшие файлы целиком хранятся в файловой записи.

Файловая запись состоит из заголовка (Header) и набора атрибутов (Attribute). В заголовке содержится служебная информация о файловой записи, например, её тип и размер. Все данные, относящиеся непосредственно к файлу, хранятся в виде атрибутов. Названия атрибутов, так же как и системных файлов, начинаются с "$". Например, отдельными атрибутами являются имя файла ($FILE_NAME), информация о его свойствах ($STANDARD_INFORMATION), данные файла ($DATA). Типичная файловая запись представлена на рис.17.3.

Файловая запись


Рис. 17.3.  Файловая запись

На диске файловая запись всегда расположена в начале сектора, первые байты файловой записи кодируют слово "FILE" (ASCII-коды: 46 49 4C 45). Конец записи определяется 4 байтовой последовательностью FF FF FF FF.

Физически атрибут файла хранится в виде потока байтов (stream) – простой последовательности байтов. Такое представление позволяет одинаковым образом работать с разнотипными атрибутами, а также добавлять нестандартные пользовательские атрибуты.

Каждый атрибут состоит из заголовка (attribute header), определяющего тип атрибута и его свойства, и тела (attribute body), содержащего основную информацию атрибута.

Более подробная структура файловой записи представлена на рис.17.4.

Структура файловой записи


Рис. 17.4.  Структура файловой записи

По расположению относительно MFT атрибуты бывают резидентные и нерезидентные. Резидентные атрибуты (resident attributes) полностью помещаются в файловую запись MFT, нерезидентные атрибуты (nonresident attributes) хранятся вне MFT. Область, в которой расположен нерезидентный атрибут, называется группой (run). Поскольку нерезидентных атрибутов в файле может быть несколько, то и групп бывает тоже несколько. Множество групп файла называется списком групп (RunList). Файловая запись при наличии нерезидентных атрибутов содержит ссылку на расположение группы на диске (см. пример на рис.17.2 "Главная таблица файлов MFT").

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

Поля заголовка и атрибутов файловой записи


Рис. 17.5.  Поля заголовка и атрибутов файловой записи

В начале файловой записи находится признак её начала – слово "FILE" (46 49 4C 45). По смещению 0x14 расположено двухбайтовое поле, в котором записано смещение первого атрибута относительно начала файловой записи. В примере в этом поле записано 38, т. е. первый атрибут расположен по смещению 38.

В следующем поле хранится тип файловой записи: значение 01 обозначает файл, 02 – каталог (directory). В примере файловая запись соответствует файлу (значение 01 по смещению 16).

Ещё одно поле в заголовке содержит размер всей записи. В примере на рис.17.5 в этом поле записано 1A0, т. е. размер записи составляет 416 байт.

Каждый атрибут имеет поля, указывающие тип, длину и резидентность атрибута. Все типы атрибутов имеют свои численные значения, например, атрибуту $FILE_NAME соответствует значение 0x30, атрибуту $STANDARD_INFORMATION – 0x10, атрибуту $DATA – 0x80.

Если атрибут резидентный, в поле резидентности записывается 0x00, иначе – 0x01. В случае нерезидентного атрибута предусмотрены поля для хранения номеров кластеров, в которых располагается группа или несколько групп, выделенных для размещения файла.

В примере на рис.17.5 показаны два атрибута. Первый атрибут имеет тип $STANDARD_INFORMATION (значение 10), длина атрибута 96 байт (6016 = 9610), атрибут является резидентным (00). У второго атрибута тип $DATA (80), длина – 72 байта (4816 = 7210), атрибут является нерезидентным (01).

Для обозначения кластеров используются два типа номеров: LCN и VCN. При помощи первого типа, LCN (Logical Cluster Number – логический номер кластера), нумеруются все кластеры на диске, от первого до последнего. LCN применяются, чтобы найти начальный кластер группы. Номера VCN (Virtual Cluster Number – виртуальный номер кластера) обозначают порядковый номер кластера внутри группы. Схема нумерации кластеров LCN VCN проиллюстрирована на рис.17.6.

Схема нумерации кластеров с использованием LCN VCN


Рис. 17.6.  Схема нумерации кластеров с использованием LCN VCN

В случае нерезидентных атрибутов в заголовке атрибута содержатся следующие поля: номер VCN первого кластера группы (обычно равен 0х00), номер VCN последнего кластера группы и список групп (RunList), описывающий расположение групп на диске.

Рассмотрим пример описания расположения групп, приведенный на рис.17.5 (справа). В этом примере значения полей следующие:

Расположение кластеров для данного примера приведено на рис.17.7.

Расположение кластеров группы для примера на рис. 17.5


Рис. 17.7.  Расположение кластеров группы для примера на рис. 17.5

В этом примере значение для списка групп

0x21 40 55 20 00

обозначает следующее:

Указанные обозначения проиллюстрированы на рис.17.8.

Список группы


Рис. 17.8.  Список группы

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

Структуры данных для управления файлами

Рассмотрим структуры данных, задействованные при работе с файловой системой NTFS (рис.17.9).

Структуры данных, связанные с NTFS


Рис. 17.9.  Структуры данных, связанные с NTFS

В структуре данных EPROCESS, описывающей процесс в Windows (см. лекцию 7 "Процессы и потоки"), имеется поле ObjectTable (файл base\ntos\inc\ps.h, строка 293), в котором содержится указатель на таблицу дескрипторов процесса типа HANDLE_TABLE (файл base\ntos\inc\ex.h, строка 5179). В строках этой таблицы содержатся ссылки на ресурсы, открытые процессом, и в том числе, ссылки на объекты типа FILE_OBJECT, которые упоминались в лекции 15 "Управление устройствами".

Структура FILE_OBJECT (файл base\ntos\inc\io.h, строка 1763) содержит следующие основные поля:

В структуре VPB (Volume Parameter Block) содержатся следующие поля (файл base\ntos\inc\io.h, строка 1288):

На рис.17.9 изображен физический диск, разбитый на три тома (логических диска) – два тома NTFS и один том FAT32. Поле DeviceObject структуры VPB указывает на объект DEVICE_OBJECT, который ссылается на первый из томов NTFS. Поле RealDevice структуры VPB указывает на объект DEVICE_OBJECT, связанный с физическим диском.

С каждым файлом NTFS, с которым операционная система в данный момент работает, связана структура данных, называемая блок управления файлом (File Control Block, FCB). В этой структуре хранится указатель на запись в таблице MFT для данного файла (см. рис.17.9).

Поскольку в файле существует в общем случае несколько потоков, для каждого потока создается своя структура данных – блок управления потоком (Stream Control Block, SCB), в котором содержится ссылка на FCB. Поле FsContext структуры FILE_OBJECT указывает на SCB для открытого потока, таким образом, структура FILE_OBJECT на самом деле связана с потоком, а не с файлом. Чтобы открыть другой поток в том же файле требуется создавать новый объект FILE_OBJECT. На рис.17.9 показана ситуация, когда процесс открыл два разных потока одного и того же файла. В WRK можно найти описание блока управления потоком SCB – структуру FSRTL_ADVANCED_FCB_HEADER (файл base\ntos\inc\fsrtl.h, строка 120) вместе с её основным полем, представляющим структуру FSRTL_COMMON_FCB_HEADER (тот же файл, строка 65).

Резюме

В этой лекции приведен обзор файловых систем, поддерживаемых Windows, и подробно рассматривается основная файловая система Windows – NTFS. Дается определение базовым понятиям – диск, раздел, том, сектор, кластер. Перечисляются возможности NTFS. Описывается структура NTFS тома, особое внимание уделяется главной таблице файлов MFT. Рассматриваются виды и структура файловых записей MFT. В заключение приводятся структуры данных Windows Research Kernel, связанные с файловой системой NTFS.

Контрольные вопросы

  1. Какие файловые системы поддерживаются Windows?
  2. Дайте определения понятиям "диск", "раздел", "том", "сектор", "кластер".
  3. Какие ограничения по размеру существуют для тома NTFS? Для файлов на томе NTFS?
  4. Приведите структуру NTFS-тома.
  5. Приведите структуру главной таблицы файлов MFT.
  6. Приведите структуру файловой записи MFT.
  7. Что такое "атрибут файловой записи"? Какие виды атрибутов вы знаете?
  8. Опишите структуры данных Windows Research Kernel, используемые для взаимодействия с файловой системой.

Лекция 18. Структура файловой системы NTFS

Цель работы: исследовать структуру файловой системы NTFS и файловые записи MFT.

Задание 1. Создать виртуальный жесткий диск.

Замечание. В данной лабораторной работе используется виртуальный жесткий диск (Virtual Hard Disk, VHD) – формат файла, в котором можно сохранить образ жесткого диска. В VHD файлах, например, хранятся образы жестких дисков Microsoft Virtual PC.

Встроенная поддержка VHD реализована в Windows 7. Если ваша операционная система выпущена ранее Windows 7, рекомендуется пропустить первое задание, а остальные выполнять с использованием флеш диска (отформатировав его в файловой системе NTFS).

Помните, что при форматировании все данные на диске стираются.

Указания к выполнению.

1. Наберите в командной строке (нажмите кнопку Пуск) следующую команду:



Управление компьютером

Откроется оснастка (snap-in) Управление компьютером. Выберите пункт Управление дисками:



2. В меню Действие выберите пункт Создать виртуальный жёсткий диск:



Откроется диалоговое окно. Введите имя файла, в котором будет храниться виртуальный жесткий диск, и размер файла 100 МБ:



После процедуры создания новый жесткий диск должен отобразиться в списке дисков:



3. Чтобы с новым диском можно было работать, его следует проинициализировать, создать на нем том и отформатировать.

Щелкните правой кнопкой мыши на диске и выберите пункт Инициализировать диск (параметры оставьте по умолчанию):



После инициализации, щелкните правой кнопкой мыши на нераспределенном пространстве диска и выберите Создать простой том…. Откроется Мастер создания простых томов. Выберите следующие параметры:



Нажмите кнопку Готово.



В результате на вашем компьютере должен появиться новый логический диск:



Задание 2. Получить информацию об NTFS томе.

Указания к выполнению.

1. Скачайте с сайта Sysinternals утилиту NTFSInfo1). Сохраните её в папку на жестком диске, например, c:\Instruments.

2. Запустите командную строку: нажмите кнопку Пуск – в текстовом окне введите cmd – нажмите Enter.

3. Перейдите в каталог с утилитой NTFSInfo: введите в командной строке:

cd c:\Instruments

4. Запустите утилиту NTFSInfo. Введите команду ntfsinfo.exe, указав букву виртуального диска, например:

ntfsinfo.exe d:



Утилита отображает следующую информацию.

Задание 3. Узнать размер на диске, занимаемый небольшим файлом.

Указания к выполнению.

1. Создайте на виртуальном жестком диске текстовый файл Test.txt, наберите в нем, например, следующий текст: "This is test". Сохраните файл.

2. В свойствах файла (правая кнопка мыши – Свойства) посмотрите, чему равен размер файла и его размер на диске:



Как можно объяснить полученный результат?

Файл Test.txt не удаляйте – он нам потребуется в дальнейшем.

Задание 4. Изучить содержимое MFT.

Указания к выполнению.

1. Скачайте утилиту Nfi. Для этого перейдите по следующему адресу:

http://support.microsoft.com/kb/253066/en-us

На этой странице скачайте OEM Support Tools по ссылке, обозначенной Download Oem3sr2.zip now. Извлеките из архива папку Nfi с одноименной утилитой.

2. Поместите утилиту Nfi в каталог c:\Instruments.

3. Запустите командную строку и перейдите в папку c:\Instruments таким же образом как в задании 2.

4. Введите в командной строке следующую команду (указав букву виртуального диска, например, d):

nfi d >> log.txt

Данная команда записывает в файл log.txt информацию обо всех файлах на диске D (виртуальном жестком диске).

5. Откройте файл log.txt, расположенный в том же каталоге, что и утилита Nfi. Просмотрите его содержимое. Сравните с информацией из лекции 17 "Файловая система NTFS".

На рисунке ниже приведен пример вывода утилиты Nfi для первой записи в таблице MFT – о самом файле $Mft:



Обратите внимание на запись для файла Test.txt в конце файла log.txt:



Задание 5. Исследовать внутреннюю структуру тома NTFS.

Указания к выполнению.

1. Для изучения внутренней структуры дисков существует множество программ. В этой лабораторной работе воспользуемся программой DiskExplorer for NTFS. Бесплатная оценочная версия доступна по адресу: http://www.runtime.org/diskexplorer.htm.

2. После установки и запуска программы (под учетной записью администратора) откроется главное окно:



В меню File выберите Drive… и в разделе Logical drives выделите Hard drive, соответствующий виртуальному диску:



Нажмите ОК и отобразится окно с загрузочным сектором (Boot sector) NTFS:



Дважды щелкните по надписи Boot sector (NTFS) или в меню Goto выберите Mft, программа перейдет к отображению файловых записей в таблице MFT:



Относительно каждой файловой записи программа показывает следующую информацию.

В первой строке:

Во второй строке:

В крайней левой колонке таблицы (Sector) показывается номер сектора, в котором располагается начало файловой записи. Проверьте, что этот номер совпадает с номером сектора для $Mft в файле log.txt, полученным в предыдущем задании. В нашем примере этот номер равен 0x102A8.

Проверьте соответствие других записей в программе DiskExplorer и в файле log.txt.

Обратите внимание, что номера секторов в столбце Sector увеличиваются на 2, т.е. файловая запись занимает 2 сектора (1 КБ).

Задание 6. Изучить файловую запись для резидентного файла.

Указания к выполнению.

1. Найдите в списке файловых записей DiskExplorer запись для файла Test.txt:



Дважды щелкните на этой записи (или нажмите клавишу F7) – откроется окно файловой записи:



В этом окне три основных раздела:

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



Изучите содержимое заголовка файловой записи и всех атрибутов. Определите способ хранения информации при помощи окна Raw data. Например, на рисунке ниже красным цветом в разделах Interpretation of data и Raw data выделено поле Length (Длина) для атрибута $STANDARD_INFORMATION, а синим цветом – поле Data status (Резидентность): значение 00 означает, что атрибут резидентный.

Информацию о структуре файловой записи NTFS можно найти в лекции 17 "Файловая система NTFS", а также в источниках [17; 5].



3. В теле атрибута $DATA ($80) найдите текст, хранящийся в файле.

Задание 7. Изучить файловую запись для нерезидентного файла.

Указания к выполнению.

1. Чтобы вернуться к списку файловых записей, можно нажать кнопку Go back (Назад) в правой верхней части окна DiskExplorer:



или нажать клавишу F6, или выбрать пункт as File entry в меню View:



2. Найдите в списке файловых записей $MftMirr – зеркальную копию MFT (следующая запись после $Mft):



Дважды щелкните на записи $MftMirr (или нажмите клавишу F7) – откроется окно с подробной информацией о файле $MftMirr:



Выделите пункт Attributes (Атрибуты) в окне Structures:



Как видно из рисунка, атрибут $80 ($DATA) является нерезидентным. Изучим его расположение в памяти.

Щелкните на заголовок (Header) атрибута $80 в окне Structures:



На представленном рисунке зеленым цветом выделен признак атрибута non resident в разделе Interpretation of data и соответствующий байт в разделе Raw data (01).

Синим цветом выделены начальный (Start VCN) и конечный (Last VCN) виртуальные номера кластеров (см. лекцию 11 "Файловая система NTFS"). Поскольку они совпадают (равны 0), то группа (Run) занимает всего один кластер.

Красным цветом выделен список групп (Run list): 11:01 02.

Перейдя на второй кластер (щелкнув ссылку x00000002 в окне Interpretation of data, выделенную синим цветом шрифта) и нажав клавишу F6, можно убедиться, что атрибут $DATA файла $MftMirr содержит первые 4 записи таблицы MFT:



Обратите внимание, что поскольку одна файловая запись занимает 1 КБ (2 сектора), то 4 записи будут занимать 4 КБ или 8 секторов или 1 кластер.

Задания для самостоятельного выполнения

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

Задание 2. Исследовать представление каталогов в файловых записях NTFS.

Указания к выполнению.

  1. Создайте каталог на томе виртуального жесткого диска.
  2. В программе DiskExplorer найдите файловую запись для созданного каталога и изучите её содержимое.

Задание 3. Определите расположение в атрибутах файловых записей NTFS следующей информации (указаны также виды атрибутов, для которых нужно определять расположение). Проверить соответствие информации в файловой записи и информации, отображаемой в окне свойств файла в Windows.

Для всех атрибутов:

  1. длина тела атрибута.

Для атрибута $STANDARD_INFORMATION:

  1. время создания файла;
  2. время изменения файла;
  3. время последнего чтения файла;
  4. признаки MS DOS (скрытый, системный и т.д.);

Для атрибута $FILE_NAME:

  1. родительский каталог;
  2. имя файла.

Дополнения


Литература

  1. Russinovich M., Solomon D., Ionescu I, Windows Internals. – 5th edition, Microsoft Press, 2009
  2. Russinovich M., Solomon D., Ionescu I, Windows Internals. Part 1. – 6th edition, Microsoft Press, 2012
  3. Олифер В. Г., Олифер Н. А, Сетевые операционные системы. – 2-е изд, СПб.: Питер, 2009
  4. , Рекомендации по преподаванию программной инженерии и информатики в университетах (Software Engineering 2004: Curriculum Guidelines for Undergraduate Degree Program in Software Engineering; Computing Curricula 2001: Computer Science): пер. с англ., М.: ИНТУИТ.РУ, 2007
  5. Руссинович М., Соломон Д, Внутреннее устройство Microsoft Windows: Windows Server 2003, Windows XP и Windows 2000. – 4?е изд., М.: Издательство «Русская Редакция»; СПб.: Питер, 2008
  6. Таненбаум Э, Современные операционные системы. – 3-е изд, СПб.: Питер, 2012
  7. , A history of Windows,
  8. , How NTFS Works,
  9. , Разработка приложений в стиле Metro для Windows,
  10. , Windows API List,
  11. Probert D, Windows Research Kernel. Source overview & project, 2006
  12. Probert D, Architecture of the Windows Kernel, 2008
  13. Schmidt A., Polze A., Probert D, Teaching Operating Systems – Windows Kernel Projects, ACM Special Interest Group on Computer Science Education (SIGCSE’10), 2010. Pp. 490–494
  14. , Windows Sysinternals (Winternals),
  15. , Windows Academic Program,
  16. , Windows Version History,
  17. Касперски К, Файловая система NTFS извне и изнутри,
  18. Руссинович М., Маргозис А, Утилиты Sysinternals. Справочник администратора, СПб.: БХВ-Петербург, 2012