Когда мы разрабатываем настольную операционную систему, очень легко попасть в архитектурную ловушку: начать строить систему вокруг одного типа приложений. Исторически так и происходило: Windows запускала Windows‑приложения, macOS запускала macOS‑приложения и Linux — Linux‑приложения. Но пользователь живет не в этом мире. Ему неважно, на каком языке написана программа, под какую платформу она создавалась и какая у нее экосистема. Он хочет, чтобы нужные приложения запускались у него быстро и максимально удобно.

Привет, Хабр! Меня зовут Илья Клементьев, я старший инженер-программист в отделe разработки сервисов настольной ОС в YADRO. Современные ОС постепенно двигаются в сторону универсальных хостов — то есть систем, которые способны запускать приложения из разных программных экосистем. В статье я расскажу, как можно запускать Android- и Windows-приложения внутри Linux, какие технологии для этого используются и как устроена многоуровневая архитектура системы с виртуальной машиной и контейнерами.

Как построить десктопную ОС, принимающую чужие экосистемы

Я занимаюсь разработкой сервисов для настольной операционной системы KvadraOS. Параллельно у нас существует продукт — планшет, который работает на собственной системе на базе AOSP. Фактически это Android‑подобная система. У нее есть:

  • собственные сервисы;

  • инфраструктура;

  • push‑система;

  • идентификация пользователей;

  • поддержка огромного количества Android‑приложений.

Когда мы разрабатывали настольную ОС, возник очевидный инженерный вопрос: если рядом уже есть огромная экосистема приложений, почему бы не использовать ее на десктопе?

Если смотреть на проблему архитектурно, то задача звучит так: построить систему, которая может безопасно принимать приложения из разных экосистем. Мы хотели создать мультиэкосистемный продукт. ChromiumOS уже давал для этого хорошую основу — оставалось лишь отвязаться от инфраструктуры Google и усилить внутреннюю интеграцию компонентов системы. Не становясь при этом другой системой — не превращаясь в Android, Windows и классический Linux‑дистрибутив.

А оставаясь хостом.

Практическая польза универсального хоста

В этот момент обычно возникает простой вопрос: зачем вообще нужна такая сложная архитектура? На практике у этого есть довольно понятная польза.

Исчезает жесткая привязка к одной экосистеме. В реальной работе почти всегда возникает ситуация, когда нужная программа существует только для другой платформы. Какой-то инструмент есть только под Linux, корпоративное приложение — только под Windows, а мобильный сервис — в виде Android-клиента. В классической системе это приводит к виртуальным машинам, отдельным компьютерам или удаленным подключениям. Универсальный хост позволяет запускать такие приложения внутри одной системы, и для пользователя они выглядят как обычные программы.

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

Иногда приходится работать сразу с несколькими платформами: тестировать Android-приложение, запускать Linux-сервисы, проверять совместимость с Windows-клиентом. Когда все эти среды живут рядом, разработка и тестирование становятся заметно проще.

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

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

Первый шаг: начать с одной среды

Самая большая ошибка — попытаться реализовать все сразу: Linux, Android, Windows, Web. Такой проект быстро становится неуправляемым. Поэтому первый практический шаг — выбрать одну дополнительную экосистему.

В нашем случае это был Android. Причины чисто инженерные: рядом уже была система на базе AOSP, был накоплен слой сервисов и уже существовала экосистема приложений. 

Но важно понимать, что нашей целью не было сделать десктоп на Android. Мы хотели оставить Linux‑систему основной, но научить ее запускать Android‑приложения как отдельный тип приложений.

Как запустить Android внутри Linux

Первым инструментом для нас стал Android Cuttlefish. Это open source-проект Google, который предназначен для запуска Android внутри Linux‑системы.

Cuttlefish — не классический эмулятор, он не эмулирует железо полностью. Вместо этого он использует: KVM, виртуализацию и паравиртуализованные устройства VirtIO. Это делает его значительно быстрее обычных эмуляторов.

Архитектура выглядит примерно так:

Поверх запускается Android‑виртуальная машина, которая использует VirtIO‑устройства. Так Android получает доступ к реальным ресурсам системы без полной эмуляции.

Что в итоге получилось при использовании Cuttlefish

Первый результат был впечатляющим. Android‑приложения действительно запускались, даже простые игры работали быстро. Графика пробрасывалась через Wayland, и все выглядело почти нативно.

На этом этапе легко сказать: «Отлично, задача решена». Но именно здесь начинается настоящая инженерия. То, что система работает, еще не означает, что ее можно использовать в продукте. Демо и продукт — две совершенно разные вещи.

Главная проблема — безопасность. Когда несколько разных сред используют одну систему, возникает проблема мультиарендности (multitenancy). Это ситуация, когда разные приложения используют одну инфраструктуру.

Плюсы очевидны: экономия ресурсов, простота архитектуры. Но есть и минус — изоляция. Если появляется уязвимость в общем ядре, злоумышленник может получить доступ ко всей системе.

