Внутреннее устройство Windows. 7-е изд.

Глава 2. Архитектура системы

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

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

Разработка спецификации Windows NT в 1989 году определялась следующими требованиями:

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

• Выполнение на разных аппаратных архитектурах и платформах.

• Выполнение и хорошее масштабирование в системах с симметричной многопроцессорной обработкой.

• Эффективная платформа для распределенных вычислений (как в роли сетевого клиента, так и в роли сервера).

• Выполнение большинства существующих 16-разрядных приложений MS-DOS и Microsoft Windows 3.1.

• Выполнение правительственных требований к соответствию спецификации POSIX 1003.1.

• Выполнение правительственных и отраслевых требований к безопасности ОС.

• Простая адаптация к глобальному рынку за счет поддержки Юникода.

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

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

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

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

• Совместимость. Хотя система Windows NT должна расширять существующую технологию, ее пользовательский интерфейс и API должны быть совместимыми с предыдущими версиями Windows и MS-DOS. Система также должна взаимодействовать с другими системами, такими как UNIX, OS/2 и NetWare.

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

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

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

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

Как и многие системы UNIX, Windows является монолитной ОС в том смысле, что большая часть кода ОС и кода драйверов устройств использует одно защищенное пространство памяти защищенного режима. Это означает, что любой компонент ОС или драйвер устройства теоретически может повредить данные, используемые другими системными компонентами ОС. Однако, как было показано в главе 1 «Концепции и средства», Windows решает эту проблему за счет повышения качества и контроля происхождения сторонних драйверов через такие программы, как WHQL и KMCS, с одновременным внедрением дополнительных технологий защиты ядра, таких как безопасность на базе виртуализации, функции Device Guard и Hyper Guard. В этом разделе вы увидите, как эти компоненты взаимодействуют между собой, а более подробная информация будет приведена в главе 7 «Безопасность» и в главе 8 «Системные механизмы».

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

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

Несмотря на повсеместное использование объектов для представления общих системных ресурсов, Windows не является объектно-ориентированной системой в прямом смысле слова. Большая часть кода ОС написана на языке C для обес­печения портируемости. В языке программирования C нет прямой поддержки объектно-ориентированных конструкций, таких как полиморфные функции или наследование классов. Таким образом, реализация объектов на базе C в системе Windows заимствует некоторые возможности объектно-ориентированных языков, но не зависит от них.

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

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

На рис. 2.1 прежде всего обратите внимание на линию, разделяющую части пользовательского режима и режима ядра ОС Windows. Блоки выше линии представляют процессы пользовательского режима, а компоненты ниже линии — сервисные средства ОС режима ядра. Как упоминалось в главе 1, потоки пользовательского режима выполняются в закрытом адресном пространстве процессов (хотя во время выполнения в режиме ядра они получают доступ к системному пространству).

386438.png 

Рис. 2.1. Упрощенная архитектура Windows

Таким образом, системные процессы, процессы служб, пользовательские процессы и подсистемы среды обладают собственными закрытыми адресными пространствами. Также хорошо видна вторая разделяющая линия между компонентами режима ядра Windows и гипервизором. Строго говоря, гипервизор также работает на том же уровне привилегий процессора (0), что и ядро, но благодаря использованию специализированных команд процессора (VT-x у процессоров Intel, SVM у AMD) он может изолироваться от ядра одновременно с сохранением контроля над ним (и приложениями). По этим причинам также часто приходится слышать термин «кольцо -1» (формально неточный).

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

• Пользовательские процессы. Эти процессы относятся к одному из следующих типов: 32-разрядные или 64-разрядные приложения Windows (приложения Windows Apps, работающие на базе среды Windows Runtime в Windows 8 и выше, включаются в эту категорию), 16-разрядные приложения Windows 3.1, 16-разрядные приложения MS-DOS, 32-разрядные и 64-разрядные приложения POSIX. Следует заметить, что 16-разрядные приложения могут выполняться только в 32-разрядных версиях Windows, а приложения POSIX в Windows 8 уже не поддерживаются.

• Процессы служб. К этой категории относятся процессы, являющиеся хостами для служб Windows, например службы планировщика задач и диспетчера печати. Обычно к службам предъявляется требование независимости выполнения от входа пользователя. Многие серверные приложения Windows (такие, как Microsoft SQL Server и Microsoft Exchange Server) также включают компоненты, выполняемые как службы. Службы более подробно описаны в главе 9 «Механизмы управления», часть 2.

• Системные процессы. Фиксированные процессы (такие, как процесс входа или диспетчер сеансов) не являются службами Windows. Иначе говоря, они не запускаются диспетчером служб.

• Серверные процессы подсистем среды. Эти процессы реализуют часть поддержки среды ОС, предоставляемой пользователю и программисту. Windows NT изначально поставлялась с тремя подсистемами среды: Windows, POSIX и OS/2. Но подсистема OS/2 включалась только до Windows 2000, а подсистема POSIX в последний раз была включена в поставку Windows XP. Ultimate- и Enterprise-выпуски клиента Windows 7, а также все серверные версии Windows 2008 R2, включают поддержку расширенной подсистемы POSIX, которая называется SUA (Subsystem for UNIX-based Applications). Сейчас подсистема SUA не поддерживается и уже не включается как необязательная часть в версии Windows (клиентские или серверные).

ПРИМЕЧАНИЕ Windows 10 версии 1607 включает подсистему Windows для Linux (WSL, Windows Subsystem for Linux) в бета-версии только для разработчиков. Однако она не является полноценной подсистемой по критериям, описанным в этом разделе. В этой главе WSL и поставщики Pico рассматриваются более подробно. За информацией о процессах Pico обращайтесь к главе 3 «Процессы и задания».

На рис. 2.1 обратите внимание на блок DLL подсистем под блоками Процессы служб и Пользовательские процессы. В системе Windows пользовательские приложения не вызывают низкоуровневые сервисные функции ОС Windows напрямую. Вместо этого они проходят через одну или несколько динамических библиотек (DLL) подсистем. Роль DLL подсистем заключается в преобразовании документированных функций в соответствующие внутренние (и обычно недокументированные) вызовы системных функций, реализованные в основном в Ntdll.dll. Это преобразование может включать (а может не включать) отправку сообщения процессу, обслуживающему пользовательский процесс.

К категории режима ядра Windows относятся следующие компоненты:

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

• Ядро Windows. Низкоуровневые функции ОС: планирование потоков, диспетчеризация прерываний и исключений и многопроцессорная синхронизация. Оно также предоставляет набор функций и базовых объектов, которые используются исполнительной системой для реализации высокоуровневых конструкций.

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

• Слой абстрагирования оборудования (HAL). Прослойка кода, изолирующая ядро, драйверы устройств и прочий исполняемый код Windows от платформенно-зависимых различий в работе оборудования (например, различий между системными платами).

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

• Уровень гипервизора. Состоит всего из одного компонента: самого гипервизора. В этой среде нет ни драйверов, ни других модулей. При этом сам гипервизор состоит из нескольких внутренних уровней и служб: собственный диспетчер памяти, планировщик виртуальных процессов, управление прерываниями и таймером, функции синхронизации, разделы (экземпляры виртуальных машин) и внутрипроцессные коммуникации (IPC, Inter-Process Communication) и многие другие.

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

Таблица 2.1. Основные системные файлы Windows

Имя файла

Компоненты

Ntoskrnl.exe

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

Hal.dll

HAL

Win32k.sys

Часть подсистемы Windows режима ядра (GUI)

Hvix64.exe (Intel), Hvax64.exe (AMD)

Гипервизор

.sys files in \SystemRoot\System32\Drivers

Основные файлы драйверов: Direct X, Volume Manager, TCP/IP, TPM и поддержка ACPI

Ntdll.dll

Внутренние вспомогательные функции и заглушки диспетчеризации системных сервисных функций

Kernel32.dll, Advapi32.dll, User32.dll, Gdi32.dll

DLL основных подсистем Windows

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

Портируемость

Система Windows проектировалась с учетом возможности работы на разных аппаратных архитектурах. Исходная версия Windows NT должна была поддерживать архитектуры x86 и MIPS. Вскоре была добавлена поддержка Alphs AXP от DEC (Digital Equipment Corporation была куплена компанией Compaq, которая позднее объединилась с Hewlett-Packard). (Хотя Alpha AXP является 64-разрядным процессором, Windows NT работала в 32-разрядном режиме. Во время разработки Windows 2000 64-разрядная версия работала на Alpha AXP, но так и не была выпущена.)

Поддержка четвертой архитектуры процессоров — Motorola PowerPC — была добавлена в Windows NT 3.51. Тем не менее из-за изменившихся требований рынка поддержка архитектур MIPS и PowerPC была прекращена до начала работы над Windows 2000. Позднее компания Compaq прекратила поддержку архитектуры Alpha AXP, и в результате Windows 2000 поддерживалась только на архитектуре x86. В Windows XP и Windows Server 2003 была добавлена поддержка двух семейств 64-разрядных процессоров: семейства Intel Itanium IA-64 и семейства AMD64 с ее эквивалентом Intel 64-bit Extension Technology (EM64T). Две последние реализации назывались 64-разрядными расширениями и в этой книге называются x64. (О том, как Windows выполняет 32-разрядные приложения в 64-разрядной системе, рассказано в главе 8 части 2.) Кроме того, на момент выхода Server 2008 R2 системы IA-64 в Windows более не поддерживаются.

Новые версии Windows поддерживают архитектуру процессоров ARM. Например, Windows RT была версией Windows 8, работавшей на архитектуре ARM, хотя поддержка этой версии с тех пор была прекращена.

Windows 10 Mobile — наследник операционных систем Windows Phone 8.x — работает на процессорах на базе ARM, таких как модели Qualcomm Snapdragon. Windows 10 IoT работает как на x86, так и на устройствах ARM, таких как Raspberry Pi 2 (использует процессор ARM Cortex-A7) и Raspberry Pi 3 (использует ARM Cortex-A53). Возможно, с переходом оборудования ARM на 64-разрядную модель в какой-то момент появится поддержка нового семейства процессоров AArch64, или ARM64, на котором работает все большее количество устройств.

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

Многоуровневая архитектура. Windows имеет многоуровневую архитектуру: на нижнем уровне находятся части системы, зависящие от конкретной архитектуры процессора или от платформы и оформленные в виде отдельных модулей, чтобы верхние уровни системы были изолированы от различий между архитектурами и аппаратными платформами. Два ключевых компонента, обеспечивающих портируемость ОС, — ядро (находится в файле Ntoskrnl.exe) и HAL (находится в файле Hal.dll). Оба компонента более подробно описаны далее в этой главе. Функции, зависящие от архитектуры (например, переключение контекста потоков и диспетчеризация ловушек (traps)) реализованы в ядре. Функции, которые могут различаться между системами с одной архитектурой (например, разные системные платы), реализованы в HAL. Кроме них единственным компонентом со значительным объемом кода, привязанного к конкретной архитектуре, является диспетчер памяти, но даже он относительно мал по сравнению с системой в целом. Гипервизор использует похожую архитектуру: большинство компонентов используется совместно реализациями для AMD (SVM) и Intel (VT-x), с набором специализированных компонентов для каждого процессора — отсюда два имени файла в табл. 2.1.

