Микроядерные ОС нынче не то что обесценились — устройства на их основе мало пересекаются с привычными компьютерными системами. Тем не менее большая часть этих ОС, находящихся в коммерческом применении, закрыта, а открытые проекты, как правило, уже устарели и неприменимы в реальных целях. Относительно недавно, однако, появился свободный фреймворк для создания ОС — Genode, про который мы и расскажем в этой статье.

Введение

История микроядерных систем началась где-то в семидесятых годах прошлого века. Правда, на железе того времени их, понятно, реализовать было затруднительно. Реально работающие микроядра (такие как Chorus и Mach) появились уже в начале восьмидесятых. Mach, однако, был достаточно медленным и в конечном счете не выдержал испытания временем (хотя стоит отметить, что его взяли за основу ядра Mac OS X), а Chorus, строго говоря, нельзя считать полноценным микроядром — часть его подсистем работают в привилегированном режиме.

В конце девяностых была предложена оптимизированная архитектура микроядер — L4. К сожалению, руководитель проекта (Йохен Лидтке) попал в аварию и не смог закончить свою реализацию данной архитектуры. Тем не менее на основе описания родилось целое семейство L4-микроядер:

  • L4Ka::Pistachio — оригинальная разработка, руководителем которой и был Йохен Лидтке. Сейчас, понятное дело, проект заморожен, но послужил прародителем других ядер L4;
  • OKL4 — ядро, разработанное в Open Kernel Labs, компании — ответвления от NICTA. На данный момент OKL4 достигла версии 3 и используется в мобильных телефонах — в частности, производства Qualcomm;
  • L4.verified — разработан в NICTA. Является формально верифицированным микроядром. Это условно означает, что по каждой строчке кода доказывается теорема, — это позволяет быть математически уверенным в надежности кода и соответствии его определенным требованиям. Подобные ядра используются, как правило, в военпроме и медицине;
  • Fiasco.OC — написан на C++, поддерживает множество аппаратных платформ.

Genode же появился в 2008-м и представляет собой конструктор для создания узкоспециализированных систем из «кирпичиков». Набор «кирпичиков» включает в себя набор «фундаментов» для нескольких вариантов ядер, стеки протоколов и драйверы устройств. Рассмотрим архитектуру и особенности данного фреймворка.

Архитектура

Фреймворк может работать на нескольких ядрах:

  • Linux — как x86, так и x64. Является ядром по умолчанию для Genode;
  • Fiasco.OC;
  • L4ka::Pistachio;
  • OKL4;
  • Codezero — ответвление от OKL4, изначально ядро было открытым, затем исходники закрыли и оно стало коммерческим;
  • Fiasco — ядро, оптимизированное для поддержки систем с низкими задержками; поддерживает как x86/x64, так и ARM;
  • NOVA — микрогипервизор для архитектуры x64, поддерживающий аппаратную виртуализацию и IOMMU.

Genode разрабатывался с учетом минимально возможного использования глобальных пространств имен, что крайне полезно для изоляции, а следовательно, и большей защищенности (оно и понятно — как можно атаковать то, чего не видно?). В фреймворке нет нужды знать имена файлов, идентификаторы процессов и потоков и прочую информацию, которая уникальным образом определяет объект в пределах системы. Фреймворк спроектирован в основном для ядер, поддерживающих локальные имена, защищенные в привилегированном режиме, — в терминологии Genode они именуются capabilities. Стоит отметить, что в Linux подобная возможность отсутствует и эмулируется процессом Core.

Поверх привилегированного ядра запускается процесс Core. Этот процесс получает доступ ко всем физическим ресурсам и преобразует их в некие абстракции, которые затем могут использоваться различными программами для получения данных ресурсов. Так, Core выводит абстракцию, называемую пространствами данных (dataspaces), — смежные области физической памяти с произвольным размером (зависящим, тем не менее, от размера страниц). Система, которую разрабатывают на основе «фундамента» Core, вместо использования страниц физической памяти оперирует единой абстракцией, позволяющей работать как с RAM и memmapped IO, так и с областями ROM. Core предоставляет множество примитивов и сервисов, которые имеет смысл описать подробнее. Отмечу, что практически каждый примитив присваивается определенной сессии (собственно, они и называются именами таких-то примитивов).

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

ROM-сервис же предоставляет доступ к файлам, доступным во время загрузки, таким как файлы, загруженные начальным загрузчиком или содержащиеся в какой-либо области ROM. Каждая сессия соответствует открытому файлу, и его содержимое доступно клиенту в качестве read-only пространства данных.

