Что необходимо знать о микроядре L4

Прежде всего необходимо знать историю микроядра L4.

Также необходимо иметь представление о L4 IPC. IPC - это аббревиатура от словосочетания Inter Process Communication. В переводе на русский это означает "Взаимодействие между процессами". Каким же образом осуществляется такое взаимодействие в системах, построенных на базе L4? Это взаимодействие основано на обмене синхронными сообщениями между процессами и программными потоками (нитями).

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

Понятие процесса опирается на понятие нить. Процесс представляет собой одну или несколько нитей, которые исполняются в выделенном адресном пространстве. По сути, процесс L4 весьма схож с процессом системы Unix или процессом системы Windows. Иными словами, исполняемая программа представляет собой процесс.

Не буду загружать тебя объяснением, что есть адресное пространство, поскольку это знание не обязательно для прикладного программирования, а вернусь к описанию IPC микроядра L4.

Вся красота и мощь IPC микроядра L4 заключается в их синхронности. Что означает термин синхронные IPC?
Это означает, что для того чтобы сообщение было передано от одной нити к другой, передающая нить должна находиться в фазе передачи сообщения, а принимающая нить, соответственно, в фазе приёма сообщения. Если не выполняется любое из этих двух условий, то происходит следующее:
1. Если в момент передачи сообщений, приёмник сообщения не находится в фазе приёма, то передающая нить блокируется до готовности приёмника, но не дольше чем на время t.
2. Аналогично, приёмник сообщения блокируется до получения сообщения, но не дольше чем на время t.
В случае таймаута при передаче сообщения, инициатору IPC возвращается соответствующий код ошибки.

Время t, значение которого задаётся программно, варьируется в диапазоне от нуля до бесконечности. При указании нулевого таймаута, IPC не блокируется, но передача сообщения происходит только в случае готовности обеих сторон, т.е. приёмника и передача. В случае же, когда времен равно бесконечности, исполнение нити блокируется до успешной передачи сообщения или до тех пор, пока IPC не будет прервана внешним процессом, имеющем соответствующие права доступа. Следует обратить внимание, что таймаут - экспоненциальная величина, и её точность падает при увеличении таймаута. Впрочем, на при малых значениях таймаута точность находится в пределах 1 микросекунды. Для более подробной информации смотри раздел 3.3 Справочного руководства L4 X.2.

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

И вот, наконец, мы подошли к самому главному. L4 IPC может содержать две фазы - фазу передачи и/или фазу приёма сообщений. Фактически, один системный вызов может использоваться для запроса сообщения и получения ответа. При этом операция передачи сообщения будет атомарной. Вот как выглядит описание системного вызова IPC микроядра L4 на языке С:

MsgTag Ipc (ThreadId to, FromSpecifier, Word Timeouts, ThreadId& from)

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

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

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

Если взаимодействие между процессами не подразумевает фазу передачи сообщения, то
в качестве приёмника сообщения необходимо указать константу L4_nilthread. В этом случае, минуя фазу передачи, IPC сразу войдут в фазу приёма/ожидания сообщения.

Системный вызов IPC возвращает тег сообщения, описывающий сообщение и результат его передачи. Рассмотрим его более внимательно:

При передаче сообщения:

При приёме сообщения:

Поле Label идентифицирует сообщение. На 32-х разрядных архитектурах это поле занимает 16 бит, на 64-х разрядных архитектурах это поле занимает 48 бит.

Бит p используется для перенаправления сообщения. Более подробную информация смотрите в разделе 5.6 Справочного руководства L4 X.2.

Важными параметрами, описывающими сообщения, являются поля t и u. Поле t задаёт количество типизированных виртуальных регистров, которые содержит сообщение. Соотвественно, поле u содержит количество нетипизированных виртуальных регистров.

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

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

Для передачи больших объёмов данных между различными адресными пространствами, микроядро L4 предоставляет следующие возможности:
Строки, Составные Строки, Элементы Отображения Адресного Пространства. При помощи этих примитивов реализуется обмен данными между нитями, выполняемыми в различных адресных пространствах. Замечу, что ничто не мешает использовать эти примитивы для обмена сообщениями между нитями в пределах одного адресного пространства.

Алексей Мандрыкин
22 ноября 2007 года