Обновления системы 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 Verified Boot не требует обновлений A/B.)

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

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

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

  • Решите, когда выполнять обновление. Поскольку обновления 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/ . Пользователи с идентификатором группы журналов смогут получить доступ к журналам файловой системы.

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

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

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

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

На пользовательских устройствах не всегда достаточно места на /data для загрузки пакета обновлений. Поскольку ни OEM-производители, ни пользователи не хотят тратить место на разделе /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), он должен быть совместим со старым ядром (например, 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 , устанавливает разрешение на доступ перед перезагрузкой устройства и удаляет извлеченный файл после успешной загрузки системы в новую версию.