Сервис IO_MEM позволяет драйверам режима пользователя получать ресурсы устройства, отображенные в память, — опять же в виде пространства данных. Каждая сессия соответствует диапазону адресов памяти, для которого клиенту предоставляется пространство данных. Драйвер режима пользователя может сделать доступным данный ресурс устройства в его, драйвера, адресном пространстве с помощью подключения пространства данных к RM-сессии.

Сервис IO_PORT предоставляет доступ к портам ввода-вывода устройства через интерфейс RPC. Сессия же соответствует праву доступа к диапазону портов.

Сервис IRQ отвечает за аппаратные прерывания. Каждая сессия соответствует подсоединенному к клиенту прерыванию. Стоит отметить, что в каждый конкретный момент прерывание может быть подсоединено только к одной сессии.

RM — сервис, управляющий разбивкой адресного пространства. Сессия соответствует разбивке адресного пространства, в которой можно разместить несколько пространств данных. Таким образом, сессию RM можно представить как некую абстракцию для таблицы страниц, в которой и находятся ссылки на «страницы» — пространства данных.

Сервис CPU позволяет создавать потоки и управлять ими. Сессия же — аллокатор процессорного времени, с его помощью можно выделять процессорное время для потоков.

Сервис PD позволяет создавать изолированные адресные пространства. Данный сервис используется приложениями напрямую крайне редко. Зато его использует подсистема создания процессов для внутренних целей.

Как уже было сказано, Capability — уникальная в пределах системы сущность, которая обычно ссылается на какой-то объект, реализованный сервисом. Сервис же CAP как раз и выделяет данные сущности и освобождает их.

Сервис LOG используется низкоуровневыми системными компонентами (такими как процесс init) для вывода отладочной печати.

Genode имеет древовидную (и рекурсивную) структуру, а каждый процесс видит только свое пространство имен, взаимодействует с остальными процессами посредством объявления или запроса сервиса и осуществляет полный контроль над своими потомками. Поэтому настройка самого первого процесса, init, позволяет задавать дальнейшую политику взаимодействия процессов.

Иерархическая структура систем на основе Genode
Иерархическая структура систем на основе Genode

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

В Genode также предоставлен и графический сервер — Nitpicker. Данный сервер делает с традиционными графическими серверами (такими как X.Org) то же, что гипервизоры делают с традиционными ОС — виртуализирует пользовательский ввод и вывод на фреймбуфер таким образом, что пользователь может выполнять несколько оконных систем одновременно на одном и том же экране, изолируя каждую систему от других. Nitpicker также призван решить проблему безопасности назначения меток отдельным частям экрана (что позволяет его использовать в MLS-системах) и спроектирован с учетом устойчивости к DoS-атакам. При всем при том размер данного графического сервера крайне мал — всего около 1500 строк кода, в то время как у того же X.Org их более 80 тысяч.

Демонстрационный образ Genode — главный экран
Демонстрационный образ Genode — главный экран
На Genode портирован и Qt-браузер Arora на движке WebKit
На Genode портирован и Qt-браузер Arora на движке WebKit
Стандартный тест OpenGL в Genode
Стандартный тест OpenGL в Genode
Одновременный запуск браузера и паравиртуализированного Linux поверх Genode
Одновременный запуск браузера и паравиртуализированного Linux поверх Genode

seL4

В 2014 году был открыт под GPL исходный код ядра seL4. Разработчики утверждают, что это единственное формально верифицированное ядро общего назначения. Помимо этого, у данного ядра есть следующие особенности:

  • новая модель управления ресурсами, расширяющая возможности их изоляции;
  • полный анализ таймингов, в частности задержек прерываний. Это делает его единственным ядром защищенного режима, которое действительно гарантирует поддержку жесткого реального времени;
  • в плане быстродействия IPC — это ядро если не впереди планеты всей, то, во всяком случае, впереди всех остальных ядер L4.

SeL4 поддерживает как x86, так и ARM, но формально верифицирован только ARM-вариант.

Процесс сборки

Для сборки чего-либо на основе Genode рекомендуется использовать их тулчейн. Поскольку существует великое множество дистрибутивов и, соответственно, примерно столько же вариантов связок GCC/binutils, адаптация кода под каждую связку представляется занятием крайне тяжелым и бессмысленным. Помимо этого, в отдельных тулчейнах, входящих в поставки некоторых дистрибутивов, используются специфические возможности для сборки низкоуровневого кода. Так, по умолчанию включена опция -fstack-protector, которую при сборке Genode-систем требуется отключать, что поддерживается отнюдь не всеми тулчейнами.

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

К сожалению, использование данного тулчейна не помогло решить всех проблем. В частности, скрипт сборки тулчейна зависим от библиотеки libc, присутствующей на хост-системе, поскольку заголовочные файлы стандартной библиотеки используются при сборке библиотек-хелперов для GCC. В Genode же используется libc, основанный на стандартной библиотеке C FreeBSD, что делает невозможным применение библиотек-хелперов GCC из-за различия в интерпретации некоторых низкоуровневых вещей. Еще одна проблема состояла в том, что разработчики не смогли найти пути для сборки тулчейна под платформу иную, чем используется на хостовой машине.

