Лекция 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).
Примеры различных типов ядер:
- монолитное ядро – MS-DOS, Linux, FreeBSD;
- микроядро – Mach, Symbian, MINIX 3;
- гибридное ядро – NetWare, BeOS, Syllable.
Обсуждение того, к какому типу относится ядро Windows NT, приведено в [5; 2]. В [2] говорится о том, что Windows NT имеет монолитное ядро, однако, поскольку в Windows NT имеется несколько ключевых компонентов, работающих в пользовательском режиме (например, подсистемы окружения и системные процессы – см. Лекцию 4 "Архитектура Windows"), то относить Windows NT к истинно монолитным ядрам нельзя, скорее к гибридным.
Кроме ядра в привилегированном режиме (в большинстве операционных систем) работают драйверы (driver) – программные модули, управляющие устройствами.
В состав операционной системы также входят:
- системные библиотеки (system DLL – Dynamic Link Library, динамически подключаемая библиотека), преобразующие системные вызовы приложений в системные вызовы ядра;
- пользовательские оболочки (shell), предоставляющие пользователю интерфейс – удобный способ работы с операционной системой.
Пользовательские оболочки реализуют один из двух основных видов пользовательского интерфейса:
- текстовый интерфейс (Text User Interface, TUI), другие названия – консольный интерфейс (Console User Interface, CUI), интерфейс командной строки (Command Line Interface, CLI);
- графический интерфейс (Graphic User Interface, GUI).
Пример реализации текстового интерфейса в Windows – интерпретатор командной строки cmd.exe; пример графического интерфейса – Проводник Windows (explorer.exe).
Классификация операционных систем
Классификацию операционных систем можно осуществлять несколькими способами.
- По способу организации вычислений:
- системы пакетной обработки (batch processing operating systems) – целью является выполнение максимального количества вычислительных задач за единицу времени; при этом из нескольких задач формируется пакет, который обрабатывается системой;
- системы разделения времени (time-sharing operating systems) – целью является возможность одновременного использования одного компьютера несколькими пользователями; реализуется посредством поочередного предоставления каждому пользователю интервала процессорного времени;
- системы реального времени (real-time operating systems) – целью является выполнение каждой задачи за строго определённый для данной задачи интервал времени.
- По типу ядра:
- системы с монолитным ядром (monolithic operating systems);
- системы с микроядром (microkernel operating systems);
- системы с гибридным ядром (hybrid operating systems).
- По количеству одновременно решаемых задач:
- однозадачные (single-tasking operating systems);
- многозадачные (multitasking operating systems).
- По количеству одновременно работающих пользователей:
- однопользовательские (single-user operating systems);
- многопользовательские (multi-user operating systems).
- По количеству поддерживаемых процессоров:
- однопроцессорные (uniprocessor operating systems);
- многопроцессорные (multiprocessor operating systems).
- По поддержке сети:
- локальные (local operating systems) – автономные системы, не предназначенные для работы в компьютерной сети;
- сетевые (network operating systems) – системы, имеющие компоненты, позволяющие работать с компьютерными сетями.
- По роли в сетевом взаимодействии:
- серверные (server operating systems) – операционные системы, предоставляющие доступ к ресурсам сети и управляющие сетевой инфраструктурой;
- клиентские (client operating systems) – операционные системы, которые могут получать доступ к ресурсам сети.
- По типу лицензии:
- открытые (open-source operating systems) – операционные системы с открытым исходным кодом, доступным для изучения и изменения;
- проприетарные (proprietary operating systems) – операционные системы, которые имеют конкретного правообладателя; обычно поставляются с закрытым исходным кодом.
- По области применения:
- операционные системы мэйнфреймов – больших компьютеров (mainframe operating systems);
- операционные системы серверов (server operating systems);
- операционные системы персональных компьютеров (personal computer operating systems);
- операционные системы мобильных устройств (mobile operating systems);
- встроенные операционные системы (embedded operating systems);
- операционные системы маршрутизаторов (router operating systems).
Требования к операционным системам
Основное требование, предъявляемое к современным операционным системам – выполнение функций, перечисленных выше в параграфе "Функции операционных систем". Кроме этого очевидного требования существуют другие, часто не менее важные [3]:
- расширяемость – возможность приобретения системой новых функций в процессе эволюции; часто реализуется за счет добавления новых модулей;
- переносимость – возможность переноса операционной системы на другую аппаратную платформу с минимальными изменениями;
- совместимость – способность совместной работы; может иметь место совместимость новой версии операционной системы с приложениями, написанными для старой версии, или совместимость разных операционных систем в том смысле, что приложения для одной из этих систем можно запускать на другой и наоборот;
- надежность – вероятность безотказной работы системы;
- производительность – способность обеспечивать приемлемые время решения задач и время реакции системы.
Резюме
В этой лекции приведено определение операционной системы, представлены виды программного обеспечения, рассмотрены функции и структура операционной системы. Особое внимание уделено понятию "ядра". Также приведены различные способы классификации операционных систем и требования, предъявляемые к современным операционным системам.
В следующей лекции будет представлен обзор операционных систем Microsoft Windows.
Контрольные вопросы
- Дайте определение понятию "операционная система".
- Назовите примеры прикладного, инструментального и системного программного обеспечения.
- Дайте определение понятий "системный вызов", "API", "драйвер", "ядро".
- Какие виды ядер вы знаете? К каким видам относятся ядра известных вам операционных систем?
- Чем ядро отличается от операционной системы?
- Приведите несколько способов классификации операционных систем.
- Назовите требования к современным операционным системам и объясните, что они означают.
Лекция 2. Обзор операционных систем Windows
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, предназначенных для использования на разных типах компьютеров:
- семейство клиентских операционных систем Windows NT (Windows XP, Windows Vista, Windows 7, Windows 8 и др.);
- семейство серверных операционных систем Windows NT Server (Windows Server 2003, Windows Server 2008 и др.);
- семейство мобильных операционных систем Windows Mobile и Windows Phone (Windows Mobile 6, Windows Phone 7 и др.);
- семейство встроенных операционных систем реального времени Windows CE (Windows CE 7.0 и др.).
Кроме того, в прошлом выпускались 16 разрядные операционные системы (Windows 1.0, Windows 2.х, Windows 3.х) и семейство операционных систем Windows 9x (Windows 95, Windows 98, Windows Me).
В данной лекции представлен краткий обзор семейств операционных систем Microsoft Windows (рис.2.1).

увеличить изображение
Рис. 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]:
- 32 разрядность;
- поддержка многопроцессорных систем;
- поддержка вытесняющей многозадачности и виртуальной памяти;
- высокая производительность;
- возможность работы в качестве сервера и клиента;
- переносимость;
- совместимость с другими версиями Windows и MS DOS, а также частичная совместимость с UNIX;
- безопасность;
- надежность;
- поддержка Unicode.
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.
Контрольные вопросы
- Перечислите основные семейства операционных систем Windows и дайте их краткую характеристику.
- Назовите основных представителей 16 разрядных Windows.
- Перечислите основные отличия операционных систем Windows NT от Windows 9x.
- Чем отличаются клиентские и серверные версии Windows NT?
- Охарактеризуйте операционные системы семейства Windows CE.
- Охарактеризуйте операционные системы семейства Windows Mobile/Windows Phone.
Лекция 3. 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).

Рис. 3.1. Интерфейс Modern UI
Впервые Modern UI появился в Windows Phone 7 в 2010 году. Принцип, используемый в этом интерфейсе, – на первом месте содержание, а не графическое оформление. Поэтому в Modern UI минимизировано использование элементов интерфейса – кнопок и меню; вместо иконок используются плитки (tiles), внутри которых текст выводится при помощи легко читаемых шрифтов, а для динамичного отображения информации широко используется анимация.
Традиционный рабочий стол также присутствует – его можно вызвать, щелкнув на плитку Desktop. Обратно к интерфейсу Modern UI можно вернуться, подведя указатель мыши в левый нижний угол экрана (один из четырех "активных углов") или нажав кнопку Windows на клавиатуре.
Другим изменением в интерфейсе стало использование Ribbon Interface (Ленточный интерфейс) в Проводнике Windows (рис.3.2).

Рис. 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).

Рис. 3.3. Диспетчер задач (Task Manager)
История файлов
Функция История файлов (File history) автоматически сохраняет копии изменяемых файлов, так что при необходимости можно откатить изменения и вернуться к старым версиям файлов.
Восстановление системы
Добавлены две функции по восстановлению системы без использования носителей с дистрибутивом – Обновление (Refresh) и Сброс (Reset). При Обновлении система переустанавливается с сохранением пользовательских файлов и настроек; при Сбросе диск форматируется и система устанавливается с нуля.
Storage Spaces
Функция Storage Spaces позволяет объединять физические диски, построенные по разным технологиям (SATA, USB, SAS), в единый виртуальный диск с автоматическим резервированием информации.
Версии Windows 8
Планируется выпуск четырех версий Windows 8:
- Windows 8 – базовая версия для 32 разрядных (x86) и 64 разрядных (x64) платформ;
- Windows 8 Pro – версия для продвинутых и бизнес-пользователей. В неё будут добавлены поддержка доменов Windows, групповые политики, шифрование файлов, технологии виртуализации;
- Windows 8 Enterprise – версия для корпоративных пользователей. Присутствуют все возможности Windows 8 Pro и добавляются следующие технологии: Windows To Go (возможность загрузки с переносных устройств – внешних жестких дисков и флеш дисков), DirectAccess (простой и безопасный доступ к ресурсам корпоративной сети через Интернет), BranchCache (кэширование файлов корпоративной сети), AppLocker (гибкое управление разрешениями на запуск приложений);
- Windows RT – версия, которая будет поддерживать мобильные процессоры ARM. Будет доступна только как предварительно установленная операционная система на компьютерах с ARM процессорами. В состав системы будет входить пакет Microsoft Office Home & Student 2013 RT.
Минимальные системные требования для Windows 8 практически совпадают с требованиями для Windows 7:
- процессор не менее 1 ГГц;
- оперативная память не менее 1 ГБ для 32 разрядных систем, 2 ГБ для 64 разрядных;
- свободное пространство на жестком диске не менее 20 ГБ;
- видеокарта с поддержкой DirectX 9 и с WDDM драйвером.
Разработка приложений для Windows 8
Для Windows 8 стала возможной разработка нового типа Windows приложений – приложений в стиле Modern UI (см. раздел на MSDN [MSDN Apps]).
Особенности приложений в стиле Modern UI
У приложений в стиле Modern UI есть ряд особенностей, которые отличают их от традиционных Windows-приложений:
- наличие одного окна – приложение имеет одно окно, по умолчанию развернутое на весь экран, лишенное необязательных элементов интерфейса;
- поддержка сенсорного ввода – Windows 8 предоставляет приложениям средства для поддержки ввода с разных устройств – клавиатуры, мыши, пера, сенсорной панели;
- контракты приложений – приложения могут объявлять поддержку контрактов – соглашений по предоставлению определенных сервисов. В Windows 8 поддерживается несколько контрактов:
- поиск (Search) – соглашение о возможности поиска по содержимому;
- общий доступ (Sharing) – соглашение о предоставлении своего содержимого другим приложениям;
- воспроизведение (Play To) – соглашение о передаче данных из приложения на устройство воспроизведения;
- выбор между приложениями (App to App picking) – соглашение о возможности напрямую выбирать файлы;
- параметры (Settings) – соглашение о доступности параметров приложения;
- печать (Print) – соглашение о возможности печати на любом совместимом принтере;
- плитки вместо ярлыков – приложение представляется пользователю в виде плитки, в которой может быть текст или динамически изменяющееся содержимое, даже когда само приложение не работает;
- новые элементы интерфейса:
- строка команд приложения (App bar) – располагается внизу экрана, вызывается как контекстное меню. В этой строке можно размещать основные команды приложения (рис.3.4);
- панель Charms – располагается в правой части экрана и содержит кнопки для поиска, общего доступа, вызова начального экрана, работы с устройствами и работы с параметрами (рис.3.5).

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

Рис. 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 и начинаем с рассмотрения архитектуры системы.
Контрольные вопросы
- Расскажите об истории разработки Windows 8.
- Перечислите основные особенности Windows 8.
- Назовите версии Windows 8 и отличия между ними.
- Каковы минимальные системные требования для установки Windows 8?
- Что такое "контракты" при разработке приложений в стиле Modern UI?
- Чем отличается Modern UI от традиционных интерфейсов?
- Какие новые элементы интерфейса появились в Modern UI?
Лекция 4. Архитектура Windows
Общая схема архитектуры
Windows представляет собой операционную систему с гибридным ядром (см. лекцию 1 "Введение в операционные системы"). В ней основные системные функции по управлению процессами, памятью, устройствами, файловой системой и безопасностью реализованы в компонентах, работающих в режиме ядра; но существует ряд важных системных компонентов пользовательского режима, например системные процессы входа в систему, локальной аутентификации, диспетчера сеансов, а также подсистемы окружения.
Архитектура Windows представлена на рис.4.1 [5; 2].

Рис. 4.1. Архитектура Windows
Компоненты пользовательского режима
В пользовательском режиме работают следующие виды процессов:
- системные процессы (system processes) – компоненты Windows, отвечающие за решение критически важных системных задач (т. е. аварийное завершение одного из этих процессов вызывает крах или нестабильную работу всей системы), но выполняемые в пользовательском режиме.
Основные системные процессы:
- Winlogon.exe – процесс входа в систему и выхода из неё;
- Smss.exe (Session Manager – диспетчер сеансов) – процесс выполняет важные операции при инициализации системы (загрузка необходимых DLL, запуск процессов Winlogon и Csrss и др.), а затем контролирует работу Winlogon и Csrss;
- Lsass.exe (Local Security Authentication Subsystem Server – сервер подсистемы локальной аутентификации) – процесс проверяет правильность введенных имени пользователя и пароля;
- Wininit.exe – процесс инициализации системы (например, запускает процессы Lsass и Services);
- Userinit.exe – процесс инициализации пользовательской среды (например, запускает системную оболочку – по умолчанию, Explorer.exe);
- Services.exe (SCM, Service Control Manager – диспетчер управления службами) – процесс, отвечающий за выполнение служб – см. ниже;
- службы (сервисы, services) – приложения, работающие в фоновом режиме и не требующие взаимодействия с пользователем. Службы могут быть как частью операционной системы (например, Windows Audio – служба для работы со звуком, или Print Spooler – диспетчер печати), так и частью пользовательского приложения (например, служба СУБД SQL Server). За службы отвечает системный процесс Services.exe;
- пользовательские приложения (user applications) ¬– прикладные программы, запускаемые пользователем;
- подсистемы окружения (environment subsystems) – компоненты, предоставляющие доступ приложениям к некоторому подмножеству системных функций. Windows поддерживает две подсистемы окружения:
- собственно Windows – при помощи данной подсистемы выполняются 32 разрядные приложения Windows (Win32), а также 16 разрядные приложения Windows (Win16), приложения MS DOS и консольные приложения (Console). За подсистему Windows отвечает системный процесс Csrss.exe и драйвер режима ядра Win32k.sys;
- POSIX (Portable Operating System Interface for UNIX – переносимый интерфейс операционных систем UNIX) – подсистема для UNIX-приложений. Начиная с Windows Server 2003 R2 компонент, реализующий эту подсистему, называется SUA (Subsystem for UNIX-based Applications). Компонент не устанавливается в Windows по умолчанию.
Все перечисленные процессы пользовательского режима (кроме подсистемы POSIX1)) для взаимодействия с модулями режима ядра используют библиотеки Windows DLL (Dynamic Link Library – динамически подключаемая библиотека). Каждая DLL экспортирует набор Windows API функций, которые может вызывать процесс.
Windows API (Windows Application Programming Interface, WinAPI) – это способ взаимодействия процессов пользовательского режима с модулями режима ядра. WinAPI включает тысячи функций и хорошо документирован [10].
Основные Windows DLL следующие:
- Kernel32.dll – базовые функции, в том числе работа с процессами и потоками, управление памятью и вводом выводом;
- Advapi32.dll – функции, в основном связанные с управлением безопасностью и доступом к реестру;
- User32.dll – функции, отвечающие за управление окнами и их элементами в GUI приложениях (Graphical User Interface – графический интерфейс пользователя);
- Gdi32.dll – функции графического пользовательского интерфейса (Graphics Device Interface, GDI), обеспечивающие рисование на дисплее и принтере графических примитивов и вывод текста.
Библиотека 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), которые реализуют основные задачи операционной системы:
- диспетчер процессов (process manager) – управление процессами и потоками (см. лекцию 6 "Процессы и потоки");
- диспетчер памяти (memory manager) – управление виртуальной памятью и отображение её на физическую (см. лекцию 8 "Управление памятью");
- монитор контроля безопасности (security reference monitor) – управление безопасностью (см. лекцию 9 "Безопасность");
- диспетчер ввода вывода (I/O manager), диспетчер кэша (cache Manager), диспетчер Plug and Play (PnP Manager) – управление внешними устройствами и файловыми системами (см. лекцию 10 "Управление устройствами" и лекцию 11 "Файловая система NTFS");
- диспетчер электропитания (power manager) – управление электропитанием и энергопотреблением;
- диспетчер объектов (object manager), диспетчер конфигурации (configuration manager), механизм вызова локальных процедур (local procedure call) – управление служебными процедурами и структурами данных, которые необходимы остальным компонентам.
Ядро (Kernel) содержит функции, обеспечивающие поддержку компонентам исполнительной системы и осуществляющие планирование потоков (см. лекцию 7 "Планирование потоков"), механизмы синхронизации, обработку прерываний.
Компонент Windows USER и GDI отвечает за пользовательский графический интерфейс (окна, элементы управления в окнах – меню, кнопки и т. п., рисование), является частью подсистемы Windows и реализован в драйвере Win32k.sys.
Взаимодействие диспетчера ввода вывода с устройствами обеспечивают драйверы (drivers) – программные модули, работающие в режиме ядра, обладающие максимально полной информацией о конкретном устройстве (драйверы подробнее рассматриваются в лекции 10 "Управление устройствами").
Однако, и драйверы, и ядро не взаимодействуют с физическими устройствами напрямую – посредником между программными компонентами режима ядра и аппаратурой является HAL (Hardware Abstraction Layer) – уровень абстрагирования от оборудования, реализованный в Hal.dll. HAL позволяет скрыть от всех программных компонентов особенности аппаратной платформы (например, различия между материнскими платами), на которой установлена операционная система.
Резюме
В лекции представлена архитектура операционной системы Windows и описаны основные компоненты пользовательского режима и режима ядра.
В следующей лекции рассматривается исследовательское ядро Windows (Windows Research Kernel) – вариант Ntoskrnl.exe с исходным кодом, доступным академическим организациям.
Контрольные вопросы
- К какому типу ядер в большей степени относится Windows NT, к монолитным или микроядрам? Ответ обоснуйте.
- Перечислите основные компоненты пользовательского режима.
- Перечислите основные компоненты режима ядра.
- Что такое Windows API? Где можно найти информацию по этому вопросу?
- Каковы основные функции исполнительной системы, входящей в состав Ntoskrnl.exe?
- Каковы основные функции ядра, входящего в состав Ntoskrnl.exe?
- Что такое HAL?
Лекция 5. Исследовательское ядро Windows
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 XP – Windows Internals Curriculum Resource Kit (CRK). Составлены в соответствии с рекомендациями ACM/IEEE по преподаванию курса "Операционные системы" (Operating systems, OS) [4]. Материалы включают презентации лекций, указания к лабораторным работам (в том числе лабораторные работы для Windows 7), задания, тесты, а также материалы для преподавателей (Instructor Supplement);
- среда ProjectOZ для экспериментального исследования ядра Windows;
- описание опыта университетов (Faculty Experiences) по преподаванию в рамках академической программы Microsoft.
Все компоненты Windows Academic Program, кроме WRK и материалов для преподавателей (Instructor Supplement), доступны любому желающему. WRK и Instructor Supplement можно получить, подтвердив свой статус преподавателя или по подписке Microsoft Developer Network Academic Alliance (MSDN AA).
Microsoft, предоставляя академическому сообществу исходные коды ядра Windows, преследовало следующие цели [11]:
- облегчить студентам и преподавателям сравнение Windows с другими операционными системами;
- предоставить студентам возможность для изучения исходных кодов ядра и создания собственных проектов на их основе;
- поддержать исследования и публикации по внутреннему устройству Windows;
- содействовать разработке учебников по операционным системам на основе ядра Windows;
- упростить лицензирование.
Исследовательское ядро Windows включает более 800 000 строк исходного кода, в основном на языке программирования C, но есть файлы и на ассемблере. В процессе подготовки к опубликованию исходный код в некоторых местах был упрощен, а комментарии улучшены [11].
На рис.5.1 представлена схема, отражающая покрытие исходным кодом WRK компонентов ядра [13].

Рис. 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:
- Ke (префикс) – функция входит в состав ядра;
- Start (операция) – функция начинает выполнение объекта;
- Thread (объект) – объектом является поток.
В таблице 5.1 приведены основные компоненты WRK (см. соответствие с компонентами на рис.5.1) с указанием префиксов входящих в их состав функций.
Компонент WRK | Префикс функций | Название компонента на англ. языке | Название компонента на русском языке |
---|---|---|---|
cache | Cc | Cache manager | диспетчер кэша |
config | Cm | Configuration manager | диспетчер конфигурации |
dbgk | Dbgk | Debugging Framework | подсистема отладки |
ex | Ex | Executive support routines | функции поддержки исполнительной системы – синхронизация, таймеры, структуры данных исполнительной системы, системная информация |
fsrtl | FsRtl | File system driver run-time library | библиотека функций поддержки файловой системы времени выполнения |
io | Io | Input/Output manager | диспетчер ввода-вывода |
ke | Ke | Kernel | ядро |
lpc | Lpc | Local Procedure Call | механизм вызова локальных процедур |
mm | Mm | Memory manager | диспетчер памяти |
ob | Ob | Object manager | диспетчер объектов |
perf | Perf | Performance | функции для сбора информации о производительности системы |
ps | Ps | Process manager | диспетчер процессов |
raw | Raw | Raw File System | функции для Raw File System1) |
rtl | Rtl | Run-Time Library | библиотека функций времени выполнения |
se | Se | Security manager | диспетчер безопасности |
wmi | Wmi | Windows Management Instrumentation | поддержка WMI – инструментальные средства управления Windows |
Кроме перечисленных в таблице, в WRK есть ещё два важных компонента:
- inc – общедоступные заголовочные файлы;
- init – функции инициализации системы.
Приведем ещё один префикс часто встречающихся в 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).

Рис. 5.2. HTML документация по Windows Research Kernel
По информации, предоставляемой HTML документацией, WRK содержит 4167 функций и 1957 типов данных.
Резюме
В данной лекции представлен обзор исследовательского ядра Windows (Windows Research Kernel, WRK). Перечислены компоненты WRK. Предложено использование HTML документации по WRK.
В следующей лекции будут рассмотрены основные объекты, отвечающие за работу приложений – процессы и потоки.
Контрольные вопросы
- Расскажите о составе академической программы Microsoft.
- Что такое Windows Research Kernel?
- Какие компоненты ядра присутствуют в Windows Research Kernel?
- Перечислите основные компоненты Windows Research Kernel.
- Какие префиксы используются в названиях функций WRK?
- Какая информация имеется в HTML документации по WRK?
Лекция 6. Сборка исследовательского ядра 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 виртуальную машину со следующими параметрами:
- оперативная память – не менее 256 Мб (желательно 512 Мб);
- жесткий диск не менее 3 Гб.
Информация по созданию виртуальной машины и установке операционной системы на виртуальную машину доступна по адресу:
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. Скопируйте в эту папку два файла:
- wrkx86.exe – собранное в предыдущем задании ядро WRK (папка WRK v1.2\base\ntos\BUILD\EXE);
- halacpim.dll – модуль HAL (см. лекцию 4 "Архитектура Windows") из папки WRK-v1.2\WS03SP1HALS\x86\halacpim.
Копирование можно осуществлять двумя способами. Для обоих способов нужно установить в 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 и найдите в списке два параметра:
- Show hidden files and folders
- Hide protected operating system files (Recommended)
Первый параметр отметьте, а у второго уберите флажок:

Нажмите кнопку 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. В данном случае, указаны три варианта загрузки:
- "Windows Server 2003, Enterprise" – загрузка стандартного ядра Windows;
- "Windows Research Kernel [debugger enabled]" – загрузка ядра WRK с возможностью отладки;
- "Windows Research Kernel [no debugger]" – загрузка ядра WRK без отладки.
Обратите внимание, мы использовали следующие опции в файле boot.ini:
- /kernel – указание файла, используемого в качестве ядра;
- /hal – указание файла, используемого в качестве модуля HAL;
- /debug – активизация режима отладки;
- /debugport=com1 – для отладки используется COM порт.
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, в которое нужно ввести пути к файлам с отладочными символами. Введем два пути:
- путь к файлу отладочной информации WRK – wrkx86.pdb (например, C:\WRK-v1.2\base\ntos\BUILD\EXE);
- путь к файлам с отладочными символами других компонентов Windows – можно указать отладчику скачать эти данные с сайта Microsoft и сохранить их в папке на жестком диске, например C:\Symbols.
В окне 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 в этом окне:
- Baud Rate: 115200
- Port: \\.\pipe\debugPipe
- Pipe: отметить
- Reconnect: отметить
- Resets: 0

Нажмите кнопку OK, при этом начнется процесс подключения к виртуальной машине по COM порту. На вопрос о сохранении информации (Save information for workspace) можете ответить Yes.
Запустите виртуальную машину. В меню загрузки выберите Windows Research Kernel [debugger enabled]. В процессе загрузки в окне Command отладчика должна появиться следующая информация:

5. Узнать информацию о загруженных модулях.
Остановите процесс выполнения виртуальной машины, нажав кнопку на панели инструментов отладчика, или выбрав в меню Debug пункт Break, или нажав Ctrl+Break.
Отладчик перейдет в режим ввода команд (внизу окна Command сделается активной строка для текстового ввода с подсказкой kd>):

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

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

Чтобы продолжить работу виртуальной машины нажмите F5, или нажмите кнопку на панели инструментов отладчика, или введите команду 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++:
- десятичные числа представляются обычным способом: 12345;
- чтобы явно указать, что число записано в десятичной системе счисления, нужно добавить префикс 0n: 0n12345;
- шестнадцатеричные числа обозначаются с префиксом 0x, например: 0x12FF14;
- при написании шестнадцатеричных цифр можно использовать как заглавные латинские буквы, так и строчные: 0xAB10CE = 0xab10ce;
3. Если не указывается префикс, число в WinDbg считается записанным в текущей системе счисления, которую можно узнать и, при необходимости, изменить при помощи команды n (Set Number Base):

В данном примере сначала проверяется текущая система счисления (команда n), она оказывается шестнадцатеричной (base is 16), затем устанавливается десятичная система счисления (команда n 10) и возвращается шестнадцатеричная (команда n 16).
4. Для перевода чисел из одной системы счисления в другую можно воспользоваться командой ? (Evaluate Expression):

В примере на рисунке сначала проверяется, что текущая система счисления – шестнадцатеричная (команда n), затем выполняется команда перевода числа 10, которое, в соответствии с текущей системой счисления рассматривается как шестнадцатеричное (команда ? 10), далее явно указывается, что переводимое число десятичное (команда ? 0n10), и, наконец, явно указывается, что переводимое число шестнадцатеричное (команда ? 0x10).
Задания для самостоятельного выполнения
Задание 1. Найдите информацию о следующих командах отладчика:
- ? (Command Help)
- ? (Evaluate Expression)
- bp
- d, da, db, dw, dd
- dt
- n
- r
- s
Задание 2. Найдите информацию о следующих командах расширений отладчика:
- !address
- !handle
- !sd
- !token
Указания к выполнению.
1. Описание команд расширений содержится в разделе справки Debugger Reference – Debugger Commands – General Extension Commands.
Задание 3. Найдите информацию о следующих командах расширений режима ядра:
- !devobj
- !drvobj
- !fileobj
- !memusage
- !object
- !process
- !sysinfo
- !thread
- !vm
Указания к выполнению.
1. Описание команд расширений содержится в разделе справки Debugger Reference – Debugger Commands – Kernel Mode Extension Commands.
Задание 4. Изучить пример хорошего комментирования кода.
Указания к выполнению.
- Откройте в Visual Studio файл WRK: base\ntos\mm\addrsup.c.
- В этом файле комментирование построено на основе связи с книгой Дональда Кнута (Donald Knuth) "Искусство программирования. Том 3. Сортировка и поиск" ("The Art of Computer Programming, Volume 3, Sorting and Searching").
- Просмотрите комментарии к алгоритмам, приведенным в файле.
Лекция 7. Процессы и потоки
Основные понятия
Программа (program) – это последовательность команд, реализующая алгоритм решения задачи. Программа может быть записана на языке программирования (например, на Pascal, С++, BASIC); в этом случае она хранится на диске в виде текстового файла с расширением, соответствующим языку программирования (например, .PAS, .CPP, .VB). Также программа может быть представлена при помощи машинных команд; тогда она хранится на диске в виде двоичного исполняемого файла (executable file), чаще всего с расширением .EXE. Исполняемый файл генерируется из текста программы при компиляции.
Процесс (process) – это программа (пользовательская или системная) в ходе выполнения.
В современных операционных системах процесс представляет собой объект – структуру данных, содержащую информацию, необходимую для выполнения программы. Объект "Процесс" создается в момент запуска программы (например, пользователь дважды щелкает мышью на исполняемом файле) и уничтожается при завершении программы.
Если операционная система умеет запускать в одно и то же время несколько процессов, она называется многозадачной (multitasking) (пример – Windows), иначе – однозадачной (пример – MS DOS).
Процесс может содержать один или несколько потоков (thread) – объектов, которым операционная система предоставляет процессорное время. Сам по себе процесс не выполняется – выполняются его потоки. Таким образом, машинные команды, записанные в исполняемом файле, выполняются на процессоре в составе потока. Если потоков несколько, они могут выполняться одновременно.
Замечание. "Одновременное" (или "параллельное") выполнение потоков подразумевает одну из двух принципиально разных ситуаций, зависящих от количества процессоров (ядер) на компьютере. В том случае, если имеется всего один процессор с одним ядром, в один момент времени может выполняться только один поток. Однако операционная система может быстро переключать процессор с выполнения одного потока на другой и вследствие высокой частоты процессоров у пользователя возникает иллюзия одновременной работы нескольких программ. Такая ситуация называется псевдопараллельное выполнение потоков. Если в компьютере установлен многоядерный процессор или количество процессоров больше одного, то возможно истинно параллельное или просто параллельное выполнение потоков.
Операционные системы, поддерживающие несколько процессоров, называются многопроцессорными.
Если система допускает наличие нескольких потоков в одном процессе, она называется многопоточной (multithreading).
Многопоточность – это средство распараллеливания действий внутри процесса.
Примеры.
- В современных браузерах каждой вкладке соответствует свой поток.
- В текстовом редакторе один поток может управлять вводом текста, второй – проверять орфографию, третий – выводить документ на принтер.
- В компьютерной игре-стратегии за обработку действий каждого игрока отвечает отдельный поток.
Каждый процесс имеет свое собственное виртуальное адресное пространство (см. лекцию 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). Рассмотрим некоторые её поля.
- Pcb (Process Control Block – блок управления процессом) – представляет собой структуру KPROCESS, хранящую данные, необходимые для планирования потоков, в том числе указатель на список потоков процесса (файл base\ntos\inc\ke.h, строка 944).
- CreateTime и ExitTime – время создания и завершения процесса.
- UniqueProcessId – уникальный идентификатор процесса.
- ActiveProcessLinks – элемент двунаправленного списка (тип LIST_ENTRY), содержащего активные процессы.
- QuotaUsage, QuotaPeak, CommitCharge – квоты (ограничения) на используемую память.
- ObjectTable – таблица дескрипторов процесса.
- Token – маркер доступа.
- ImageFileName – имя исполняемого файла.
- ThreadListHead – двунаправленный список потоков процесса.
- Peb (Process Environment Block – блок переменных окружения процесса) – информация об образе исполняемого файла (файл public\sdk\inc\pebteb.h, строка 75).
- PriorityClass – класс приоритета процесса (см. лекцию 9 "Планирование потоков").
Структура для потока в Windows называется ETHREAD и описывается в файле base\ntos\inc\ps.h (строка 545). Её основные поля следующие:
- Tcb (Thread Control Block – блок управления потоком) – поле, которое является структурой типа KTHREAD (файл base\ntos\inc\ke.h, строка 1069) и необходимо для планирования потоков.
- CreateTime и ExitTime – время создания и завершения потока.
- Cid – структура типа CLIENT_ID, включающая два поля – идентификатор процесса-владельца данного потока и идентификатор самого потока.
- ThreadsProcess – указатель на структуру EPROCESS процесса-владельца.
- StartAddress – адрес системной стартовой функции потока. При создании потока сначала вызывается системная стартовая функция, которая запускает пользовательскую стартовую функцию.
- Win32StartAddress – адрес пользовательской стартовой функции.
Создание процесса
Процессы создаются либо пользователем, либо другим процессом, либо автоматически при загрузке операционной системы.
Процесс, создавший другой процесс, называется родителем, а созданный процесс – потомком. Таким образом, формируется иерархия процессов.
Любой процесс начинает свою работу с основного (main), или первичного, потока, который может запускать (порождать) другие потоки – так образуется иерархия потоков.
В Windows для создания процессов применяется одна из следующих WinAPI-функций: CreateProcess, CreateProcessAsUser, CreateProcessWithTokenW, CreateProcessWithLogonW [10]. Далее при описании будем использовать функцию CreateProcess.
Создание процессов в Windows включает 7 этапов [5; 2].
1. Проверка и преобразование параметров.
Параметры функции CreateProcess проверяются на корректность и преобразуются к внутреннему формату системы.
2. Открытие исполняемого файла.
Происходит поиск файла, который содержит запускаемую программу. Обычно это файл с расширением .EXE, но могут быть также расширения .COM, .PIF, .BAT, .CMD. Определяется тип исполняемого файла:
- Windows приложение (.EXE) – продолжается нормальное создание процесса;
- приложение MS-DOS или Win16 (.EXE, .COM, .PIF) – запускается образ поддержки Ntvdm.exe;
- командный файл (.BAT, .CMD) – запускается образ поддержки Cmd.exe;
- приложение POSIX – запускается образ поддержки Posix.exe.
3. Создание объекта "Процесс".
Формируются структуры данных EPROCESS, KPROCESS, PEB, инициализируется адресное пространство процесса. Для этого вызывается системная функция NtCreateProcess (файл base\ntos\ps\create.c, строка 826), которая затем вызывает функцию NtCreateProcessEx (тот же файл, строка 884), а та, в свою очередь, функцию PspCreateProcess (тот же файл, строка 1016).
Замечание. Начиная с Windows Vista при создании процесса вызов нескольких функций (NtCreateProcess, NtWriteVirtualMemory, NtCreateThread) заменен вызовом одной функции NtCreateUserProcess.
Рассмотрим некоторые важные действия, выполняемые функцией PspCreateProcess.
- Если в параметрах функции PspCreateProcess указан процесс-родитель:
- по его дескриптору определяется указатель на объект EPROCESS (функция ObReferenceObjectByHandle, строка 1076);
- наследуется от процесса родителя маска привязки к процессорам (Affinity, строка 1092).
- Устанавливается минимальный и максимальный размеры рабочего набора (WorkingSetMinimum = 20 МБ и WorkingSetMaximum = 45 МБ, строки 1093 и 1094, см. лекцию 11 "Управление памятью").
- Создается объект "Процесс" (структура EPROCESS) при помощи функции ObCreateObject (строка 1108).
- Инициализируется двунаправленный список потоков при помощи функции InitializeListHead (строка 1134).
- Копируется таблица дескрипторов родительского процесса (строка 1270).
- Создается структура KPROCESS при помощи функции KeInitializeProcess (строка 1289).
- Маркер доступа и другие данные, связанные с безопасностью (см. лекцию 13 "Безопасность", копируются из родительского процесса (функция PspInitializeProcessSecurity, строка 1302).
- Устанавливается приоритет процесса, равный Normal; однако, если приоритет родительского процесса был Idle или Below Normal, то данный приоритет наследуется (строки 1307–1312, см. лекцию 9 "Планирование потоков").
- Инициализируется адресное пространство процесса (строки 1337–1476).
- Генерируется уникальный идентификатор процесса (функция ExCreateHandle) и сохраняется в поле UniqueProcessId структуры EPROCESS (строки 1482–1488).
- Создается блок PEB и записывается в соответствующее поле структуры EPROCESS (строки 1550–1607).
- Созданный объект вставляется в хвост двунаправленного списка всех процессов (строки 1613–1615) и в таблицу дескрипторов (строки 1639–1644). Первая вставка обеспечивает доступ к процессу по имени, вторая – по ID.
- Определяется время создания процесса (функция KeQuerySystemTime) и записывается в поле CreateTime структуры EPROCESS (строка 1733).
4. Создание основного потока.
Формируется структура данных ETHREAD, стек и контекст потока, генерируется идентификатор потока. Поток создается при помощи функции NtCreateThread, определенной в файле base\ntos\ps\create.c, (строка 117), которая вызывает функцию PspCreateThread (тот же файл, строка 295). При этом выполняются следующие действия:
- создается объект ETHREAD (строка 370).
- Заполняются поля структуры ETHREAD, связанные с процессом-владельцем, – указатель на структуру EPROCESS (ThreadsProcess) и идентификатор процесса (Cid.UniqueProcess) (строки 396 и 398).
- Генерируется уникальный идентификатор потока (функция ExCreateHandle) и сохраняется в поле Cid.UniqueThread структуры EPROCESS (строки 400–402).
- Заполняются стартовые адреса потока, системный (StartAddress) и пользовательский (Win32StartAddress) (строки 468-476).
- Инициализируются поля структуры KTHREAD при помощи вызова функции KeInitThread (строки 490–498 для потока пользовательского режима и 514–522 для потока режима ядра).
- Функция KeStartThread заполняет остальные поля структуры ETHREAD и вставляет поток в список потоков процесса (строка 564).
- Если при вызове функции PspCreateThread установлен флаг CreateSuspended ("Приостановлен") поток переводится в состояние ожидания (функция KeSuspendThread, строка 660); иначе вызывается функция KeReadyThread (строка 809), которая ставит поток в очередь готовых к выполнению потоков (см. лекцию 9 "Планирование потоков").
5. Уведомление подсистемы Windows.
Подсистеме Windows отправляется сообщение о вновь созданных процессе и его основном потоке, в которое входят их дескрипторы, идентификаторы и другая информация. Подсистема Windows добавляет новый процесс в общий список всех процессов и готовится к запуску основного потока.
6. Запуск основного потока.
Основной поток стартует, но начинают выполняться системные функции, завершающие создание процесса – осуществляется его инициализация.
7. Инициализация процесса.
- Проверяется, не запущен ли процесс в отладочном режиме;
- проверяется, следует ли производить предвыборку блоков памяти (тех участков памяти, которые при прошлом запуске использовались в течение первых 10 секунд работы процесса);
- инициализируются необходимые компоненты и структуры данных процесса, например, диспетчер кучи;
- загружаются динамически подключаемые библиотеки (DLL – Dynamic Link Library);
- начинается выполнение стартовой функции потока.
Резюме
В этой лекции введены понятия "процесса" и "потока". Рассмотрены структуры данных, представляющие в операционной системе процесс (EPROCESS) и поток (ETHREAD). Описан ход создания процесса с использованием структур данных и функций Windows Research Kernel.
Следующая лекция посвящена алгоритмам планирования потоков и реализации этих алгоритмов в Windows.
Контрольные вопросы
- Приведите определение понятий "программа", "процесс", "поток", "стек".
- Опишите основные поля структуры EPROCESS.
- Какой структурой является поле Pcb структуры EPROCESS? Опишите поля этой структуры.
- Опишите основные поля структуры ETHREAD.
- Перечислите этапы создания процесса.
- Опишите этапы создания объекта "процесс".
- Опишите этапы создания основного потока.
Лекция 8. Создание и управление процессами и потоками
Задание 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 или щелкнув кнопку на панели инструментов.
Внизу окна 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 или щелкните кнопку на панели инструментов.
На виртуальной машине запустите утилиту 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. Проверьте работоспособность созданного приложения.
Нажмите кнопку на панели инструментов или нажмите 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. Исследовать в отладчике ход создания потока.
Указания к выполнению.
- Используйте описание хода создания потока в лекции 6 "Процессы и потоки", раздел "Создание процесса", шаг 4.
- Используйте проект, созданный в задании 3 основной части лабораторной работы.
- Установите в отладчике точку останова на функции NtCreateThread:
bp nt! NtCreateThread
Лекция 9. Планирование потоков
Если операционная система поддерживает многопоточность, она может распределять процессорное время либо между процессами, либо между потоками. В операционной системе Windows процессор предоставляется потокам, иначе говоря, осуществляется планирование на уровне потоков.
Таким образом, если один процесс имеет пять потоков, а второй – десять, то первый процесс будет занимать процессор в два раза больше времени, чем второй (при условии, конечно, что все потоки имеют равный приоритет и выполняют примерно одинаковую работу).
Алгоритмы планирования
Существуют разные алгоритмы планирования. Рассмотрим основные виды.
1. Вытесняющие/невытесняющие алгоритмы.
В случае вытесняющего алгоритма операционная система в любой момент времени может прервать выполнение текущего потока и переключить процессор на другой поток. В невытесняющих алгоритмах поток, которому предоставлен процессор, только сам решает, когда передать управление операционной системе.
2. Алгоритмы с квантованием.
Каждому потоку предоставляется квант времени, в течение которого поток может выполняться на процессоре. По истечении кванта операционная система переключает процессор на следующий поток в очереди. Квант обычно равен целому числу интервалов системного таймера1).
3. Алгоритмы с приоритетами.
Каждому потоку назначается приоритет (priority) – целое число, обозначающее степень привилегированности потока. Операционная система при наличии нескольких готовых к выполнению потоков выбирает из них поток с наибольшим приоритетом.
В Windows реализован смешанный алгоритм планирования – вытесняющий, на основе квантования и приоритетов.
Состояния потоков
За время своего существования поток может находиться в нескольких состояниях. Перечислим основные состояния:
- Готовность (Ready) – поток готов к выполнению и ждет своей очереди занять процессор.
- Выполнение (Running) – поток выполняется на процессоре.
- Ожидание (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).

