[Previous] [Next]

Цели разработки

Впервые концепции Windows NT ('New Technology', безусловно, вызывающее название из числа тех еще!) начали приобретать черты реальности в начале 1989 года. Однако хотя дата рождения столь отдаленна, пять фундаментальных NT задач остались неизменными.

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

Уровни аппаратных привилегий в Windows NT 5

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

Пользовательское приложение функционирует в специальном режиме (относительно аппаратного обеспечения), называемом 'user mode' — пользовательский режим. В пределах этого режима, код приложения ограничен выполнением "безвредных" инструкций. Например, через реализацию "таинственного" маппинга (mapping, отображение) виртуальной памяти (страничное представление виртуальной памяти) пользовательский код лишается возможности доступа к виртуальной памяти, предоставленной другим приложениям (за исключением случаев обоюдного согласия, что реализуется специально предназначенными на тот случай методами). Инструкции аппаратного ввода/вывода также не могут быть выполнены кодом пользовательского режима. Целый класс инструкций центрального процессора (называемых привилегированными) запрещен в Windows NT для выполнения кодом пользовательского режима, как, например, команды процессора IN, OUT (результат таких попыток запечатлен на рисунке 1.1). Если вдруг приложению потребуется выполнить что-нибудь из числа таких запрещенных для нее действий, оно должно запросить соответствующую службу операционной системы.

Код самой операционной системы выполняется в так называемом 'kernel mode' — режиме ядра (режиме уровня ядра). Код режима ядра вправе выполнить любую процессорную инструкцию, не исключая инструкций ввода/вывода. Память, принадлежащая любому приложению, может быть доступна коду режима ядра, конечно, если страничная память приложения в данный момент не сброшена на жесткий диск.

Современные процессоры реализуют несколько форм привилегированного режима в отличие от непривилегированного. Код режима ядра выполняется в привилегированном контексте, в то время как пользовательский код выполняется в непривилегированной среде. Так как разные процессоры (и платформы на их основе) реализуют привилегированные режимы по-разному, то, для обеспечения переносимости, разработчики операционной системы ввели особые абстрактные элементы программирования, которые позволяют разграничивать пользовательский режим и режим ядра. Код операционной системы использует их для переключения привилегированного/непривилегированного контекста, следовательно, при перенесении операционной системы только лишь код этих дополнительных элементов необходимо "портировать" (переписывать конкретно под специфику новой аппаратной платформы). На платформе Intel пользовательский режим реализуется из набора инструкций Ring 3 (Кольца 3), в то время как режим ядра реализован с использованием Ring 0 (Кольца 0).

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

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

В качестве способа решения задачи переносимости конструкторы NT выбрали многослойную архитектуру, как показано на рисунке 4.1.

Рис. 4.1
Слои операционной системы Windows NT 5

Слой аппаратных абстракций (Hardware Abstraction Layer, HAL) изолирует процессорные и платформенные особенности от кода операционной системы. Его услугами Microsoft предлагает пользоваться и разработчику драйверного кода. Вполне возможно так написать драйвер, что для перенесения его на другую платформу потребуется разве что перекомпилировать его. Как можно это сделать, если изначально драйвер есть такая программная единица, которая жестко привязана и к своему устройству, и к конкретному процессору, и к конкретной платформе?! Просто драйвер должен обратиться к использованию средств уровня HAL для взаимодействия с аппаратными регистрами и аппаратной поддержкой шин. В отдельных случаях разработчик драйвера может опереться на код, предоставляемый Диспетчером ввода/вывода, для работы с совместно используемыми аппаратными ресурсами. Например, при DMA операциях (прямого доступа к памяти) используется такая абстракция программирования, как объект адаптера

Расширяемость

На рисунке 4.1 обозначена и еще одна важная особенность представленной архитектуры — ядро отделено от слоя, который носит название "исполнительные компоненты" (Executive).

В данном случае, ядро несет ответственность за планировку активности программных потоков (threads). Поток является всего лишь "независимой тропинкой" в выполнении программного кода. Чтобы сохранить независимость от деятельности других потоков, для каждого из них необходимо сохранять уникальный потоковый контекст (thread context). Потоковый контекст состоит из состояния регистров процессора (включая также изолированный стек и счетчик инструкций, Program Counter), сохраненного ID (идентификатора потока, так называемого Thread ID или TID), значения приоритета, распределения памяти, связанной с потоком (Thread Local Storage), и другой информации, имеющей отношение к данному потоку.

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

Так как основной задачей ядра является управление потоками, работа по управлению памятью, вопросами доступа (security) и действиями по вводу/выводу возлагается на другие компоненты операционной системы. Эти компоненты известны под собирательным названием 'Executive', Исполнительные Компоненты. Они сконструированы как модульное программное обеспечение (хотя, Диспетчер ввода/вывода сам является существенным исключением из этого правила).

Идея поддержания ядра как "маленького и чистого", при сохранении модульности исполнительных компонентов, обеспечивает основу заявления Microsoft o сохранении курса NT на расширяемость. По крайней мере, следует признать, что эта операционная система выдержала более десяти лет переработок и регулировок, значительно улучшив свои показатели.

Производительность

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

Во-первых, все слои, обсуждаемые далее, выполняются в одном аппаратном режиме — режиме ядра. Следовательно, межслойные вызовы не используют ничего сложнее, чем инструкция процессора CALL. Средства же, предоставляемые уровнем HAL, в основном, представляет собой макроопределения, являющиеся inline-включаемым кодом.

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

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