Реализация динамических разделов

Динамическое разбиение реализовано с помощью модуля dm-linear device-mapper в ядре Linux. super содержит метаданные, в которых перечислены имена и диапазоны блоков каждого динамического раздела в пределах super . Во время первой стадии init эти метаданные анализируются и проверяются, и создаются виртуальные блочные устройства для представления каждого динамического раздела.

При применении OTA динамические разделы автоматически создаются, изменяются в размере или удаляются по мере необходимости. Для устройств A/B существуют две копии метаданных, и изменения применяются только к копии, представляющей целевой слот.

Поскольку динамические разделы реализованы в пользовательском пространстве, разделы, необходимые загрузчику, не могут быть сделаны динамическими. Например, boot , dtbo и vbmeta считываются загрузчиком и поэтому должны оставаться физическими разделами.

Каждый динамический раздел может принадлежать к группе обновлений . Эти группы ограничивают максимальное пространство, которое могут занимать разделы в этой группе. Например, system и vendor могут принадлежать к группе, которая ограничивает общий размер system и vendor .

Реализуйте динамические разделы на новых устройствах

В этом разделе подробно описывается, как реализовать динамические разделы на новых устройствах, запускаемых с Android 10 и выше. Чтобы обновить существующие устройства, см . Обновление устройств Android .

Изменения разделов

Для устройств, запускаемых с Android 10, создайте раздел с именем super . Раздел super обрабатывает слоты A/B внутренне, поэтому устройствам A/B не нужны отдельные разделы super_a и super_b . Все разделы AOSP только для чтения, которые не используются загрузчиком, должны быть динамическими и должны быть удалены из таблицы разделов GUID (GPT). Разделы, специфичные для поставщика, не обязательно должны быть динамическими и могут быть размещены в GPT.

Чтобы оценить размер super , добавьте размеры разделов, удаляемых из GPT. Для устройств A/B это должно включать размер обоих слотов. На рисунке 1 показан пример таблицы разделов до и после преобразования в динамические разделы.

Макет таблицы разделов
Рисунок 1. Новая структура таблицы физических разделов при преобразовании в динамические разделы

Поддерживаемые динамические разделы:

  • Система
  • Продавец
  • Продукт
  • Система Ext
  • ОДМ

Для устройств, работающих под управлением Android 10, параметр командной строки ядра androidboot.super_partition должен быть пустым, чтобы команда sysprop ro.boot.super_partition была пустой.

Выравнивание разделов

Модуль device-mapper может работать менее эффективно, если super не выровнен должным образом. super ДОЛЖЕН быть выровнен по минимальному размеру запроса ввода-вывода , определенному блочным слоем. По умолчанию система сборки (через lpmake , который генерирует образ super ) предполагает, что выравнивания в 1 МБ достаточно для каждого динамического раздела. Однако поставщики должны гарантировать, что super выровнен должным образом.

Вы можете определить минимальный размер запроса блочного устройства, проверив sysfs . Например:

# ls -l /dev/block/by-name/super
lrwxrwxrwx 1 root root 16 1970-04-05 01:41 /dev/block/by-name/super -> /dev/block/sda17
# cat /sys/block/sda/queue/minimum_io_size
786432

Проверить выравнивание super можно аналогичным образом:

# cat /sys/block/sda/sda17/alignment_offset

Смещение выравнивания ДОЛЖНО быть равно 0.

Изменения конфигурации устройства

Чтобы включить динамическое разбиение, добавьте следующий флаг в device.mk :

PRODUCT_USE_DYNAMIC_PARTITIONS := true

Изменения конфигурации платы

Вам необходимо задать размер super :

BOARD_SUPER_PARTITION_SIZE := <size-in-bytes>

На устройствах A/B система сборки выдает ошибку, если общий размер образов динамических разделов превышает половину размера super .

Список динамических разделов можно настроить следующим образом. Для устройств, использующих группы обновления, перечислите группы в переменной BOARD_SUPER_PARTITION_GROUPS . Затем каждое имя группы имеет переменную BOARD_ group _SIZE и BOARD_ group _PARTITION_LIST . Для устройств A/B максимальный размер группы должен охватывать только один слот, поскольку имена групп имеют внутренний суффикс слота.

Вот пример устройства, которое помещает все разделы в группу под названием example_dynamic_partitions :

