BYOD в контейнере: виртуализуем Android. Часть вторая

от автора

Продолжаю рассказ о технологии виртуализации Android, сделанной в Parallels Labs группой студентов кафедры МиИТ Академического университета Санкт-Петербурга в рамках магистерской работы. Во вчерашней статье мы рассмотрели общую концепцию виртуализации устройств на базе Android, позволяющую сделать это. Задача сегодняшнего поста – разобраться с виртуализацией телефонии, звука и системы пользовательского ввода.

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

1. API пакета com.android.internal.telephony.
2. Демон rild, мультиплексирующий запросы пользовательских приложений в аппаратуре доступа к мобильным сетям.
3. Проприетарная библиотека доступа к оборудованию.
4. Драйвер GSM-модема.

Интерфейс между компонентами (2) и (3) документирован и называется Radio Interface Layer, или RIL. Лежит он тут: development/pdk/docs/porting/telephony.jd.

Проприетарные реализации RIL пытаются повторно инициализацилизировать оборудование;
Не определен способ маршрутизации входящих звонков и SMS;
Не определен механизм управления исходящими звонками и SMS.

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

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

Аудиоустройство

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

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

Прокси-клиент

Подменяемым аппаратно независимым интерфейсом к звуковому устройству является интерфейс AudioHardwareInterface, о чем нам говорит Android Platform Developer’s Guide. Кроме того, он является самым низкоуровневым и простым аппаратно независимым аудиоинтерфейсом. По сути, он представляет из себя поток для записи звука на воспроизведение. Кроме звукового потока, воспроизводимого Android, в его реализации можно получить и другую полезную информацию.

Можно было бы выполнить подмену на более высоком уровне. Например, подменить часть аудио фреймворка Android. Но анализ показал, что аудиофреймворк значительно сложнее, чем AudioHardwareInterface. Поэтому прокси-клиент в случае с виртуализацией аудиоустройства является реализацией AudioHardwareInterface.

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

Прокси-сервер

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

Самым простым решением, которое позволяет микшировать и воспроизводить звук в Android, является использование аудиоклассов из Android SDK. В нашем решении звуковой поток каждого пользователя воспроизводится отдельным объектом класса AudioTrack из Android SDK. Использование классов позволяет аудиофреймворку Android микшировать все воспроизводимые звуковые потоки и отправляет результирующий звуковой поток на воспроизведение динамиком устройства или на выход на наушники.

Нужно отметить, что классы из Android SDK имеют Java-природу, и для их использования требуется запуск виртуальной Java-машины Dalvik. Запуск Dalvik в случае с Android означает запуск всей ОС из-за использования в нем механизма типа bootstrapping. При решении этой задачи «в лоб» нас подстерегает проблема: запущенный Android занимает 200–250 Мб RAM и потребляет вычислительные ресурсы. Это непозволительные накладные расходы для задачи воспроизведения и микшировани звуковых потоков, но в данной работе их удалось избежать. Фактически мы используем не Java-классы из Android SDK, а их нативный бэкенд, написанный на C++. Вот почему запуск Dalvik, а значит, старт полного Android-контейнера не требуется.

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

Регулирование уровня звука

Пользователь, вероятно, хочет, чтобы громкость звука разных Android можно было регулировать. Выяснилось, что в Android 2.x отсутсвует абстракция аппаратного микшера, т.е. микширование выполняется через CPU. Поскольку микширование звуковых потоков внутри каждого Android полностью независимо (нет общего разделяемого аппаратного микшера), то общий уровень громкости каждого Android можно настроить в самом Android. Прокси-сервер просто не вносит изменений в уровни звука каждой ОС, а юзер настраивает уровень звука каждого Android в пользовательском интерфейсе.

Устройства вывода звука

Мобильные устройства, как правило, имеют множество устройств вывода звука. Например, большой динамик, трубка, наушники, bluetooth-гарнитура. Запущенные ОС одновременно могут требовать воспроизведения своих звуковых потоков на разных устройствах вывода. В некоторых случаях их требования будут несовместимы. Например, не имеет смысла проигрывать один звук на большом динамике, а другой в наушниках или трубке. Также будет странно, если во время звонка в трубке будет слышен не только голос собеседника, но и игравшая до звонка музыка.

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

Взаимодействие с сервисом телефонии