При желании мошенники смогут добраться до ядра хостовой операционной системы
При желании мошенники смогут добраться до ядра хостовой операционной системы

Поэтому главный вопрос архитектуры звучит так: как запускать чужие среды, не разрушая безопасность системы?

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

Три уровня изоляции

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

1. Отсутствие изоляции. Приложение работает практически напрямую в системе. Это самый простой и быстрый вариант, но и самый рискованный.

2. Слабая изоляция. Здесь используются механизмы ядра Linux — namespaces, cgroups, capabilities, которые позволяют ограничивать процессы и ресурсы.

Этот уровень уже значительно безопаснее, но все среды все еще используют одно и то же ядро. А значит, уязвимость ядра остается общей.

3. Строгая изоляция. В этом случае нам нужны три вещи: гипервизор (KVM), виртуальная машина и монитор виртуальных машин (CrosVM).

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

Почему CrosVM

CrosVM использует KVM как гипервизор, работает с паравиртуализованными устройствами VirtIO и запускает guest-kernel напрямую. Но ключевое архитектурное решение CrosVM — это строгая изоляция через механизм песочниц (sandboxing).

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

Принцип работы CrosVM
Принцип работы CrosVM

CrosVM проще анализировать, контролировать и встроить в архитектуру ОС. Не самый универсальный инструмент — но самый подходящий для архитектуры, где безопасность стоит на первом месте.

Почему не QEMU

На первый взгляд самым очевидным выбором монитора виртуальных машин был QEMU. Но его универсальность оказалась проблемой. 

QEMU — это огромный проект с многолетней историей, поддержкой множества архитектур, режимов эмуляции и типов устройств. Для нашей задачи такой уровень универсальности был избыточным. Чем больше и сложнее кодовая база, тем труднее ее анализировать и тем больше потенциальная поверхность атаки.

CrosVM

QEMU

~50000 строк кода

>2 млн. строк кода

Rust

C

Фокус на KVM

Поддерживает множество архитектур и режимов эмуляции

Минимальная модель девайсов

Полная эмуляция девайсов

QEMU и CrosVM — мониторы виртуальных машин пользовательского пространства. И в их схожести можно убедиться на примере ниже:

Проект с использованием CrosVM

Чтобы показать, как CrosVM работает на практике, можно создать небольшую виртуальную машину на базе Ubuntu. Сначала собираем корневую файловую систему — образ rootfs. По сути, это обычный образ файловой системы, из которого будет запускаться виртуальная машина.

У CrosVM есть важная особенность: он запускает ядро напрямую, без загрузчика вроде GRUB. Поэтому из образа rootfs нужно заранее извлечь бинарные файлы ядра и передать их CrosVM.

Дальше все просто:

  1. Запускаем CrosVM.

  2. Передаем ему ядро Linux.

  3. Указываем образ rootfs как диск виртуальной машины.

Поскольку rootfs — это образ раздела диска, ядру нужно явно сказать, какой раздел использовать в качестве корневой файловой системы. После запуска мы получаем полноценную виртуальную машину с Ubuntu. Можно подключиться к ее консоли и работать внутри системы.

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

Но если мы уже умеем запускать изолированную среду и Android-приложения, зачем двигаться дальше? Изначальная цель выходит за рамки мобильных приложений. Нам важно использовать полноценные Linux-программы — такие как LibreOffice, GIMP или инструменты разработки вроде PHP. А это требует уже другого уровня интеграции с Linux-экосистемой.

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

Следующий шаг — минимальная VM

Виртуальная машина сама по себе решает не все. Нужен был еще один шаг — отделить не только guest от host, но и сделать внутреннюю Linux-среду более гибкой, не разрушая при этом общую модель безопасности.

Эту архитектуру можно усилить еще одним уровнем защиты — добавив контейнеры Linux. Идея простая: поверх виртуальной машины появляется дополнительный слой изоляции. Для этого используется специальная минималистичная виртуальная машина — Termina VM.

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

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

Внутри Termina работает менеджер контейнеров LXD, который управляет жизненным циклом контейнеров. И только поверх этой инфраструктуры появляется полноценная пользовательская Linux-среда. Уже в ней пользователь может делать все привычное: открывать терминал, запускать приложения, работать в браузере и устанавливать дополнительные программы.

Получается многоуровневая система: сначала виртуальная машина, внутри нее контейнерный менеджер и лишь затем пользовательская среда.

Контейнеры

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

Как запускается Linuх-приложение

Чтобы понять, как в такой системе запускается приложение, удобно разобрать самый простой пример — запуск терминала.

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

Сначала пользовательский интерфейс хостовой системы отправляет запрос на запуск приложения через D-Bus: хостовая ОС сообщает, что нужно открыть терминал. Дальше запрос принимает concierge — сервис на стороне host, который управляет жизненным циклом виртуальной машины и следит за работой CrosVM.

