Обновления системы A/B (бесшовные)

Устаревшие обновления системы A/B, также известные как бесшовные обновления, гарантируют сохранение работоспособной загрузочной системы на диске во время беспроводного обновления (OTA) . Такой подход снижает вероятность выхода устройства из строя после обновления, что означает меньше замен устройств и перепрошивок в ремонтных и гарантийных центрах. Другие коммерческие операционные системы, такие как ChromeOS , также успешно используют A/B-обновления.

Дополнительную информацию об обновлениях системы A/B и о том, как они работают, см. в разделе Выбор раздела (слоты) .

Обновления системы A/B обеспечивают следующие преимущества:

  • Обновления OTA могут выполняться во время работы системы, не прерывая работу пользователя. Пользователи могут продолжать использовать свои устройства во время обновления OTA — единственный простой во время обновления — это перезагрузка устройства с использованием обновлённого раздела диска.
  • После обновления перезагрузка занимает не больше времени, чем обычная перезагрузка.
  • Если обновление OTA не будет применено (например, из-за неисправной прошивки), пользователь не пострадает. Он продолжит использовать старую ОС и сможет повторно попытаться выполнить обновление.
  • Если обновление OTA установлено, но не загружается, устройство перезагрузится в старый раздел и останется работоспособным. Клиент может повторить попытку обновления.
  • Любые ошибки (например, ошибки ввода-вывода) затрагивают только неиспользуемый набор разделов и могут быть устранены повторно. Вероятность возникновения таких ошибок также снижается, поскольку нагрузка ввода-вывода намеренно занижена, чтобы не снижать удобство работы пользователя.
  • Обновления можно передавать потоком на устройства A/B, устраняя необходимость загрузки пакета перед его установкой. Потоковая передача означает, что пользователю не требуется достаточно свободного места для хранения пакета обновления в каталоге /data или /cache .
  • Раздел кэша больше не используется для хранения пакетов обновлений OTA, поэтому нет необходимости следить за тем, чтобы раздел кэша был достаточно большим для будущих обновлений.
  • dm-verity гарантирует, что устройство загрузит неповреждённый образ. Если устройство не загружается из-за ошибки OTA или проблемы с dm-verity, оно может перезагрузиться со старым образом. ( Проверенная загрузка Android не требует обновлений A/B.)

Об обновлениях системы A/B

A/B-обновления требуют изменений как в клиентской, так и в системной среде. Однако сервер пакетов OTA не должен требовать изменений: пакеты обновлений по-прежнему передаются по HTTPS. Для устройств, использующих инфраструктуру OTA Google, все системные изменения находятся в AOSP, а клиентский код предоставляется сервисами Google Play. OEM-производители, не использующие инфраструктуру OTA Google, смогут повторно использовать системный код AOSP, но им потребуется предоставить собственный клиент.

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

  • Определите, когда устанавливать обновление. Поскольку A/B-обновления происходят в фоновом режиме, они больше не инициируются пользователем. Чтобы не отвлекать пользователей, рекомендуется планировать обновления, когда устройство находится в режиме обслуживания, например, ночью, и подключено к Wi-Fi. Однако ваш клиент может использовать любые эвристические алгоритмы по вашему выбору.
  • Проверьте наличие обновления на серверах OTA-пакетов. Он должен быть практически таким же, как и код вашего текущего клиента, за исключением того, что вам нужно будет сообщить, что устройство поддерживает A/B. (Клиент Google также включает кнопку «Проверить сейчас» , позволяющую пользователям проверить наличие последнего обновления.)
  • Вызовите update_engine с URL-адресом HTTPS для вашего пакета обновлений, предполагая, что он доступен. update_engine обновит необработанные блоки на неиспользуемом в данный момент разделе по мере потоковой передачи пакета обновлений.
  • Сообщайте об успешной или неудачной установке на ваши серверы на основе кода результата update_engine . Если обновление установлено успешно, update_engine сообщит загрузчику о необходимости загрузки новой ОС при следующей перезагрузке. Загрузчик откатится к старой ОС, если новая ОС не загрузится, поэтому никаких действий со стороны клиента не требуется. В случае сбоя обновления клиент должен решить, когда (и стоит ли) повторять попытку, основываясь на подробном коде ошибки. Например, хороший клиент может определить, что частичный (diff) пакет OTA не работает, и попробовать вместо него полный пакет OTA.

По желанию клиент может:

  • Отображается уведомление с предложением перезагрузить устройство. Если вы хотите реализовать политику, побуждающую пользователя регулярно обновляться, это уведомление можно добавить в клиент. Если клиент не предлагает пользователям обновиться, обновление всё равно будет установлено при следующей перезагрузке. (В клиенте Google предусмотрена настраиваемая задержка обновления для каждого обновления.)
  • Показывать пользователям уведомление о том, загрузили ли они новую версию ОС или это должно было произойти, но они вернулись к старой версии ОС. (Клиент Google обычно не делает ни того, ни другого.)

Со стороны системы обновления системы A/B влияют на следующее:

  • Выбор разделов (слотов), демон update_engine и взаимодействие загрузчика (описано ниже)
  • Процесс сборки и генерация пакета обновления OTA (описано в разделе «Реализация обновлений A/B» )

Выбор раздела (слотов)

Обновления системы A/B используют два набора разделов, называемых слотами (обычно слот A и слот B). Система запускается из текущего слота, а разделы в неиспользуемом слоте не доступны работающей системе в обычном режиме работы. Такой подход делает обновления отказоустойчивыми, сохраняя неиспользуемый слот в качестве резервного: если во время обновления или сразу после него возникает ошибка, система может вернуться к старому слоту и продолжить работу. Для достижения этой цели ни один раздел, используемый текущим слотом, не должен обновляться в рамках обновления OTA (включая разделы, для которых существует только одна копия).

Каждый слот имеет атрибут «загрузочный» , который указывает, содержит ли слот корректную систему, с которой устройство может загрузиться. Текущий слот является загрузочным, когда система работает, но другой слот может содержать старую (но всё ещё корректную) версию системы, более новую версию или недействительные данные. Независимо от текущего слота, один слот является активным (тот, с которого загрузчик будет загружаться при следующей загрузке) или предпочтительным .

Каждый слот также имеет атрибут успешного выполнения , устанавливаемый пользовательским пространством, который имеет значение только в том случае, если слот также является загрузочным. Успешно выполненный слот должен иметь возможность загружаться, запускаться и обновляться. Загрузочный слот, который не был отмечен как успешный (после нескольких попыток загрузки с него), должен быть отмечен загрузчиком как незагружаемый, включая изменение активного слота на другой загрузочный слот (обычно на слот, работавший непосредственно перед попыткой загрузки в новый активный слот). Конкретные детали интерфейса определены в boot_control.h .

Обновление демона движка

Обновления системы A/B используют фоновый демон update_engine для подготовки системы к загрузке новой обновлённой версии. Этот демон может выполнять следующие действия:

  • Выполните чтение из текущих разделов слота A/B и запишите любые данные в неиспользуемые разделы слота A/B, как указано в пакете OTA.
  • Вызовите интерфейс boot_control в предопределенном рабочем процессе.
  • Запустите программу после установки из нового раздела после записи всех неиспользуемых разделов слотов, как указано в пакете OTA. (Подробности см. в разделе После установки ).

Поскольку демон update_engine не участвует в самом процессе загрузки, его возможности во время обновления ограничены политиками и функциями SELinux в текущем слоте (такие политики и функции не могут быть обновлены, пока система не загрузится в новую версию). Для поддержания надёжной работы системы процесс обновления не должен изменять таблицу разделов, содержимое разделов в текущем слоте или содержимое разделов, отличных от A/B, которые нельзя удалить при сбросе к заводским настройкам.

Обновление исходного кода движка

Исходный код update_engine находится в system/update_engine . Файлы A/B OTA dexopt распределяются между installd и менеджером пакетов:

Рабочий пример можно найти в /device/google/marlin/device-common.mk .

Обновить журналы двигателя

Для версий Android 8.x и более ранних логи update_engine можно найти в logcat и в отчёте об ошибке. Чтобы сделать логи update_engine доступными в файловой системе, внесите следующие изменения в вашу сборку:

Эти изменения сохраняют копию последнего журнала update_engine в /data/misc/update_engine_log/update_engine. YEAR - TIME . Помимо текущего журнала, пять последних журналов сохраняются в /data/misc/update_engine_log/ . Пользователи с идентификатором группы журналов смогут получить доступ к журналам файловой системы.

Взаимодействие загрузчика

Уровень HAL boot_control используется модулем update_engine (и, возможно, другими демонами) для указания загрузчику, с чего следует производить загрузку. Ниже приведены распространённые примеры сценариев и связанных с ними состояний:

  • Обычный случай : система работает в текущем слоте (A или B). Обновления пока не применялись. Текущий слот системы является загрузочным, успешно загружен и активен.
  • Выполняется обновление : система работает со слота B, поэтому слот B является загрузочным, успешным и активным. Слот A был отмечен как неподходящий для загрузки, поскольку его содержимое обновляется, но ещё не завершено. Перезагрузка в этом состоянии должна продолжить загрузку со слота B.
  • Обновление применено, ожидается перезагрузка : система запущена из слота B, слот B загружается успешно, но слот A отмечен как активный (и, следовательно, отмечен как загрузочный). Слот A ещё не отмечен как успешный, и загрузчик должен предпринять несколько попыток загрузки из слота A.
  • Система перезагружена для нового обновления : система впервые запущена из слота A, слот B по-прежнему загружается и успешно загрузился, в то время как слот A только загружается и всё ещё активен, но не успешно. Демон пользовательского пространства update_verifier должен отметить слот A как успешный после выполнения некоторых проверок.

Поддержка потокового обновления

На пользовательских устройствах не всегда достаточно места в разделе /data для загрузки пакета обновлений. Поскольку ни производители оригинального оборудования, ни пользователи не хотят тратить место в разделе /cache , некоторые пользователи обходятся без обновлений, поскольку устройству негде хранить пакеты обновлений. Для решения этой проблемы в Android 8.0 была добавлена ​​поддержка потоковых обновлений A/B, которые записывают блоки непосредственно в раздел B по мере загрузки, без необходимости сохранять блоки в разделе /data . Потоковые обновления A/B практически не требуют временного хранилища и требуют достаточного объёма для хранения примерно 100 КБ метаданных.

Чтобы включить потоковые обновления в Android 7.1, выберите следующие исправления:

Эти исправления необходимы для поддержки потоковых обновлений A/B в Android 7.1 и более поздних версиях, независимо от того, используются ли Google Mobile Services (GMS) или любой другой клиент обновлений.

Жизнь A/B-обновления

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

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

После того, как полезная нагрузка станет доступна, процесс обновления будет следующим:

Шаг Деятельность
1 Текущий слот (или «исходный слот») помечается как успешный (если еще не помечен) с помощью markBootSuccessful() .
2 Неиспользуемый слот (или «целевой слот») помечается как неподходящий для загрузки с помощью вызова функции setSlotAsUnbootable() . Текущий слот всегда помечается как успешный в начале обновления, чтобы предотвратить возврат загрузчика к неиспользуемому слоту, в котором вскоре появятся недействительные данные. Если система достигла точки, в которой можно начать установку обновления, текущий слот помечается как успешный, даже если другие важные компоненты повреждены (например, пользовательский интерфейс в цикле сбоев), поскольку для исправления этих проблем можно установить новое программное обеспечение.

Полезная нагрузка обновления представляет собой непрозрачный блок с инструкциями по обновлению до новой версии. Полезная нагрузка обновления состоит из следующих элементов:
  • Метаданные . Относительно небольшая часть полезной нагрузки обновления, метаданные содержат список операций для создания и проверки новой версии в целевом слоте. Например, операция может распаковать определённый двоичный объект и записать его в определённые блоки целевого раздела или прочитать данные из исходного раздела, применить двоичный патч и записать данные в определённые блоки целевого раздела.
  • Дополнительные данные . Как и основная часть полезной нагрузки обновления, дополнительные данные, связанные с операциями, в этих примерах состоят из сжатого двоичного двоичного объекта (BLOB-объекта) или двоичного патча.
3 Загружаются метаданные полезной нагрузки.
4 Для каждой операции, определенной в метаданных, по порядку связанные данные (если таковые имеются) загружаются в память, операция применяется, а связанная память сбрасывается.
5 Все разделы перечитываются и проверяются на соответствие ожидаемому хешу.
6 Выполняется этап послеустановки (если таковой имеется). В случае ошибки на любом этапе обновление завершается неудачей и повторяется, возможно, с другой полезной нагрузкой. Если все этапы до сих пор были выполнены успешно, обновление завершается успешно, и выполняется последний этап.
7 Неиспользуемый слот помечается как активный с помощью вызова setActiveBootSlot() . Пометка неиспользуемого слота как активного не означает, что загрузка будет завершена. Загрузчик (или сама система) может снова сделать слот активным, если не обнаружит успешного состояния.
8 Послеустановочный этап (описанный ниже) включает запуск программы из версии «нового обновления» при одновременной работе старой версии. Если он определён в пакете OTA, этот шаг обязателен , и программа должна вернуть код завершения 0 ; в противном случае обновление завершится ошибкой.
9 После того как система успешно загрузится достаточно глубоко в новый слот и завершит проверки после перезагрузки, текущий слот (ранее «целевой слот») помечается как успешный с помощью вызова markBootSuccessful() .

После установки

Для каждого раздела, для которого определен шаг послеустановки, update_engine монтирует новый раздел в указанное место и выполняет программу, указанную в OTA относительно смонтированного раздела. Например, если программа послеустановки определена как usr/bin/postinstall в системном разделе, этот раздел из неиспользуемого слота будет смонтирован в фиксированное место (например, /postinstall_mount ), и будет выполнена команда /postinstall_mount/usr/bin/postinstall .

Для успешной пост-установки старое ядро ​​должно уметь:

  • Смонтируйте новый формат файловой системы . Тип файловой системы не может быть изменён, если его поддержка отсутствует в старом ядре, включая такие детали, как алгоритм сжатия, используемый при использовании сжатой файловой системы (например, SquashFS).
  • Изучите формат программы после установки нового раздела . Если используется исполняемый файл в формате ELF (Executable and Linkable Format), он должен быть совместим со старым ядром (например, 64-битная новая программа, работающая на старом 32-битном ядре, если архитектура перешла с 32-битных сборок на 64-битные). Если загрузчику ( ld ) не указано использовать другие пути или собрать статический двоичный файл, библиотеки будут загружены из образа старой системы, а не из новой.

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

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

Выбранная программа послеустановки запускается в контексте SELinux postinstall . Все файлы в новом смонтированном разделе будут помечены тегом postinstall_file , независимо от их атрибутов после перезагрузки в новую систему. Изменение атрибутов SELinux в новой системе не повлияет на этап послеустановки. Если программе послеустановки требуются дополнительные разрешения, их необходимо добавить в контекст послеустановки.

После перезагрузки

После перезагрузки update_verifier запускает проверку целостности с помощью dm-verity. Эта проверка запускается до zygote, чтобы избежать внесения службами Java необратимых изменений, которые могли бы помешать безопасному откату. Во время этого процесса загрузчик и ядро ​​также могут инициировать перезагрузку, если проверенная загрузка или dm-verity обнаружат какие-либо повреждения. После завершения проверки update_verifier отмечает успешную загрузку.

update_verifier будет читать только блоки, перечисленные в /data/ota_package/care_map.txt , который входит в пакет A/B OTA при использовании кода AOSP. Клиент обновления системы Java, такой как GmsCore, извлекает care_map.txt , устанавливает разрешение доступа перед перезагрузкой устройства и удаляет извлечённый файл после успешной загрузки новой версии системы.