Рис. 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).

Рис. 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).
Относительный приоритет | Смещение для динамических приоритетов | Смещение для приоритетов реального времени |
---|---|---|
Time Critical | Базовый приоритет = 15 | Базовый приоритет = 31 |
Highest | +2 | +2 |
Above Normal | +1 | +1 |
Normal | 0 | 0 |
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 содержит следующие поля, требуемые для планирования:
- CurrentThread – указатель на текущий выполняющийся поток;
- NextThread – указатель на следующий поток для выполнения;
- IdleThread – указатель на поток простоя;
- DispatcherReadyListHead – массив списков, содержащих указатели на потоки, готовые к выполнению. Количество элементов массива совпадает с количеством уровней приоритета в системе (32), т. е. для каждого приоритета поддерживается своя очередь потоков в состоянии готовности;
- ReadySummary – 32 битное число, каждый из разрядов которого отвечает за один уровень приоритета. Единица в N-ом разряде означает, что очередь готовых к выполнению потоков с приоритетом N не пустая. Это поле используется для ускорения поиска при выборе потока для выполнения.
Выбор потока с максимальным приоритетом из массива 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);
- приоритет потока в очереди готовности динамически увеличился (см. далее в этой лекции);
- в системе создан поток с большим приоритетом.
В любом случае выполняющийся поток вытесняется, помещается в начало очереди готовности с соответствующим приоритетом; при этом неистраченная часть кванта остается за потоком.

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

Рис. 9.7. Завершение кванта
Динамическое повышение приоритета
Если бы операционная система осуществляла планирование потоков только на основе выше рассмотренных ситуаций, большинство потоков с низким приоритетом вообще никогда не выполнялись бы – диспетчер ядра все время выбирал бы потоки с наивысшим приоритетом.
Чтобы дать всем потокам шанс на выполнение операционная система применяет механизм динамического повышения приоритета (Priority Boosts), который работает в следующих случаях:
- возникает событие диспетчера ядра;
- завершается операции ввода/вывода;
- происходит событие пользовательского интерфейса;
- поток слишком долго ожидает ресурс;
- поток слишком долго ожидает своей очереди на выполнение.
Замечание. Никогда не повышаются приоритеты потоков реального времени (16–31).
Резюме
В этой лекции рассмотрены основные алгоритмы планирования потоков, в том числе, вытесняющие и невытесняющие, с квантованием и с приоритетами. Описаны состояния, в которых могут находиться потоки. Приведены особенности реализации квантования и приоритетов в Windows. Рассмотрен алгоритм планирования потоков, используемый в Windows.
В следующей лекции рассказывается, каким образом в Windows реализуется управление виртуальной и физической памятью.
Контрольные вопросы
- Перечислите виды алгоритмов планирования.
- Нарисуйте схему состояний потоков, переходов и условий переходов между состояниями.
- Какие кванты используются в Windows? Каким образом можно изменить величину кванта?
- Нарисуйте схему уровней приоритетов в Windows. Укажите соответствие уровней и классов приоритета.
- Рассмотрите основные ситуации, возникающие при планировании потоков и действия диспетчера ядра Windows в этих ситуациях.
- Возможна ли в Windows ситуация, когда какой-либо созданный поток вообще не получит процессорного времени?
Лекция 10. Задания по планированию потоков
Задание 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.
Указания к выполнению.
- Базовый приоритет потока хранится в поле BasePriority, а текущий – в поле Priority структуры KTHREAD.
- Также приоритет потока отображается при выполнении команды !thread.
- Ещё один способ узнать приоритет потока – с помощью утилиты 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 предоставляется важнейший ресурс – виртуальная память (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). Отметим, что каждый процесс имеет свое собственное пользовательское ВАП, а системное ВАП для всех процессов одно и то же.

Рис. 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. Структура виртуального адресного пространства
В системном ВАП расположены:
- образы ядра (ntoskrnl.exe), исполнительной системы, HAL (hal.dll), драйверов устройств, требуемых при загрузке системы;
- таблицы страниц процесса;
- системный кэш;
- пул подкачиваемой памяти (paged pool) – системная куча подкачиваемой памяти;
- пул подкачиваемой памяти (nonpaged pool) – системная куча неподкачиваемой памяти;
- другие элементы (см. [5]).
Переменные, в которых хранятся границы разделов в системном ВАП, приведены в [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), содержащей следующие основные поля:
- флаг (однобитовое поле) Valid: если виртуальная страница расположена в физической памяти, Valid = 1;
- флаг Accessed: если к странице были обращения для чтения, Accessed = 1;
- флаг Dirty: если содержимое страницы было изменено (была произведена операция записи), Dirty = 1;
- флаг LargePage: если страница является большой (4 МБ), LargePage = 1;
- флаг Owner: если страница доступна из пользовательского режима, Owner = 1;
- 20 битовое поле PageFrameNumber: указывает номер страничного фрейма (PFN, Page Frame Number).
В поле 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), имеющей следующие поля:
- Size – текущий размер файла (в страницах);
- MaximumSize, MinimumSize – максимальный и минимальный размеры файла (в страницах);
- FreeSpace, CurrentUsage – число свободных и занятых страниц;
- PageFileName – имя файла;
- PageFileNumber – номер файла;
- FileHandle – дескриптор файла.
В 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.
Тип памяти | 32-разрядные Windows | 64-разрядные Windows |
---|---|---|
Виртуальное адресное пространство | 4 ГБ | 16 ТБ (16 000 ГБ) |
Пользовательское ВАП | 2 ГБ; до 3 ГБ в случае использования специальных ключей при загрузке | 8 ТБ |
Системное ВАП | 2 ГБ; от 1 до 2 ГБ в случае использования специальных ключей при загрузке | 8 ТБ |
Версия Windows | 32-разрядные | 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) |
Версия Windows | 32-разрядные | 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.
Контрольные вопросы
- Дайте определение "виртуальной памяти". Чем виртуальная память отличается от физической?
- Нарисуйте структуру виртуального адресного пространства в 32 разрядной системе Windows.
- Что такое страница памяти? Какие страницы используются в Windows?
- Опишите достоинства и недостатки различных способов выделения памяти.
- Опишите процесс трансляции адресов. Какую роль в этом процессе играют таблицы страниц? Каталоги страниц?
- Возможно ли в 32 разрядной системе Windows наличие большего объема физической памяти, чем виртуальной памяти процесса?
Лекция 12. Функции по управлению памятью
Задание 1. Определить значения системных переменных, отвечающих за границы областей виртуального адресного пространства (ВАП).
Указания к выполнению.
1. Узнаем значения 4 х системных переменных:
- MmHighestUserAddress – наибольший адрес пользовательского ВАП;
- MmSystemRangeStart – начальный адрес системного ВАП;
- MiSystemCacheEndExtra – конечный адрес области системного кэша или начальный адрес области таблицы страниц;
- MmNonPagedSystemStart – начальный адрес системных PTE.
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):
- конфигурацию Release;
- Библиотека времени выполнения – Многопоточная (/MT).
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

На экран выводится таблица со следующими столбцами:
- VAD – адрес дескриптора виртуального адреса – узла дерева;
- level – уровень в дереве, на котором находится узел;
- start – номер начальной виртуальной страницы (VPN) в блоке, за который отвечает данный VAD;
- end – номер последней виртуальной страницы в блоке;
- commit – счетчик переданных страниц в блоке;
- тип памяти блока;
- параметры защиты блока;
- файлы, отображаемые на блок памяти.
После таблицы показывается общее количество узлов в дереве, средний уровень дерева и его максимальная глубина.
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 состоит из следующих частей:
- номер версии – поле Revision (1 байт);
- код агента идентификатора (identifier authority) – поле IdentifierAuthority (6 байт);
- коды субагентов (subauthority values) – поле SubAuthority (от 1 до 15 кодов по 4 байта каждый). Количество кодов субагентов хранится в поле SubAuthorityCount.
В текстовом виде 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) и имеющей следующие основные поля:
- TokenId – идентификатор маркера;
- UserAndGroups – SID учетной записи пользователя и групп, в которые данная учетная запись входит. При проверке возможности доступа пользователя к определенному ресурсу, например, файлу на диске NTFS, система проверяет, входит ли SID учетной записи в список доступа файла.
- Privileges – список привилегий.
- DefaultDacl – список управления избирательным доступом по умолчанию (DACL, Discretionary Access-Control List). При создании процессом объектов, из маркера доступа процесса извлекается данное поле и помещается в атрибуты безопасности вновь созданного объекта.

Рис. 13.2. Создание и наследование маркера доступа
Дескриптор защиты
Объекты, к которым могут получать доступ процессы, имеют специальный атрибут – дескриптор защиты (security descriptor), содержащий информацию обо всех пользователях, которым разрешен или запрещен доступ к объекту.
Структура данных SECURITY_DESCRIPTOR, представляющая дескриптор защиты, описана в файле public\sdk\inc\ntseapi.h (строка 1173) и включает следующие основные поля:
- Owner – SID владельца;
- Dacl – список управления избирательным доступом;
- Sacl – системный список управления доступом.
Списки управления доступом (ACL, Access-Control List) в системе представлены заголовком (ACL Header) и последовательностью элементов списка (ACE, Access-Control Entry) (рис.13.3).

Рис. 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).
Стандартными являются следующие права доступа:
- DELETE – право на удаление объекта;
- READ_CONTROL – право на просмотр информации о дескрипторе защиты объекта;
- SYNCHRONIZE – право на использование объекта для синхронизации;
- WRITE_DAC – право на изменение списка DACL;
- WRITE_OWNER – право на смену владельца объекта.
Списки управления доступом бывают двух видов: 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). На вход функции поступают следующие параметры:
- дескриптор защиты объекта (SecurityDescriptor);
- маркер доступа процесса (элемент структуры SubjectSecurityContext);
- маска запрашиваемого доступа (DesiredAccess);
- маска ранее предоставленного доступа (PreviouslyGrantedAccess);
- режим работы процессора (AccessMode);
Функция возвращает TRUE, если процессу возможно предоставить доступ к объекту, а также маску предоставленного доступа (GrantedAccess). Если доступ запрещен, функция возвращает FALSE.
Функция SeAccessCheck осуществляет следующие действия:
- Сначала анализируется режим работы процессора – если это режим ядра, доступ предоставляется без дальнейшего анализа (строки 3396–3416).
- В случае пользовательского режима проверяется дескриптор защиты: если он отсутствует (SecurityDescriptor == NULL), в доступе отказывается (строки 3423–3428).
- Если маска запрашиваемого доступа равна нулю (DesiredAccess == 0), возвращается маска ранее предоставленного доступа (строки 3442–3454).
- Если запрашивается доступ на изменение списка DACL (WRITE_DAC) или на чтение информации в дескрипторе защиты (READ_CONTROL), то при помощи функции SepTokenIsOwner проверяется, не является ли процесс владельцем объекта, к которому требуется получить доступ (строки 3477–3483). Если является, то ему предоставляются указанные права (3485–3492), а если запрашиваются только эти права, то функция успешно возвращает требуемую маску доступа (строки 3498–3506).
- Вызывается функция SepAccessCheck (определенная в том же файле, строка 1809), которая просматривает список DACL объекта в поисках соответствия идентификаторов безопасности SID в маркере доступа процесса. В том случае, если список DACL пустой, процессу предоставляется доступ (строка 3510–3527).
Права и привилегии
Кроме операций с объектами система должна контролировать множество других действий пользователей, например, вход в систему, включение/выключение компьютера, изменение системного времени, загрузка драйверов и т.д.
Для управления такими действиями, не связанными с доступом к конкретным объектам, система использует два механизма – права учетных записей и привилегии.
Право учетной записи (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 с внешними устройствами.
Контрольные вопросы
- Какие требования к безопасности предъявляются к Windows?
- Что такое идентификатор защиты (SID)? Как его узнать?
- Что такое дескриптор защиты (security descriptor)? Где он хранится?
- Что такое маркер доступа (access token)? Где он хранится?
- Опишите схему получения доступа процесса к объекту.
- Что такое права и привилегии учетных записей? Чем они отличаются?
Лекция 14. Обеспечение безопасности в Windows
Задание 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):
- конфигурацию Release;
- Библиотека времени выполнения – Многопоточная (/MT).
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"):
- диспетчер ввода-вывода (I/O manager – Input/Output manager) – основной компонент; обеспечивает интерфейс между приложениями и устройствами;
- диспетчер PnP (Plug and Play manager) – компонент, реализующий принцип Plug and Play ("подключи и работай") – автоматическое распознавание и конфигурацию подключаемых к системе устройств;
- диспетчер электропитания (power manager) – обеспечивает поддержку различных режимов энергопотребления системы и устройств;
- драйверы устройств – программы, реализующие операции ввода-вывода для конкретного устройства; драйверы больше других компонентов системы "знают" о специфике своего устройства;
- HAL (Hardware Abstraction Layer) – уровень абстрагирования от аппаратных средств; скрывает от других компонентов особенности реализации конкретных процессоров, системных плат и контроллеров прерываний;
- реестр (registry) – используется как база данных для параметров устройств и драйверов.
Далее будут рассмотрены общая схема ввода-вывода, функции и структуры данных диспетчера ввода-вывода, представленные в 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), имеющим следующие основные поля:
- Type – поле, определяющее тип структуры подсистемы ввода-вывода. Значения этого поля могут быть следующими – IO_TYPE_DRIVER, IO_TYPE_FILE, IO_TYPE_DEVICE, IO_TYPE_IRP и др. (см. файл base\ntos\inc\io.h, строка 25);
- Size – размер объекта в байтах;
- DeviceObject – ссылка на первый объект DEVICE_OBJECT в списке устройств, управляемых данным драйвером (см. рис.15.1). Следующие устройства в списке можно определять по полю NextDevice объекта DEVICE_OBJECT;
- Flags – флаги, определяющие тип драйвера (см. файл base\ntos\inc\io.h, строка 1530);
- DriverName – имя драйвера в системе;
- HardwareDatabase – путь в реестре к информации о драйвере;
- DriverStart, DriverSize, DriverSection – информация о расположении драйвера в памяти;
- DriverInit – адрес процедуры DriverEntry (точка входа в драйвер), отвечающей за инициализацию драйвера;
- DriverUnload – адрес процедуры выгрузки драйвера;
- MajorFunction – массив адресов процедур, каждая из которых отвечает за определенную операцию с устройством. Максимальное количество таких процедур равно константе IRP_MJ_MAXIMUM_FUNCTION+ 1 = 2 8 (файл base\ntos\inc\io.h, строка 80), которая определяет также количество кодов IRP (см. далее).
Устройства представлены объектами типа DEVICE_OBJECT, который включает следующие главные поля (файл base\ntos\inc\io.h, строка 1397):
- Type, Size – совпадают по назначению с полями типа DRIVER_OBJECT;
- ReferenceCount – счетчик количества открытых дескрипторов для устройства. Позволяет отслеживать, используется кем-либо устройство или нет;
- DriverObject – ссылка на драйвер, который управляет устройством;
- NextDevice – указатель на следующее устройство в списке устройств для данного драйвера;
- Flags, Characteristics – поля, уточняющие характеристики устройства;
- DeviceType – тип устройства; возможные типы перечислены в файле public\sdk\inc\devioctl.h (строка 26);
- SecurityDescriptor – дескриптор безопасности, сопоставленный с устройством (см. лекцию 9 "Безопасность в Windows").
Пакеты запроса на ввод-вывод описываются типом IRP (I/O Request Packet), состоящим из двух частей – заголовка фиксированной длины (тело IRP) и одного или нескольких блоков стека. В заголовке описывается информация, общая для запроса. Каждый блок стека содержит данные об одной операции ввода-вывода.
Заголовок включает следующие основные поля:
- Type, Size – поля, по назначению аналогичные соответствующим полям типов DRIVER_OBJECT и DEVICE_OBJECT;
- IoStatus – статус операции при завершении;
- RequestorMode – режим, в котором работает поток, инициировавший операцию ввода-вывода, – пользовательский или режим ядра;
- StackCount – количество блоков стека;
- Tail.Overlay.Thread – указатель на структуру ETHREAD потока, запросившего операцию ввода-вывода;
- Tail.Overlay.CurrentStackLocation – указатель на блок стека (IRP Stack Location), который описывается структурой IO_STACK_LOCATION.
Структура блока стека IO_STACK_LOCATION описана в файле base\ntos\inc\io.h, строка 2303) и имеет следующие главные поля:
- MajorFunction – номер основной функции, определяющий запрошенную операцию ввода-вывода и совпадающий с номером функции драйвера в массиве MajorFunction (структура DRIVER_OBJECT, см. выше), которую нужно вызвать для выполнения запрошенной операции. Как уже отмечалось, всего кодов 28 (IRP_MJ_MAXIMUM_FUNCTION + 1), они описаны в файле base\ntos\inc\io.h (строки 51–79);
- DeviceObject – указатель на структуру DEVICE_OBJECT, определяющую устройство для данной операции ввода-вывода;
- FileObject – указатель на структуру FILE_OBJECT (файл base\ntos\inc\io.h, строка 1763), которая ассоциирована со структурой DEVICE_OBJECT.
В следующем параграфе разобран пример операции чтения с использованием рассмотренных выше структур данных.
Пример ввода-вывода
Для ввода-вывода используются следующие основные функции:
- создание/открытие файла – IoCreateFile (файл base\ntos\io\iomgr\iosubs.c, строка 4795);
- чтение из файла – NtReadFile (файл base\ntos\io\iomgr\read.c, строка 90);
- запись в файл – NtWriteFile (файл base\ntos\io\iomgr\write.c, строка 87);
- закрытие файла – IopDeleteFile файл base\ntos\io\iomgr\objsup.c, строка 465).
Рассмотрим пример чтения с устройства, используя изученные структуры данных и функцию 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.
Контрольные вопросы
- Перечислите компоненты подсистемы ввода вывода в Windows.
- Дайте определение понятия "файл".
- Опишите основные структуры данных, участвующие в процессе ввода вывода.
- Расскажите о взаимодействии объектов FILE_OBJECT, DEVICE_OBJECT, DRIVER_OBJECT, IRP в процессе ввода вывода.
- Какую роль играет массив MajorFunction в структуре DRIVER_OBJECT?
- Приведите пример ввода вывода с описанием участвующих в нем структур данных и функций Windows Research Kernel.
Лекция 16. Функции для управления устройствами
Задание 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 имеет следующие параметры:
- FileHandle – дескриптор файла, информацию из которого требуется прочитать;
- Event – дескриптор объекта "Событие", которое может быть установлено при завершении операции чтения (необязательный параметр);
- ApcRoutine, ApcContext – зарезервированные неиспользуемые параметры;
- IoStatusBlock – указатель на структуру IO_STATUS_BLOCK, в которой будет содержаться статус операции после завершения чтения, в частности, количество реально прочитанных байт (см. файл public\sdk\inc\ntioapi.h, строка 451);
- Buffer – указатель на область памяти (буфер), в которую будут помещены прочитанные данные;
- Length – размер буфера в байтах;
- ByteOffset – определяет для операции чтения смещение в байтах относительно начала файла;
- Key – необязательный параметр, используемый в случае блокировки файла.
Значения параметров можно посмотреть в окне 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

Из рисунка видно, что последний блок стека сейчас заполнен:
- MajorFunction = 3 (константа IRP_MJ_READ);
- FileObject = 81FFF2F8 (адрес объекта FILE_OBJECT для файла input.txt);
- Args = 50 (размер буфера).
Чтобы посмотреть структуру 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
Основные понятия
Файловая система (file system) – способ организации данных в виде файлов на устройствах внешней памяти (жестких и оптических дисках, устройствах флеш-памяти и т. п.).
Файловая система должна обеспечивать:
- безопасное и надежное хранение данных (т. е. защищенное от несанкционированного использования и различного рода сбоев и ошибок);
- программный интерфейс доступа к файлам;
- организацию файлов в виде иерархии каталогов.
Windows поддерживает несколько файловых систем для различных внешних устройств:
- NTFS – основная файловая система семейства Windows NT;
- FAT (File Allocation Table – таблица размещения файлов) – простая файловая система используемая Windows для устройств флеш памяти, а также для совместимости с другими операционными системами при установке на диски с множественной загрузкой. Основным элементом этой файловой системы является таблица размещения файлов FAT (по имени которой названа вся файловая система), необходимая для определения расположения файла на диске. Существует три варианта FAT, отличающихся разрядностью идентификаторов, указывающих размещение файлов: FAT12, FAT16 и FAT32;
- exFAT (Extended FAT – расширенная FAT) – развитие файловой системы FAT, использующее 64 разрядные идентификаторы. Применяется в основном для устройств флеш-памяти;
- CDFS (CD ROM File System) – файловая система для CD дисков, объединяющая форматы ISO 96601) и Joliet2);
- UDF (Universal Disk Format – универсальный формат дисков) – файловая система для CD и DVD дисков, разработанная для замены ISO 9660.
Для дальнейшего изложения необходимо знать следующие важные понятия: диск, раздел, простые и составные тома, сектор, кластер.
Диск (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]:
- восстанавливаемость (recoverability) – способность файловой системы возвращаться к работоспособному состоянию после возникновения сбоя. Реализуется такая возможность, во первых, за счет поддержки атомарных транзакций, во вторых, за счет избыточности хранения информации. Атомарная транзакция (atomic transaction) – операция с файловой системой, приводящая к её изменению, которая либо полностью успешно выполняется, либо не выполняется вообще (т. е. в случае сбоя во время атомарной транзакции все изменения откатываются). Избыточность используется при хранении важнейших данных файловой системы, критически необходимых для её корректной работы;
- безопасность (security) – защищенность файлов от несанкционированного доступа. Реализуется при помощи модели безопасности Windows, рассмотренной в лекции 9 "Безопасность в Windows";
- шифрование (encryption) – преобразование файла в зашифрованный код, который невозможно прочесть без ключа. Обычные механизмы безопасности, такие как назначение прав доступа пользователей к файлам, не обеспечивают полной защиты информации, например, в случае перемещения диска на другой компьютер. Администратор операционной системы всегда может получить доступ к файлам других пользователей, даже на томе NTFS. Поэтому в NTFS включена поддержка шифрующей файловой системы EFS (Encrypting File System), которая позволяет легко зашифровывать и расшифровывать файлы;
- поддержка RAID (Redundant Array of Inexpensive (Independent) Disks – массив недорогих (независимых) дисков с избыточностью) – возможность использования для хранения информации нескольких дисков; данные с одного диска автоматически копируются на другие, обеспечивая тем самым повышенную надежность;
- дисковые квоты для пользователей (Per-User Volume Quotas) – возможность выделения для каждого пользователя определенного пространства на диске (квоты); NTFS не позволяет пользователю записывать данные на диск сверх выделенной квоты.
Структура NTFS
Структура тома NTFS представлена на рис.17.1.

Рис. 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).