BOARD_SUPER_PARTITION_GROUPS := example_dynamic_partitions
BOARD_EXAMPLE_DYNAMIC_PARTITIONS_SIZE := 6442450944
BOARD_EXAMPLE_DYNAMIC_PARTITIONS_PARTITION_LIST := system vendor product

Вот пример устройства, которое помещает системные и продуктовые службы в group_foo , а vendor , product и odm в group_bar :

BOARD_SUPER_PARTITION_GROUPS := group_foo group_bar
BOARD_GROUP_FOO_SIZE := 4831838208
BOARD_GROUP_FOO_PARTITION_LIST := system product_services
BOARD_GROUP_BAR_SIZE := 1610612736
BOARD_GROUP_BAR_PARTITION_LIST := vendor product odm
  • Для устройств запуска Virtual A/B сумма максимальных размеров всех групп должна быть не более:
    BOARD_SUPER_PARTITION_SIZE - накладные расходы
    См. Реализация виртуального A/B .
  • Для устройств запуска A/B сумма максимальных размеров всех групп должна быть:
    BOARD_SUPER_PARTITION_SIZE / 2 - накладные расходы
  • Для устройств, отличных от A/B, и модернизированных устройств A/B сумма максимальных размеров всех групп должна быть:
    BOARD_SUPER_PARTITION_SIZE - накладные расходы
  • Во время сборки сумма размеров образов каждого раздела в группе обновлений не должна превышать максимальный размер группы.
  • Накладные расходы требуются при вычислении для учета метаданных, выравниваний и т. д. Разумные накладные расходы составляют 4 МБ, но вы можете выбрать больший размер накладных расходов в зависимости от потребностей устройства.

Размер динамических разделов

До появления динамических разделов размеры разделов были избыточно распределены, чтобы гарантировать, что у них будет достаточно места для будущих обновлений. Фактический размер принимался как есть, и большинство разделов только для чтения имели некоторое количество свободного места в своей файловой системе. В динамических разделах это свободное пространство не может быть использовано и может быть использовано для увеличения разделов во время OTA. Крайне важно гарантировать, что разделы не тратят место впустую и выделяются в минимально возможном размере.

Для образов ext4 только для чтения система сборки автоматически выделяет минимальный размер, если не указан жестко закодированный размер раздела. Система сборки подгоняет образ так, чтобы в файловой системе оставалось как можно меньше неиспользуемого пространства. Это гарантирует, что устройство не будет тратить впустую пространство, которое может быть использовано для OTA.

Кроме того, образы ext4 можно дополнительно сжать, включив дедупликацию на уровне блоков. Чтобы включить это, используйте следующую конфигурацию:

BOARD_EXT4_SHARE_DUP_BLOCKS := true

Если автоматическое выделение минимального размера раздела нежелательно, есть два способа управления размером раздела. Вы можете указать минимальный объем свободного пространства с помощью BOARD_ partition IMAGE_PARTITION_RESERVED_SIZE или вы можете указать BOARD_ partition IMAGE_PARTITION_SIZE , чтобы принудительно задать определенный размер динамических разделов. Ни один из этих вариантов не рекомендуется, если в этом нет необходимости.

Например:

BOARD_PRODUCTIMAGE_PARTITION_RESERVED_SIZE := 52428800

Это принудительно оставляет в файловой системе product.img 50 МБ неиспользуемого пространства.

Изменения в системе как root

Устройства, работающие под управлением Android 10, не должны использовать root-доступ к системе.

Устройства с динамическими разделами (независимо от того, запускаются ли они с динамическими разделами или модернизируют их) не должны использовать system-as-root. Ядро Linux не может интерпретировать super и, следовательно, не может монтировать саму system . Теперь system монтируется init первого этапа, который находится в ramdisk.

Не устанавливайте BOARD_BUILD_SYSTEM_ROOT_IMAGE . В Android 10 флаг BOARD_BUILD_SYSTEM_ROOT_IMAGE используется только для того, чтобы различать, монтируется ли система ядром или первой стадией init в ramdisk.

Установка BOARD_BUILD_SYSTEM_ROOT_IMAGE в true приводит к ошибке сборки, если PRODUCT_USE_DYNAMIC_PARTITIONS также имеет true .

