Модульная структура Embox

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

Файлы описания модулей

Пакеты модулей

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

Пример задания имени пакета:

package embox.arch

Интерфейсы и абстрактные модули

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

Пример задания абстрактного модуля:

package embox.arch

//...

abstract module interrupt { }
//...

Для указания наследования используется ключевое слово extends.

Пример наследования от абстрактного модуля:

    module interrupt_stub extends embox.arch.interrupt {
    //...
    }

Атрибуты модулей

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

Файлы исходного кода

Каждый модуль может указывать список файлов, которые необходимо скомпилировать и включить в итоговый образ в случае включения данного модуля в сборку. Список файлов указывается в атрибуте source модуля. Помимо “обычных” файлов на языке Си или ассемблера, можно также добавлять заголовочные файлы и дополнительные линкер-скрипты. Тип файлов различается по расширению:

  • .c/.S - исходные коды на языке Си или ассемблера. При сборке компилируются и включаются в итоговый образ системы. Во время компиляции есть возможность получить значения опций модуля, к которому относятся эти файлы исходного кода.
  • .h - заголовочные файлы, содержащие объявления и определения, необходимые для реализации модулем какого-либо интерфейса. При сборке для каждого включенного модуля генерируется специальный заголовочный файл, включающий все перечисленные .h-файлы данного модуля, а также модулей, расширяющих данный. Это позволяет использовать различные реализации того или иного интерфейса без изменения исходного кода модулей, которые его используют. Такой способ абстракции необходим, поскольку различные реализации могут определять ту или иную структуру по разному, в то время как структура может использоваться другими модулями без знания деталей реализации. То же самое относится и к макросам, inline-функциям и константам.
  • .lds.S - линкер-скрипты, позволяющие влиять на процесс компоновки модулей в итоговый образ. Типичное использование таких скриптов - это добавление новых секций.

Пример задания заголовочного файла с реализацией абстрактного модуля:

    module interrupt_stub extends embox.arch.interrupt {
        source "interrupt_stub.h"
}

Пример задания файла с линкер скриптом и файла с исходным кодом:

    module static_heap extends heap_place {
        // ...
        source "heap.lds.S"
        source "static_heap.c"
        // ...
    }

Опции

Опции позволяют определить на этапе конфигурирования числовые, логические или строковые параметры, которые могут влиять способ сборки, инициализацию или работу модуля. Опции также могут иметь или не иметь значение по умолчанию. В последнем случае значение должно быть указано во время конфигурирования, в первом - это не обязательно, тогда будет использоваться значение по умолчанию. Опции разделяются на три типа в зависимости от типа задаваемого значения. Тип опции задается после ключевого слова option перед именем опции:

  • string - строковые опции
  • number - целочисленные данные
  • boolean - булевое значение - true или false

Чтобы получить значение опции при компиляции исходного кода используются специальные макросы:

  • OPTION_STRING_GET - для получения значения строковых опций
  • OPTION_NUMBER_GET - для числовых опций
  • OPTION_BOOLEAN_GET - для булевых опций

Аргументом макроса выступает имя опции, определенное в my-файле.

Зависимости

Зависимости являются способом указать системе сборки, что корректное функционирование данного модуля невозможно без некоторых других модулей. Список зависимостей может включать интерфейсы, при этом это означает, что в сборку должен быть включен ровно один модуль, реализующий требуемый интерфейс. Межмодульные зависимости указываются через атрибут depends. В значении атрибута можно перечислять как модули, так и интерфейсы. Система сборки гарантирует, что при включении данного модуля, будут добавлены и все его зависимости. В случае зависимости от интерфейса используется одна из его реализаций. Знание о межмодульных зависимостях используется как для получения списка модулей для сборки, так и в момент загрузки системы (см. далее). В некоторых случаях требуется просто включить нужный модуль вместе с данным, без изменения порядка загрузки, например, для использования таких глобальных модулей (аспектов), как поддержка многопроцессорности, логгирование или отладочные утверждения (assert). Поскольку у таких модулей нет как такового состояния (загружен или не загружен), для указания их в качестве зависимости атрибут depends дополняется аннотацией @NoRuntime. В этом случае зависимость будет использоваться во время сборки, но не будет определять порядок загрузки модулей относительно друг друга.

Аннотации

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

Пример задания с помощью аннотации реализации абстрактного модуля по умолчанию:

    @DefaultImpl(embox.arch.generic.interrupt_stub)
    abstract module interrupt { }

Описание конфигурации

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

Структура конфигурации

Конфигурация образа происходит редактированием файлов конфигурации в каталоге conf/. Его содержимое следующее:

  • lds.conf - Файл lds.conf содержит определение карты памяти, использующейся на конкретной аппаратной платформе.
  • mods.config - Файл mods.config содержит названия и опции модулей, которые будут включены в образ ОС. Также, для каждого из перечисленного в этом файле модуля возможно указать новые значения опций.
  • rootfs/ - Директория rootfs содержит файлы, которые будут включены в состав файловой системы, которая будет доступна на ранних этапах загрузки.

Процесс конфигурирования

Использование модуля в образе ОС подразумевает его включение в конфигурацию ОС.

Базовая конфигурация

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

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

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

make confload-x86/qemu

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

make confload

Включение модуля в конфигурацию

Список модулей для включения в конфигурацию находится в файле conf/mods.config, который имеет следующую структуру:

package genconfig

configuration conf {
    [список_модулей]
}

[список_модулей] определяет положение набора строк, каждая из которых определяет включение модуля. Добавьте новую строку к списку модулей следющего содержания:

    include pkg.new_package.empty

В итоге, файл conf/mods.config должен иметь следующее содержание:

package genconfig

configuration conf {
    [список_модулей]
include pkg.new_package.empty
}

После этого, модуль empty из пакета pkg.new_package будет включен сборку. Чтобы проверить непротиворечивость полученной сборки и, в случае успеха, произвести создание образа ОС, наберите

    make

В случае положительного результата будет выведено сообщение “Build complete”. Убедиться в том, что ОС содержит новый модуль можно запустив ОС на исполнение и выполнить команду lsmod c параметрами -n и empty, которая выведет список модулей, у которых в имени модуля присутствует подстрока “empty”.

lsmod -n empty

Результатом lsmod будет являться печать

    *  pkg.new_package.empty

Это означает, что модуль pkg.new_package.empty имеется в системе, а символ “*” обозначает, что в данный момент модуль загружен и работает.