С появлением версии Genode 11.11 разработчики смогли решить эту проблему путем полного отделения от хостовой системы, на которой он собирается. Наиболее важным шагом оказалось удаление зависимости GCC от glibc. Библиотека эта требуется для сборки библиотек-хелперов GCC. Разработчики вынесли некоторые функции в микрозаглушку, представляющую собой единственный заголовочный файл, содержащий все типы и функции, которые используются библиотеками-хелперами GCC. Помимо этого, разработчики удалили из тулчейна все GNU-специфичные проекты. Фактически получился тулчейн, не зависящий от ОС, который, в отличие от других аналогичных, поддерживает и C++.

Помимо тулчейна, в фреймворке есть еще и своя система сборки. Данная система сборки предусматривает использование отдельного, внешнего каталога. Это позволяет ей, во-первых, не трогать каталог с исходным кодом, а во-вторых, иметь несколько различных каталогов для разных аппаратных платформ.

После создания каталога сборки с помощью create_builddir в данном каталоге будет находиться файл Makefile (который на самом деле является симлинком к tool/builddir/build.mk и не предназначен для редактирования, поскольку make — всего-навсего фронтенд к собственной системе сборки) и каталог etc/, в котором, как правило, находится единственный файл — build.conf. Данный файл определяет, какие части дерева исходного кода Genode будут использоваться в процессе сборки. Части эти называются «репозиториями».

Концепция репозиториев позволяет четко разделять исходный код на различные подсистемы. Так, платформозависимый код для каждой платформы находится в каталоге base-<имя платформы>. Кроме того, на репозитории делятся также различные слои абстракции и всяческие особенности. Makefile определяет набор репозиториев, участвующих в сборке. Система сборки создает оверлей, объединяющий все каталоги, перечисленные в соответствующем объявлении, в единое логическое дерево исходного кода. Изменение списка репозиториев приводит, соответственно, к изменению «точки зрения» системы сборки.

Стоит заметить, что имеет значение и порядок списка в наборе репозиториев. Те из них, которые идут первыми, имеют большее значение, чем последующие. Это делает данный механизм чрезвычайно гибким — так, добавление репозитория перед каким-либо еще дает возможность применять свои версии исходных файлов, не затрагивая уже существующие.

Одной из отличительных черт фреймворка Genode является его кросс-ядерность. Различные ядра, однако, исповедуют разные подходы к конфигурированию, развертыванию и загрузке системы. Для использования конкретного ядра, следовательно, необходимо знать о его механизмах загрузки и о том, какие утилиты требуются для его начальной настройки. Для облегчения портирования между ядрами фреймворк поставляется со средствами, которые должны помочь избежать перечисленного. К числу таких средств относятся и Run-скрипты.

Run-скрипт предназначен для цельного описания конкретной системы вне зависимости от использованного ядра. Созданный скрипт может быть использован для интеграции и тестирования собираемой системы напрямую из каталога сборки. Рассмотрим простейший случай его использования:

  1. Сборка компонентов системы.
  2. Создание boot-каталога. По завершении работы скрипта в нем должны находиться все компоненты конечной системы.
  3. Затем в каталог копируется файл конфигурации процесса init.
  4. Создание загрузочного образа. На этом шаге происходит копирование заданного списка файлов из каталога bin/ в boot-каталог и выполнение платформозависимых операций для преобразования содержимого этого каталога в загружаемую форму, которая может быть как ELF-, так и ISO-образом.
  5. Выполнение загрузочного образа. В зависимости от платформы может использоваться какой-либо эмулятор — чаще всего QEMU. В Linux, однако, происходит выполнение ELF-файла.

Процедура сборки и развертывания систем на базе Genode, таким образом, получается крайне настраиваемой и гибкой.

Заключение

Подведем итоги нашего краткого обзора базовых особенностей фреймворка. Genode на данном этапе развития более всего подходит для построения Embedded-систем — и то из-за отсутствия драйверов это может быть проблематичным и невыгодным. Попытка создать на основе этого фреймворка ОС общего назначения, которую разработчики сейчас декларируют в качестве основной цели, кажется заведомо провальной по одной простой причине: система может быть сколь угодно замечательной, однако без приложений грош ей цена. Портирование же приложений из существующих систем выглядит очень сложной операцией ввиду существенных различий в API. Есть более реальный вариант применения — использовать фреймворк в качестве базы для создания гипервизора, что явно противоречит указанной цели, поскольку гипервизор не является системой общего назначения.

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

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии