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

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

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

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

  • ОТА-обновления могут происходить во время работы системы, не прерывая работу пользователя. Пользователи могут продолжать использовать свои устройства во время 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, но им потребуется предоставить собственный клиент.

Для 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 еще не помечен как успешный, и загрузчик должен предпринять некоторое количество попыток загрузки из слота 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() . Текущий слот всегда помечается как успешный в начале обновления, чтобы предотвратить возврат загрузчика к неиспользуемому слоту, который вскоре будет содержать неверные данные. Если система достигла точки, когда она может начать применять обновление, текущий слот помечается как успешный, даже если другие основные компоненты не работают (например, пользовательский интерфейс в цикле сбоя), поскольку можно подтолкнуть новое программное обеспечение, чтобы исправить эти проблемы.

Полезные данные обновления — это непрозрачный большой двоичный объект с инструкциями по обновлению до новой версии. Полезная нагрузка обновления состоит из следующего:
  • Метаданные . Относительно небольшая часть полезной нагрузки обновления, метаданные содержат список операций для создания и проверки новой версии в целевом слоте. Например, операция может распаковать определенный большой двоичный объект и записать его в определенные блоки в целевом разделе или прочитать из исходного раздела, применить двоичное исправление и записать в определенные блоки в целевом разделе.
  • Дополнительные данные . Как основная часть полезной нагрузки обновления, дополнительные данные, связанные с операциями, состоят из сжатого большого двоичного объекта или двоичного исправления в этих примерах.
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. Эта проверка начинается до zygote, чтобы службы Java не вносили какие-либо необратимые изменения, которые могли бы помешать безопасному откату. Во время этого процесса загрузчик и ядро ​​​​также могут вызвать перезагрузку, если проверенная загрузка или dm-verity обнаружат какое-либо повреждение. После завершения проверки update_verifier помечает загрузку как успешную.

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