Эволюция системы обновления Android

Индиана устанавливает «новый активный раздел»: меняет золотого идола на мешок с песком в легендарной сцене из фильма «Индиана Джонс: В поисках утраченного ковчега» Индиана устанавливает «новый активный раздел»: меняет золотого идола на мешок с песком в легендарной сцене из фильма «Индиана Джонс: В поисках утраченного ковчега» 

В этой статье мы рассмотрим все возможные варианты обновления прошивки на устройствах под управлением Fuchsia Android. Особое внимание уделим самому популярному способу — обновлению по воздуху или OTA (over-the-air) — и расскажем об этапах его развития.

Итак, как можно обновить Android на мобильных устройствах? Занимаясь разработкой ТВ-приставок под управлением этой ОС, мы определили для себя 4 способа, отбросив совсем уж экзотические варианты:

  1. перепрошивка flash-памяти через аппаратный интерфейс JTAG (если есть);

  2. перепрошивка flash-памяти с использованием загрузчика (bootloader);

  3. обновление через Recovery Mode;

  4. OTA (over-the-air).

Рассмотрим подробнее каждый из вариантов.

1. Обновление Android через JTAG-интерфейс

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

Однако обычно JTAG-интерфейс присутствует только на отладочных платах, что сильно сужает область применения этого  варианта обновится.

2. Обновление Android через Recovery Mode

Обычно загрузчик является проприетарным, его разрабатывает производитель чипа. Именно bootloader инициализирует доверенную среду выполнения (TEE, trusted execution environment) и проверяет целостность разделов boot и recovery перед переносом выполнения в ядро Linux. Сам загрузчик часто является составным, часть его уровней может быть открытой (например, на базе U-boot), а часть — проприетарной.

Bootloader Android позволяет перепрошивать flash-память устройства подготовленными образами разделов. Для этого используется протокол fastboot либо его аналог (в случае Amlogic это будет протокол WorldCup Device). Fastboot, как и его аналог WorldCup Device, — это протокол взаимодействия с bootloader через USB-интерфейс или локальную сеть Ethernet.

Для перепрошивки необходимо подключить устройство через USB к хосту (есть вариант использовать LAN Ethernet), перевести загрузчик (bootloader) в специальный update-режим и в этом режиме перепрошить flash-память устройства.

Плюсы и минусы данного метода всё те же, что и для JTAG: так как обновление проходит без участия самой системы Android, при перепрошивке нет ограничений, связанных с версией системы/сборки или ключами безопасности.

Но, как всегда, есть одно НО. :-) Bootloader должен быть разблокирован, а это значит, что мы можем перепрошить сам загрузчик или разделы устройства. Блокировка/разблокировка производится командой fastboot flashing lock/unlock, но для выполнения этой команды может понадобится пароль, установленный тем, кто добрался до этого устройства раньше вас (обычно это производитель).

3. Обновление Android через Recovery Mode и OTA

Если первые два варианта обновления оставались неизменными на протяжении всего времени развития Android, то следующие два варианта — обновление через Recovery Mode и OTA —  реализуются средствами самой Android и эволюционировали вместе со всей ОС.

Стоит упомянуть, что Recovery Mode и OTA — это два различных варианта вызова движка обновления Android.

Recovery или non-A/B System Updates

Recovery и движок обновления updater (bootable/recovery/updater) — это как раз то, с чего началась система обновления Android (располагается в bootable/recovery в дереве исходников AOSP).

Схема обновления Recovery (или non-A/B System Updates)  задействует специальный раздел восстановления (Recovery), где содержится специальная ОС на основе ядра Linux. Эта ОС на базе Linux содержит программное обеспечение для распаковки загруженного образа обновления и его применения к другим разделам. Так и проходит обновление Android.

Пример разметки flash-памяти на устройстве с Android 6.0:

Карта разделов Android 6.0.1

[mmcblk0p01] bootloader offset 0×000000000000, size 0×000000400000

[mmcblk0p02] reserved offset 0×000002400000, size 0×000004000000

[mmcblk0p03] cache offset 0×000006c00000, size 0×000020000000

[mmcblk0p04] env offset 0×000027400000, size 0×000000800000

[mmcblk0p05] logo offset 0×000028400000, size 0×000002000000

[mmcblk0p06] recovery offset 0×00002ac00000, size 0×000002000000

[mmcblk0p07] rsv offset 0×00002d400000, size 0×000000800000

[mmcblk0p08] tee offset 0×00002e400000, size 0×000000800000

[mmcblk0p09] crypt offset 0×00002f400000, size 0×000002000000

[mmcblk0p10] misc offset 0×000031c00000, size 0×000002000000

[mmcblk0p11] instaboot offset 0×000034400000, size 0×000020000000

[mmcblk0p12] boot offset 0×000054c00000, size 0×000002000000

[mmcblk0p13] system offset 0×000057400000, size 0×000060000000

[mmcblk0p14] data offset 0×0000b7c00000, size 0×0002ec200000

Сам процесс обновления происходит в два этапа:

  1. после загрузки с раздела Recovery происходит обновления всех остальных разделов Android;

  2. и уже после перезагрузки и запуска новой версии Android происходит обновление раздела Recovery.

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

Обновиться по схеме Recovery можно как локально, выбрав в bootloader режим Recovery Mode и запустив движок обновления updater через меню Recovery Mode, либо удаленно, через OTA, когда приложение, работающее в Android, вызывает тот же updater из Java. И как раз при таком удаленном запуске можно организовать массовое обновление целой серии устройств. Этот вариант используют операторы цифрового ТВ при обновлении своих абонентских ТВ-приставок.

Сам раздел Recovery при non-A/B-схеме обновления является физическим разделом во flash-памяти. С появлением A/B-схемы раздел Recovery переместился на RAM-диск в оперативной памяти устройства, но возможность сделать его отдельным физическим разделом так же осталась.

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

Одним из важных недостатков схемы Recovery или non-A/B System Updates является то, что при любом сбое во время обновления или битой прошивке мы получаем пусть и не «кирпич» (с раздела Recovery всё еще можно запустить устройство в Recovery Mode), но всё же не полнофункциональное и требующее восстановления устройство.

С этим, видимо, решено было что-то делать, потому что следующим этапом эволюции системы обновления стало бесшовное обновление (seamless updates) или A/B-схема обновления. 

Бесшовное обновление или A/B-схема

Эта возможность появилась в Android 7.0, она реализована в новом движке update_engine, который располагается в system/update_engine в дереве исходников AOSP.

Главной особенностью A/B-схемы стало то, что в случае сбоев при обновлении можно загрузится с предыдущей рабочей версии системы Android. Flash-память устройства содержит дублирующиеся системные разделы или слоты (slot A и B), отсюда и название — A/B system updates (вечная проблема с выбором названий). За выбор слота для загрузки (A или B) отвечает bootloader, анализируя состояние слотов.

Принцип бесшовного обновления Android по A/B-схеме (активный раздел отмечен птичкой)Принцип бесшовного обновления Android по A/B-схеме (активный раздел отмечен птичкой)

Итак, как же происходит обновление:

1) Загружая систему, например, со слотов A, мы скачиваем и прошиваем обновления на слоты B.

2) После перезагрузки со слотов B мы проверяем работоспособность системы, и, если все ОК, сообщаем bootloader, что обновление прошло успешно.

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

На официальном сайте для разработчиков — Android Source — этот процесс расписан более детально в 9 шагах, также там объясняется, как все работает после перезагрузки.

Особенность бесшовной A/B-схемы обновление — это «съедение» большего объема flash- памяти. Насколько большего? Это можно оценить по приведенным ниже схемам разделов для Android 9.0. Как уже упоминалось ранее, разработчик может выбирать, какую из схем — A/B или non-A/B — применять в конфигурации системы.

Карта разделов Android P (recovery)

[mmcblk0p01] bootloader offset 0×000000000000, size 0×000000400000

[mmcblk0p02] reserved offset 0×000002400000, size 0×000004000000

[mmcblk0p03] cache offset 0×000006c00000, size 0×000046000000

[mmcblk0p04] env offset 0×00004d400000, size 0×000000800000

[mmcblk0p05] logo offset 0×00004e400000, size 0×000000800000

[mmcblk0p06] recovery offset 0×00004f400000, size 0×000001800000

[mmcblk0p07] misc offset 0×000051400000, size 0×000000800000

[mmcblk0p08] dtbo offset 0×000052400000, size 0×000000800000

[mmcblk0p09] cri_data offset 0×000053400000, size 0×000000800000

[mmcblk0p10] param offset 0×000054400000, size 0×000001000000

[mmcblk0p11] boot offset 0×000055c00000, size 0×000001000000

[mmcblk0p12] rsv offset 0×000057400000, size 0×000001000000

[mmcblk0p13] metadata offset 0×000058c00000, size 0×000001000000

[mmcblk0p14] vbmeta offset 0×00005a400000, size 0×000000200000

[mmcblk0p15] tee offset 0×00005ae00000, size 0×000002000000

[mmcblk0p16] vendor offset 0×00005d600000, size 0×000040000000

[mmcblk0p17] odm offset 0×00009de00000, size 0×000008000000

[mmcblk0p18] system offset 0×0000a6600000, size 0×000050000000

[mmcblk0p19] product offset 0×0000f6e00000, size 0×00000800000

Карта разделов Android P (A/B-схема)

[mmcblk0p01] bootloader offset 0×000000000000, size 0×000000400000

[mmcblk0p02] reserved offset 0×000002400000, size 0×000004000000

[mmcblk0p03] cache offset 0×000006c00000, size 0×000000000000

[mmcblk0p04] env offset 0×000007400000, size 0×000000800000

[mmcblk0p05] logo offset 0×000008400000, size 0×000000800000

[mmcblk0p06] boot_a offset 0×000009400000, size 0×000001000000

[mmcblk0p07] misc offset 0×00000ac00000, size 0×000000800000

[mmcblk0p08] dtbo_a offset 0×00000bc00000, size 0×000000800000

[mmcblk0p09] dtbo_b offset 0×00000cc00000, size 0×000000800000

[mmcblk0p10] cri_data offset 0×00000dc00000, size 0×000000800000

[mmcblk0p11] param offset 0×00000ec00000, size 0×000001000000

[mmcblk0p12] boot_b offset 0×000010400000, size 0×000001000000

[mmcblk0p13] rsv offset 0×000011c00000, size 0×000001000000

[mmcblk0p14] metadata_a offset 0×000013400000, size 0×000001000000

[mmcblk0p15] metadata_b offset 0×000014c00000, size 0×000001000000

[mmcblk0p16] vbmeta_a offset 0×000016400000, size 0×000000200000

[mmcblk0p17] vbmeta_b offset 0×000016e00000, size 0×000000200000

[mmcblk0p18] tee offset 0×000017800000, size 0×000002000000

[mmcblk0p19] vendor_a offset 0×00001a000000, size 0×000040000000

[mmcblk0p20] vendor_b offset 0×00005a800000, size 0×000040000000

[mmcblk0p21] odm_a offset 0×00009b000000, size 0×000008000000

[mmcblk0p22] odm_b offset 0×0000a3800000, size 0×000008000000

[mmcblk0p23] system_a offset 0×0000ac000000, size 0×000050000000

[mmcblk0p24] system_b offset 0×0000fc800000, size 0×000050000000

[mmcblk0p25] product_a offset 0×00014d000000, size 0×000008000000

[mmcblk0p26] product_b offset 0×000155800000, size 0×000008000000

[mmcblk0p27] data offset 0×00015e000000, size 0×000245e00000

Если сравнить эти две конфигурации, то можно заметить, что раздел data при A/B-схеме меньше на 1,6 ГБ, и это цена дублирующихся системных разделов. Много это или мало — каждый решает сам, ориентируясь на характеристики своего устройства/проекта.

Проект Treble

Следующие изменения в системе обновления произошли в Android 8.0. Начиная с Android O (8.0) и продолжая в Android P (9.0), Google реализует свой проект Treble. Идея проекта состоит в том, чтобы упростить технологический процесс создания обновления для андроид-устройства. Google предложил разделить с помощью неизменных интерфейсов части прошивки,   созданием которых занимаются разные компании. Процесс разработки прошивки для конкретного девайса можно упрощенно разделить на следующие шаги:

  1. команда Android создает новую версию своей OC;

  2. разработчик чипа или системы на кристалле (Silicon Manufacturer) создает аппаратно-зависимые патчи для запуска этой версии Android на своих платах;

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

Проект Treble разделяет ОС Android с дополнениями от производителей чипов/СнК и код разработчика конечного устройства, так что теперь операционная система может получать обновления без реализации изменений от производителя устройства.

Разделение происходит как с помощью программного интерфейса (переход с Hardware Abstraction Layer 1.0 на HAL2.0), так и за счет выделения отдельных разделов на flash-памяти для Silicon Manufacturer и Vendor (выше в карте разделов Android 9.0 можно увидеть разделы odm, vendor, product).

Переход с HAL1.0 на HAL2.0 заключается в отказе от прямого связывания с системными библиотеками. Вместо этого, используя IPC Binder, можно подключаться к системным сервисам.

81d3182395a91d03f350760398afba01

И еще одно небольшое, но полезное изменение: начиная с Android 8.0, в update_engine добавлена поддержка потоковых обновлений по A/B-схеме, в ходе которых идет прямая запись в слот B без необходимости промежуточного хранения данных в /data.  Для таких потоковых обновлений практически не требуется временное хранилище, достаточно всего лишь 100 килобайт для сохранения метаданных.

При этом необходимо, чтобы http-сервер, используемый для скачивания обновления, поддерживал HTTP range requests или другими словами докачку.

Проект Mainline

Следующим серьезным этапом в развитии системы обновления Android стал проект Mainline. Реализация этого проекта началась с Android 10.0 и продолжилась в текущем Android 11.0.

Проект Mainline позволяет обновлять отдельные системные компоненты без обновления ОС целиком. Нужные данные загружаются через Google Play отдельно от OTA-обновления прошивки от производителя. Предполагается, что прямая доставка обновлений, не привязанных к оборудованию частей Android, позволит существенно сократить время получения обновлений, увеличить оперативность исправления уязвимостей и снизить зависимость от производителей устройств в поддержке безопасности ОС.

Для реализации проекта Mainline выбранные компонентов системы Android преобразуется в модули. Часть этих модулей имеет старый формат APK, а часть конвертируется в новый APEX-формат, который отличается от APK возможностью применения на раннем этапе загрузки системы. На случай возможных сбоев предусмотрен режим отката изменений.

С APEX-пакетами работает системный сервис APEX manager (apexd). Это нативный сервис, который после проверки распаковывает APEX-пакет в пользовательское пространство на диске и добавляет запись о нем в свою базу данных. При следующей загрузке системы APEX manager проверяет все пакеты из базы данных, создает loop-устройство для ext4-образа каждого APEX-пакета и монтирует его по пути /apex/name@ver.

Модули с обновлениями изначально будут поставляться с открытым кодом, они будут сразу доступны в репозиториях AOSP (Android Open Source Project) и смогут включать улучшения и исправления, подготовленные сторонними участниками.

В рамках проекта Mainline в Android 10 было добавлено 13 обновляемых модулей, а в Android 11 в дополнение к уже существующим прибавилось еще 11 модулей.

cc3f4d2ddc5e06cf8bd7bb3c29ad1871

Схема Virtual A/B

Также в Android 11 к схемам non-A/B и A/B была добавлена схема Virtual A/B. Этот новый механизм обновления сочетает преимущества обоих предшественников, он обеспечивает устойчивое к сбоям обновление устройства, задействуя при этом минимальный объем flash-памяти. Это стало возможным благодаря созданию снимков файловой системы (snapshot) с использованием технологии Device-mapper (подсистема ядра Linux, позволяющая создавать виртуальные блочные устройства) и Dynamic Partitions.

Dynamic Partitions — это система организации динамических разделов для Android. С ее помощью можно создавать, изменять размер или уничтожать разделы прямо в процессе обновления по воздуху (OTA). При использовании динамических разделов разработчикам больше не нужно беспокоиться о размере отдельных разделов, таких как system, vendor и product. Вместо них на устройстве выделяется суперраздел, внутри которого можно динамически изменять размер подразделов. Больше нет необходимости оставлять свободное пространство для будущих OTA-обновлений внутри отдельных образов разделов. Оставшееся свободное место в суперразделе теперь доступно для всех динамических подразделов.

И в заключении последние слухи конца 2020 года — вишенка на торте. Google конвертирует Android Runtime в модуль Mainline. Android Runtime или ART — это среда выполнения Android-приложений, включающая компиляцию байт-кода приложения в машинные инструкции. Так что есть вероятность, что уже в Android 12 можно будет обновить ART через GooglePlay, установив APEX-пакет.

Также, вероятно, система обновления Android мигрирует в Fuchsia, новую ОС Google, которая сейчас находится в процессе разработки. Они традиционно копируют удачные решения в своих программных продуктах. Так, например, update_engine для A/B-схемы, который применяется сейчас в Android, используется в еще одной ОC Google — Chrome OS. Или еще один пример: в Fuchsia предлагается библиотека Machina, которая позволяет запускать Linux-программы в специальной изолированной виртуальной машине по аналогии с тем, как организован запуск Linux-приложений в той же Chrome OS.

Желаем всем успешных обновлений!  

P.S. Как там было в «Индиане Джонсе»?
— Как вы меня узнали?
— У вас глаза вашего отца.
— И уши моей матери. Но все остальное принадлежит вам.

© Habrahabr.ru