Найти в Дзене
IT. Как это работает?

От транзистора до фреймворка. Часть 20. Программное прерывание

Оглавление

Видео: YouTube

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

Задача перемещения данных из пространства ядра в пространство пользователя
Задача перемещения данных из пространства ядра в пространство пользователя

Рассмотрим простую программу на языке . В функции main() выделяется место для хранения строки, далее вызывается функции ввода данных с клавиатуры, после чего происходит вывод на экран.

Цепочка вызовов функций

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

Функция библиотеки языка Си

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

Цепочка вызовов функций
Цепочка вызовов функций

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

Функция программного интерфейса приложений (API)

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

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

Ситуация у нас обычная, пользователь хочет считать данные, поступившие с клавиатуры.

Вызов обработчика прерывания
Вызов обработчика прерывания

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

Перемещение данных
Перемещение данных

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

Слои программного обеспечения

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

Цепочка вызовов функций
Цепочка вызовов функций

Это происходит благодаря смене контекста. Новое содержимое регистров описывает совершенно иной набор страниц памяти со своими атрибутами и правами доступа. Функции ядра в итоге обратятся к драйверам устройств, а это тоже программный код, и это все выполняется в адресном пространстве ядра операционной системы. Таким образом, существует граница, изображенная красным пунктиром, отделяющая друг от друга разные контексты. Выше границы программный код работает в пользовательском режиме с ограниченными привилегиями. Ниже границы программный код работает в режиме ядра с максимальными привилегиями. В каждом слое можно выделить еще слои в виде последовательности вызовов функций, но это уже условности. Из приложения можно обращаться к API функциям напрямую.
Принятая модель программных слоев операционной системы с четырьмя уровнями это отражение аппаратных возможностей регистров архитектуры
x86, в которой под уровень приоритета программы выделено два бита и теоретически ими можно организовать четыре уровня привилегий. От минимального третьего уровня приложений, до максимального нулевого уровня привилегий у ядра.

Уровни привилегий программного кода
Уровни привилегий программного кода

В нашем случае используется всего два уровня привилегий и кодируются они одним битом. Практика показывает, что этого вполне достаточно. Не все встроенные в язык функции обращаются к операционной системе. Обращаются только те, которые имеют в себе функцию из списка функций прикладного интерфейса. Так например, в операционной системе Unix версии 5, созданной еще в 70-х годах, было предусмотрено 34 таких функции.

Фрагмент оригинальной документации по ОС Unix v5
Фрагмент оригинальной документации по ОС Unix v5

Множество функций не нуждаются в работе каких-либо периферийных устройств. Например, функция вычисления квадратного корня числа sqrt().

Слои программного кода
Слои программного кода

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

Как это устроено в Windows?

Напоследок заглянем в наши любимые окна. Компания Майкрософт разрабатывая операционную систему Windows не поддержала существующий уже к тому моменту порядок с небольшим количеством API функций. Существующие к тому моменту другие операционные системы имели постепенно стандартизующийся набор API функций с более менее устоявшимся набором параметров. В Майкрософт разработали свой набор системных вызовов. В интерфейс, названный Win32API было помещено по разным подсчетам до десяти тысяч функций и только часть из них обращается к ядру. Остальные вполне себе выполняются в адресном пространстве пользователя. Получился довольно таки своеобразный интерфейс прикладного уровня. И все его неудобства скрылись только под массой написанных позже библиотек, при помощи которых можно организовать в этом хоть какой-то порядок. В частности, функция чтения данных с клавиатуры, предоставляемая в библиотеке языка Си, конкретно в Windows обращается к API функции ReadFile() c запредельным количеством параметров.

Упрощенная цепочка вызовов функций в ОС Windows
Упрощенная цепочка вызовов функций в ОС Windows

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

Структурная схема ОС Windows NT
Структурная схема ОС Windows NT

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

Продолжение следует...