Если нужная виртуальная машина еще не запущена, concierge инициирует ее старт. Для этого он вызывает CrosVM, передает ему нужные параметры и поднимает Termina VM.

Как только Termina готова, внутри нее запускается LXD-контейнер. Получается, что приложение стартует не напрямую на host и не просто в VM, а внутри контейнера, который сам находится внутри минимальной виртуальной машины.

Следующий важный участник цепочки — Garçon (от французского — мальчик, официант). В архитектуре системы Garçon играет роль «посредника». Он работает уже внутри контейнера и нужен для того, чтобы связать контейнерную Linux-среду с хостовой системой. Он регистрирует Linux-приложения в host OS и обеспечивает seamless-интеграцию. Garçon помогает сделать так, чтобы приложение, запущенное глубоко внутри изолированной среды, выглядело для пользователя как обычное нативное окно.

После этого Garçon отправляет обратно сигнал, что все готово к запуску. Терминал стартует внутри контейнера, а его графический вывод пробрасывается назад через Wayland. Поэтому пользователь не видит ни виртуальную машину, ни контейнер, ни промежуточные сервисы. Он просто нажимает на иконку и через пару секунд получает обычное окно терминала на рабочем столе. В докладе отдельно подчеркивается, что для пользователя это происходит «за секунды».

Такая сложная схема нужна ради дополнительного слоя безопасности.

Что делать с Windows‑приложениями

После Linux и Android-приложений появляется следующий логичный вопрос: что делать с Windows‑приложениями. Решение — Wine. 

Wine — это не эмулятор Windows, он работает иначе. Его задача — перехватывать вызовы Windows API, которые делает приложение, и переводить их в соответствующие системные вызовы Linux.

Схема выглядит примерно так:

  1. Windows-приложение вызывает функцию из стандартной DLL.

  2. Wine подменяет эту библиотеку своей реализацией.

  3. Вызов переводится в Linux-совместимый syscall.

  4. Дальше управление передается уже ядру Linux.

Windows предоставляет огромное количество API, часть из которых плохо документирована, а часть меняется между версиями системы. Поэтому поддержка Wine — это постоянная работа по совместимости.

Пример запуска приложения через Wine

Для начала скачиваются исходники Wine и запускается какое-нибудь обычное Windows-приложение. В качестве примера используется именно «Сапер». 

# Сборка Wine из исходников
git clone https://gitlab.winehq.org/wine/wine.git
cd wine
./configure
make -j $(nproc)
# Запуск тестового приложения
wine <app_name>.exe

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

Дальше задача — изменить поведение программы, не трогая саму игру. Для этого нужно перехватить системный вызов, который Windows-приложение делает к системе. Wine предоставляет мощный механизм отладки: можно включить переменную WINEDEBUG=all, которая заставляет Wine записывать в лог все вызовы API, выполняемые приложением.

# Логирование всех API-вызовов
WINEDEBUG=all wine <app_name>.exe &> /tmp/wine_app_name_calls.log
# Анализ лога
grep -i "timer" wine_app_name_calls.log

Лог получается огромным, но среди множества записей можно найти вызов таймера. В нем передается параметр 1000 — значение в миллисекундах. Это означает, что таймер обновляется раз в одну секунду.

После этого можно открыть исходный код Wine и найти место, где обрабатывается этот вызов — например, в файле message.c, который отвечает за работу с сообщениями и таймерами. Если изменить значение таймера или преобразовать его, поведение приложения изменится. В результате «Сапер» начинает работать иначе — например, таймер может ускориться и считать время значительно быстрее.

Этот пример хорошо показывает, как работает Wine. Он не эмулирует всю систему Windows. Вместо этого Wine перехватывает вызовы Windows API и переводит их в соответствующие вызовы Linux. Благодаря этому можно менять поведение приложений на уровне реализации API, даже не изменяя саму программу.

Когда изоляция мешает взаимодействию

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

Решение — единые сервисы. Для печати используется CUPS. На хосте работает сервер, и все среды выступают клиентами.

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

Вместо заключения

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

Если сравнивать с ChromiumOS, то подходы принципиально различаются. У Google это тонкий клиент, который, помимо веб-приложений, может запускать Linux- и Android-приложения, но при этом остается жестко завязанным на облачный аккаунт и инфраструктуру.

В нашем случае система выступает как полноценный виртуальный оркестратор: она не только объединяет приложения на уровне интерфейса (например, иконки в общем лаунчере), но и интегрирует системные сервисы — такие как сервис печати. В этом аспекте решение идет дальше и глубже интеграции, чем у Google. А еще  важное отличие — ориентация на локального пользователя: данные остаются на устройстве и не требуют обязательной привязки к облаку.

Комментарии (1)


  1. thereisnocolor
    05.05.2026 12:57

    она не только объединяет приложения на уровне интерфейса (например, иконки в общем лаунчере), но и имеет красивые обои