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

в 5:20, , рубрики: android, Parallels, виртуализация, Разработка под android, метки: , ,

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

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

Введение: зачем всё это нужно?

Концепция Bring Your Own Device (или BYOD), когда сотруднику разрешено работать с самым удобным для него «железом», — палка о двух концах. С одной стороны, юзеру удобно использовать на службе то, чем он привык пользоваться дома. Это теоретически повышает его производительность труда. С другой — BYOD добавляет головной боли системным администраторам и IT-директорам, которые вынуждены каким-то образом решать задачу защиты корпоративных приложений и данных, которые теперь крутятся на смартфоне. Один из основных подводных камней – пользовательское поведение. Часто юзеры игнорируют запрет на закачку и установку демо-версий стороннего ПО, хотя внутри таких приложений может прятаться вирус или «троянец».

Очевидный способ оградиться от такого рода угроз – создать для ненадежного ПО специальную «песочницу»; либо, наоборот, создать такую «песочницу» для корпоративного софта и бизнес-данных. Оба подхода должны быть реализованы деликатно, то есть без посягательств на частную (SMS, фотки с вечеринок и др.) информацию сотрудника. Такие «песочницы» встречаются сплошь и рядом в индустрии разработки и тестирования ПО и создаются с помощью виртуализации. Мы решили сделать нечто подобное, но – на смартфонах.

Ищем аналоги

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

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

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

VMware Horizon Mobile — полноценный гипервизор второго типа, виртуализирующий аппаратное обеспечение абстрактной машины. Под проект было создано ядро Linux с патчами Android, поверх которого запускается созданное VMware пользовательское окружение Android. Гипервизор работает на распространенных типах процессоров ARM, не имеющих расширений для аппаратной виртуализации, и является процессом в хостовой (основной) ОС — например, в родной для Android-девайса операционке. Очевидно, что в данном случае виртуализация всего аппаратного обеспечения требует достаточно много вычислительных ресурсов. Но, что еще важнее, запуск более чем одного гипервизора будет слишком быстро «высаживать» батарейку. Планшет, разряжающийся за полчаса, никому не нужен. К тому же подход VMware показался существенно сложнее в реализации, чем подход Cells.

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

Реализация сценариев BYOD и создание песочницы различна: в случае Cells и VMware политики настраиваются на уровне пользовательских окружений Android; при использовании же TrustDroid пользователю необходимо настраивать политики и домены вручную для каждого приложения. Это сложнее и муторнее для пользователя, потому что пользовательских окружений может быть несколько. У TrustDroid не все ОК с безопасностью. Предполагается, что стандартные системные приложения Android и ядро ОС никогда не скомпрометированы, но это может быть неверно, т.к. при получении прав суперпользователя их можно подменить.

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

EmbeddedXEN — проект по портированию XEN на платформу ARM. Проект, насколько мне известно, пока не вышел из стадии активной разработки, а посему не может рассматриваться как законченное решение. В настоящее время в проекте уже адаптированы версии ядра Linux HTC Desire HD для работы в качестве Dom0.

Контейнерная виртуализация – это выход

Разобравшись с возможными решениями, и попытками виртуализации, мы пришли к выводу что именно контейнерная виртуализация – то, что нам подходит больше всего. Во-первых, мы хотим использовать максимум готовых существующих решений, а ядро операционной системы Linux, поверх которого работает Android, имеет встроенный механизм разделения системных ресурсов. Этот механизм широко используется провайдерами для реализации VPS-хостинга и называется контейнерами Linux (или LXC — от Linux Containers). Он выполняет несколько функций: работает как интерфейс инфраструктуры пространств имен ядра Linux для изоляции идентификаторов процессов, виртуализации сети, а также как «точка доступа» к инфраструктуре управления ресурсами cgroup.

  • текущая экспериментальная версия патча OpenVZ есть для ядра версии 2.6.32, но на смартфонах, на которых разрабатывалась и тестировалась технология, работают ядра версии 2.6.35;
  • при попытке наложить патч OpenVZ на одно из используемых ядер не удалось пропатчить 166 файлов из 1682; кроме того, патч модифицирует 34 файла, специфичных для архитектуры x86.

Портирование такого количества изменений на ядро 2.6.35 показалось нам чрезмерным. Но, отказавшись от OpenVZ, мы лишились готового решения по виртуализации sysfs: при создании нового виртуального окружения OpenVZ копирует дерево системных объектов (kobject) корневого пользовательского окружения, что потребовало от нас дополнительных усилий.

Итак, решено было использовать стандартные Linux-контейнеры LXC и всю инфраструктуру ядра, отвечающую за реализацию пространств имен (namespaces) и разграничение групп ресурсов.

На рисунке показана архитектура нашего решения.

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

  • Панель управления. Графический интерфейс к LXC; позволяет управлять контейнерами (запуск, остановка, переключение активного контейнера).
  • Cупервизор ядра, на рисунке — AndCont supervisor. Инициализирует виртуальное состояние драйверов, перехватывая момент запуска контейнера, реализует переключение между контейнерами.
  • Механизм межконтейнерного взаимодействия (ICC). Средство коммуникации между контейнерами на основе Netlink, обходящее проверки в прикладных сетевых протоколах и на основе разделяемой памяти.
  • Диспетчер ввода. Нужен для совместного использование устройств ввода Android, работающих параллельно.
  • Драйвер виртуального фреймбуфера. Обеспечивает возможность одновременного использования экрана устройства.
  • Драйвер GPU. Реализует возможность одновременного использования GPU всеми контейнерами.
  • Binder. Драйвер IPC, сделанный в рамках проекта Android Open Source Project (AOSP).
  • Alarm. Интерфейс к часам.
  • Прокси-сервер для механизма управления радиоинтерфейсами (Radio Interface Layer, RIL). Обеспечивает совместный доступ нескольких контейнеров Android к оборудованию доступа к мобильным сетям.
  • Прокси-клиент RIL. Принимает запросы к сервису телефонии от приложений в контейнере и передает их прокси-серверу RIL.
  • Прокси-клиент аудио. Получает контроль над звуковыми потоками контейнера и передает их прокси-серверу аудио.
  • Прокси-сервер аудио. Воспроизводит звуковые потоки контейнеров.
  • Вспомогательный контейнер. Предназначен для запуска системных демонов и библиотек Android, по разным причинам вынесенных из гостевых Android.
Механизм управления контейнерами
  • Заблокирован сброс флага CAP_SYS_BOOT, отвечающего за возможность перезагрузки системы, который используется виртульной java-машиной Dalvik при ее инициализации.
  • Опущены проверки открытых файловых дескрипторов, отличных от стандартных в lxc_start.
  • Исправлен дефект, приводящий к утечке файловых дескрипторов из демона adbd, который используется для доступа к смартфону через USB.
  • Добавлены нотификации супервизора уровня ядра о запуске нового контейнера перед запуском процесса init.
Binder

Драйвер ядра binder — это механизм IPC, разработанный в рамках проекта AOSP. Наличие оригинального, отличного от стандартного IPC до конца не ясно, однако детали реализации binder были одним из препятствий для запуска нескольких виртуальных окружений Android. Проблема состояла в следующем. В этом драйвере есть статические переменные, значения которых можно установить системным вызовом ioctl. Этот системный вызов делает программа servicemanager, запускаемая в процессе инициализации Android. Драйвер binder проверяет, инициализированы ли эти переменные и, если да, то возвращает ошибку, что приводит к завершению программы servicemanager, а поскольку эта программа необходима для работы Android – то и к прекращению инициализации Android.

Таким образом, потребовалось создавать экземпляры упомянутых выше переменных для каждого контейнера и обращаться к экземпляру виртуального состояния, принадлежащему контейнеру, в контексте которого был сделан системный вызов ioctl().

Виртуализация периферийных устройств

Большой задачей виртуализации Android является виртуализация периферийных устройств и мультиплексирование потоков данных. Почти везде мы использовали один и тот же подход, который можно назвать виртуализацией состояний.

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

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

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

  • Физическое устройство подменяется на виртуальное, благодаря чему все запросы Android к физическому устройству направляются в виртуальное устройство.
  • Виртуальное устройство выполняет часть запросов Android на физическом устройстве, а часть виртуально, например, изменяя виртуальное состояние в соответствии со своей политикой.
  • Виртуальное устройство имеет тот же интерфейс и поведение, что и физическое.
  • Интерфейс должен быть максимально совместимым со всеми доступными версиями Android;
  • Интерфейс должен быть единственным и использоваться для всех задач доступа к устройству.

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

Дисплей

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

Согласно Android Porting Guide, для доступа к экрану используется драйвер типа Linux Framebuffer, и для портирования необходимо реализовать данный драйвер. Linux framebuffer имеет простой интерфейс, состоящий из двух десятков функций, большинство из которых имеют реализацию по умолчанию. Драйвер используется из пространства пользователя.

Перепрограммирование MMU GPU

Кроме драйвера фреймбуфера к физическому экрану имеет доступ GPU. Чтобы неактивные ОС не отображали свой UI на экране, требуется подменить операцию отображения их кадров. Выяснилось, что в случае с подопытными Google Nexus S и Samsung Galaxy SII экраны смартфонов копируют кадр для отображения из RAM при помощи DMA. Драйвер фреймбуфера сообщает экрану адрес, по которому находится кадр, предназначенный для отображения (память экрана). Таким образом, операция отображения кадра на экране выглядит как операция записи кадра в RAM устройства по определенному адресу.

Чтобы подменить операцию записи в RAM, требуется установить кем она может выполняться. В случае с обработкой графики запись кадра могут выполнять GPU или CPU. Современные GPU и CPU имеют в своем составе MMU. Для того чтобы кадры неактивных Android не записывались в память экрана, достаточно подменить отображение адресов в MMU так, чтобы адреса неактивных Android, отображаемые в память экрана, фактически указывали на обычный буфер в RAM устройства. Назовем его теневым буфером.

Теневой фреймбуфер

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

Если выделить теневой буфер для каждого Android, то при переключении активной ОС достаточно скопировать кадр на экране в теневой буфер предыдущего активного Android. Чтобы отобразить Android, ставший активным, требуется скопировать содержимое его теневого буфера в память экрана. Но теневой буфер занимает от 2 до 4 Мб физической памяти. Имея возможность вызывать перерисовку кадра при переключении активного Android, можно избавиться от выделения теневого буфера для каждого Android, оставив единственный теневой буфер.

Такая возможность предоставляется механизмом fb_early_suspend. Теневой буфер теперь становится не местом хранения кадра Android, а местом куда все неактивные ОС пишут свою картинку. Кроме того благодаря наличию MMU можно сократить размер теневого буфера до одной страницы физической памяти, отобразив все 2−4 Мб адресов в единственную страницу.

Виртуальный драйвер Linux Framebuffer

Типичный сценарий использования драйвера Linux Framebuffer заключается в отображении памяти экрана в адресное пространство пользовательского процесса системным вызовом mmap() и вызовах стандартных и специальных IOCTL.

Стандартные IOCTL обрабатываются стандартным обработчиком IOCTL подсистемы Linux Framebuffer в ядре ОС. Специальные IOCTL имеют нестандартную семантику и могут быть обработаны только драйвером физического Linux Framebuffer. Так как физический Linux Framebuffer был подменен на виртуальный, то mmap() и IOCTL вызываются у драйвера виртуального Linux Framebuffer.

Все вызовы mmap() обрабатываются полностью в драйвере виртуального Linux Framebuffer без использования драйвера физического Linux Framebuffer, т.к. семантика этого системного вызова стандартна. Процессы активного Android при вызове mmap() получают доступ к памяти экрана. Процессы неактивных Android при вызове mmap() получают доступ к теневому буферу вместо памяти экрана. Для всех запущенных Android создается виртуальное состояние драйвера Linux Framebuffer. Для активного Android все вызовы в драйвер виртуального Linux Framebuffer немедленно перенаправляются в драйвер физического Linux Framebuffer, который изменяет состояние физического драйвера Linux Framebuffer. Для неактивных Android стандартные IOCTL'ы изменяют их виртуальные состояния Linux Framebuffer. Нестандартные IOCTL'ы для неактивных Android возвращают ошибку. На устройствах Google Nexus S и Samsung Galaxy SII этого было достаточно.

Промежуточные выводы

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

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

Автор: master1981

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js