Если BOARD_USES_RECOVERY_AS_BOOT установлен в значение true, образ восстановления создается как boot.img, содержащий ramdisk восстановления. Ранее загрузчик использовал параметр командной строки ядра skip_initramfs для определения режима загрузки. Для устройств Android 10 загрузчик НЕ ДОЛЖЕН передавать skip_initramfs в командную строку ядра. Вместо этого загрузчик должен передавать androidboot.force_normal_boot=1 чтобы пропустить восстановление и загрузить обычный Android. Устройства, запускаемые с Android 12 или более поздней версии, должны использовать bootconfig для передачи androidboot.force_normal_boot=1 .

Изменения конфигурации AVB

При использовании Android Verified Boot 2.0 , если устройство не использует цепочечные дескрипторы разделов , то никаких изменений не требуется. Однако, если используются цепочечные разделы, и один из проверенных разделов является динамическим, то изменения необходимы.

Ниже приведен пример конфигурации устройства, которое связывает vbmeta для system и vendor разделов.

BOARD_AVB_SYSTEM_KEY_PATH := external/avb/test/data/testkey_rsa2048.pem
BOARD_AVB_SYSTEM_ALGORITHM := SHA256_RSA2048
BOARD_AVB_SYSTEM_ROLLBACK_INDEX := $(PLATFORM_SECURITY_PATCH_TIMESTAMP)
BOARD_AVB_SYSTEM_ROLLBACK_INDEX_LOCATION := 1

BOARD_AVB_VENDOR_KEY_PATH := external/avb/test/data/testkey_rsa2048.pem
BOARD_AVB_VENDOR_ALGORITHM := SHA256_RSA2048
BOARD_AVB_VENDOR_ROLLBACK_INDEX := $(PLATFORM_SECURITY_PATCH_TIMESTAMP)
BOARD_AVB_VENDOR_ROLLBACK_INDEX_LOCATION := 1

При такой конфигурации загрузчик ожидает найти нижний колонтитул vbmeta в конце разделов system и vendor . Поскольку эти разделы больше не видны загрузчику (они находятся в super ), необходимо внести два изменения.

  • Добавьте разделы vbmeta_system и vbmeta_vendor в таблицу разделов устройства. Для устройств A/B добавьте vbmeta_system_a , vbmeta_system_b , vbmeta_vendor_a и vbmeta_vendor_b . При добавлении одного или нескольких из этих разделов они должны быть того же размера, что и раздел vbmeta .
  • Переименуйте флаги конфигурации, добавив VBMETA_ , и укажите, на какие разделы распространяется цепочка:
    BOARD_AVB_VBMETA_SYSTEM := system
    BOARD_AVB_VBMETA_SYSTEM_KEY_PATH := external/avb/test/data/testkey_rsa2048.pem
    BOARD_AVB_VBMETA_SYSTEM_ALGORITHM := SHA256_RSA2048
    BOARD_AVB_VBMETA_SYSTEM_ROLLBACK_INDEX := $(PLATFORM_SECURITY_PATCH_TIMESTAMP)
    BOARD_AVB_VBMETA_SYSTEM_ROLLBACK_INDEX_LOCATION := 1
    
    BOARD_AVB_VBMETA_VENDOR := vendor
    BOARD_AVB_VBMETA_VENDOR_KEY_PATH := external/avb/test/data/testkey_rsa2048.pem
    BOARD_AVB_VBMETA_VENDOR_ALGORITHM := SHA256_RSA2048
    BOARD_AVB_VBMETA_VENDOR_ROLLBACK_INDEX := $(PLATFORM_SECURITY_PATCH_TIMESTAMP)
    BOARD_AVB_VBMETA_VENDOR_ROLLBACK_INDEX_LOCATION := 1

Устройство может использовать один, оба или ни один из этих разделов. Изменения необходимы только при присоединении к логическому разделу.

Изменения загрузчика AVB

Если в загрузчик встроен libavb , включите следующие исправления:

При использовании связанных разделов включите дополнительный патч:

  • 49936b4c0109411fdd38bd4ba3a32a01c40439a9 — «libavb: Поддержка объектов vbmeta в начале раздела».

Изменения командной строки ядра

