Добавлен: 29.10.2018
Просмотров: 48023
Скачиваний: 190
956
Глава 11. Изучение конкретных примеров: Windows 8
теты аппаратных прерываний, способы доступа к регистрам устройств ввода-вывода,
управление передачей в режиме DMA, управление таймерами и часами реального
времени, синхронизацию многопроцессорности, работу с прошивкой, например с ACPI
(Advanced Configuration and Power Interface — усовершенствованный интерфейс
управления конфигурированием и энергопотреблением), и т. д. Компания Microsoft
предприняла серьезную попытку сокрытия этих особенностей компьютерных систем
внутри тонкого уровня в самом низу — он называется HAL (как уже упоминалось).
Задача HAL — представить остальной части операционной системы некое абстрактное
оборудование, которое скрывает специфические подробности (версию процессора, чип-
сет и прочие особенности конфигурации). Эти абстракции уровня HAL представлены
в форме независимых от компьютера служб (вызовов процедур и макросов), которые
могут использоваться драйверами и NTOS.
При использовании служб HAL и отсутствии прямых обращений к оборудованию для
драйверов и ядра требуется меньше изменений при переносе на новые процессоры —
и почти во всех случаях они могут работать без всякой модификации на всех систе-
мах с одинаковой архитектурой процессора (несмотря на разные их версии и разные
чипсеты).
HAL не обеспечивает абстракций или служб для конкретных устройств ввода-вывода,
таких как клавиатура, мышь, диски или устройство управления памятью. Эти средства
разбросаны по компонентам режима ядра, и без использования HAL при переносе при-
шлось бы модифицировать большое количество кода (даже при небольших различиях
в оборудовании). Перенос самого HAL прост, поскольку весь машино зависимый код
сконцентрирован в одном месте и цели переноса хорошо определены: реализация
всех служб HAL. Для многих версий компания Microsoft поддерживала набор HAL
Development Kit, который позволял производителям систем создавать собственный
HAL, чтобы прочие компоненты ядра могли работать на новых системах без всякой
модификации (при условии, что изменения в оборудовании не были слишком значи-
тельными).
В качестве примера того, что делает HAL, рассмотрим сравнение ввода-вывода с ото-
бражением в память и портов ввода-вывода. Некоторые компьютеры имеют первый
вариант, а другие — второй. Как же программировать драйвер: использовать ввод-вывод
с отображением в память или нет? Вместо жесткого выбора, который сделает драйвер
непереносимым на компьютер другого типа, уровень HAL предлагает три процедуры
для чтения регистров устройства и еще три — для записи в них:
uc = READ_PORT_UCHAR(port); WRITE_PORT_UCHAR(port, uc);
us = READ_PORT_USHORT(port); WRITE_PORT_USHORT(port, us);
ul = READ_PORT_ULONG(port); WRITE_PORT_LONG(port, ul);
Эти процедуры читают и пишут соответственно беззнаковые целые 8, 16 и 32-битные
числа в указанный порт. Нужен ли здесь ввод-вывод с отображением в память, решать
уровню HAL. Таким образом, драйвер можно без изменения перенести на компьютер,
который отличается способом реализации регистров устройства.
Драйверам часто нужно обращаться к конкретным устройствам ввода-вывода. На
уровне оборудования устройство имеет один или несколько адресов на шине. Совре-
менные компьютеры часто имеют несколько шин (PCI, PCIe, USB, IEEE 1394 и т. д.),
поэтому может случиться так, что один и тот же адрес имеют разные устройства на
разных шинах и нужен способ для их различения. Уровень HAL предоставляет службу
11.3. Структура системы
957
для идентификации устройств (она устанавливает соответствие адресов устройств
на шине логическим адресам системы). Таким образом, драйверам не нужно следить,
к какой шине подключено устройство. Этот механизм также скрывает от более высоких
уровней свойства альтернативных шинных структур и соглашения адресации.
Аналогичная проблема с прерываниями — они также зависят от шины. Здесь HAL
предоставляет службы для именования прерываний по всей системе, а также такие
способы, которые позволяют драйверам прикреплять процедуры обработки к преры-
ваниям, чтобы выполнять их перенос без необходимости знать что-либо о том, какой
вектор прерывания предназначен для данной шины. Управление уровнем запроса
прерываний также производится в HAL.
Еще одна служба HAL — настройка передачи данных в режиме DMA и управление
ею независимым от устройств образом. Можно работать как с системным DMA, так
и с DMA для карт ввода-вывода. Ссылки на устройства делаются по их логическим
адресам. HAL реализует программное распределение/сборку (запись или чтение из
несмежных блоков физической памяти).
HAL также переносимым образом управляет часами и таймерами. Время измеряется
единицами по 100 нс, начиная с 1 января 1601 года, — это первый день предыдущего
четырехсотлетия, что упрощает вычисление високосных лет. (Вопрос на сообрази-
тельность: был ли 1800 год високосным? Ответ: нет.) Службы времени развязывают
драйверы от тех фактических частот, на которых работают системные часы.
Компоненты ядра иногда должны синхронизироваться на очень низком уровне, осо-
бенно для предотвращения состояния гонки в многопроцессорных системах. HAL
обеспечивает примитивы для управления такой синхронизацией (например, спин-
блокировки), когда один процессор просто ждет освобождения ресурса, удерживае-
мого другим процессором, особенно в ситуациях, когда ресурс удерживается всего на
несколько машинных команд.
И наконец, после загрузки системы HAL опрашивает прошивку компьютера (BIOS)
и изучает конфигурацию системы, чтобы определить, какие шины и устройства ввода-
вывода содержит система и как они были сконфигурированы. Эта информация затем
помещается в реестр. На рис. 11.5 дана сводка некоторых вещей, которые делает HAL.
Рис. 11.5. Некоторые из функций оборудования, которыми управляет HAL
958
Глава 11. Изучение конкретных примеров: Windows 8
Уровень ядра
Над уровнем HAL находится NTOS, состоящий из двух уровней: ядра и исполни-
тельной системы
. Термин «ядро» в Windows сбивает с толку. Он может относиться ко
всему коду, который работает в режиме ядра процессора. А может относиться также
к файлу
ntoskrnl.exe
, который содержит NTOS — основную часть операционной си-
стемы Windows. Может также относиться к уровню ядра внутри NTOS (именно так
мы используем этот термин в данном разделе). Он даже используется для именования
библиотеки kernel32.dll (которая обеспечивает оболочки для собственных системных
вызовов) пользовательского режима Win32.
В операционной системе Windows уровень ядра (показан на рис. 11.4 над исполни-
тельным уровнем) предоставляет набор абстракций для управления процессором.
Центральной абстракцией является поток, однако ядро реализует также обработку
исключений, ловушки и несколько видов прерываний. Создание и уничтожение струк-
тур данных (которые поддерживают потоки) реализовано в исполнительном уровне.
Уровень ядра отвечает за планирование и синхронизацию потоков. Поддержка по-
токов на отдельном уровне позволяет исполнительному уровню реализовываться при
помощи той же самой модели многопоточности с вытеснением, которая использована
для написания параллельного кода пользовательского режима (хотя примитивы син-
хронизации в исполнительном уровне узкоспециализированные).
Планировщик потоков ядра отвечает за то, какие потоки выполняются на процессорах
системы. Каждый поток выполняется до тех пор, пока прерывание таймера не сигна-
лизирует о том, что пора переключаться на другой поток (квант закончился), или до
тех пор, когда потоку нужно ждать какого-то события (завершения ввода-вывода или
снятия блокировки), либо до тех пор, пока работоспособным не станет поток с более
высоким приоритетом (которому требуется процессор). При переключении с одного
потока на другой планировщик обеспечивает сохранение регистров и прочего состо-
яния оборудования. Затем планировщик выбирает для выполнения на процессоре
другой поток и восстанавливает ранее сохраненное состояние (для выбранного потока).
Если следующий подлежащий выполнению поток находится в другом адресном про-
странстве (то есть принадлежит другому процессу) — не в том, где находился поток,
с которого произошло переключение, то планировщик должен также изменить адресное
пространство. Подробности алгоритма планировщика мы будем обсуждать далее в этой
главе (когда перейдем к процессам и потокам).
Кроме обеспечения абстракции оборудования и работы с переключениями потоков
уровень ядра имеет еще одну ключевую функцию — предоставление поддержки низ-
кого уровня для двух классов механизмов синхронизации: объектов управления и дис-
петчерских объектов. Объекты управления (control objects) — это структуры данных
для управления процессором, которые уровень ядра предоставляет как абстракции
для исполнительного уровня. Они выделяются исполнительным уровнем, но работают
с ними процедуры, предоставляемые уровнем ядра. Диспетчерские объекты (dispatcher
objects) — это класс обычных объектов исполнительного уровня, который использует
общую структуру данных для синхронизации.
Отложенные вызовы процедур
Объекты управления включают объекты-примитивы для потоков, прерываний, тай-
меров, синхронизации, профилирования, а также два специальных объекта для реа-
11.3. Структура системы
959
лизации DPC и APC. Объекты DPC (Deferred Procedure Call — отложенный вызов
процедуры) используются для уменьшения времени выполнения ISR (Interrupt Service
Routines — процедура обслуживания прерываний), которая запускается по прерыванию
от устройства. Ограничение времени, затрачиваемого на ISR-процедуры, сокращает
шансы утраты прерывания.
Оборудование системы присваивает прерываниям аппаратный уровень приоритета.
Процессор также связывает уровень приоритета с выполняемой им работой. Процес-
сор реагирует только на те прерывания, которые имеют более высокий приоритет, чем
используемый им в данный момент. Нормальный уровень приоритета (в том числе
уровень приоритета всего пользовательского режима) — это 0. Прерывания устройств
происходят с уровнем 3 или более высоким, а ISR для прерывания устройства обычно
выполняется с тем же уровнем приоритета, что и прерывание (чтобы другие менее
важные прерывания не происходили при обработке более важного прерывания).
Если ISR выполняется слишком долго, то обслуживание прерываний более низкого
приоритета будет отложено, что, возможно, приведет к потере данных или замедлит
ввод-вывод системы. В любой момент времени может выполняться несколько ISR, при
этом каждая последующая ISR будет возникать от прерываний со все более высоким
уровнем приоритета.
Для уменьшения времени обработки ISR выполняются только критические операции,
такие как запись результатов операций ввода-вывода и повторная инициализация
устройства. Дальнейшая обработка прерывания откладывается до тех пор, пока уровень
приоритета процессора не снизится и не перестанет блокировать обслуживание других
прерываний. Объект DPC используется для представления подлежащей выполнению
работы, а ISR вызывает уровень ядра для того, чтобы поставить DPC в список DPC
конкретного процессора. Если DPC является первым в списке, то ядро регистрирует
специальный аппаратный запрос на прерывание процессора с уровнем 2 (на котором
NT вызывает уровень DISPATCH). Когда завершается последняя из существующих
ISR, уровень прерывания процессора падает ниже 2, и это разблокирует прерывание
для обработки DPC. ISR для прерывания DPC обработает каждый из объектов DPC
(которые ядро поставило в очередь).
Методика использования программных прерываний для откладывания обработки пре-
рываний является признанным методом уменьшения латентности ISR. UNIX и другие
системы начали использовать отложенную обработку в 1970-х годах (для того, чтобы
справиться с медленным оборудованием и ограниченным размером буферов после-
довательных подключений к терминалам). ISR получала от оборудования символы
и ставила их в очередь. После того как вся обработка прерываний высшего уровня
была закончена, программное прерывание запускало ISR с низким приоритетом для
обработки символов (например, для реализации возврата курсора на одну позицию —
для этого на терминал посылался управляющий символ для стирания последнего
отображенного символа, и курсор перемещался назад).
Аналогичным примером в современной системе Windows может служить клавиатура.
После нажатия клавиши клавиатурная ISR читает из регистра код клавиши, а затем
опять разрешает клавиатурное прерывание, но не делает обработку клавиши немед-
ленно. Вместо этого она использует DPC для постановки обработки кода клавиши
в очередь (до того момента, пока все подлежащие обработке прерывания устройства
не будут отработаны).
960
Глава 11. Изучение конкретных примеров: Windows 8
Поскольку DPC работают на уровне 2, они не мешают выполнению ISR для устройств,
но мешают выполняться любым потокам до тех пор, пока все поставленные в очередь
DPC не завершатся и уровень приоритета процессора не упадет ниже 2. Драйверы
устройств и система не должны слишком долго выполнять ISR или DPC. Поскольку
потокам не разрешается выполняться, то ISR и DPC могут сделать систему медлитель-
ной и породить сбои при проигрывании музыки (затормаживая те потоки, которые
пишут музыку из буфера в звуковое устройство). Другой частый случай использования
DPC — это выполнение процедур по прерыванию таймера. Во избежание блокирования
потоков события таймера (которые должны выполняться продолжительное время)
должны ставить в очередь запросы к пулу рабочих потоков (который ядро поддержи-
вает для фоновой работы).
Асинхронные вызовы процедур
Другой специальный управляющий объект ядра — APC (asynchronous procedure
call — асинхронный вызов процедуры). APC похожи на DPC в том плане, что они от-
кладывают обработку системной процедуры, но в отличие от DPC, которые работают
в контексте конкретного процессора, АРС выполняются в контексте конкретного
потока. При обработке нажатия клавиши не важно, в каком контексте работает DPC,
поскольку DPC — это просто другая часть обработки прерывания, а прерываниям нуж-
но только управлять физическим устройством и выполнять не зависимые от потоков
операции (такие как запись данных в буфер в пространстве ядра).
Процедура DPC работает в контексте того потока, который выполнялся при возникно-
вении исходного прерывания. Он вызывает систему ввода-вывода для сообщения о том,
что операция ввода-вывода завершилась, а система ввода-вывода ставит АРС в очередь
на выполнение в контексте того потока, который сделал первоначальный запрос ввода-
вывода (где он может получить доступ к адресному пространству пользовательского
режима того потока, который будет обрабатывать ввод).
В ближайший же удобный момент времени уровень ядра доставляет АРС потоку
и планирует его выполнение. АРС разработан так, что он выглядит как неожиданный
вызов процедуры, немного похожий на обработчик сигнала в UNIX. АРС режима ядра
для завершения ввода-вывода выполняется в контексте того потока, который иниции-
ровал ввод-вывод (но в режиме ядра). Это дает АРС доступ как к буферу режима ядра,
так и ко всему адресному пространству пользовательского режима, принадлежащему
тому процессу, который содержит данный поток. Время доставки АРС зависит от того,
что делает поток в данный момент (и даже от типа системы). В многопроцессорной
системе получающий АРС поток может начать выполняться даже до завершения вы-
полнения DPC.
АРС пользовательского режима можно также использовать для доставки уведомле-
ний о завершении ввода-вывода в пользовательском режиме тому потоку, который
инициировал ввод-вывод. АРС пользовательского режима вызывает назначенную
приложением процедуру пользовательского режима, но только тогда, когда целевой
поток заблокирован в ядре и помечен как готовый принимать АРС. Ядро прерывает
ожидание потока и делает возврат в пользовательский режим, но уже со стеком поль-
зовательского режима и регистрами, модифицированными для выполнения процедуры
диспетчеризации АРС из системной библиотеки
ntdll.dll
. Процедура диспетчеризации
АРС вызывает процедуру пользовательского режима, которую приложение связало
с операцией ввода-вывода. Помимо указания АРС пользовательского режима как сред-