В прокси-клиенте, являющемся реализацией AudioHardwareInterface, мы можем получить информацию о том, идет ли в данный момент разговор по телефону или нет. Если разговор идет, то требуется настроить аудиоподсистему для записи и воспроизведению голоса и взаимодействия с сервисом телефонии. Фактически для этого достаточно перевести реализацию AudioHardwareInterface, поставляемую с устройством, в режим звонка при помощи вызова метода setMode(MODE_IN_CALL) и установить роутинг звука в трубку, иначе голос собеседника будет воспроизводиться текущим устройством вывода звука. Таким образом, за реализацию взаимодействия с сервисом телефонии отвечает поставщик устройства.

Телефония

Клиентская часть

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

Серверная часть

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

Политика маршрутизации запросов

Для каждого типа запросов, поступающих на OnRequest, определена политика обработки. В настоящее время определены три политики:

  • Безусловное блокирование запроса. Политика установлена для всех запросов, которые ни разу не встречались в процессе разработки прокси-сервера.
  • Безусловная передача запроса проприетарной библиотеке. Политика распространяется на все запросы, не изменяющие состояние проприетарной библиотеки и GSM-модема.
  • Передача запроса от активного контейнера проприетарной библиотеке. Служит для запросов, связанных с отправкой SMS и совершением звонков.

Для каждого типа асинхронных уведомлений также определены политики его маршрутизации:

  • Безусловное блокирование уведомления.
  • Маршрутизация в активный контейнер. Политика установлена для уведомлений, связанных с принятием SMS и звонков.
  • Маршрутизация во все контейнеры. Для служебных уведомлений «железа» — например, об изменении уровня сигнала сотовой сети.

Подсистема ввода

Android использует подсистему evdev для сбора событий ввода ядра. Для каждого процесса, открывшего файл /dev/input/eventX, этот драйвер создает очередь событий, поступающих из ядра подсистемы ввода.

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

Немного о тестировании

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

Основные цели тестирования были такие:

  • демонстрация готовности разработанной технологии;
  • выявление утечек памяти в разработанных компонентах;
  • определение энергопотребления разработанной технологии.

Тестирование проводилось на смартфоне Samsung Galaxy S II по следующим сценариям использования смартфона:

1. Простой (включили телефон и ничего с ним не делаем);
2. Воспроизведение музыки с включенной подсветкой экрана;
3. Angry Birds (куда ж без них?) и одновременное проигрывние музыки.

Тестовые сценарии исполнялись на трех конфигурациях:
1. Оригинальное окружение CyanogenMod 7 для Samsung Galaxy S II (1);
2. Один контейнер с CyanogenMod 7 (2);
3. Два контейнера с CyanogenMod 7: в одном контейнере проигрывалась музыка, в другом запущена игра (3).

В каждом тестовом прогоне измерялись такие параметры:
1. Объем свободной памяти и размер кеша файловой системы (по информации /proc/memрinfo);
2. Уровень заряда аккумулятора (по информации /sys/class/power_supply/battery/capacity).

Каждый тестовый прогон длился 30 минут, измерения делались с частотой 2 c. Вот что у нас получилось:

Может показаться, странным, что расходы памяти в случае с контейнерами оказываются меньше, чем в реальном окружении. Точной причины не установлено (мы не пытались пока в этом вопросе разобраться), однако этот же самый эффект наблюдается и проекте Cells, который они описали в своей статье.

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

Как видно из значений потребления памяти, при запуске второго контейнера оно возрастает более чем в 2 раза, что является препятствием для запуска нескольких контейнеров на одном смартфоне: эксперименты показывают, что смартфоне Google Nexus S, имеющем 380 Мб, невозможен запуск более чем одного контейнера без активации файла подкачки.

Заключение

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

Несколько любопытных цифр и фактов о создании нашего решения:

  • Основных разработчиков проекта трое, двое из которых являлись на момент выполнения работы студентами Академического университета в Петербурге, а один руководил дипломным проектом;
  • Длился проект 11 месяцев;
  • Проекту помогали советами инженеры московского офиса Parallels;
  • В проекте было задействовано три Android-устройства.

Если что-то важное осталось за кадром, задавайте вопросы в комментариях. Я постараюсь на них ответить.

ссылка на оригинал статьи http://habrahabr.ru/post/175071/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *