Обновления системы 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 . Файлы dexopt A/B OTA разделены между 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 помечен как незагружаемый, поскольку содержимое слота A обновляется, но еще не завершено. Перезагрузка в этом состоянии должна продолжить загрузку из слота B.
  • Обновление применено, ожидается перезагрузка : система работает из слота B, слот B является загрузочным и работает успешно, но слот 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() . Текущий слот всегда помечается как успешный в начале обновления, чтобы предотвратить возврат загрузчика к неиспользуемому слоту, который вскоре будет иметь неверные данные. Если система достигла точки, когда она может начать применять обновление, текущий слот помечается как успешный, даже если другие основные компоненты сломаны (например, пользовательский интерфейс в аварийном цикле), поскольку можно выпустить новое программное обеспечение для исправления этих проблем. проблемы.

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

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

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

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

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

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

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

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

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

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

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