В командную строку ядра необходимо добавить новый параметр androidboot.boot_devices . Он используется init для включения символических ссылок /dev/block/by-name . Это должен быть компонент пути устройства к базовой символической ссылке by-name, созданной ueventd , то есть /dev/block/platform/ device-path /by-name/ partition-name . Устройства, запускаемые с Android 12 или более поздней версии, должны использовать bootconfig для передачи androidboot.boot_devices в init .

Например, если символическая ссылка на суперраздел по имени — /dev/block/platform/ soc/100000.ufshc /by-name/super , вы можете добавить параметр командной строки в файл BoardConfig.mk следующим образом:

BOARD_KERNEL_CMDLINE += androidboot.boot_devices=soc/100000.ufshc
Параметр bootconfig можно добавить в файл BoardConfig.mk следующим образом:
BOARD_BOOTCONFIG += androidboot.boot_devices=soc/100000.ufshc

изменения fstab

Дерево устройств и оверлеи дерева устройств не должны содержать записи fstab. Используйте файл fstab, который будет частью ramdisk.

Для логических разделов необходимо внести изменения в файл fstab:

  • Поле флагов fs_mgr должно включать logical флаг и флаг first_stage_mount , представленный в Android 10, который указывает, что раздел должен быть смонтирован на первом этапе.
  • Раздел может указать avb= vbmeta partition name как флаг fs_mgr , а затем указанный раздел vbmeta инициализируется на первом этапе init перед попыткой монтирования каких-либо устройств.
  • Поле dev должно содержать имя раздела.

Следующие записи fstab устанавливают систему, поставщика и продукт как логические разделы в соответствии с приведенными выше правилами.

#<dev>  <mnt_point> <type>  <mnt_flags options> <fs_mgr_flags>
system   /system     ext4    ro,barrier=1        wait,slotselect,avb=vbmeta,logical,first_stage_mount
vendor   /vendor     ext4    ro,barrier=1        wait,slotselect,avb,logical,first_stage_mount
product  /product    ext4    ro,barrier=1        wait,slotselect,avb,logical,first_stage_mount

Скопируйте файл fstab на виртуальный диск первого этапа.

Изменения SELinux

Устройство блока суперраздела должно быть помечено меткой super_block_device . Например, если символическая ссылка по имени суперраздела — /dev/block/platform/ soc/100000.ufshc /by-name/super , добавьте следующую строку в file_contexts :

/dev/block/platform/soc/10000\.ufshc/by-name/super   u:object_r:super_block_device:s0

fastbootd

Загрузчик (или любой непользовательский инструмент прошивки) не понимает динамические разделы, поэтому не может их прошивать. Чтобы решить эту проблему, устройства должны использовать реализацию пользовательского пространства протокола fastboot, называемую fastbootd.

Дополнительную информацию о том, как реализовать fastbootd, см. в разделе Перемещение Fastboot в пространство пользователя .

adb перемонтировать

Для разработчиков, использующих сборки eng или userdebug, adb remount чрезвычайно полезен для быстрой итерации. Динамические разделы представляют проблему для adb remount , поскольку в каждой файловой системе больше нет свободного места. Чтобы решить эту проблему, устройства могут включить overlayfs. Пока в суперразделе есть свободное место, adb remount автоматически создает временный динамический раздел и использует overlayfs для записи. Временный раздел называется scratch , поэтому не используйте это имя для других разделов.

Дополнительную информацию о включении overlayfs см. в README overlayfs в AOSP.

Обновление Android-устройств

Если вы обновляете устройство до Android 10 и хотите включить поддержку динамических разделов в OTA, вам не нужно менять встроенную таблицу разделов. Требуется некоторая дополнительная настройка.

Изменения конфигурации устройства

Для модернизации динамического разбиения на разделы добавьте следующие флаги в device.mk :

PRODUCT_USE_DYNAMIC_PARTITIONS := true
PRODUCT_RETROFIT_DYNAMIC_PARTITIONS := true

Изменения конфигурации платы

Вам необходимо установить следующие переменные платы:

  • Установите BOARD_SUPER_PARTITION_BLOCK_DEVICES в список блочных устройств, используемых для хранения экстентов динамических разделов. Это список имен существующих физических разделов на устройстве.
  • Установите BOARD_SUPER_PARTITION_ partition _DEVICE_SIZE на размеры каждого блочного устройства в BOARD_SUPER_PARTITION_BLOCK_DEVICES соответственно. Это список размеров существующих физических разделов на устройстве. Обычно это BOARD_ partition IMAGE_PARTITION_SIZE в существующих конфигурациях платы.
  • Отменить установку существующего BOARD_ partition IMAGE_PARTITION_SIZE для всех разделов в BOARD_SUPER_PARTITION_BLOCK_DEVICES .
  • Установите BOARD_SUPER_PARTITION_SIZE равным сумме BOARD_SUPER_PARTITION_ partition _DEVICE_SIZE .
  • Установите BOARD_SUPER_PARTITION_METADATA_DEVICE на блочное устройство, где хранятся метаданные динамического раздела. Это должно быть одно из BOARD_SUPER_PARTITION_BLOCK_DEVICES . Обычно это значение равно system .
  • Установите BOARD_SUPER_PARTITION_GROUPS , BOARD_ group _SIZE и BOARD_ group _PARTITION_LIST соответственно. Подробности см. в разделе Изменения конфигурации платы на новых устройствах .

Например, если на устройстве уже есть системные и вендорные разделы, и вы хотите преобразовать их в динамические разделы и добавить новый раздел продукта во время обновления, установите следующую конфигурацию платы:

BOARD_SUPER_PARTITION_BLOCK_DEVICES := system vendor
BOARD_SUPER_PARTITION_METADATA_DEVICE := system

# Rename BOARD_SYSTEMIMAGE_PARTITION_SIZE to BOARD_SUPER_PARTITION_SYSTEM_DEVICE_SIZE.
BOARD_SUPER_PARTITION_SYSTEM_DEVICE_SIZE := <size-in-bytes>

# Rename BOARD_VENDORIMAGE_PARTITION_SIZE to BOARD_SUPER_PARTITION_VENDOR_DEVICE_SIZE
BOARD_SUPER_PARTITION_VENDOR_DEVICE_SIZE := <size-in-bytes>

# This is BOARD_SUPER_PARTITION_SYSTEM_DEVICE_SIZE + BOARD_SUPER_PARTITION_VENDOR_DEVICE_SIZE
BOARD_SUPER_PARTITION_SIZE := <size-in-bytes>

# Configuration for dynamic partitions. For example:
BOARD_SUPER_PARTITION_GROUPS := group_foo
BOARD_GROUP_FOO_SIZE := <size-in-bytes>
BOARD_GROUP_FOO_PARTITION_LIST := system vendor product

Изменения SELinux

Устройства блока суперраздела должны быть помечены атрибутом super_block_device_type . Например, если устройство уже имеет system и vendor разделы, вы хотите использовать их как блочные устройства для хранения экстентов динамических разделов, а их поименованные символические ссылки помечены как system_block_device :

/dev/block/platform/soc/10000\.ufshc/by-name/system   u:object_r:system_block_device:s0
/dev/block/platform/soc/10000\.ufshc/by-name/vendor   u:object_r:system_block_device:s0

Затем добавьте следующую строку в device.te :

typeattribute system_block_device super_block_device_type;

Для других конфигураций см. раздел Реализация динамических разделов на новых устройствах .

Дополнительную информацию об обновлениях см. в разделе OTA для устройств A/B без динамических разделов .

Заводские изображения

Для устройств, запускаемых с поддержкой динамических разделов, избегайте использования быстрой загрузки в пользовательском пространстве для прошивки заводских образов, поскольку загрузка в пользовательском пространстве происходит медленнее, чем другие методы прошивки.

Чтобы решить эту проблему, make dist теперь создает дополнительный образ super.img , который можно напрямую прошить в суперраздел. Он автоматически объединяет содержимое логических разделов, то есть содержит system.img , vendor.img и т. д. в дополнение к метаданным super . Этот образ можно напрямую прошить в super без дополнительных инструментов или использования fastbootd. После сборки super.img помещается в ${ANDROID_PRODUCT_OUT} .

Для устройств A/B, которые запускаются с динамическими разделами, super.img содержит образы в слоте A. После прямой прошивки суперобраза отметьте слот A как загрузочный перед перезагрузкой устройства.

Для устройств с модифицированной версией make dist создает набор образов super_*.img , которые можно напрямую прошить в соответствующие физические разделы. Например, make dist создает super_system.img и super_vendor.img , когда BOARD_SUPER_PARTITION_BLOCK_DEVICES — поставщик системы. Эти образы помещаются в папку OTA в target_files.zip .

Настройка устройства хранения-картографа устройств

Динамическое разбиение на разделы вмещает ряд недетерминированных объектов device-mapper. Они не все могут создаваться так, как ожидается, поэтому вам необходимо отслеживать все монтирования и обновлять свойства Android всех связанных разделов с их базовыми устройствами хранения.

Механизм внутри init отслеживает монтирования и асинхронно обновляет свойства Android. Количество времени, которое это займет, не гарантируется в пределах определенного периода, поэтому вы должны предоставить достаточно времени для реагирования всех триггеров on property . Свойствами являются dev.mnt.blk. <partition> , где <partition> — это root , system , data или vendor , например. Каждое свойство связано с базовым именем устройства хранения, как показано в следующих примерах:

taimen:/ % getprop | grep dev.mnt.blk
[dev.mnt.blk.data]: [sda]
[dev.mnt.blk.firmware]: [sde]
[dev.mnt.blk.metadata]: [sde]
[dev.mnt.blk.persist]: [sda]
[dev.mnt.blk.root]: [dm-0]
[dev.mnt.blk.vendor]: [dm-1]

blueline:/ $ getprop | grep dev.mnt.blk
[dev.mnt.blk.data]: [dm-4]
[dev.mnt.blk.metadata]: [sda]
[dev.mnt.blk.mnt.scratch]: [sda]
[dev.mnt.blk.mnt.vendor.persist]: [sdf]
[dev.mnt.blk.product]: [dm-2]
[dev.mnt.blk.root]: [dm-0]
[dev.mnt.blk.system_ext]: [dm-3]
[dev.mnt.blk.vendor]: [dm-1]
[dev.mnt.blk.vendor.firmware_mnt]: [sda]

Язык init.rc позволяет расширять свойства Android как часть правил, а устройства хранения могут настраиваться платформой по мере необходимости с помощью таких команд:

write /sys/block/${dev.mnt.blk.root}/queue/read_ahead_kb 128
write /sys/block/${dev.mnt.blk.data}/queue/read_ahead_kb 128

После начала обработки команды на втором этапе init epoll loop становится активным, и значения начинают обновляться. Однако, поскольку триггеры свойств не активны до late- init , их нельзя использовать на начальных этапах загрузки для обработки root , system или vendor . Можно ожидать, что read_ahead_kb ядра по умолчанию будет достаточным, пока скрипты init.rc не смогут переопределить в early-fs (когда запускаются различные демоны и службы). Поэтому Google рекомендует использовать функцию on property в сочетании со свойством, контролируемым init.rc , например sys.read_ahead_kb , для синхронизации операций и предотвращения состояний гонки, как в этих примерах:

on property:dev.mnt.blk.root=* && property:sys.read_ahead_kb=*
    write /sys/block/${dev.mnt.blk.root}/queue/read_ahead_kb ${sys.read_ahead_kb:-2048}

on property:dev.mnt.blk.system=* && property:sys.read_ahead_kb=*
    write /sys/block/${dev.mnt.blk.system}/queue/read_ahead_kb ${sys.read_ahead_kb:-2048}

on property:dev.mnt.blk.vendor=* && property:sys.read_ahead_kb=*
    write /sys/block/${dev.mnt.blk.vendor}/queue/read_ahead_kb ${sys.read_ahead_kb:-2048}

on property:dev.mnt.blk.product=* && property:sys.read_ahead_kb=*
    write /sys/block/${dev.mnt.blk.system_ext}/queue/read_ahead_kb ${sys.read_ahead_kb:-2048}

on property:dev.mnt.blk.oem=* && property:sys.read_ahead_kb=*
    write /sys/block/${dev.mnt.blk.oem}/queue/read_ahead_kb ${sys.read_ahead_kb:-2048}

on property:dev.mnt.blk.data=* && property:sys.read_ahead_kb=*
    write /sys/block/${dev.mnt.blk.data}/queue/read_ahead_kb ${sys.read_ahead_kb:-2048}

on early-fs:
    setprop sys.read_ahead_kb ${ro.read_ahead_kb.boot:-2048}

on property:sys.boot_completed=1
   setprop sys.read_ahead_kb ${ro.read_ahead_kb.bootcomplete:-128}