Устаревшие 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. Производители оборудования, не использующие инфраструктуру OTA от Google, смогут повторно использовать системный код AOSP, но им потребуется предоставить собственный клиент.
Для производителей оригинального оборудования (OEM), поставляющих продукцию собственному клиенту, клиенту необходимо:
- Определите, когда следует проводить обновление. Поскольку A/B-обновления происходят в фоновом режиме, они больше не инициируются пользователем. Чтобы избежать неудобств для пользователей, рекомендуется планировать обновления на время, когда устройство находится в режиме ожидания, например, ночью, и подключено к Wi-Fi. Однако ваш клиент может использовать любые эвристические алгоритмы по вашему выбору.
- Проверьте наличие обновлений на серверах OTA-пакетов. В основном это будет аналогично существующему клиентскому коду, за исключением того, что вам нужно будет указать, что устройство поддерживает A/B-тестирование. (Клиент Google также включает кнопку «Проверить сейчас» , позволяющую пользователям проверить наличие последних обновлений.)
- Вызовите
update_engine, указав 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 и менеджером пакетов:
- В
frameworks/native/cmds/installd/ota* находятся скрипт postinstall, исполняемый файл для 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/ . Пользователи с идентификатором группы журналов смогут получить доступ к журналам файловой системы.
Взаимодействие загрузчика
HAL boot_control используется update_engine (и, возможно, другими демонами) для указания загрузчику, с чего именно загружаться. Типичные примеры сценариев и соответствующие им состояния включают следующее:
- Обычный случай : система работает из текущего слота, либо слота A, либо слота B. Обновления пока не применялись. Текущий слот системы загружается успешно и является активным слотом.
- Выполняется обновление : система работает из слота B, поэтому слот B является загрузочным, успешно загрузочным и активным слотом. Слот A был помечен как незагружаемый, поскольку содержимое слота 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, выберите следующие патчи с помощью cherry-pick:
- Разрешить отмену запроса на разрешение спора через прокси-сервер
- Исправлена ошибка, приводящая к завершению передачи данных при разрешении прокси-серверов.
- Добавить модульный тест для функции TerminateTransfer между диапазонами.
- Очистка памяти функции RetryTimeoutCallback()
Эти патчи необходимы для поддержки потоковых обновлений 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 .
Для успешного завершения установки старое ядро должно обладать следующими возможностями:
- Смонтируйте новый формат файловой системы . Тип файловой системы не может измениться, если он не поддерживается в старом ядре, включая такие детали, как алгоритм сжатия, используемый при использовании сжатой файловой системы (например, 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 , устанавливает права доступа перед перезагрузкой устройства и удаляет извлеченный файл после успешной загрузки системы в новую версию.