увеличить изображение
Рис. 17.2. Главная таблица файлов MFT
Главная таблица файлов MFT состоит из множества записей о файлах (файловых записей), расположенных на томе. Размер одной записи – 1 КБ (2 сектора). Самая первая запись в MFT – это запись о самом файле $Mft. Во второй записи содержится информация о файле $MftMirr – зеркальной копии MFT. В этом файле дублируются первые 4 записи таблицы MFT, в том числе запись о $Mft. В случае возникновения сбоя, если MFT окажется недоступной, информация о системных файлах будет считываться из $MftMirr (в загрузочной записи имеется адрес $MftMirr).
Перечислим следующие несколько записей в таблице MFT и кратко опишем назначение соответствующих системных файлов:
- $LogFile – файл журнала, в котором записывается информация о всех операциях, изменяющих структуру тома NTFS, например, создание файлов и каталогов. Файл журнала используется при восстановлении тома NTFS после сбоев;
- $Volume – файл информации о томе, в котором содержатся имя тома (Volume label), версия NTFS и набор флагов состояния тома, например, флаг (т. н. грязный бит, dirty bit), установка которого означает, что том был поврежден и требует восстановления при помощи системной утилиты Chkdsk;
- $AttrDef – таблица определения атрибутов (Attribute Definition Table), содержащая возможные на данном томе типы атрибутов файлов (см. далее);
- Root Directory (обозначается также обратным слешем "\") – файл с информацией о корневом каталоге тома. В нем хранятся ссылки на файлы и каталоги, содержащиеся в корневом каталоге;
- $BitMap – файл битовой карты (bitmap), каждый бит в этой карте соответствует кластеру на томе: если бит равен 1, кластер занят, иначе – свободен;
- $Boot – файл загрузочной записи тома;
- $BadClus – файл плохих кластеров (bad clusters), содержащий информацию обо всех кластерах, имеющих сбойные секторы (bad sectors).
Кроме перечисленных, имеются и другие системные файлы 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.

Рис. 17.6. Схема нумерации кластеров с использованием LCN VCN
В случае нерезидентных атрибутов в заголовке атрибута содержатся следующие поля: номер VCN первого кластера группы (обычно равен 0х00), номер VCN последнего кластера группы и список групп (RunList), описывающий расположение групп на диске.
Рассмотрим пример описания расположения групп, приведенный на рис.17.5 (справа). В этом примере значения полей следующие:
- первый VCN = 0x00;
- последний VCN = 0x3F;
- список групп (RunList) = 0x21 40 55 20 00.
Расположение кластеров для данного примера приведено на рис.17.7.

Рис. 17.7. Расположение кластеров группы для примера на рис. 17.5
В этом примере значение для списка групп
0x21 40 55 20 00
обозначает следующее:
- 0x21 – первый байт кодирует размер двух полей, которые за ним следуют:
- младший полубайт обозначает размер поля (в байтах), в котором хранится длина группы в кластерах; в данном случае значение 1 указывает, что на длину группы отводится один байт;
- старший полубайт обозначает размер поля (в байтах), в котором расположен номер LCN первого кластера группы; в данном случае значение 2 указывает на двухбайтовое поле;
- 0x40 – длина группы. Поскольку в первом байте размер поля длины группы определен в один байт, в качестве длины группы рассматриваем однобайтовое поле; в данном примере оно равно 0x40 (64 кластера);
- 0x2055 – LCN номер первого кластера. В первом байте размер поля номер первого кластера определен в два байта, поэтому в качестве LCN номера первого кластера рассматривается двухбайтовое поле, которое в примере равно 0x2055 (обратите внимание, байты на диске записываются в обратном порядке: сначала младшие – 55, затем старшие – 20);
- 0x00 – признак окончания описания списка групп.
Указанные обозначения проиллюстрированы на рис.17.8.

Рис. 17.8. Список группы
Отметим, что в рассмотренном примере нерезидентный атрибут содержится всего в одной группе; в общем случае групп может быть несколько.
Структуры данных для управления файлами
Рассмотрим структуры данных, задействованные при работе с файловой системой NTFS (рис.17.9).

Рис. 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) содержит следующие основные поля:
- FileName – строковое имя файла;
- Vpb – указатель на структуру VPB, которая представляет том на устройстве внешней памяти;
- FsContext – указатель на блок управления потоком данных NTFS;
- DeviceObject – указатель на объект типа DEVICE_OBJECT, связанный с физическим диском, на котором находится файл.
В структуре VPB (Volume Parameter Block) содержатся следующие поля (файл base\ntos\inc\io.h, строка 1288):
- VolumeLabelLength – размер тома в байтах;
- DeviceObject – указатель на объект типа DEVICE_OBJECT, ассоциированный с данным томом (логическим диском);
- RealDevice – указатель на объект типа DEVICE_OBJECT, ассоциированный с физическим устройством внешней памяти (физическим диском);
- SerialNumber – серийный номер тома;
- ReferenceCount – счетчик количества ссылок на структуру; если это поле не равно нулю, значит, структура кем-то используется;
- VolumeLabel – метка (строковое имя) тома. Максимальная длина метки определяется константой MAXIMUM_VOLUME_LABEL_LENGTH, равной 32 двухбайтовым символам (см. файл base\ntos\inc\io.h, строка 1286).
На рис.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.
Контрольные вопросы
- Какие файловые системы поддерживаются Windows?
- Дайте определения понятиям "диск", "раздел", "том", "сектор", "кластер".
- Какие ограничения по размеру существуют для тома NTFS? Для файлов на томе NTFS?
- Приведите структуру NTFS-тома.
- Приведите структуру главной таблицы файлов MFT.
- Приведите структуру файловой записи MFT.
- Что такое "атрибут файловой записи"? Какие виды атрибутов вы знаете?
- Опишите структуры данных Windows Research Kernel, используемые для взаимодействия с файловой системой.
Лекция 18. Структура файловой системы NTFS
Задание 1. Создать виртуальный жесткий диск.
Замечание. В данной лабораторной работе используется виртуальный жесткий диск (Virtual Hard Disk, VHD) – формат файла, в котором можно сохранить образ жесткого диска. В VHD файлах, например, хранятся образы жестких дисков Microsoft Virtual PC.
Встроенная поддержка VHD реализована в Windows 7. Если ваша операционная система выпущена ранее Windows 7, рекомендуется пропустить первое задание, а остальные выполнять с использованием флеш диска (отформатировав его в файловой системе NTFS).
Помните, что при форматировании все данные на диске стираются.
Указания к выполнению.
1. Наберите в командной строке (нажмите кнопку Пуск) следующую команду:

Управление компьютером
Откроется оснастка (snap-in) Управление компьютером. Выберите пункт Управление дисками:

2. В меню Действие выберите пункт Создать виртуальный жёсткий диск:

Откроется диалоговое окно. Введите имя файла, в котором будет храниться виртуальный жесткий диск, и размер файла 100 МБ:

После процедуры создания новый жесткий диск должен отобразиться в списке дисков:

3. Чтобы с новым диском можно было работать, его следует проинициализировать, создать на нем том и отформатировать.
Щелкните правой кнопкой мыши на диске и выберите пункт Инициализировать диск (параметры оставьте по умолчанию):

После инициализации, щелкните правой кнопкой мыши на нераспределенном пространстве диска и выберите Создать простой том…. Откроется Мастер создания простых томов. Выберите следующие параметры:
- Размер тома – оставьте по умолчанию;
- Назначить букву диска – можно выбрать любую;
- Форматирование раздела – NTFS, размер кластера по умолчанию, Быстрое форматирование:

Нажмите кнопку Готово.

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

Задание 2. Получить информацию об NTFS томе.
Указания к выполнению.
1. Скачайте с сайта Sysinternals утилиту NTFSInfo1). Сохраните её в папку на жестком диске, например, c:\Instruments.
2. Запустите командную строку: нажмите кнопку Пуск – в текстовом окне введите cmd – нажмите Enter.
3. Перейдите в каталог с утилитой NTFSInfo: введите в командной строке:
cd c:\Instruments
4. Запустите утилиту NTFSInfo. Введите команду ntfsinfo.exe, указав букву виртуального диска, например:
ntfsinfo.exe d:

Утилита отображает следующую информацию.
- Информацию о томе (Volume Size):
- размер тома (Volume size);
- количество секторов (Total sectors);
- количество кластеров (Total clusters);
- количество свободных кластеров (Free clusters);
- свободное место на диске (Free space).
- Информацию о размерах единиц данных (Allocation Size):
- количество байт в секторе (Bytes per sector);
- количество байт в кластере (Bytes per cluster);
- количество байт файловой записи (Bytes per MFT record);
- количество кластеров в файловой записи (Clusters per MFT record).
- Информацию о MFT (MFT Information):
- размер MFT (MFT size);
- начальный кластер MFT (MFT start cluster);
- кластеры зоны MFT (MFT zone clusters);
- размер зоны MFT (MFT zone size);
- начальный кластер файла $MftMirr (MFT mirror start).
Задание 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 – начальный сектор данной файловой записи: сверху – в шестнадцатеричном виде, снизу – в десятичном;
- Name – имя файла (или каталога);
- Type – тип файловой записи (файл или каталог);
- Attributes – DOS атрибуты файла (например, s – системный, h – скрытый); не путать с NTFS атрибутами;
- Size – размер всего файла в байтах (не только файловой записи); отображается в десятичном виде;
- Date – дата и время модификации файла (или каталога);
- 1st cluster – первый кластер файла, если он имеет нерезидентные атрибуты;
- NT Attributes – NTFS атрибуты (коды см. в лекции 17 "Файловая система NTFS").
Во второй строке:
- No – номер записи в MFT;
- Parent – номер записи в MFT для родительского каталога; например, для всех первых файлов в MFT этот номер будет равен 5 – номеру записи для корневого каталога (обозначается точкой);
- Run – список групп для нерезидентных файлов.
В крайней левой колонке таблицы (Sector) показывается номер сектора, в котором располагается начало файловой записи. Проверьте, что этот номер совпадает с номером сектора для $Mft в файле log.txt, полученным в предыдущем задании. В нашем примере этот номер равен 0x102A8.
Проверьте соответствие других записей в программе DiskExplorer и в файле log.txt.
Обратите внимание, что номера секторов в столбце Sector увеличиваются на 2, т.е. файловая запись занимает 2 сектора (1 КБ).
Задание 6. Изучить файловую запись для резидентного файла.
Указания к выполнению.
1. Найдите в списке файловых записей DiskExplorer запись для файла Test.txt:

Дважды щелкните на этой записи (или нажмите клавишу F7) – откроется окно файловой записи:

В этом окне три основных раздела:
- Structures – структура файловой записи. Показаны заголовок (Header) файловой записи и атрибуты; каждый атрибут имеет заголовок (Header) и тело (Body);
- Interpretation of data – интерпретация данных. Для текстового файла приведено его содержимое в текстовом виде;
- Raw data – содержимое файловой записи в виде набора байт (приведены шестнадцатеричные значения и ASCII коды).
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.
- 11 – определяет размер следующих двух полей:
- младший полубайт (1) кодирует размер поля длины группы; поскольку в данном случае полубайт равен 1, размер поля длины группы составляет 1 байт;
- старший полубайт (1) кодирует размер поля номера LCN стартового кластера; в данном случае размер поля составляет 1 байт;
- 01 – размер группы составляет 1 кластер (это значение совпадает с определенной нами ранее длиной группы по номерам начального и конечного кластеров);
- 02 – LCN номер начального кластера.
Перейдя на второй кластер (щелкнув ссылку x00000002 в окне Interpretation of data, выделенную синим цветом шрифта) и нажав клавишу F6, можно убедиться, что атрибут $DATA файла $MftMirr содержит первые 4 записи таблицы MFT:

Обратите внимание, что поскольку одна файловая запись занимает 1 КБ (2 сектора), то 4 записи будут занимать 4 КБ или 8 секторов или 1 кластер.
Задания для самостоятельного выполнения
Задание 1. Определить максимальный размер обычного текстового файла, который целиком помещается в файловую запись NTFS.
Задание 2. Исследовать представление каталогов в файловых записях NTFS.
Указания к выполнению.
- Создайте каталог на томе виртуального жесткого диска.
- В программе DiskExplorer найдите файловую запись для созданного каталога и изучите её содержимое.
Задание 3. Определите расположение в атрибутах файловых записей NTFS следующей информации (указаны также виды атрибутов, для которых нужно определять расположение). Проверить соответствие информации в файловой записи и информации, отображаемой в окне свойств файла в Windows.
Для всех атрибутов:
- длина тела атрибута.
Для атрибута $STANDARD_INFORMATION:
- время создания файла;
- время изменения файла;
- время последнего чтения файла;
- признаки MS DOS (скрытый, системный и т.д.);
Для атрибута $FILE_NAME:
- родительский каталог;
- имя файла.