Устаревшие обновления системы A/B, также известные как параллельные обновления, гарантируют, что во время беспроводного обновления на диске останется работоспособная загрузочная система. Это снижает вероятность того, что устройство будет неактивным после обновления, а значит, уменьшает количество замен и перепрошивок в сервисных и гарантийных центрах. Другие коммерческие операционные системы, например ChromeOS, также успешно используют обновления A/B.
Подробнее об обновлениях системы A/B и их работе можно узнать в разделе Выбор раздела (слотов).
Обновления системы A/B имеют следующие преимущества:
- Беспроводные обновления могут выполняться во время работы системы, не прерывая пользователя. Пользователи могут продолжать использовать свои устройства во время обновления по воздуху. Единственный период простоя во время обновления – это когда устройство перезагружается в обновленный раздел диска.
- После обновления перезагрузка занимает не больше времени, чем обычно.
- Если обновление по беспроводной сети не удастся применить (например, из-за неправильной прошивки), это не повлияет на пользователя. Пользователь продолжит работать со старой версией ОС, а клиент сможет повторить попытку обновления.
- Если беспроводное обновление установлено, но устройство не загружается, оно перезагрузится в старый раздел и останется пригодным для использования. Клиент может повторить попытку обновления.
- Любые ошибки (например, ошибки I/O) влияют только на неиспользуемый набор разделов и могут быть устранены повторной попыткой. Кроме того, такие ошибки становятся менее вероятными, поскольку нагрузка на ввод-вывод намеренно снижена, чтобы не ухудшать качество работы.
-
Обновления можно передавать на устройства с разделами A/B, поэтому перед установкой не нужно скачивать пакет. При потоковой передаче пользователю не нужно иметь достаточно свободного места для хранения пакета обновлений на
/dataили/cache. - Раздел кеша больше не используется для хранения пакетов беспроводных обновлений, поэтому нет необходимости следить за тем, чтобы он был достаточно большим для будущих обновлений.
- dm-verity гарантирует, что устройство загрузит неповрежденный образ. Если устройство не загружается из-за ошибки OTA или dm-verity, оно может перезагрузиться со старым образом. ( проверка при запуске в Android не требует обновлений A/B).
Обновления системы A/B
Для A/B-обновлений требуются изменения как на стороне клиента, так и в системе. Однако сервер пакетов OTA не требует изменений: пакеты обновлений по-прежнему обслуживаются по протоколу HTTPS. Для устройств, использующих инфраструктуру OTA от Google, все системные изменения вносятся в AOSP, а клиентский код предоставляется сервисами Google Play. Производители, которые не используют инфраструктуру Google для беспроводного обновления, смогут повторно использовать системный код AOSP, но им потребуется предоставить собственный клиент.
Если производитель оригинального оборудования предоставляет собственный клиент, ему необходимо:
- Вы сами решаете, когда устанавливать обновления. Поскольку A/B-обновления выполняются в фоновом режиме, они больше не инициируются пользователем. Чтобы не мешать пользователям, рекомендуется планировать обновления на время, когда устройство находится в режиме обслуживания в фоновом режиме, например ночью, и подключено к Wi-Fi. Однако клиент может использовать любые эвристические методы.
- Проверьте серверы пакетов OTA и определите, доступно ли обновление. Он должен быть практически таким же, как существующий код клиента, за исключением того, что вам нужно будет указать, что устройство поддерживает A/B. (Клиент Google также включает кнопку Проверить сейчас, чтобы пользователи могли проверить наличие обновлений.)
-
Вызовите
update_engineс URL по HTTPS пакета обновлений, если он доступен.update_engineбудет обновлять необработанные блоки в неиспользуемом в данный момент разделе, передавая пакет обновления. -
Сообщайте на свои серверы об успешной или неудачной установке на основе кода результата
update_engine. Если обновление будет применено успешно,update_engineсообщит загрузчику, что при следующей перезагрузке нужно загрузить новую ОС. Если новая ОС не загрузится, загрузчик вернется к старой версии, поэтому никаких действий со стороны клиента не требуется. Если обновление не удалось, клиент должен решить, когда (и стоит ли) повторить попытку, на основе подробного кода ошибки. Например, хороший клиент может распознать, что частичный пакет OTA (diff) не работает, и попытаться использовать полный пакет OTA.
Клиент также может:
- Показать уведомление с просьбой перезагрузить устройство. Если вы хотите, чтобы пользователи регулярно обновляли приложение, добавьте в клиент уведомление. Если клиент не предложит пользователям выполнить обновление, оно будет установлено при следующей перезагрузке. (В клиенте Google есть настраиваемая задержка для каждого обновления.)
- Показывать пользователям уведомление о том, загрузилась ли новая версия ОС или произошел откат к старой версии. (Клиент Google обычно не делает ни того, ни другого.)
На стороне системы обновления A/B влияют на следующее:
-
Выбор раздела (слота), демон
update_engineи взаимодействие с загрузчиком (описано ниже). - Процесс сборки и создание пакета беспроводного обновления (описано в разделе Реализация A/B-обновлений).
Выбор раздела (слоты)
При обновлении системы A/B используются два набора разделов, которые называются слотами (обычно слот A и слот B). Система работает со слотом current, а разделы в слоте unused не используются работающей системой при нормальной работе. Такой подход обеспечивает устойчивость к сбоям, поскольку неиспользуемый слот является резервным. Если во время или сразу после обновления произойдет ошибка, система сможет вернуться к старому слоту и продолжить работу. Для этого ни один раздел, используемый текущим слотом, не должен обновляться в рамках беспроводного обновления (включая разделы, для которых существует только одна копия).
У каждого слота есть атрибут bootable, который указывает, содержит ли слот корректную систему, с которой может загрузиться устройство. Текущий слот загружается при запуске системы, а в другом слоте может находиться старая (но все ещё рабочая) версия системы, более новая версия или недействительные данные. Независимо от того, какой слот текущий, есть один активный слот (тот, с которого загрузчик будет загружаться при следующем запуске) или предпочтительный слот.
У каждого слота также есть атрибут successful, заданный пользовательским пространством. Он имеет значение, только если слот также является загрузочным. Успешно работающий слот должен быть способен загружаться, работать и обновляться самостоятельно. Загрузчик должен пометить как незагружаемый загрузочный слот, который не был отмечен как успешный (после нескольких попыток загрузки из него), в том числе переключить активный слот на другой загрузочный слот (обычно на слот, который работал непосредственно перед попыткой загрузки в новый активный слот). Подробная информация об интерфейсе приведена в
boot_control.h.
Демон службы обновлений
При обновлении системы A/B используется фоновый демон update_engine, который подготавливает систему к загрузке новой версии. Этот демон может выполнять следующие действия:
- Считывать данные из текущих разделов A/B и записывать их в неиспользуемые разделы A/B в соответствии с инструкциями из пакета OTA.
- Вызовите интерфейс
boot_controlв рамках заранее определенного рабочего процесса. - Запустите программу post-install из нового раздела после записи всех неиспользуемых разделов слота, как указано в пакете OTA. Подробнее о том, что делать после установки…
Поскольку демон update_engine не участвует в процессе загрузки, его возможности во время обновления ограничены правилами SELinux и функциями в текущем слоте. Эти правила и функции нельзя обновить, пока система не загрузится в новой версии. Чтобы система работала стабильно, в процессе обновления не следует изменять таблицу разделов, содержимое разделов в текущем слоте или содержимое разделов, не относящихся к A/B, которые нельзя очистить с помощью сброса до заводских настроек.
Источник службы обновлений
Источник update_engine находится в system/update_engine. Файлы dexopt для беспроводных обновлений A/B разделены между installd и менеджером пакетов:
-
frameworks/native/cmds/installd/ota* включает скрипт, выполняемый после установки, двоичный файл для chroot, клон installd, который вызывает dex2oat, скрипт, перемещающий артефакты после OTA, и файл rc для скрипта перемещения. -
frameworks/base/services/core/java/com/android/server/pm/OtaDexoptService.java(плюсOtaDexoptShellCommand) – это менеджер пакетов, который подготавливает команды dex2oat для приложений.
Рабочий пример можно найти в разделе /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/. Пользователи с идентификатором группы log смогут получить доступ к системным журналам.
Взаимодействие с загрузчиком операционной системы
boot_control HAL используется update_engine (и, возможно, другими демонами), чтобы указать загрузчику, откуда загружаться. Ниже приведены примеры распространенных сценариев и связанных с ними статусов.
- Обычный случай. Система работает из текущего слота, A или B. Обновления пока не применялись. Текущий слот системы является загрузочным, успешным и активным.
- Обновление выполняется. Система работает со слота B, поэтому он является загрузочным, активным и успешным. Слот A был помечен как незагружаемый, поскольку его содержимое обновляется, но ещё не завершено. При перезапуске в этом состоянии загрузка должна продолжиться из слота Б.
- Обновление применено, ожидается перезагрузка – система работает со слота Б, слот Б загружается и работает успешно, но слот А отмечен как активный (и поэтому отмечен как загружаемый). Слот A ещё не помечен как успешный, и загрузчик должен предпринять несколько попыток загрузки из слота A.
-
Система перезагружена с новым обновлением. Система впервые запущена из слота A. Слот B по-прежнему загружается успешно, а слот A – только загружается, но не работает. Демон пользовательского пространства,
update_verifier, должен пометить слот А как успешный после выполнения некоторых проверок.
Поддержка потоковых обновлений
На устройствах пользователей не всегда достаточно места на /data, чтобы скачать пакет обновления. Поскольку ни производители, ни пользователи не хотят тратить место на раздел /cache, некоторые пользователи не получают обновления, потому что на устройстве негде хранить пакет обновления. Чтобы решить эту проблему, в Android 8.0 добавлена поддержка потоковой передачи обновлений A/B, при которой блоки записываются непосредственно в раздел B по мере их скачивания, без необходимости хранить их на /data. Для потоковых A/B-обновлений почти не требуется временное хранилище. Достаточно места для примерно 100 КиБ метаданных.
Чтобы включить потоковые обновления в Android 7.1, выберите следующие исправления:
- Разрешить отмену запроса на разрешение прокси-сервера
- Исправлена ошибка, из-за которой передача данных завершалась при разрешении прокси-серверов
- Добавлен модульный тест для функции TerminateTransfer между диапазонами
- Очистка RetryTimeoutCallback()
Эти исправления необходимы для поддержки потоковой передачи обновлений A/B в Android 7.1 и более поздних версий при использовании сервисов Google для мобильных устройств (GMS) или любого другого клиента обновлений.
Жизненный цикл обновления A/B
Процесс обновления начинается, когда для скачивания становится доступен пакет OTA (в коде он называется полезной нагрузкой). Правила на устройстве могут отложить скачивание и применение полезной нагрузки в зависимости от уровня заряда батареи, действий пользователя, статуса зарядки или других правил. Кроме того, поскольку обновление выполняется в фоновом режиме, пользователи могут не знать, что оно выполняется. Это означает, что процесс обновления может быть прерван в любой момент из-за правил, неожиданных перезагрузок или действий пользователя.
Метаданные в пакете OTA могут указывать, что обновление можно транслировать. Этот же пакет можно использовать для установки без трансляции. Сервер может использовать метаданные, чтобы сообщить клиенту, что выполняется потоковая передача, и клиент правильно передаст OTA в update_engine. Производители устройств, у которых есть собственный сервер и клиент, могут включить потоковую передачу обновлений, убедившись, что сервер определяет, что обновление передается потоком (или предполагает, что все обновления передаются потоком), а клиент правильно вызывает update_engine для потоковой передачи. Производители могут использовать тот факт, что пакет относится к потоковой версии, чтобы отправить клиенту флаг, который активирует передачу на сторону фреймворка как потоковую передачу.
После того как полезная нагрузка станет доступна, процесс обновления будет выглядеть следующим образом:
| Шаг | Объекты activity |
|---|---|
| 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.
Чтобы установка прошла успешно, старое ядро должно:
- Смонтируйте новую файловую систему. Тип файловой системы нельзя изменить, если старое ядро не поддерживает его, включая такие детали, как алгоритм сжатия, используемый при работе со сжатой файловой системой (например, 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, который входит в пакет OTA для A/B-обновлений при использовании кода AOSP. Клиент системного обновления Java, например GmsCore, извлекает care_map.txt, настраивает разрешение на доступ перед перезагрузкой устройства и удаляет извлеченный файл после того, как система успешно загрузится в новой версии.