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

Динамическое разделение реализовано с помощью модуля 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. Новый макет таблицы физических разделов при преобразовании в динамические разделы

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

  • Система
  • Продавец
  • Товар
  • Расширение системы
  • ОДМ

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

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

Модуль сопоставления устройств может работать менее эффективно, если 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.

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

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

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

Когда BOARD_USES_RECOVERY_AS_BOOT установлено значение true, образ восстановления создается как boot.img, содержащий виртуальный диск восстановления. Раньше загрузчик использовал параметр командной строки ядра 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 . Это должен быть компонент пути устройства к базовой символической ссылке по имени, созданной 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, который будет частью виртуального диска.

Необходимо внести изменения в файл 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

быстрая загрузка

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

Дополнительные сведения о реализации fastbootd см. в разделе Перемещение Fastboot в пространство пользователя .

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

Для разработчиков, использующих сборки eng или userdebug, adb remount чрезвычайно полезен для быстрой итерации. Динамические разделы создают проблему для adb remount , поскольку в каждой файловой системе больше нет свободного места. Чтобы решить эту проблему, устройства могут включать оверлеи. Пока в суперразделе есть свободное место, 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 , который можно прошить непосредственно в раздел super. Он автоматически объединяет содержимое логических разделов, то есть содержит 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 становится активным, и значения начинают обновляться. Однако, поскольку триггеры свойств не активны до поздней init , их нельзя использовать на начальных этапах загрузки для обработки root , system или vendor . Вы можете ожидать, что read_ahead_kb ядра по умолчанию будет достаточным, пока сценарии init.rc не смогут переопределить в Early early-fs 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}