Использование языка С. Подавляющее большинство кода Windows написано на языке C, лишь некоторые части написаны на C++. Язык ассемблера используется только для частей О, которые должны напрямую взаимодействовать с системным оборудованием (например, обработчики прерываний) или которые в высшей степени чувствительны к быстродействию (например, переключение контекста). Код на языке ассемблера встречается не только в ядре и HAL, но и в других местах базовой ОС (например, функциях, реализующих заблокированные команды, а также в одном модуле поддержки локального вызова процедур), в части режима ядра подсистемы Windows и даже в некоторых библиотеках пользовательского режима (например, в коде запуска процессов в Ntdll.dll (системная библиотека, рассматривается далее в этой главе).

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

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

Как упоминалось в начале главы, одна из ключевых целей при проектировании Windows заключалась в том, что ОС должна хорошо работать на многопроцессорных компьютерных системах. Windows является ОС с симметричной многопроцессорной обработкой (SMP, Symmetric Multiprocessing). «Главного» процессора не существует — и ОС, и пользовательские потоки могут планироваться для выполнения на любом процессоре. Кроме того, все процессоры используют всего одно пространство памяти. Эта модель отличается от асимметричной многопроцессорной обработки (ASMP, Asymmetric Multiprocessing), в которой ОС обычно выбирает один процессор для выполнения кода ядра ОС, а на остальных процессорах выполняется только пользовательский код. Различия между двумя многопроцессорными моделями продемонстрированы на рис. 2.2.

Windows также поддерживает четыре основные характеристики современных многопроцессорных систем: многоядерность, одновременную многопоточность (SMT, Simultaneous Multi-threading), разнородность и асимметричный доступ к памяти (NUMA, Non-Uniform Memory Access). Эти характеристики кратко описаны далее. (За полным, подробным описанием поддержки планирования в таких системах обращайтесь к соответствующему разделу главы 4 «Потоки».)

345874.png 

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

Одновременная многопоточность впервые появилась в системах Windows при включении поддержки технологии гиперпоточности Intel (Hyper-Threading), позволяющей смоделировать два логических процессора для каждого физического ядра. В более новых процессорах с микроархитектурой Zen реализована похожая технология SMT, также удваивающая количество логических процессоров.

Каждый логический процессор обладает собственным состоянием, но исполнительное ядро и встроенный кэш используются совместно. Это позволяет одному логическому процессору действовать, пока другие логические процессоры простаивают (например, после промаха кэша или неверного прогнозирования ветвления). По неизвестной причине в маркетинговой литературе обеих компаний дополнительные ядра называются «потоками», поэтому часто приходится слышать формулировки типа «четыре ядра, восемь потоков». Это означает, что планироваться могут до восьми потоков; соответственно, существуют восемь логических процессоров. Алгоритмы планирования усовершенствованы для оптимального использования машин с SMT, например, посредством планирования потоков на свободном физическом процессоре вместо выбора свободного логического процессора на физическом процессоре, остальные логические процессоры которого заняты. За дополнительной информацией о планировании потоков обращайтесь к главе 4.

В системах NUMA процессоры объединяются в группы, называемые узлами (nodes). Каждый узел использует собственные процессоры и память и подключается к большей системе через соединительную шину с когерентностью кэша. Windows на системах NUMA также работает как система SMP, в которой все процессоры обладают доступом ко всей памяти; просто обращение к локальной памяти узлов осуществляется быстрее, чем обращение к памяти других узлов. Система стремится повысить быстродействие за счет планирования потоков на процессоры того же узла, к которому относится используемая память. Она пытается выполнять запросы на выделение памяти в узле, но при необходимости выделяет память из других узлов.

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

Наконец, ARM-версии Windows также поддерживают технологию разнородной многопроцессорной обработки, реализация которой на таких процессорах называется big.LITTLE. Эта разновидность архитектур на базе SMP отличается от традиционных тем, что не все ядра процессоров идентичны по своим возможностям, но, в отличие от «чистой» разнородной многопроцессорной обработки, могут выполнять те же команды. Различия проявляются в тактовой частоте и потребляемой мощности в состоянии полной нагрузки/бездействия, что позволяет объединять группы более медленных ядер с более быстрыми.

Представьте операцию отправки электронной почты на старой двухъядерной 1-гигагерцовой системе, подключенной к Интернету по современному каналу связи. Вряд ли она будет работать медленнее, чем восьмиядерная машина с частотой 3,6 ГГц, потому что «узкими местами» обычно становятся скорость ввода с клавиатуры и пропускная способность канала, а не простая вычислительная мощность. Тем не менее даже в самом глубоком режиме энергосбережения такая современная система с большой вероятностью будет использовать существенно большую мощность, чем старая система. Даже если бы машина могла понизить свою тактовую частоту до 1 ГГц, старая система могла бы, например, понизить свою тактовую частоту до 200 МГц.

Возможность объединения таких старых мобильных процессоров с ультрасовременными платформами на базе ARM в сочетании с совместимым планировщиком ядра ОС позволяет поднять вычислительную мощность до максимума, когда требуется включение всех ядер, необходимо выдержать баланс (поддержание части мощных ядер в активном состоянии и части менее мощных для выполнения других задач) или же работать в режиме чрезвычайно низкого энергопотребления (в активном состоянии остается только одно маломощное ядро — этого достаточно для получения SMS и отправки электронной почты). За счет поддержания так называемых разнородных политик планирования Windows 10 позволяет потокам выбирать политику, отвечающую их потребностям, и взаимодействовать с планировщиком и диспетчером питания для ее оптимальной поддержки. Эти политики более подробно рассматриваются в главе 4.

Система Windows изначально не проектировалась с расчетом на ограничение количества процессоров, если не считать политик лицензирования, различающихся в разных выпусках Windows. Однако для удобства и эффективности Windows отслеживает информацию о процессорах (общее количество, занятость и т.д.) в битовой маске (иногда называемой маской сходства), количество бит в которой совпадает с разрядностью встроенного типа данных машины (32-разрядная или 64-разрядная). Это позволяет процессору напрямую выполнять операции с битами в регистрах. По этой причине количество процессоров в системах Windows изначально ограничивалось разрядностью машинного слова, потому что разрядность маски сходства невозможно было увеличивать произвольно. Для обеспечения совместимости, а также для поддержки систем с большим количеством процессоров в Windows реализована конструкция более высокого порядка: группа процессоров (processor group). Группа процессоров представляет собой набор процессоров, которые могут определяться одной маской сходства, а ядро и приложения могут выбирать нужную группу при обновлениях маски сходства. Совместимые приложения могут запрашивать информацию о количестве поддерживаемых групп (в настоящее время ограничивается 20; максимальное количество логических процессоров в настоящее время ограничивается 640), а затем перебрать битовые маски всех групп. В настоящее время старые приложения продолжают работать, видя только текущую группу. За дополнительной информацией о том, как Windows распределяет процессоры по группам (которая также относится к NUMA), обращайтесь к главе 4.

Как упоминалось ранее, фактическое количество поддерживаемых лицензированных процессоров зависит от выпуска Windows (см. табл. 2.2). Это число хранится в файле системной политики лицензирования (который фактически содержит набор пар «имя/значение») %SystemRoot%\ServiceProfiles\LocalService\AppData\Local\Microsoft\WSLicense\tokens.dat в переменной с именем kernel-RegisteredProcessors.

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

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

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

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

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

• Механизмы программирования, облегчающие эффективную реализацию многопоточных серверных процессов, хорошо масштабируемых в многопроцесорных системах, такие как порты завершения ввода/вывода (см. главу 6 «Подсистема ввода/вывода»).

Масштабируемость ядра Windows улучшалась со временем. Например, в Win­dows 2003 появились очереди планирования на уровне отдельных процессоров с высокоточными блокировками, реализующими возможность параллельного принятия решений планировки потоков на нескольких процессорах. В Windows 7 и Windows Server 2008 R2 была исключена глобальная блокировка планировщика во время операций диспетчеризации с ожиданием. Это постепенное повышение точности блокировок также происходило и в других областях: в диспетчере памяти, диспетчере кэша и диспетчере объектов.

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

Windows поставляется как в клиентских, так и в серверных вариантах. Существуют шесть настольных клиентских версий Windows 10: Windows 10 Home, Windows 10 Pro, Windows 10 Education, Windows 10 Pro Education, Windows 10 Enterprise и Windows 10 Enterprise Long Term Servicing Branch (LTSB). К числу других, не-настольных, изданий относятся Windows 10 Mobile, Windows 10 Mobile Enterprise и Windows 10 IoT Core, IoT Core Enterprise и IoT Mobile Enterprise. Существует еще больше разновидностей, предназначенных для регионов с особыми потребностями, например серии N.

Windows Server 2016 существует в шести разных версиях: Windows Server 2016 Datacenter, Windows Server 2016 Standard, Windows Server 2016 Essentials, Windows Server 2016 MultiPoint Premium Server, Windows Storage Server 2016 и Microsoft Hyper-V Server 2016.

Ниже перечислены различия между этими версиями:

• Модель ценообразования по количеству ядер (а не по количеству процессоров) для Server 2016 Datacenter и Standard.

• Общее количество поддерживаемых логических процессоров.

• Для серверных систем — разрешенное для выполнения количество контейнеров Hyper-V (клиентские системы поддерживают только контейнеры Windows на базе пространств имен).

• Объем поддерживаемой физической памяти (фактически наивысший физический адрес ОЗУ, который может использоваться системой; более подробно об ограничениях физической памяти см. в главе 5 «Управление памятью»).

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

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

• Поддержка таких функций, как BitLocker, загрузка VHD, AppLocker, Hyper-V и более 100 других настраиваемых параметров политики лицензирования.

• Многоуровневые службы, поставляемые в выпуски Windows Server, которые не входят в клиентские выпуски (службы каталогов, Host Guardian, Storage Spaces Direct, защищенные виртуальные машины и кластеризация).

В табл. 2.2 перечислены различия в поддержке памяти и процессоров в некоторых выпусках Windows 10, Windows Server 2012 R2 и Windows Server 2016. Подробная сравнительная схема разных выпусков Windows Server 2012 R2 доступна по адресу . Информация об ограничениях работы с памятью в выпусках Windows 10 и Server 2016 и более ранних доступна по адресу .

Таблица 2.2. Ограничения по количеству процессоров и объему памяти в некоторых выпусках Windows

 

Количество поддерживаемых физических процессоров (32-разрядная версия)

Поддерживаемая физическая память (32-разрядная версия)

Количество логических/физических процессоров (64-разрядная версия)

Поддерживаемая физическая память (версии x64)

Windows 10 Home

1

4 Гбайт

1 физический процессор

128 Гбайт

Windows 10 Pro

2

4 Гбайт

2 физических процессора

2 Тбайт

Windows 10 Enterprise

2

4 Гбайт

2 физических процессора

2 Тбайт

Windows Server 2012 R2 Essentials

2 физических процессора

64 Гбайт

Windows Server 2016 Standard

512 логических процессоров

24 Тбайт

Windows Server 2016 Datacenter

512 логических процессоров

24 Тбайт

И хотя ОС Windows существует в нескольких клиентских или серверных вариантах, они совместно используют общий набор базовых системных файлов, включая образ ядра Ntoskrnl.exe (и PAE-версию Ntkrnlpa.exe), библиотеки HAL, драйверы устройств, базовые системные средства и DLL.

При таком количестве разных выпусков Windows, имеющих одинаковый образ ядра, как система узнает, какой выпуск загружается? По значениям параметров реестра ProductType и ProductSuite из раздела HKLM\SYSTEM\CurrentControlSet\Control\ProductOptions. Параметр ProductType указывает, является ли система клиентской или серверной (любого типа). Эти значения загружаются в реестр на основании файла политики лицензирования (см. выше). Допустимые значения перечислены в табл. 2.3. Их можно запросить из пользовательского режима функцией VerifyVersionInfo или из драйвера устройства функциями режима ядра RtlGetVersion и RtlVerifyVersionInfo (обе функции документированы в WDK).

Таблица 2.3. Значения параметра реестра ProductType

Выпуск Windows

Значение ProductType

Клиент

WinNT

Сервер (контроллер домена)

LanmanNT

Сервер (только сервер)

ServerNT

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

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

Эксперимент: определение возможностей, активизируемых политикой лицензирования

Как упоминалось ранее, Windows поддерживает более 100 разных функциональных возможностей, которые могут активизироваться механизмом лицензирования программного продукта. Эти параметры политики определяют различия не только между клиентской и серверной установкой, но и между выпусками ОС: как, например, поддержка BitLocker (доступная и в серверных версиях Windows, и в выпусках Pro и Enterprise клиентской версии Windows). Для вывода многих параметров политики можно воспользоваться программой SlPolicy из загружаемых материалов, прилагаемых к книге.

Параметры политики упорядочиваются по подсистемам, представляющим модуль, к которому применяется политика. Для вывода списка всех подсистем, известных программе, запустите SlPolicy.exe с ключом -f:

C:\>SlPolicy.exe -f

Software License Policy Viewer Version 1.0 (C)2016 by Pavel Yosifovich

Desktop Windows Manager

Explorer

Fax

Kernel

IIS

Укажите имя любой подсистемы после ключа, чтобы вывести данные политики для этой подсистемы. Например, для получения информации об ограничении количества процессоров и доступной памяти используется подсистема Kernel. Примерный вывод программы на машине с Windows 10 Pro выглядит так:

C:\>SlPolicy.exe -f Kernel

Software License Policy Viewer Version 1.0 (C)2016 by Pavel Yosifovich

Kernel

------

Maximum allowed processor sockets: 2

Maximum memory allowed in MB (x86): 4096

Maximum memory allowed in MB (x64): 2097152

Maximum memory allowed in MB (ARM64): 2097152

Maximum physical page in bytes: 4096

Device Family ID: 3

Native VHD boot: Yes

Dynamic Partitioning supported: No

Virtual Dynamic Partitioning supported: No

Memory Mirroring supported: No

Persist defective memory list: No

Или другой пример: вывод для подсистемы kernel в выпуске Windows Server 2012 R2 Datacenter будет выглядеть примерно так:

Kernel

------

Maximum allowed processor sockets: 64

Maximum memory allowed in MB (x86): 4096

Maximum memory allowed in MB (x64): 4194304

Add physical memory allowed: Yes

Add VM physical memory allowed: Yes

Maximum physical page in bytes: 0

Native VHD boot: Yes

Dynamic Partitioning supported: Yes

Virtual Dynamic Partitioning supported: Yes

Memory Mirroring supported: Yes

Persist defective memory list: Yes

Отладочная сборка

Существует специальная внутренняя отладочная версия Windows, называемая отладочной сборкой (внешний доступ к ней открыт только для Windows 8.1 и более ранних версий в составе подписки MSDN Operating Systems). Она представляет собой результат компиляции исходного кода Windows с установленным флагом времени компиляции DBG, с которым включается код условной отладки и трассировки. Кроме того, для простоты понимания машинного кода двоичные файлы Windows не подвергаются последующей обработке для оптимизации структуры кода с целью ускорения выполнения. (См. раздел «Debugging performance-optimi­zed code» в справочном файле средств отладки для Windows.)

Отладочная сборка изначально предоставлялась для содействия разработчикам драйверов устройств, потому что она выполняет более жесткую проверку ошибок при вызове функций режима ядра драйверами устройств или другим системным кодом. Например, если драйвер (или другой фрагмент кода режима ядра) обратится с некорректным вызовом к системной функции, проверяющей параметры (например, при получении спин-блокировки на неверном уровне запроса прерывания), система остановит выполнение при обнаружении проблемы и не допустит порчу структуры данных и последующий возможный фатальный сбой системы. Так как полная отладочная сборка часто была нестабильной и не работала во многих средах, компания Microsoft предоставляет отладочную версию ядра и HAL только для Windows 10 и более поздних версий. Это позволяет разработчикам получить ту же пользу при взаимодействии с кодом ядра и HAL, не сталкиваясь с проблемами полной отладочной сборки. Отладочные версии ядра и HAL свободно распространяются в составе WDK (каталог \Debug корневого пути установки). За подробными инструкциями по поводу того, как с ними работать, обращайтесь к разделу «Installing Just the Checked Operating System and HAL» в документации WDK.

Эксперимент: проверка выполнения отладочной сборки

Не существует встроенных средств, которые выводили бы информацию о том, выполняете вы отладочную или нормальную версию ядра. Однако эту информацию можно получить из свойства Debug класса WMI Win32_OperatingSystem.

Следующий сценарий PowerShell выводит это свойство. (Чтобы воспроизвести этот пример, откройте сервер сценариев PowerShell.)

PS C:\Users\pavely> Get-WmiObject win32_operatingsystem | select debug

debug

-----

False

В этой системе отладочная сборка не выполняется, потому что свойство Debug содержит значение False.

Большая часть дополнительного кода в двоичных файлах отладочных сборок является результатом использования макросов ASSERT и/или NT_ASSERT, определяемых в заголовочном файле WDK Wdm.h и описываемых в документации WDK. Эти макросы проверяют условие — например, действительность структуры данных или параметра. Если результат выражения равен FALSE, макрос либо вызывает функцию режима ядра RtlAssert, которая вызывает DbgPrintEx для отправки текста отладочного сообщения в буфер отладочных сообщений, либо выдает прерывание проверки условия (прерывание 0x2B в системах x64 и x86). При наличии присоединенного отладчика ядра и загруженной символической информации сообщение выводится автоматически, а за ним пользователю предлагается выбрать дальнейшее действие (активизировать точку останова, игнорировать, завершить процесс или завершить поток). Если система не была загружена с отладчиком ядра (при помощи параметра debug в базе данных конфигурации загрузки) и отладчик ядра не присоединен, нарушенная проверка условия приведет к фатальному сбою системы. Небольшой список проверок, выполняемых некоторыми вспомогательными функциями ядра, приведен в разделе «Checked Build ASSERTs» документации WDK (учтите, что этот список не поддерживается и содержит устаревшую информацию).

Отладочная сборка также полезна для системного администратора из-за дополнительной подробной трассировки, которую можно включить для некоторых компонентов. (За подробными инструкциями обращайтесь к статье Microsoft Knowledge Base Article 314743 «HOWTO: Enable Verbose Debug Tracing in Various Drivers and Subsystems».) Выводимая информация передается во внутренний отладочный буфер сообщений с использованием функции DbgPrintEx, упоминавшейся ранее. Чтобы увидеть отладочные сообщения, присоедините отладчик ядра к целевой системе (для чего необходимо загрузить целевую систему в отладочном режиме), используйте команду !dbgprint во время выполнения локальной отладки ядра или воспользуйтесь программой Dbgview.exe из пакета Sysinternals. Тем не менее многие последние версии Windows отказались от такого типа отладочного вывода и используют трассировку препроцессора Windows (WPP) или технологию TraceLogging; оба способа строятся на базе технологии Event Tracing for Windows (ETW). Преимущество новых механизмов вывода информации заключается в том, что они не ограничиваются отладочными версиями компонентов (что особенно полезно сейчас, когда полная отладочная сборка стала недоступной), а для просмотра информации можно пользоваться такими средствами, как программа WPA (Windows Performance Analyzer), ранее известная под названием XPerf или Windows Perfomance Toolkit, TraceView (из WDK), или команда расширения !wmiprint отладчика ядра.

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

Обзор архитектуры безопасности на основе виртуализации

Как было показано в главе 1 и в этой главе, разделение между пользовательским режимом и режимом ядра обеспечивает защиту ОС от кода пользовательского режима (злонамеренного или нет). Но если нежелательный фрагмент кода режима ядра проникает в систему (из-за неисправленного ядра или уязвимости драйвера или потому, что пользователя обманом убедили установить вредоносный или уязвимый драйвер), безопасность системы фактически нарушена, потому что весь код режима ядра обладает полным доступом ко всей системе. Технологии, описанные в главе 1, использующие гипервизор для предоставления дополнительных гарантий против атак, образуют набор средств безопасности на основе виртуализации (VBS, Virtualization-Based Security), совершенствующих естественную изоляцию на основе привилегий через введение виртуальных уровней безопасности (VTL, Virtual Trust Levels). Кроме простого введения нового ортогонального механизма изоляции доступа к памяти, оборудованию и ресурсам процессора, технология VTL также требует нового кода и компонентов для управления более высокими уровнями доверия. Обычному ядру и драйверам, работающим на уровне VTL 0, не разрешается управление и определение ресурсов VTL 1; это будет противоречить самой цели.

На рис. 2.3 показана архитектура Windows 10 Enterprise и Server 2016 при активной безопасности VBS. (Также иногда используется термин Virtual Secure Mode, или VSM.) В релизах Windows 10 версии 1607 и Server 2016 он всегда активен по умолчанию, если поддерживается оборудованием. В более старых версиях Windows 10 его можно активизировать при помощи политики или в диалоговом окне Add Windows Features (выберите режим Isolated User Mode).

Как показано на рис. 2.3, код пользовательского режима/режима ядра, рассмот­ренный ранее, работает на базе гипервизора Hyper-V, как и на рис. 2.1. Различие состоит в том, что с включенным механизмом VBS появляется уровень VTL 1, который содержит свое собственное безопасное ядро, работающее в привилегированном режиме процессора (кольцо 0 в x86/x64). Аналогичным образом появился пользовательский режим времени выполнения, называемый IUM (Isolated User Mode), который работает в непривилегированном режиме (кольцо 3).

346225.png 

Рис. 2.3. Архитектура VBS в Windows 10 и Server 2016

В этой архитектуре безопасное ядро представлено отдельным двоичным файлом, который хранится на диске под именем securekernel.exe. Что касается IUM, это и среда, которая ограничивает системные вызовы, разрешенные для DLL пользовательского режима (определяя тем самым, какие из этих DLL могут быть загружены), и фреймворк, который добавляет специальные вызовы системных функций, выполняемых только в VTL 1. Доступ к этим дополнительным системным вызовам осуществляется по аналогии с обычными системными вызовами: через внутреннюю системную библиотеку с именем Iumdll.dll (версия Ntdll.dll для VTL 1) и библиотеку, обращенную к подсистеме Windows, с именем Iumbase.dll (версия Kernelbase.dll для VTL 1). Реализация IUM, в основном использующая те же стандартные библиотеки Win32 API, обеспечивает сокращение затрат памяти приложений пользовательского режима VTL 1, потому что в них, по сути, задействован тот же код пользовательского режима, что и в их аналогах VTL 0. Важное замечание: механизмы копирования при записи, о которых вы подробнее узнаете в главе 5, не позволяют приложениям VTL 0 модифицировать двоичный код, используемый VTL 1.

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

Происходящее можно рассматривать так: уровни привилегий (пользовательский режим или режим ядра) обеспечивают полномочия. VTL, с другой стороны, обеспечивают изоляцию. Хотя приложение пользовательского режима VTL 1 не обладает большими полномочиями, чем приложение VTL 0 или драйвер, оно от них изолируется. По сути, приложения VTL 1 не просто обладают большими полномочиями; во многих случаях их полномочия намного меньше. Так как безопасное ядро не реализует весь диапазон системных возможностей, оно специально отбирает, какие системные вызовы будут перенаправляться ядру VTL 0. Любые разновидности ввода/вывода, включая работу с файлами, сетью и реестром, полностью запрещены. Графика также полностью исключается. Взаимодействия с любыми драйверами запрещены.

С другой стороны, безопасное ядро, работающее на уровне VTL 1 и в режиме ядра, обладает полным доступом к ресурсам и памяти VTL 0. Оно может использовать гипервизор для ограничения доступа ОС VTL 0 к некоторым участкам памяти за счет использования аппаратной поддержки процессора, называемой SLAT (Second Level Address Translation). SLAT лежит в основе технологии Credential Guard, которая позволяет хранить секретную информацию в этих ячейках.

Аналогичным образом безопасное ядро может использовать технологию SLAT для управления выполнением определенных участков памяти — ключевой аспект Device Guard.

Чтобы обычные драйверы устройства не могли использовать оборудование для прямых обращений к памяти, система задействует другой аппаратный компонент — блок управления памятью (I/O MMU, Input/Output Memory Management Unit), который фактически виртуализирует доступ к памяти для устройств. Он предотвращает прямые обращения к участкам физической памяти гипервизора или безопасного ядра средствами DMA (Direct Memory Access). Такие обращения работали бы в обход SLAT, потому что виртуальная память в них не задействована.

Поскольку гипервизор является первым системным компонентом, который запускается начальным загрузчиком, он может программировать SLAT и I/O MMU так, как считает нужным, определяя исполнительные среды VTL 0 и VTL 1. Затем, находясь в VTL 1, загрузчик выполняется снова и загружает безопасное ядро, которое может продолжить настройку системы по своим потребностям. Только после этого VTL снижается, и начинает работать нормальное ядро; теперь оно существует в своей «тюрьме» VTL 0, не имея возможности выйти за ее пределы.

Так как процессы пользовательского режима в VTL 1 изолированы, потенциально вредоносный код, хотя и бессилен оказать влияние на систему, может работать скрытно, пытаться вызывать защищенные системные функции (что позволит ему подписывать собственные секреты) и теоретически создавать нежелательные взаимодействия с другими процессами VTL 1 или ядром. По этой причине в VTL 1 может выполняться только особый класс специально подписанных двоичных трастлетов (trustlets). Каждый трастлет имеет уникальный идентификатор и сигнатуру, а в безопасном ядре жестко закодирована информация о трастлетах, созданных до настоящего момента. Соответственно, невозможно создавать новые трастлеты без обращения к безопасному ядру (которое доступно только для Microsoft), а существующие трастлеты модифицироваться не могут (тогда специальная электронная подпись Microsoft стала бы недействительной). Подробнее о трастлетах см. в главе 3.

Введение безопасного ядра и VBS — интересный шаг в архитектуре современных ОС. Дополнительные аппаратные модификации шин (таких, как PCI и USB) делают возможной поддержку целого класса безопасных устройств, которые в сочетании с минималистичным безопасным HAL, безопасным диспетчером Plug-and-Play и безопасной инфраструктурой User-Mode Device Framework предоставят некоторым приложениям VTL 1 прямой и изолированный доступ к специальным устройствам (например, устройствам ввода биометрических данных или смарт-карт). Скорее всего, эти возможности будут использоваться в новых версиях Windows 10.

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

После знакомства с высокоуровневой архитектурой Windows можно переходить к более глубокому изучению внутреннего строения и роли каждого из ключевых компонентов ОС. На рис. 2.4 представлена более подробная и полная диаграмма базовой архитектуры системы Windows и компонентов, изображенных на рис. 2.1. Как и в предыдущем случае, на диаграмме представлены не все компоненты (особенно сетевая поддержка, которая подробно рассматривается в главе 10 «Сеть», часть 2).

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

В главе 11 «Запуск и завершение работы» описывается процесс запуска и завершения работы Windows, а в главе 9 освещаются такие управляющие механизмы, как реестр, процессы служб и WMI. В других главах еще подробнее исследуется внутренняя структура и принципы работы ключевых областей: процессов и потоков, управления памятью, безопасности, диспетчера ввода/вывода, управления дисковым пространством, диспетчера кэша, файловой системы Windows (NTFS) и сети.

386608.png 

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

Подсистемы среды и DLL среды

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

Каждый исполняемый образ (.exe) связывается с одной и только одной подсистемой. При выполнении образа код создания процесса анализирует код типа подсистемы в заголовке образа, чтобы оповестить правильную подсистему о новом процессе. Код типа задается параметром компоновщика Microsoft Visual Studio /SUBSYSTEM (или при помощи записи SubSystem в странице свойств Linker/System свойств проекта).

Как упоминалось ранее, пользовательские приложения не вызывают системные функции Windows напрямую. Вместо этого они проходят через одну или несколько DLL подсистем. Эти библиотеки экспортируют документированный интерфейс, который может вызываться программами, скомпонованными с этой подсистемой. Например, DLL-библиотеки подсистемы Windows (такие, как Kernel32.dll, Advapi32.dll, User32.dll и Gdi32.dll) реализуют функции Windows API. DLL-библиотека подсистемы SUA (Psxdll.dll) используется для реализации функций SUA API (в версиях Windows с поддержкой POSIX).

Эксперимент: просмотр типа подсистемы образа

Для просмотра типа подсистемы образа можно воспользоваться программой Dependency Walker (Depends.exe). Например, обратите внимание на типы двух разных образов Windows, Notepad.exe (простой текстовый редактор) и Cmd.exe (командная строка Windows):

2-62.tif 

2-63.tif 

Как видите, Блокнот (Notepad) является программой с графическим интерфейсом, а Cmd — консольной (текстовой) программой. Может показаться, что для графических и текстовых программ используются две разные подсистемы, но в действительности подсистема всего одна, а графические программы могут создавать консоли (вызовом функции AllocConsole) подобно тому, как консольные программы могут отображать графический интерфейс.

Когда приложение вызывает функцию из DLL подсистемы, возможны три варианта:

• Функция полностью реализована в пользовательском режиме в DLL подсистемы. Другими словами, процессу подсистемы среды сообщение не посылается, и сервисные функции исполнительной системы Windows не вызываются. Функция выполняется в пользовательском режиме, а результаты возвращаются вызывающей стороне. К числу таких функций относятся GetCurrentProcess (всегда возвращает –1 — значение, которым обозначается текущий процесс во всех функциях, связанных с процессами) и GetCurrentProcessId. (Идентификатор работающего процесса не изменяется, поэтому значение читается из кэша, чтобы обойтись без вызова ядра.)

• Функция требует одного или нескольких вызовов к исполнительной среде Windows. Например, функции Windows ReadFile и WriteFile требуют вызова внутренних (и недокументированных для использования из пользовательского режима) системных функций ввода/вывода Windows NtReadFile и NtWriteFile соответственно.

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

Некоторые функции представляют собой комбинации второго и третьего пункта — как, например, функции Windows CreateProcess и ExitWindowsEx.

Запуск подсистемы

Подсистемы запускаются процессом диспетчера сеансов (Smss.exe). Информация для запуска подсистемы хранится в разделе реестра HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\SubSystems. На рис. 2.5 показаны значения из этого раздела (для Windows 10 Pro).

FIGURE%202-5.tif 

Рис. 2.5. Информация подсистемы в редакторе реестра Windows

В параметре Required перечисляются подсистемы, загружаемые при загрузке системы. Значение состоит из двух строк: Windows и Debug. Параметр Windows определяет файл подсистемы Windows Csrss.exe (Client/Server Runtime Subsystem). Параметр Debug пуст (это значение не используется после Windows XP, но значение остается в реестре для обеспечения совместимости), он ничего не делает.

Параметр Optional описывает необязательные подсистемы; в данном случае он также пуст, потому что подсистема SUA в Windows 10 не поддерживается. Если бы она была доступна, то значение данных Posix указывало бы на другой параметр, ссылающийся на файл Psxss.exe (процесс подсистемы POSIX). Параметр Optional означает «загружаемый по требованию», т.е. в первый раз, когда будет обнаружен образ POSIX. Параметр реестра Kmode содержит имя файла, содержащего часть подсистемы Windows режима ядра — Win32k.sys (см. далее в этом разделе).

А теперь поближе познакомимся с подсистемами среды Windows.

Подсистема Windows

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

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

Подсистема Windows состоит из следующих основных компонентов:

• Для каждого сеанса экземпляр процесса подсистемы среды (Csrss.exe) загружает четыре DLL-библиотеки (Basesrv.dll, Winsrv.dll, Sxssrv.dll и Csrsrv.dll), которые обеспечивают поддержку следующей функциональности:

• Различные служебные задачи, связанные с созданием и удалением процессов и потоков.

• Завершение работы приложений Windows (через API ExitWindowsEx).

• Файл .ini с аналогами различных параметров реестра для обеспечения обратной совместимости.

• Отправка уведомлений ядра (например, от диспетчера Plug-and-Play) приложениям Windows в виде сообщений Windows (WM_DEVICECHANGE).

• Компоненты поддержки процессов 16-разрядной виртуальной машины DOS (VDM) (только 32-разрядная версия Windows).

• Поддержка SxS (Side-by-Side)/Fusion и кэша манифеста.

• Некоторые функции поддержки естественных языков для организации кэширования.

ПРИМЕЧАНИЕ Пожалуй, самый важный момент заключается в том, что код режима ядра, обеспечивающий работу потока низкоуровневого ввода и потока рабочего стола (отвечающий за указатель мыши, ввод с клавиатуры и работу с окном рабочего стола), находится в потоках, работающих внутри Winsrv.dll. Кроме того, экземпляры Csrss.exe, связанные с интерактивными пользовательскими сеансами, содержат пятую DLL-библиотеку, которая называется «каноническим драйвером экрана», или CDD (Canonical Display Driver) (Cdd.dll). CDD отвечает за взаимодействие с поддержкой DirectX в ядре (см. далее) при каждом обновлении вертикальной развертки (VSync) для перерисовки видимого состояния рабочего стола без традиционной поддержки GDI с аппаратным ускорением.

• Драйвер устройства режима ядра (Win32k.sys), который содержит следующие компоненты:

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

• интерфейс графических устройств (GDI, Graphic Device Interface) — биб­лиотека функций для устройств графического вывода; включает функции вывода текста, рисования линий и геометрических фигур, а также манипуляций с графическими объектами;

• обертки для поддержки DirectX, реализованной в другом драйвере ядра (Dxgkrnl.sys).

• Процесс консольного хоста (Conhost.exe), предоставляющий поддержку консольных приложений.

• Диспетчер окон рабочего стола (Desktop Window Manager) (Dwm.exe), позволяющий формировать видимое изображение окна на одной поверхности через CDD и DirectX.

• DLL подсистемы (такие, как Kernel32.dll, Advapi32.dll, User32.dll и Gdi32.dll), преобразующие документированные функции Windows API в соответствующие им вызовы недокументированных (для пользовательского режима) системных функций Ntoskrnl.exe и Win32k.sys.

• Драйверы графических устройств для аппаратно-зависимых драйверов экрана, драйверов принтеров и драйверы для мини-портов видео.

ПРИМЕЧАНИЕ В результате инициативы по рефакторингу архитектуры Windows, которая называлась MinWin, DLL подсистемы обычно строятся из конкретных библиотек, реализующих наборы API-функций, которые затем компонуются в DLL подсистемы и обрабатываются с использованием специальной схемы перенаправления. Подробнее об этой инициативе см. в разделе «Загрузчик образов» главы 3.

Windows 10 и Win32k.sys

Базовые требования к управлению окнами на устройствах с Windows 10 сильно зависят от самого устройства. Например, полноценной настольной системе с Windows необходимы все функции диспетчера окон, такие как окна с изменением размеров, родительские окна, дочерние окна и т.д. Версиям Windows Mobile 10 на телефонах и малых планшетах многие из этих функций не нужны, потому что на переднем плане находится окно, которое нельзя свернуть. Нельзя также изменить его размер и т.д. То же относится к устройствам IoT, у которых экрана может вообще не быть.

По этим причинам функциональность Win32K.sys была распределена между несколькими модулями ядра, так что не все модули необходимы в конкретной системе. Это приводит к значительному сокращению «поверхности атаки» диспетчера окон за счет сокращения сложности кода и исключения многих унаследованных фрагментов. Несколько примеров:

• На телефонах (Windows Mobile 10) Win32k.sys загружает Win32kMin.sys и Win32kBase.sys.

• На полноценных настольных системах Win32k.sys загружает Win32kBase.sys и Win32kFull.sys.

• На некоторых системах IoT Win32k.sys может быть достаточно Win32kBase.sys.

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

GDI предоставляет набор стандартных двумерных функций, которые позволяют приложениям взаимодействовать с графическими устройствами, ничего не зная о самих устройствах. Функции GDI играют роль посредника между приложениями и графическими устройствами, такими как драйверы экрана и драйверы принтеров. GDI интерпретирует запросы приложения на графический вывод и отправляет запросы драйверам графических устройств. Кроме того, он предоставляет стандартный интерфейс для приложений, которые используют различные устройства графического вывода. Этот интерфейс позволяет коду приложения действовать независимо от аппаратных устройств и их драйверов. GDI адаптирует свои приложения для возможностей устройства, часто разделяя запрос на несколько более удобных частей. Например, некоторые устройства понимают прямые команды на рисование эллипса; другие требуют, чтобы уровень GDI интерпретировал команду как серию пикселов, расположенных по некоторым координатам. Подробнее об архитектуре графики и видеодрайвера см. в разделе «Design Guide» главы «Display (Adapters and Monitors)» документации WDK.

Так как значительная часть подсистемы — и особенно функциональность экранного ввода/вывода — работает в режиме ядра, лишь немногие функции Windows в конечном итоге отправляют сообщения процессу подсистемы Windows: это функции создания и завершения процессов и потоков, а также функции назначения букв устройств DOS (как при использовании subst.exe).

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

хост консольного окна

В исходной архитектуре подсистемы Windows процесс подсистемы (Csrss.exe) отвечал за управление консольными окнами, и каждое консольное приложение (такое, как командная строка Cmd.exe) взаимодействовало с Csrss.exe. В Windows 7 для каждого консольного окна в системе использовался отдельный процесс: хост консольного окна (Conhost.exe). (Одно консольное окно может совместно использоваться несколькими консольными приложениями, как, например, при запуске окна командной строки из командной строки. По умолчанию вторая командная строка совместно использует консольное окно первой.) Консольный хост Windows 7 подробно рассмотрен в главе 2 шестого издания этой книги.

В Windows 8 и последующих версиях консольная архитектура снова изменилась. Процесс Conhost.exe остался, но он теперь порождается от консольного процесса (вместо Csrss.exe, как в Windows 7) драйвером консоли (\Windows\System32\Drivers\ConDrv.sys). Этот процесс взаимодействует с Conhost.exe через драйвер консоли (ConDrv.sys), отправляя запросы чтения, записи, управления вводом/выводом и другие типы запросов ввода/вывода. Conhost.exe играет роль сервера, а процесс, использующий консоль, — роль клиента. Это изменение избавляет Csrss.exe от необходимости получать ввод с клавиатуры (как часть потока низкоуровневого ввода), отправлять его через Win32k.sys процессу Conhost.exe, а затем использовать ALPC для отправки его Cmd.exe. Вместо этого приложение командной строки может напрямую получать ввод от драйвера консоли через операции чтения/записи, избегая лишних переключений контекста.

На следующем экране Process Explorer показан дескриптор, который Conhost.exe держит открытым для объекта устройства, предоставляемого ConDrv.sys, с именем \Device\ConDrv. (Подробнее об именах устройств и вводе/выводе см. в главе 6.)

2-67.tif 

Обратите внимание: Conhost.exe является дочерним процессом для консольного процесса (в данном случае Cmd.exe). Создание Conhost инициируется загрузчиком образа для образов подсистемы Console или по требованию, если образ подсистемы GUI вызывает функцию Windows API AllocConsole. (Конечно, GUI и Console в каком-то смысле похожи: и то и другое — разновидности подсистем Windows.) Настоящей «рабочей силой» Conhost.exe является загружаемая DLL-библиотека (\Windows\System32\ConhostV2.dll), которая включает основной код, взаимодействующий с драйвером консоли.

Другие подсистемы

Как упоминалось ранее, система Windows изначально поддерживала подсистемы POSIX и OS/2. Так как эти подсистемы теперь в Windows не включаются, в книге они не рассматриваются. Общая концепция подсистем при этом остается в силе, а система может быть расширена новыми подсистемами, если такая необходимость вдруг возникнет в будущем.

Поставщики Pico и подсистема Windows для Linux

Традиционная модель подсистем расширяема; она, очевидно, обладает достаточной мощностью, если могла поддерживать POSIX и OS/2 в течение десятилетия, но у нее есть два технических недостатка, которые затрудняют широкое применение двоичных файлов, не относящихся к платформе Windows, за исключением нескольких специализированных ситуаций:

• Как упоминалось ранее, так как информация подсистем извлекается из заголовка PE (Portable Executable), для ее перестроения в формате исполняемого файла Windows PE (.exe) требуется исходный код исходного двоичного файла. При этом все системные вызовы и зависимости в стиле POSIX преобразуются в импортированные конструкции библиотеки Psxdll.dll.

• Модель ограничивается функциональностью, предоставляемой подсистемой Win32 (поверх которой она иногда работает) или ядром NT. Следовательно, подсистема инкапсулирует (вместо эмуляции) поведение, необходимое для приложений POSIX. Иногда это приводит к нетривиальным проблемам совместимости.

Наконец, следует заметить, что, как следует из названия, проектировщики подсистемы POSIX/SUA ориентировались на приложения POSIX/UNIX, которые доминировали на рынке серверов десятилетия назад, а не на приложения Linux, распространенные сегодня.

Решение этих проблем требовало нового подхода к построению подсистемы — подхода, который не требовал бы традиционной для пользовательского режима инкапсуляции других системных вызовов и исполнения традиционных образов PE. К счастью, проект Drawbridge от Microsoft Research предоставил идеальную основу для обновленного подхода к созданию подсистем. Результатом стала реализация модели Pico.

В этой концепции была определена концепция поставщика Pico — специального драйвера режима ядра, который получает доступ к специализированным интерфейсам ядра через API PsRegisterPicoProvider. Специализированные интерфейсы обладают двумя основными преимуществами:

• Они позволяют поставщику создавать процессы Pico и потоки с настройкой их контекстов исполнения и сегментов и сохранять данные в соответствующих структурах EPROCESS и ETHREAD (подробнее об этих структурах см. в главах 3 и 4).

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

В Windows 10 версии 1607 присутствует один поставщик Pico: Lxss.sys со своим «напарником» Lxcore.sys. Как подсказывает имя, это компонент подсистемы Windows для Linux (WSL), а эти драйверы образуют интерфейс поставщика Pico для него.

Так как поставщик Pico получает почти все возможные переходы в пользовательский режим и режим ядра и из них (будь то вызовы системных функций или исключения, например), при условии, что он распознает адресное пространство процесса Pico (или процессов), на базе которого он работает, а его код может исполняться в исходном формате, «настоящее» ядро не так уж важно — при условии, что эти переходы обрабатываются абсолютно прозрачно. Поэтому процессы Pico, работающие под управлением поставщика Pico WSL, сильно отличаются от обычных процессов Windows (см. главу 3) — например, у них нет библиотеки Ntdll.dll, которая всегда загружается в обычных процессах. Вместо этого их память содержит такие структуры, как vDSO — специальный образ, встречающийся только в системах Linux/BSD.

Более того, если процессы Linux должны работать прозрачно, они должны выполняться без перекомпиляции в исполняемый формат Windows PE. Так как ядро Windows не знает, как отображать другие типы образов, эти образы не могут запускаться через API CreateProcess процессами Windows и никогда не вызывают такие API сами (потому что понятия не имеют о том, что выполняются в Windows). Такая поддержка совместимости предоставляется совместно поставщиком Pico и диспетчером LXSS, который представляет собой службу пользовательского режима. Первый реализует закрытый интерфейс, который используется им для взаимодействия с диспетчером LXSS. Второй реализует интерфейс на базе COM, который используется для взаимодействия со специальным процессом запуска, в настоящее время известным как Bash.exe, и с управляющим процессом Lxrun.exe. На следующей диаграмме представлен обзор компонентов, образующих WSL.

347541.png 

Реализация поддержки широкого спектра приложений Linux — серьезное дело. Linux использует сотни системных вызовов — почти столько же, сколько само ядро Windows. Хотя поставщик Pico может использовать существующие функциональные возможности Windows (значительная часть которых строилась для поддержки исходной подсистемы POSIX, как, например, поддержка fork()), в некоторых случаях функциональность приходится реализовывать заново. Например, хотя для хранения файлов используется реальная файловая система NTFS (а не EXTFS), поставщик Pico содержит полную реализацию файловой системы Linux VFS (Virtual File System), которая включает поддержку индексных узлов, inotify(), /sys, /dev и других аналогичных пространств имен на базе файловой системы Linux с соответствующим поведением. Кроме того, хотя поставщик Pico может использовать WSK (Windows Sockets for Kernel) для работы с сетью, в него включаются сложные обертки для реального поведения сокетов, чтобы он мог поддерживать сокеты доменов UNIX, сокеты Linux NetLink и стандартные интернет-сокеты.

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

На момент написания книги эта функциональность официально находилась в стадии бета-тестирования с возможностью значительных изменений, поэтому в книге внутреннее строение этой подсистемы не рассматривается. Впрочем, мы еще вернемся к процессам Pico в главе 3. Когда подсистема вырастет за пределы бета-версии, вероятно, вы увидите в MSDN официальную документацию и стабильные API для взаимодействия с процессами Linux из Windows.

Ntdll.dll

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

• Заглушки диспетчеризации для системных функций исполнительной системы Windows.

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

Первая группа функций предоставляет интерфейс для системных функций Windows, которые могут вызываться из пользовательского режима. Существует более 450 таких функций: NtCreateFile, NtSetEvent и т.д. Как упоминалось ранее, многие возможности этих функций доступны через Windows API. (Впрочем, некоторые функции предназначены только для использования конкретными внут­ренними компонентами ОС.)

Для каждой из этих функций Ntdll.dll содержит одноименную точку входа. Код функции содержит команду для конкретной архитектуры, которая инициирует переход в режим ядра для вызова диспетчера системных сервисных функций. (Эта тема более подробно рассматривается в главе 8 части 2.) После проверки параметров этот диспетчер системных функций вызывает системную сервисную функцию режима ядра, которая содержит реальный код в Ntoskrnl.exe. Следующий эксперимент показывает, как выглядят такие функции.

Эксперимент: просмотр кода диспетчера системных сервисных функций

Откройте версию WinDbg, которая соответствует архитектуре вашей системы (например, версию x64 в 64-разрядной Windows). Откройте меню File и выберите команду Open Executable. Перейдите в папку %SystemRoot%\System32 и выберите файл Notepad.exe.

Запускается программа Блокнот, а отладчик прерывается на исходной точке останова. Это происходит на очень ранней стадии жизни процесса, которую можно просмотреть командой k (call stacK). Вы увидите несколько функций, начинающихся с Ldr — признак загрузчика (LoaDeR) образов. Главная функция Notepad еще не выполнена, это означает, что окна Блокнота на экране вы не увидите.

Установите точку останова в NtCreateFile в Ntdll.dll (отладчик игнорирует регистр символов):

bp ntdll!ntcreatefile

Введите команду g (Go) или нажмите F5, чтобы разрешить продолжение выполнения. Отладчик останавливается практически сразу, а вывод на экране выглядит примерно так (x64):

Breakpoint 0 hit

ntdll!NtCreateFile:

00007ffa'9f4e5b10 4c8bd1        mov      r10,rcx

Возможно, имя функции будет отображаться в виде ZwCreateFile. ZwCreateFile и NtCreateFile относятся к одному символическому имени в пользовательском режиме. Теперь введите команду u (Unassembled), чтобы просмотреть несколько ближайших команд:

00007ffa'9f4e5b10 4c8bd1          mov     r10,rcx

00007ffa'9f4e5b13 b855000000      mov     eax,55h

00007ffa'9f4e5b18 f604250803fe7f01 test     byte ptr [SharedUserData+0x308

(00000000'7ffe0308)],1

00007ffa'9f4e5b20 7503            jne ntdll!NtCreateFile+0x15

(00007ffa'9f4e5b25)

00007ffa'9f4e5b22 0f05            syscall

00007ffa'9f4e5b24 c3              ret

00007ffa'9f4e5b25 cd2e            int 2Eh

00007ffa'9f4e5b27 c3              ret

В регистр EAX записывается номер системной сервисной функции (55 в шестнадцатеричной системе в данном случае) для этой ОС (Windows 10 Pro x64). Обратите внимание на команду syscall: это она переводит процессор в режим ядра с передачей управления диспетчеру системных сервисных функций, где значение EAX используется для выбора сервисной функции NtCreateFile. Также обратите внимание на проверку флага (1) со смещением 0x308 в SharedUserData (подробнее об этой структуре см. в главе 4). Если этот флаг установлен, то выполнение идет по другому пути с использованием команды int 2Eh. Если включить функцию Credential Guard VBS, описанную в главе 7, этот флаг будет установлен на вашей машине, потому что гипервизор может реагировать на команду int более эффективно, чем команда syscall, и это поведение более желательно для Credential Guard.

Как упоминалось ранее, в главе 8 части 2 этот механизм описан более подробно (как и поведение syscall и int). А пока попробуйте найти другие системные функции, такие как NtReadFile, NtWriteFile и NtClose.

В разделе «Обзор архитектуры безопасности на основе виртуализации» было показано, что приложения IUM могут использовать другой двоичный модуль, похожий на Ntdll.dll, который называется IumDll.dll. Эта библиотека также содержит системные функции, но с другими индексами. Если в вашей системе включен механизм Credential Guard, вы можете повторить описанный эксперимент: откройте меню File в WinDbg, выберите команду Open Crash Dump и выберите файл IumDll.dll. В следующем примере вывода обратите внимание на то, что у индекса системной функции старший бит установлен, а проверка SharedUserData не производится; для таких системных вызовов (которые называются защищенными системными вызовами) всегда используется команда syscall:

0:000> u iumdll!IumCrypto

iumdll!IumCrypto:

00000001'80001130 4c8bd1          mov     r10,rcx

00000001'80001133 b802000008      mov     eax,8000002h

00000001'80001138 0f05            syscall

00000001'8000113a c3              ret

Ntdll.dll также содержит множество вспомогательных функций, включая загрузчик образов (функции с префиксом Ldr), диспетчер кучи и коммуникационные функции процессов подсистемы Windows (функции с префиксом Csr). Ntdll.dll также включает общие библиотечные функции времени выполнения (функции с префиксом Rtl), поддержку отладки в режиме пользователя (функции с префиксом DbgUi), трассировку событий для Windows (функции с префиксом Etw), диспетчер асинхронных вызовов процедур (APC, Asynchronous Procedure Call) и диспетчер исключений. (APC, как и исключения, кратко рассматривается в главе 6, а более подробно в главе 8 части 2.)

Наконец, в Ntdll.dll находится небольшое подмножество функций времени выполнения C (CRT, C Run-Time), ограниченное функциями, входящими в строковые и стандартные библиотеки (memcpy, strcpy, sprintf и т.д.); эти функции полезны для собственных приложений, описанных в следующем разделе.

Собственные образы

Некоторые образы (исполняемые модули) не принадлежат никакой подсистеме. Другими словами, они не компонуются с DLL подсистем, такими как Kernel32.dll для подсистемы Windows. Вместо этого они компонуются только с Ntdll.dll — «наименьшим общим кратным» для всех подсистем. Так как собственный (native) API, предоставляемый Ntdll.dll, в основном недокументирован, такие образы обычно строятся только компанией Microsoft. Примером служит процесс диспетчера сеансов (Smss.exe, см. далее в этой главе). Smss.exe — первый создаваемый процесс пользовательского режима (он создается непосредственно ядром), поэтому он не может зависеть от подсистемы Windows, так как Csrss.exe (процесс подсистемы Windows) еще не запустился.

Собственно, Smss.exe отвечает за запуск Csrss.exe. Другой пример — утилита Autochk, которая иногда выполняется при запуске системы для проверки дисков. Так как она выполняется на относительно ранней стадии загрузки (Autochk запускается Smss.exe), она не может зависеть от какой-либо подсистемы.

Ниже приведена информация Smss.exe в Dependency Walker, из которой видно, что программа зависит только от Ntdll.dll. Обратите внимание: в качестве типа подсистемы указано значение Native.

2-72.tif 

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

Исполнительная система образует верхний уровень Ntoskrnl.exe. (Ядро — нижний уровень.) Он включает следующие типы функций.

• Функции, экспортируемые и вызываемые из пользовательского режима.Эти функции, называемые системными сервисными функциями, экспортируются через Ntdll.dll (как функция NtCreateFile из предыдущего эксперимента). Большая часть сервисных функций доступна через Windows API или API другой подсистемы среды. Тем не менее некоторые сервисные функции недоступны ни через какие документированные функции подсистем. Примеры — функции ALPC и различные функции получения информации (такие, как NtQueryInformationProcess), специализированные функции (такие, как NtCreatePagingFile) и т.д.

• Функции драйверов устройств, вызываемые через функцию DeviceIoControl.Эта функция предоставляет общий интерфейс из пользовательского режима в режим ядра для вызова функций драйверов устройств, не связанных с чтением или записью. Хорошими примерами служат драйверы, используемые программами Process Explorer и Process Monitor из пакета Sysinternals, а также упоминавшийся ранее драйвер консоли (ConDrv.sys).

• Функции, которые вызываются только из режима ядра — экспортируемые и документируемые в WDK.К их числу относятся различные вспомогательные функции, включая функции диспетчера ввода/вывода (с префиксом Io), общие функции исполнительной системы (Ex) и другие функции, необходимые для разработчиков драйверов устройств.

• Функции, которые вызываются только из режима ядра, но не документированы в WDK. К их числу относятся функции, вызываемые видеодрайвером загрузки (функции с префиксом Inbv).

• Функции, которые определяются как глобальные символические имена, но не экспортируются. К их числу относятся внутренние вспомогательные функции, вызываемые из Ntoskrnl.exe, в числе которых функции с префиксами Iop (внутренние вспомогательные функции диспетчера ввода/вывода) или Mi (внутренние вспомогательные функции управления памятью).

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

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

• Диспетчер конфигурации. Диспетчер конфигурации, рассматриваемый в главе 9 части 2, отвечает за реализацию и управление системным реестром.

• Диспетчер процессов. Диспетчер процессов, рассматриваемый в главах 3 и 4, создает и завершает процессы и потоки. Поддержка процессов и потоков реализована в ядре Windows; исполнительная система добавляет дополнительную семантику и функции к этим низкоуровневым объектам.

• SRM (Security Reference Monitor). Монитор SRM, описанный в главе 7, обес­печивает соблюдение политик безопасности на локальном компьютере. Он реализует защиту ресурсов ОС, обеспечивая безопасность и аудит объектов.

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

• Диспетчер PnP (Plug and Play). Диспетчер PnP, описанный в главе 6, определяет, какие драйверы необходимы для поддержки конкретного устройства, и загружает эти драйверы. Он получает требования к аппаратным ресурсам для каждого устройства и затем на основании требований каждого устройства диспетчер PnP выделяет аппаратные ресурсы: порты ввода/вывода, IRQ, каналы DMA и блоки памяти. Он также отвечает за отправку уведомлений при изменении набора устройств (добавление или удаление устройства) в системе.

• Диспетчер питания. Диспетчер питания (см. главу 6), управление энергопотреб­лением процессора (PPM, Processor Power Management) и инфраструктура управления питанием (PoFx, Power management Framework) координируют события электропитания и генерируют уведомления управления питанием ввода/вывода к драйверам устройств. Когда система свободна, PPM можно настроить для сокращения энергопотребления посредством перевода ЦП в спящий режим. Изменения в энергопотреблении отдельных устройств обрабатываются драйверами устройств, но координируются диспетчером питания и PoFx. Для некоторых классов устройств терминальный диспетчер тайм-аута также управляет тайм-аутом физического вывода в зависимости от использования устройств.

• Функции WMI (Windows Management Instrumentation), WDM (Windows Driver Model). Эти функции, рассматриваемые в главе 9 части 2, разрешают драйверам устройств публиковать информацию быстродействия и конфигурации и получать команды от службы WMI пользовательского режима. Потребители информации WMI могут находиться на локальном компьютере или на удаленном компьютере в сети.

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

• Диспетчер кэша. Диспетчер кэша, рассматриваемый в главе 14 «Диспетчер кэша» части 2, повышает быстродействие файлового ввода/вывода за счет хранения часто используемых дисковых данных в основной памяти для быстрого доступа. Также операции записи на диск откладываются, а обновления некоторое время хранятся в памяти перед отправкой на диск. Как вы вскоре увидите, для этого используется поддержка отображаемых файлов диспетчером памяти.

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

• Диспетчер объектов. Диспетчер объектов создает, управляет и удаляет исполнительные объекты Windows и абстрактные типы данных, которые используются для представления ресурсов ОС, таких как процессы, потоки и различные объекты синхронизации. Диспетчер объектов рассматривается в главе 8 части 2.

• Механизм ALPC (Asynchronous LPC). Механизм ALPC, рассматриваемый в главе 8 части 2, передает сообщения между клиентским и серверным процессом на одном компьютере. Среди прочего, ALPC используется как локальный транспортный механизм удаленного вызова процедур (RPC) — Windows-реализации стандартного механизма коммуникаций между клиентскими и серверными процессами по сети.

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

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

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

• Библиотека отладчика ядра. Обеспечивает возможность отладки ядра из отладчика, поддерживающего KD — портируемый протокол, поддерживаемый на разных видах транспорта, таких как USB, Ethernet и IEEE 1394, и реализуемый отладчиками WinDbg и Kd.exe.

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

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

• Диспетчер ошибок. Диспетчер ошибок предоставляет обходные решения для нестандартных физических устройств.

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

• Трассировка событий Windows (ETW, Event Tracing for Windows). ETW предоставляет вспомогательные функции для общесистемной трассировки событий для режима ядра и пользовательского режима.

• WDI (Windows Diagnostic Infrastructure). WDI обеспечивает интеллектуальную трассировку системной активности на основании диагностических сценариев.

• Вспомогательные функции WHEA (Windows Hardware Error Architecture). Функции предоставляют общую инфраструктуру для сообщений об ошибках.

• Библиотека файловой системы времени выполнения (FSRTL, File-System Runtime Library). Предоставляет общие вспомогательные функции для драйверов файловой системы.

• KSE (Kernel Shim Engine). KSE предоставляет обертки совместимости драйверов и дополнительную поддержку ошибок устройств. Для этого используется база данных и инфраструктура оберток совместимости, описанная в главе 8 части 2.

Ядро

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

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

Объекты ядра

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

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

Один набор объектов ядра — так называемые управляющие объекты (control objects) — устанавливает семантику управления различными функциями ОС. В этот набор входит объект асинхронного вызова процедур (APC), объект отложенного вызова процедур (DPC) и несколько объектов, используемых диспетчером ввода/вывода, например объект прерывания.

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

Исполнительная система использует функции ядра для создания экземпляров объектов ядра, для выполнения операций с ними и для построения более сложных объектов, которые она предоставляет в пользовательском режиме. Объекты более подробно рассматриваются в главе 8 части 2, а процессы и потоки — в главах 3 и 4 соответственно.

KPCR и KPRCB

Для хранения данных, относящихся к конкретному процессору, ядро использует структуру данных, которая называется KPCR (Кernel Processor Control Region). В KPCR хранится основная информация: таблица диспетчеризации прерываний процессора (IDT, Interrupt Dispatch Table), сегмент состояния задачи (TSS, Task State Segment) и глобальная таблица дескрипторов (GDT, Global Descriptor Table). Также она включает состояние контроллера прерываний, которое используется совместно с другими модулями, такими как драйвер ACPI и HAL. Для простоты обращений к KPCR ядро хранит указатель на структуру в регистре fs в 32-разрядных версиях Windows и в регистре gs в системах Windows x64.

KPCR также содержит вложенную структуру данных, называемую блоком управления процессором (KPRCB, Kernel Processor Control Block). В отличие от структуры KPCR, документированной для сторонних драйверов и других внутренних компонентов ядра Windows, KPRCB — закрытая структура, используемая только кодом ядра в Ntoskrnl.exe. Она содержит следующие данные.

• Информация планирования (например, текущий, следующий поток и поток простоя, запланированные для выполнения на процессоре).

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

• Очередь DPC.

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

• Топология процессора и NUMA: информация об узлах, число ядер в пакете, число логических процессоров в ядре и т.д.

• Размеры кэшей.

• Информация учета времени (например, время DPC и прерываний).

KPRCB также содержит всю статистику процессора, в том числе:

• статистику ввода/вывода;

• статистику диспетчера кэша (см. главу 14 части 2);

• статистику DPC;

• статистику диспетчера памяти (см. главу 5).

Наконец, KPRCB иногда используется для хранения структур выравнивания границ кэша для каждого процессора, используемых для оптимизации доступа к памяти, особенно в NUMA-системах. Например, резервные списки невыгружаемой и выгружаемой памяти хранятся в KPRCB.

Эксперимент: просмотр KPCR и KPRCB

Для просмотра содержимого KPCR и KPRCB можно воспользоваться командами отладчика ядра !pcr и !prcb. Если во втором случае не включить флаги, отладчик по умолчанию выводит информацию для процессора 0. Чтобы задать конкретный процессор, добавьте его номер после команды — например, !prcb 2. С другой стороны, первая команда всегда выводит информацию о текущем процессоре, который можно изменить в сеансе удаленной отладки. При локальной отладке для получения адреса KPCR можно воспользоваться расширением !prc с номером процессора и последующей заменой @$pcr этим адресом. Не используйте другие данные, отображаемые командой !pcr. Это расширение считается устаревшим и выводит некорректные данные. Следующий пример показывает, как выглядит вывод команд dt nt!_KPCR @$pcr и !prcb (Windows 10 x64):

lkd> dt nt!_KPCR @$pcr

   +0x000 NtTib            : _NT_TIB

   +0x000 GdtBase          : 0xfffff802'a5f4bfb0 _KGDTENTRY64

   +0x008 TssBase          : 0xfffff802'a5f4a000 _KTSS64

   +0x010 UserRsp          : 0x0000009b'1a47b2b8

   +0x018 Self             : 0xfffff802'a280a000 _KPCR

   +0x020 CurrentPrcb      : 0xfffff802'a280a180 _KPRCB

   +0x028 LockArray        : 0xfffff802'a280a7f0 _KSPIN_LOCK_QUEUE

   +0x030 Used_Self        : 0x0000009b'1a200000 Void

   +0x038 IdtBase          : 0xfffff802'a5f49000 _KIDTENTRY64

   +0x040 Unused           : [2] 0

   +0x050 Irql             : 0 ''

   +0x051 SecondLevelCacheAssociativity : 0x10 ''

   +0x052 ObsoleteNumber   : 0 ''

   +0x053 Fill0            : 0 ''

   +0x054 Unused0          : [3] 0

   +0x060 MajorVersion     : 1

   +0x062 MinorVersion     : 1

   +0x064 StallScaleFactor : 0x8a0

   +0x068 Unused1          : [3] (null)

   +0x080 KernelReserved   : [15] 0

   +0x0bc SecondLevelCacheSize : 0x400000

   +0x0c0 HalReserved      : [16] 0x839b6800

   +0x100 Unused2          : 0

   +0x108 KdVersionBlock   : (null)

   +0x110 Unused3          : (null)

   +0x118 PcrAlign1        : [24] 0

   +0x180 Prcb             : _KPRCB

lkd> !prcb

PRCB for Processor 0 at fffff803c3b23180:

Current IRQL -- 0

Threads-- Current ffffe0020535a800 Next 0000000000000000 Idle fffff803c3b99740

Processor Index 0 Number (0, 0) GroupSetMember 1

Interrupt Count -- 0010d637

Times -- Dpc 000000f4 Interrupt 00000119

         Kernel 0000d952 User 0000425d

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

lkd> dt nt!_KPRCB fffff803c3b23180 MHz

   +0x5f4 MHz : 0x893

lkd> ? 0x893

Evaluate expression: 2195 = 00000000'00000893

На этой машине на момент загрузки процессор работал приблизительно на частоте 2,2 ГГц.

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

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

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

Ядро также содержит небольшой объем кода с интерфейсами для x86, необходимыми для поддержки старых 16-разрядных программ MS-DOS (в 32-разрядных системах). Эти интерфейсы x86 не являются портируемыми в том смысле, что они не могут вызываться на машине, основанной на любой другой архитектуре. Например, этот код, относящийся к x86, поддерживает вызовы для использования виртуального режима 8086, необходимые для эмуляции кода реального режима на старых видеокартах.

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

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

Слой абстрагирования оборудования (HAL)

Как упоминалось в начале этой главы, одной из важнейших целей при проектировании Windows была возможность портирования между разнообразными аппаратными платформами. С появлением OneCore и бесчисленными форм-факторами существующих устройств она становится еще более важной. Слой абстрагирования оборудования, или HAL (Hardware Abstraction Layer), играет ключевую роль в обеспечении этой портируемости. HAL представляет собой загружаемый модуль режима ядра (Hal.dll), предоставляющий низкоуровневый интерфейс к аппаратной платформе, на которой работает Windows. HAL скрывает подробности, зависящие от оборудования: интерфейсы ввода/вывода, контроллеры прерываний и механизмы многопроцессорных коммуникаций — словом, все аппаратно-зависимые функции, привязанные к конкретной архитектуре.

Итак, внутренние компоненты Windows и драйверы устройств, написанные пользователями, не работают напрямую с оборудованием, а для обеспечения портируемости вызывают функции HAL, когда им требуется информация, зависящая от платформы. По этой причине многие функции HAL документированы в WDK. За дополнительной информацией о HAL и использовании драйверов устройств обращайтесь к WDK.

И хотя в стандартный вариант установки Windows для настольной системы включена пара HAL для x86 (табл. 2.4), Windows может во время загрузки выбрать нужную версию HAL. Таким образом, устраняется проблема, существовавшая в предыдущих версиях Windows при попытке загрузить установленный экземпляр Windows в системе другого типа.

Таблица 2.4. Список HAL для x86

Имя файла HAL

Поддерживаемые системы

Halacpi.dll

Компьютеры с поддержкой ACPI (Advanced Configuration and Power Interface, «усовершенствованный интерфейс управления конфигурацией и питанием»). Используется для однопроцессорных машин без поддержки APIC. (Если хотя бы одно условие не выполняется, используется другая версия HAL в следующей строке.)

Halmacpi.dll

Компьютеры с поддержкой APIC (Advanced Programmable Interrupt Controller, «усовершенствованный программируемый контроллер прерываний») и ACPI. Существование APIC подразумевает поддержку SMP

На машинах на базе x64 и ARM существует только один образ HAL, называемый Hal.dll. Это объясняется тем, что все машины x64 имеют одинаковые конфигурации системной платы, потому что процессорам необходима поддержка ACPI и APIC. Следовательно, в поддержке машин без ACPI или стандартным контроллером PIC нет необходимости. Также все системы ARM поддерживают ACPI и используют контроллеры прерываний, как и в случае со стандартным APIC; в этом случае тоже хватает одного образа HAL.

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

Наконец, в мире IoT некоторые стандартные устройства PC (например, контроллер DMA в архитектуре Intel) могут отсутствовать, и даже в системах на базе PC может потребоваться поддержка другого контроллера. Для решения этой проблемы в старых версиях Windows каждый производитель должен был поставлять специализированную версию HAL для каждой возможной комбинации платформ. В наши дни такое решение, приводящее к значительным объемам дублирующегося кода, стало нереальным. Вместо этого Windows поддерживает модули, называемые расширениями HAL, — дополнительные DLL-библиотеки на диске, которые могут загружаться загрузчиком при наличии конкретного оборудования, запрашивающего эти библиотеки (обычно через ACPI и конфигурацию на основе реестра). Скорее всего, ваша настольная система с Windows 10 включает HalExtPL080.dll и HalExtIntcLpioDMA.dll; например, вторая библиотека используется на некоторых платформах Intel с пониженным энергопотреблением.

Создание расширений HAL требует сотрудничества с компанией Microsoft, и такие файлы должны снабжаться цифровой подписью с использованием специального сертификата расширения HAL, доступного только производителям оборудования. Кроме того, они сильно ограничиваются в возможностях использования API и взаимодействия через ограниченный механизм импорта/экспорта, не использующий традиционный механизм образов PE. Например, при попытке проведения с расширением HAL в следующем эксперименте никакие функции не отображаются.

Эксперимент: просмотр Ntoskrnl.exe и зависимостей образа HAL

Чтобы лучше понять отношения между ядром и образами HAL, воспользуйтесь программой Dependency Walker (Depends.exe) для изучения таблиц экспорта и импорта. Чтобы проанализировать образ в Dependency Walker, откройте меню File, выберите команду Open и выберите нужный файл образа.

Пример результата, который может быть получен при просмотре зависимостей Ntoskrnl.exe в этой программе (пока не обращайте внимания на ошибки, выводимые Dependency Walker из-за невозможности разобрать наборы API-функций):

2-80.tif 

Обратите внимание: Ntoskrnl.exe компонуется с библиотекой HAL, которая в свою очередь компонуется с Ntoskrnl.exe. (Они используют функции друг друга.) Ntoskrnl.exe также компонуется со следующими библиотеками:

Pshed.dll. Платформенно-зависимый драйвер ошибок оборудования (PSHED, Platform-Specific Hardware Error Driver) предоставляет абстракцию средств сообщения об ошибках оборудования для используемой платформы. Для этого он скрывает подробности механизмов обработки ошибок платформы от ОС и предоставляет логически целостный интерфейс к ОС Windows.

Bootvid.dll. Загрузочный видеодрайвер (Boot Video Driver) в системах x86 предоставляет поддержку команд VGA, необходимых для вывода загрузочных сообщений и логотипа во время запуска системы.

Kdcom.dll. Коммуникационная библиотека протокола отладчика ядра (KD, Kernel Debugger Protocol).

Ci.dll. Библиотека целостности (подробнее см. в главе 8 части 2).

Msrpc.sys. Драйвер клиента Microsoft RPC (RPC) для режима ядра позволяет ядру (и другим драйверам) взаимодействовать с сервисными функциями пользовательского режима через RPC или же выполнять маршалинг ресурсов в кодировке MES. Например, этот драйвер используется ядром для обеспечения маршалинга данных к сервисным функциям PnP пользовательского режима и обратно.

Подробное описание информации, выводимой этой программой, приведено в справочном файле Dependency Walker (Depends.hlp).

Мы предложили не обращать внимания на ошибки, выдаваемые Dependency Walker при обработке наборов API-функций, потому что авторы не обновили программу для правильной обработки этого механизма. Хотя реализация наборов API-функций будет описана в главе 3 в разделе «Загрузчик образов», вы все равно можете использовать вывод Dependency Walker для анализа других потенциальных зависимостей ядра, так как эти наборы API-функций могут указывать на реальные модули. Следует заметить, что при описании наборов API-функций используется понятие контрактов, а не DLL или библиотек. Важно заметить, что любое число таких контрактов (и даже все они) на вашей машине может отсутствовать. Их присутствие зависит от комбинации факторов: выпуска ОС, платформы и производителя оборудования.

Контракт Werkernel. Предоставляет поддержку сообщений об ошибках Windows (WER, Windows Error Reporting) в ядре, например, при оперативном создании дампа ядра.

Контракт Tm. Диспетчер транзакций ядра (KTM), описанный в главе 8 части 2.

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

Контракт Ksr. Обеспечивает горячую перезагрузку ядра (KSR, Kernel Soft Reboot) и сохранение некоторых областей памяти для ее поддержки, особенно на некоторых мобильных и IoT-платформах.

Контракт Ksecurity. Содержит дополнительные политики для процессов контейнеров приложений (т. е. приложений Windows Apps), работающих в пользовательском режиме на некоторых устройствах и выпусках Windows.

Контракт Ksigningpolicy. Содержит дополнительные политики для целостности кода пользовательского режима (UMCI, User-Mode Code Integrity) либо для поддержки процессов, не являющихся процессами контейнеров приложений в некоторых выпусках, либо для дополнительной настройки Device Guard и/или средств безопасности App Locker на некоторых платформах/выпусках Windows.

Контроллер Ucode. Библиотека обновления микрокода для платформ, которые могут поддерживать обновления микрокода процессоров (таких, как Intel и AMD).

Контроллер Clfs. Драйвер CLFS (Common Log File System), используемый (среди прочего) транзакционным реестром (TxR, Transactional Registry). Подробнее о TxR читайте в главе 8 части 2.

Контракт Ium. Дополнительные политики для трастлетов IUM, работающих в системе, которые могут понадобиться в некоторых выпусках Windows: например, для реализации защищенных виртуальных машин в Datacenter Server. Трастлеты рассматриваются в главе 3.

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

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

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

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

• В контексте пользовательского потока, инициирующего функцию ввода/вывода (такие, как операция чтения).

• В контексте системного потока режима ядра (такие, как запросы от диспетчера PnP).

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

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

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

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

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

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

• Сетевые перенаправители и серверы. Драйверы файловой системы, передающие запросы ввода/вывода файловой системы на машину в сети и получающие такие запросы соответственно.

• Драйверы протоколов. Драйверы реализуют сетевые протоколы, такие как TCP/IP, NetBEUI и IPS/SPX.

• Потоковые драйверы-фильтры ядра. Эти драйверы объединяются в цепочки для цифровой обработки потоков данных (например, записи или воспроизведения аудио и видео).

• Программные драйверы. Модули ядра, выполняющие операции, которые могут быть выполнены только в режиме ядра, по поручению некоторого процесса пользовательского режима. Многие программы из пакета Sysinternals — такие, как Process Explorer и Process Monitor, — используют драйверы для получения информации или выполнения операций, которые невозможно выполнить из API пользовательского режима.

Модель драйвера Windows

Исходная модель драйвера, созданная в первой версии NT (3.1), не поддерживала концепцию PnP (Plug and Play), потому что тогда эта технология еще не была доступна. Ситуация изменилась только после выхода Windows 2000 (и Windows 95/98 на потребительской стороне Windows).

В Windows 2000 добавилась поддержка PnP, Power Options, а также расширение модели драйвера Windows NT, называемое WDM (Windows Driver Model). Унаследованные драйверы Windows NT 4 могут работать в Windows 2000 и выше, но из-за отсутствия поддержки PnP и Power Options системы с такими драйверами будут обладать усеченной функциональностью в этих двух областях.

Изначально модель WDM предоставляла единую модель драйвера, которая была (почти) совместима на уровне исходного кода между Windows 2000/XP и Windows 98/ME. Это было сделано для того, чтобы упростить написание драйверов для физических устройств, поскольку вместо двух кодовых баз хватало одной. Модель WDM имитировалась в Windows 98/ME.

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

С точки зрения WDM драйверы делятся на три типа.

• Драйверы шин. Драйвер шины обслуживает контроллер шины, адаптер, мост или любое другое устройство, у которого имеются дочерние устройства. Драйверы шины являются обязательными, обычно их предоставляет компания Microsoft. У каждой разновидности типа шины в системе (например, PCI, PCMCIA и USB) имеется один драйвер шины. Третьи стороны могут писать драйверы шин, чтобы обеспечить поддержку новых шин, таких как VMEbus, Multibus и Futurebus.

• Функциональные драйверы. Функциональные драйверы — основная категория драйверов устройств, предоставляющих рабочий интерфейс к своему устройству. Этот драйвер необходим, если только устройство не используется в низкоуровневой реализации, при которой ввод/вывод осуществляется драйвером шины и драйверами-фильтрами шин (например, SCSI PassThru). Функциональный драйвер по определению располагает полной информацией о конкретном устройстве; обычно это единственный драйвер, который работает с регистрами устройства.

• Драйверы-фильтры. Драйвер-фильтр используется или для расширения функцио­нальности устройства существующего драйвера, или для модификации запросов или ответов ввода/вывода от других драйверов. Драйверы-фильтры часто используются для исправления ответов оборудования, возвращающего некорректную информацию о требованиях к аппаратным ресурсам. Драйверы-фильтры являются необязательными; они могут существовать в любом количестве выше или ниже функционального драйвера и выше драйвера шины. Обычно драйверы-фильтры поставляются изготовителями комплектного оборудования (OEM, Original Equipment Manufacturer) или независимыми производителями оборудования (IHV, Independent Hardware Vendor).

В среде драйверов WDM ни один драйвер не управляет всеми аспектами работы устройства. Драйвер шины обеспечивает передачу информации устройств диспетчеру PnP, тогда как функциональный драйвер непосредственно работает с устройством.

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

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

Обработка прерываний рассматривается в главе 8 части 2, а в узком контексте драйверов устройств — в главе 6. Подробнее о диспетчере ввода/вывода, WDM, Plug and Play и управлении питанием также см. в главе 6.

Windows Driver Foundation

WDF (Windows Driver Foundation) упрощает разработку драйверов Windows, предоставляя в распоряжение разработчика два фреймворка: KDMF (Kernel-Mode Driver Framework) и UMDF (User-Mode Driver Framework). Разработчики используют KMDF для написания драйверов для Windows 2000 SP4 и выше, тогда как UMDF поддерживает Windows XP и более поздние версии.

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

UMDF дает возможность реализовать некоторые классы драйверов: прежде всего шины на базе USB и других протоколов с высокой задержкой, используемых в видеокамерах, MP3-проигрывателях, сотовых телефонах и принтерах, — в виде драйверов пользовательского режима. С UMDF каждый драйвер пользовательского режима выполняется фактически в виде службы пользовательского режима; ALPC используется для взаимодействия с драйвером-оберткой пользовательского режима, который предоставляет непосредственный доступ к оборудованию. Если в драйвере UMDF происходит сбой, процесс завершается и обычно перезапускается. Таким образом, система не становится нестабильной; устройство просто остается недоступным на время перезапуска службы-хоста.

UMDF существует в двух основных версиях: версия 1.x доступна для всех версий ОС с поддержкой UMDF; последней является версия 1.11, доступная в Windows 10. Эта версия использует C++ и COM для написания драйверов, что удобно для программистов пользовательского режима, но из-за этого модель UMDF отличается от KMDF. Версия 2.0 UMDF, появившаяся в Windows 8.1, базируется на той же объектной модели, что и KMDF, поэтому модели программирования двух фреймворков очень похожи. Наконец, инструментарий WDF был переведен Microsoft на модель с открытым кодом; на момент написания книги он был доступен на GitHub по адресу .

Универсальные драйверы Windows

Начиная с Windows 10, термином «универсальные драйверы Windows» обозначается возможность написания драйверов устройств, использующих API и интерфейсы драйверов устройств (DDI, Device Driver Interface), предоставляемые общим ядром Windows 10. Эти драйверы обладают двоичной совместимостью для конкретной архитектуры процессора (x86, x64, ARM) и могут использоваться в неизменном виде для разных форм-факторов, от устройств IoT до телефонов, от HoloLens и Xbox One до портативных и настольных компьютеров. Универсальные драйверы могут использовать в качестве своей модели драйвера KMDF, UNDF 2.x или WDM.

Эксперимент: просмотр установленных драйверов устройств

Чтобы получить список установленных драйверов, запустите программу Сведения о системе (System Information) (Msinfo32.exe). Чтобы запустить программу, щелкните на кнопке Пуск (Start) и введите имя программы Msinfo32. В списке Сведения о системе раскройте узел Программная среда (Software Enviroment) и откройте раздел Системные драйверы (System Drivers). Пример вывода списка установленных драйверов:

2-85.tif 

В окне выводится список драйверов устройств, определенных в реестре, с указанием типа и состояния (работает/остановлен). Драйверы устройств и процессы служб Windows определяются в одном месте: HKLM\SYSTEM\CurrentControlSet\Services. При этом они различаются по коду типа. Например, тип 1 обозначает драйвер устройства режима ядра. За полным списком информации, хранимой в реестре для драйверов устройств, обращайтесь к главе 9 части 2.

Также для просмотра текущего списка загруженных драйверов можно выбрать процесс System в программе Process Explorer и открыть режим представления DLL. Ниже приведен пример вывода. (Чтобы открыть дополнительные столбцы, щелкните правой кнопкой мыши на заголовке столбца и выберите команду Select Columns, чтобы просмотреть все возможные столбцы для модулей на вкладке DLL.)

2-86.tif 

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

Просмотр имен экспортируемых или глобальных символических имен в ключевых системных образах (таких, как Ntoskrnl.exe, Hal.dll или Ntdll.dll) может быть весьма поучительным: вы получите представление о том, на что способна система Windows, по сравнению с тем, что официально документировано и поддерживается сегодня. Конечно, то, что вы знаете имена этих функций, еще не значит, что вы можете вызывать их (или что вам стоит это делать) — интерфейсы не документированы, а значит, подвержены изменениям. Мы рекомендуем взглянуть на эти функции просто для  того, чтобы получить более полное представление о внутренней работе Windows, а не для того, чтобы работать в обход поддерживаемых интерфейсов.

Например, при просмотре списка функций в Ntdll.dll вы получите список всех системных сервисных функций, предоставляемых системой Windows DLL-библиотекам подсистем пользовательского режима, и сможете сравнить их с подмножеством, предоставляемым каждой подсистемой. Хотя многие из этих функций однозначно соответствуют документированным и поддерживаемым функциям Windows, некоторые из них недоступны через Windows API.

И наоборот, будет интересно изучить импортируемые функции DLL подсистем Windows (такие, как Kernel32.dll или Advapi32.dll) и функции, вызываемые ими в Ntdll.dll.

Также заслуживает внимания образ Ntoskrnl.exe. Хотя многие из экспортируемых функций, используемых драйверами устройств режима ядра, документированы в WDK, есть достаточно много недокументированных. Возможно, вам будет интересно просмотреть таблицу импортирования Ntoskrnl.exe и HAL; в этой таблице перечислены функции HAL, используемые Ntoskrnl.exe, и наоборот.

В табл. 2.5 приведены наиболее распространенные префиксы имен функций для исполнительных компонентов. Каждый из основных исполнительных компонентов также использует модификацию префикса для обозначения внутренних функций — либо за первой буквой префикса следует буква i (от «internal», т.е. «внутренний»), либо за всем префиксом следует буква p (от «private», т.е. «закрытый»). Например, префикс Ki представляет внутренние функции ядра, а префикс Psp — внутренние вспомогательные функции процессов.

Таблица 2.5. Самые распространенные префиксы

Префикс

Компонент

Alpc

Расширенные локальные вызовы процедур

Cc

Общий кэш

Cm

Диспетчер конфигурации

Dbg

Поддержка отладки ядра

Dbgk

Отладочная инфраструктура для пользовательского режима

Em

Диспетчер ошибок

Etw

Трассировка событий для Windows

Ex

Исполнительные вспомогательные функции

FsRtl

Библиотека файловой системы времени выполнения

Hv

Библиотека Hive

Hvl

Библиотека гипервизора

Io

Диспетчер ввода/вывода

Kd

Отладчик ядра

Ke

Ядро

Kse

Оболочка совместимости ядра

Lsa

Локальная система безопасности

Mm

Диспетчер памяти

Nt

Системные сервисные функции NT (доступны из пользовательского режима через системные вызовы)

Ob

Диспетчер объектов

Pf

Система предварительной выборки

Po

Диспетчер питания

PoFx

Инфраструктура управления питанием

Pp

Диспетчер PnP

Ppm

Диспетчер управления питанием процессора

Ps

Поддержка процессов

Rtl

Библиотека времени выполнения

Se

SRM

Sm

Диспетчер хранилища

Tm

Диспетчер транзакций

Ttm

Диспетчер тайм-аута терминала

Vf

Проверка драйверов

Vsl

Библиотека виртуального безопасного режима

Wdi

Диагностическая инфраструктура Windows

Wfp

Windows Fingerprint

Whea

Windows Hardware Error Architecture

Wmi

Windows Management Instrumentation

Zw

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

Имена этих экспортируемых функций проще расшифровываются, если вы понимаете схему формирования имен системных функций Windows. Общий формат имен выглядит так:

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

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

Например, ExAllocatePoolWithTag — исполнительная вспомогательная функция для выделения памяти из выгружаемого или невыгружаемого пула. А KeInitializeThread — функция для создания и инициализации объекта ядра для потока.

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

Системные процессы, описанные ниже, присутствуют в каждой системе Win­dows 10. Один из них (Idle) процессом вообще не является; три других — System, Secure System и Memory Compression — не являются полноценными процессами, поскольку в них не работает исполняемый файл пользовательского режима. Такие процессы, называемые минимальными, описаны в главе 3.

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

• Процесс System. Содержит большинство системных потоков и дескрипторов режима ядра.

• Процесс Secure System. Содержит адресное пространство безопасного ядра в VTL 1 (если работает).

• Процесс Memory Compression. Содержит сжатый рабочий набор процессов пользовательского режима (см. главу 5).

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

• Подсистема Windows (Csrss.exe).

• Инициализация сеанса 0 (Wininit.exe).

• Процесс входа (Winlogon.exe).

• Диспетчер служб (Services.exe) и создаваемые им дочерние процессы (такие, как системный обобщенный процесс, выполняющий функции хоста служб (Svchost.exe).

• Локальная служба аутентификации (Lsass.exe), и если активен механизм Credential Guard — изолированный локальный сервер аутентификации (Lsaiso.exe).

Чтобы понять, как связаны эти процессы, полезно рассмотреть дерево процессов, т.е. иерархию отношений «родитель—потомок» между процессами. Если вы знаете, каким процессом был создан любой другой процесс, вам будет проще понять, откуда эти процессы взялись. На рис. 2.6 показано дерево процессов, построенное на основе загрузочной трассировки. Чтобы выполнить загрузочную трассировку, откройте меню Options в Process Monitor и выберите команду Enable Boot Logging. Затем перезапустите систему, снова откройте Process Monitor, откройте меню Tools и выберите команду Process Tree (или нажмите Ctrl+T). Благодаря Process Monitor вы видите процессы, которые уже завершились (они обозначены затемненным значком).

В следующих разделах описаны ключевые системные процессы, показанные на рис. 2.6. Хотя в этих разделах кратко указывается порядок запуска процесса, в главе 11 части 2 содержится подробное описание шагов, связанных с загрузкой и запуском Windows.

FIGURE%202-6.tif 

Рис. 2.6. Исходное дерево системных процессов

Процесс простоя

На первом месте в списке на рис. 2.6 стоит процесс Idle. Как будет показано в главе 3, процессы идентифицируются по именам своих образов. Однако этот процесс — как и процессы System, Secure System и Memory Compression processes — не выполняет реальный образ пользовательского режима. Иначе говоря, в каталоге \Windows нет файла с именем System Idle Process.exe. Кроме того, из-за подробностей реализации имя, показанное для этого процесса, отличается от программы к программе. Процесс Idle учитывает время бездействия в системе. По этой причине количество «потоков» в этом «процессе» равно количеству логических процессоров в системе. В табл. 2.6 перечислены некоторые имена, присваиваемые процессу Idle (идентификатор процесса 0). Процесс Idle подробно рассматривается в главе 3.

Таблица 2.6. Имена процесса с идентификатором 0 в разных программах

Программа

Имя процесса с идентификатором 0

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

System Idle process

Process Status (Pstat.exe)

Idle process

Process Explorer (Procexp.exe)

System Idle process

Task List (Tasklist.exe)

System Idle process

Tlist (Tlist.exe)

System process

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

Процесс System и системные потоки

Процесс System (идентификатор процесса 4) объединяет потоки особого типа, выполняемые только в режиме ядра: системные потоки режима ядра. Системные потоки обладают всеми атрибутами и контекстами обычных потоков пользовательского режима (такими, как аппаратный контекст, приоритет и т.д.), но отличаются тем, что они выполняются только в режиме ядра с выполнением кода, загруженного в системном пространстве, будь то в Ntoskrnl.exe или в любом другом загруженном драйвере устройства. Кроме того, системные потоки не имеют адресного пространства пользовательского режима, а следовательно, должны выделять всю динамическую память из куч памяти ОС, таких как выгружаемый или невыгружаемый пул.

ПРИМЕЧАНИЕ В Windows 10 версии 1511 диспетчер задач называет этот процесс System «System and Compressed Memory». Это объясняется появлением в Windows 10 новой функции сжатия памяти для хранения большего объема информации процессов в памяти (вместо выгрузки на диск). Этот механизм более подробно описан в главе 5. Запомните, что термин «процесс System» относится к этому процессу независимо от того, какое имя отображается в той или иной программе. В Windows 10 версии 1607 и Server 2016 процессу было возвращено прежнее имя System. Дело в том, что для сжатия памяти в них используется новый процесс Memory Compression. В главе 5 этот процесс рассматривается более подробно.

Системные потоки создаются функциями PsCreateSystemThread или IoCreateSystemThread (обе функции документированы в WDK). Эти потоки могут вызываться только из режима ядра. Windows, как и различные драйверы устройств, создает системные потоки во время инициализации системы для выполнения операций, требующих контекста потока, например, выдачи запросов и ожидания ввода/вывода или других объектов или опроса состояния устройства. Например, диспетчер памяти использует системные потоки для реализации таких функций, как запись измененных страниц в файл подкачки или отображаемые файлы, выгрузка и загрузка процессов в память и т.д. Ядро создает системный поток, называемый диспетчером набора балансировки (balance set manager); он активизируется один раз в секунду для инициирования различных событий, связанных с планированием и управлением памятью. Диспетчер кэша также использует системные потоки для реализации ввода/вывода с опережающим чтением и отложенной записью. Драйвер устройства файлового сервера (Srv2.sys) использует системные потоки для реакции на сетевые запросы ввода/вывода к файловым данным на дисковых разделах, находящихся в совместном доступе к сети. Даже для драйвера флоппи-дисковода создается системный поток, обеспечивающий опрос устройства. (Опрос в данном случае более эффективен, потому что драйвер флоппи-дисковода на базе прерываний потребляет большое количество системных ресурсов.) Дополнительная информация по конкретным системным потокам приводится в главах с описанием соответствующего компонента.

По умолчанию системные потоки принадлежат процессу System, но драйвер устройства может создать системный поток в любом процессе. Например, драйвер устройства подсистемы Windows (Win32k.sys) создает системный поток в каноническом драйвере экрана (Cdd.dll) — части процесса подсистемы Windows (Csrss.exe), чтобы он мог легко обращаться к данным адресного пространства пользовательского режима этого процесса.

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

Итак, если в процессе System выполняются потоки, сначала определите, какие именно (например, при помощи Системного монитора или программы Process Explorer). После того как вы определите работающий поток (или потоки), выясните, в каком драйвере началось выполнение системного потока. По крайней мере, вы будете знать, какой драйвер с большой вероятностью создал поток. Например, в Process Explorer щелкните правой кнопкой мыши на процессе System и выберите команду Properties. Затем на вкладке Threads щелкните на заголовке столбца CPU, чтобы самый активный поток оказался наверху. Выберите этот поток и щелкните на кнопке Module, чтобы увидеть файл, из которого был запущен код на вершине стека.  Так как процесс System был защищенным в более ранних версиях Windows, Process Exporer не может показать стек вызовов.

Процесс Secure System

Процесс Secure System (переменный идентификатор процесса) формально является «домом» для защищенного адресного пространства ядра VTL 1, дескрипторов и системных потоков. Несмотря на это, поскольку планирование, управление объектами и управление памятью принадлежат ядру VTL 0, никакие из этих сущностей не будут связаны с этим процессом. Его единственное назначение — предоставить пользователям (например, в таких программах, как диспетчер задач и Process Explorer) наглядный признак того, что механизм VBS в настоящее время активен (показывая тем самым, что хотя бы что-то его использует).

Процесс Memory Compression

Процесс Memory Compression использует свое адресное пространство пользовательского режима для хранения сжатых страниц памяти, которые соответствуют резервной памяти, вытесненной из рабочих наборов некоторых процессов (см. главу 5). В отличие от процесса Secure System, процесс Memory Compression действительно является хостом для нескольких системных потоков, обычно отображаемых с именами SmKmStoreHelperWorker и SmStReadThread. Оба потока принадлежат диспетчеру хранилища, который управляет сжатием памяти.

Кроме того, в отличие от других процессов System в списке, этот процесс действительно хранит свою память в адресном пространстве пользовательского режима. Это означает, что к нему применяется усечение рабочего набора и в средствах мониторинга для него может отображаться значительное использование памяти. Собственно, если вы обратитесь к вкладке Быстродействие (Performance) диспетчера задач, на которой теперь выводится как используемая, так и сжатая память, вы увидите, что размер рабочего набора процесса Memory Compression равен объему сжатой памяти.

Диспетчер сеансов

Диспетчер сеансов (%SystemRoot%\System32\Smss.exe) — первый процесс пользовательского режима, создаваемый в системе. Этот процесс создается системным потоком пользовательского режима, который выполняет последнюю фазу инициализации исполнительной системы и ядра. Он создается в виде процесса PPL (Protected Process Light), о котором рассказано в главе 3.

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

Главный экземпляр Smss.exe выполняет следующие одноразовые операции инициализации.

1. Процесс и его исходный поток помечаются как критические. Если процесс или поток, помеченный как критический, по какой-то причине завершится, в Windows происходит фатальный сбой. Подробнее см. в главе 3.

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

3. Базовый приоритет процесса повышается до 11.

4. Если система поддерживает горячее добавление процессоров, она активизирует автоматическое обновление соответствия (affinity) процессоров. Это означает, что при добавлении новых процессоров новые сеансы будут использовать новые процессоры. Подробнее о динамическом добавлении процессоров см. в главе 4.

5. Пул потоков инициализируется для обработки команд ALPC и других рабочих элементов.

6. Создается порт ALPC с именем \SmApiPort для получения команд.

7. Инициализируется локальная копия NUMA-топологии системы.

8. Создается мьютекс с именем PendingRenameMutex для синхронизации операций переименования файлов.

9. Создается исходный блок окружения процесса и обновляется переменная Safe Mode (при необходимости).

10. На основании значения параметра ProtectionMode в разделе HKLM\SYSTEM\CurrentControlSet\Control\Session Manager создаются дескрипторы безопасности, которые будут использоваться для различных системных ресурсов.

11. На основании значения параметра ObjectDirectories в разделе HKLM\SYSTEM\CurrentControlSet\Control\Session Manager создаются описанные им каталоги диспетчера объектов, такие как \RPC Control и \Windows. Также сохраняются программы, перечисленные в параметрах BootExecute, BootExecuteNoPnpSync и SetupExecute.

12. Путь к программе сохраняется в параметре S0InitialCommand из раздела HKLM\SYSTEM\CurrentControlSet\Control\Session Manager.

13. Читается значение NumberOfInitialSessions из раздела HKLM\SYSTEM\CurrentControlSet\Control\Session Manager; оно игнорируется, если система находится в режиме изготовления (manufacturing mode).

14. Читаются операции переименования файлов, указанные в параметрах PendingFileRenameOperations и PendingFileRenameOperations2 из раздела HKLM\SYSTEM\CurrentControlSet\Control\Session Manager.

15. Читаются значения параметров AllowProtectedRenames, ClearTempFiles, TempFileDirectory и DisableWpbtExecution из раздела HKLM\SYSTEM\CurrentControlSet\Control\Session Manager.

16. Читается список DLL из параметра ExcludeFromKnownDllList, находящегося в разделе HKLM\SYSTEM\CurrentControlSet\Control\Session Manager.

17. Читается информация о файле подкачки, хранящаяся в разделе HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management — в частности, списки из параметров PagingFiles и ExistingPageFiles, а также параметры конфигурации PagefileOnOsVolume и WaitForPagingFiles.

18. Читаются и сохраняются значения, хранящиеся в разделе HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\ DOS Devices.

19. Читается и сохраняется список из параметра KnownDlls, хранящегося в разделе HKLM\SYSTEM\CurrentControlSet\Control\Session Manager.

20. Создаются общесистемные переменные среды, определенные в параметре HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment.

21. Создается каталог \KnownDlls, а также каталог \KnownDlls32 в 64-разрядных системах с использованием WoW64.

22. В каталоге \Global?? в пространстве имен диспетчера объектов создаются символические ссылки на устройства, определенные в разделе HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\DOS Devices.

23. Создается корневой каталог \Sessions в пространстве имен диспетчера объектов.

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

25. Выполняются программные части списков BootExecute и BootExecuteNoPnpSync, разобранных ранее. (По умолчанию используется программа Autochk.exe, выполняющая проверку диска.)

26. Инициализируется оставшаяся часть реестра (кусты HKLM\Software, SAM и Security).

27. Выполняется двоичный модуль WPBT (Windows Platform Binary Table), зарегистрированный в соответствующей таблице ACPI (если его выполнение не было отключено в реестре). Часто используется производителями средств для борьбы с кражами для принудительного выполнения на очень ранней стадии двоичного модуля Windows, который может связаться с владельцем или организовать выполнение других сервисных функций даже в свежеустановленной системе. Эти процессы должны компоноваться только с Ntdll.dll (т. е. принадлежать «родной» подсистеме).

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

29. Инициализируется информация файла(-ов) подкачки и выделенного файла дампа на основании разделов HKLM\System\CurrentControlSet\Control\Session Manager\Memory Management и HKLM\System\CurrentControlSet\Control\CrashControl.

30. Проверяется совместимость системы с технологией охлаждения памяти, используемой в системах NUMA.

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

32. Создаются дополнительные динамические переменные среды, такие как PROCESSOR_ARCHITECTURE, PROCESSOR_LEVEL, PROCESSOR_IDENTIFIER и PROCESSOR_REVISION, основанные на данных реестра и системной информации, запрашиваемой у ядра.

33. Запускаются программы из списка HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\SetupExecute. Для них используются те же правила, что и для BootExecute на шаге 11.

34. Создается безымянный объект раздела, совместно используемый дочерними процессами (например, Csrss.exe) для обмена информацией с Smss.exe. Дескриптор этого раздела передается дочерним процессам через механизм наследования дескрипторов. Подробнее о наследовании дескрипторов см. в главе 8 части 2.

35. Открываются известные DLL, которые отображаются на постоянные разделы (отображаемые файлы), — кроме тех, которые перечислены как исключения в предшествующих проверках реестра (по умолчанию не указаны).

36. Создается поток для ответов на запросы создания сеансов.

37. Создается экземпляр Smss.exe для инициализации сеанса 0 (неинтерактивный сеанс).

38. Создается экземпляр Smss.exe для инициализации сеанса 1 (интерактивный сеанс) и при соответствующей настройке реестра — дополнительные экземпляры Smss.exe для дополнительных интерактивных сеансов для будущих возможных входов пользователей. Когда экземпляр Smss.exe создает эти экземпляры, он каждый раз запрашивает явное создание нового идентификатора сеанса с передачей флага PROCESS_CREATE_NEW_SESSION функции NtCreateUserProcess. Это эквивалентно вызову внутренней функции диспетчера памяти MiSessionCreate, которая создает необходимые сеансовые структуры данных режима ядра (такие, как объект Session) и задает диапазон виртуальных адресов сеанса, используемый частью режима ядра подсистемы Windows (Win32k.sys) и другими драйверами устройств сеансового пространства. Подробнее см. в главе 5.

После выполнения всех этих действий Smss.exe неограниченно долго ожидает по дескриптору сеанса 0 Csrss.exe. Так как процесс Csrss.exe помечен как критический (а также является защищенным процессом; см. главу 3), при завершении Csrss.exe ожидание никогда не завершится, потому что в системе произойдет фатальный сбой.

Сеансовый стартовый экземпляр Smss.exe выполняет следующие действия:

• создание процесса(-ов) подсистем для сеанса (по умолчанию подсистема Windows Csrss.exe);

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

Наконец, промежуточный процесс Smss.exe завершается, а процессы подсистемы и Winlogon (или Wininit) остаются без родителя.

Процесс инициализации Windows

Процесс Wininit.exe выполняет следующие функции инициализации системы.

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

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

3. Инициализируется поддержка разделения состояний, если она поддерживается выпуском Windows.

4. Создается событие с именем Global\FirstLogonCheck (вы можете наблюдать его в Process Explorer или WinObj в каталоге \BaseNamedObjects); оно используется процессами Winlogon для определения того, какой из процессов Winlogon должен запуститься первым.

5. Создается событие WinlogonLogoff в каталоге диспетчера объектов BaseNamedObjects, предназначенное для использования экземплярами Winlogon. Это событие инициируется в начале операции выхода.

6. Базовый приоритет самого процесса повышается до высокого (13), а приоритет его главного потока — до 15.

7. При отсутствии специальной настройки в параметре NoDebugThread из раздела HKLM\Software\Microsoft\Windows NT\CurrentVersion\Winlogon создается очередь с периодическим таймером, который передает управление процессу пользовательского режима по распоряжению отладчика ядра. Это позволяет удаленному отладчику ядра выполнить присоединение Winlogon с выходом в любые другие приложения пользовательского режима.

8. Имя компьютера сохраняется в переменной среды COMPUTERNAME, после чего происходит обновление и настройка информации, относящейся к TCP/IP, — например, имени домена и имени хоста.

9. Задаются значения переменных среды USERPROFILE, ALLUSERSPROFILE, PUBLIC и ProgramData для профиля по умолчанию.

10. Создается временный каталог %SystemRoot%\Temp (например, C:\Windows\Temp).

11. Организуется загрузка шрифтов и DWM (диспетчера окон рабочего стола), если сеанс 0 является интерактивным, что зависит от выпуска Windows.

12. Создается исходный терминал, который состоит из оконной станции (которой всегда присваивается имя Winsta0) и двух рабочих столов (Winlogon и Default) для процессов, которые должны работать в сеансе 0.

13. Инициализируется ключ шифрования LSA в зависимости от того, хранится ли он локально или должен вводиться в интерактивном режиме. За дополнительной информацией о хранении локальных ключей аутентификации обращайтесь к главе 7.

14. Создается диспетчер служб (SCM или Services.exe). Краткое описание приводится ниже, а более подробная информация — в главе 9 части 2.

15. Запускается служба подсистемы локальной аутентификации (Lsass.exe), и если активен механизм Credential Guard — изолированный трастлет LSA (Lsaiso.exe). Для этого также требуется запрос ключа VBS от UEFI. Подробнее о Lsass.exe и Lsaiso.exe см. в главе 7.

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

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

Диспетчер служб

В этом разделе рассматриваются службы, являющиеся процессами пользовательского режима. Службы похожи на процессы демонов Linux в том отношении, что они также могут настраиваться для автоматического запуска во время загрузки системы без необходимости интерактивного входа. Они также могут запускаться вручную (например, из административной программы Службы, с использованием средства sc.exe или вызовом функции Windows StartService). Как правило, службам не обязательно взаимодействовать с пользователем, выполнившим вход, хотя в некоторых особых условиях это возможно. Кроме того, хотя большинство служб работает от имени специальных учетных записей (SYSTEM или LOCAL SERVICE), другие могут выполняться в одном контексте безопасности с учетными записями пользователей, выполнивших вход. (Подробнее см. в главе 9 части 2.)

Диспетчер служб (SCM, Service Control Manager) — специальный системный процесс, выполняющий образ %SystemRoot%\System32\Services.exe, ответственный за запуск, остановку и взаимодействие с процессами служб. Он также является защищенным процессом, что усложняет возможную фальсификацию. Программы служб в действительности представляют собой обычные образы Windows, которые вызывают специальные функции Windows для взаимодействия с SCM. Эти функции выполняют такие действия, как регистрация успешного запуска службы, реакция на запросы статуса, приостановка или завершение. Службы определяются в разделе реестра HKLM\SYSTEM\CurrentControlSet\Services.

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

Чтобы связать процесс службы со службами, содержащимися в этом процессе, используйте команду tlist/s (из средств отладки Windows) или tasklist/svc (встроенная программа Windows). Учтите, что между процессами служб и работающими службами не всегда существует однозначное соответствие, потому что некоторые службы используют процесс совместно с другими службами. В реестре параметр Type в разделе службы показывает, выполняется ли служба в отдельном процессе или использует процесс вместе с другими службами в образе.

Ряд других компонентов Windows реализуется в формате служб: диспетчер печати, журнал событий, планировщик задач и разные сетевые компоненты. За дополнительной информацией о службах обращайтесь к главе 9 части 2.

Эксперимент: вывод списка установленных служб

Чтобы вывести список установленных служб, откройте панель управления, выберите категорию Администрирование (Administrative tools) и выберите приложение Службы (Services.) Также вместо этого можно щелкнуть на кнопке Пуск (Start) и ввести команду services.msc. Результат выглядит так:

2-97.1.tif 

Чтобы просмотреть подробный список свойств службы, щелкните правой кнопкой мыши на службе и выберите команду Свойства (Properties). Например, свойства службы Windows Update выглядят так:

2-97.2.tif 

Обратите внимание: в поле Исполняемый файл (Path to Executable) указана программа, содержащая службу, и ее командная строка. Помните, что некоторые службы используют процесс совместно с другими службами; однозначное соответствие существует не всегда.

2-98.tif

Эксперимент: просмотр информации о процессах служб

Process Explorer выделяет процессы, которые являются хостами для одной или нескольких служб. (По умолчанию процессы выделяются розовым цветом, но вы можете сменить цвет. Для этого откройте меню Options и выберите команду Configure Colors.) Если сделать двойной щелчок на процессе, являющемся хостом службы, открывается вкладка Services со списком служб внутри процесса, именем раздела реестра с определением службы, отображаемое имя для администратора, текст описания службы (если он есть) и для служб Svchost.exe — путь к DLL-библиотеке, реализующей службу. Например, список служб в одном из процессов Svchost.exe, выполняемых от имени учетной записи System, выглядит так:

Winlogon, LogonUI и Userinit

Процесс входа Windows (%SystemRoot%\System32\Winlogon.exe) обеспечивает вход и выход интерактивных пользователей. Winlogon.exe получает уведомления о запросах на вход, когда пользователь вводит комбинацию клавиш SAS (Secure Attention Sequence). По умолчанию в Windows используется комбинация SAS Ctrl+Alt+Delete. SAS существует для защиты пользователей от программ перехвата паролей, имитирующих процесс входа, потому что комбинация клавиш не может быть перехвачена приложением пользовательского режима.

Аспекты идентификации и аутентификации процесса входа реализуются через DLL-библиотеки, называемые поставщиками учетных данных (credential providers). Стандартные поставщики учетных данных Windows реализуют стандартные интерфейсы аутентификации Windows: пароли и смарт-карты. Windows 10 предоставляет биометрического поставщика учетных данных: механизм распознавания лиц, известный как Windows Hello. Однако разработчики могут предоставить собственных поставщиков учетных данных для реализации других механизмов идентификации и аутентификации вместо стандартного метода с вводом имени/пароля, например, основанных на анализе голоса или биометрических устройствах (таких, как сканер отпечатка пальца). Так как Winlogon.exe является критическим системным процессом, от которого зависит работа системы, поставщики учетных данных и пользовательский интерфейс для вывода диалогового окна входа выполняются в дочернем процессе Winlogon.exe с именем LogonUI.exe. Когда процесс Winlogon.exe обнаруживает SAS, он запускает процесс, инициализирующий поставщиков учетных данных. Когда пользователь вводит свои учетные данные (в форме, требуемой поставщиком) или закрывает интерфейс входа, процесс LogonUI.exe завершается. Winlogon.exe также может загрузить дополнительные DLL сетевых поставщиков, необходимые для выполнения вторичной аутентификации. Эта возможность позволяет нескольким сетевым поставщикам собрать данные идентификации и аутентификации во время нормального входа.

После того как имя пользователя и пароль (или другой информационный пакет по требованию поставщика учетных данных) будут введены, они передаются процессу локальной службы аутентификации (Lsass.exe, см. главу 7) для проверки. Lsass.exe вызывает соответствующий пакет аутентификации, реализованный в форме DLL, для выполнения фактической проверки, например проверки совпадения пароля с данными, хранящимися в Active Directory или SAM (часть реестра с определением локальных пользователей и групп). Если механизм Credential Guard активен и происходит вход в домен, Lsass.exe связывается с изолированным трастлетом LSA (Lsaiso.exe, см. главу 7) для получения машинного ключа, необходимого для проверки действительности запроса аутентификации.

При успешной аутентификации Lsass.exe вызывает функцию SRM (например, NtCreateToken) для генерирования объекта маркера доступа, содержащего профиль безопасности пользователя. Если в системе используется механизм контроля учетных записей UAC (User Account Control) и входящий пользователь является членом административной группы или обладает привилегиями администратора, Lsass.exe создает вторую, ограниченную версию маркера. Затем маркер доступа используется Winlogon для создания исходного процесса(-ов) в сеансе пользователя. Исходный процесс(-ы) хранится в параметре реестра Userinit раздела реестра HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon. По умолчанию используется Userinit.exe, но образов в списке может быть несколько.

Процесс Userinit.exe выполняет частичную инициализацию окружения пользователя, например запуск сценария входа и восстановление сетевых подключений. Затем он обращается в реестре к параметру Shell (в разделе Winlogon, упоминавшемся выше) и создает процесс для запуска оболочки, определяемой в системе (по умолчанию Explorer.exe). Затем Userinit завершает работу. Вот почему Explorer (Проводник) отображается в списке без родителя — его родитель завершился. Как было объяснено в главе 1, tlist.exe и Process Explorer выравнивают по левому краю процессы, родители которых не выполняются. Также можно сказать, что Explorer является «внуком» Winlogon.exe.

Процесс Winlogon.exe активизируется не только во время входа и выхода, но и каждый раз при перехвате SAS с клавиатуры. Например, при нажатии Ctrl+Alt+Delete при выполненном входе появляется экран безопасности Windows с командами завершения работы, запуска диспетчера задач, блокировки рабочей станции, завершения работы системы и т.д. Winlogon.exe и LogonUI.exe — процессы, обеспечивающие это взаимодействие.

Полное описание этапов процесса входа приведено в главе 11 части 2. Более подробно об аутентификации см. в главе 7. Описания вызываемых функций, взаимодействующих с Lsass.exe (функций с префиксами Lsa), приведены в документации Windows SDK.

Заключение

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

Показать оглавление

Комментариев: 6

Оставить комментарий

  1. JamesCen
    scottsdale health care studiomerliniortodonzia.it/cgi-bin/antibiotici.htm health care communities
  2. Franksog
    home remedy gingivitis studiomerliniortodonzia.it/cgi-bin/antibiotici.htm infant fever remedies
  3. KennethLib
    knee ache remedies studiomerliniortodonzia.it/cgi-bin/testosterone.htm lifetime health care
  4. KennethLib
    knee ache remedies studiomerliniortodonzia.it/cgi-bin/testosterone.htm lifetime health care
  5. KennethLib
    knee ache remedies studiomerliniortodonzia.it/cgi-bin/testosterone.htm lifetime health care
  6. KennethLib
    knee ache remedies studiomerliniortodonzia.it/cgi-bin/testosterone.htm lifetime health care