Как реализовать динамические разделы

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

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

Поддерживаются следующие динамические разделы:

  • Система
  • Поставщик
  • Товар
  • System Ext
  • ODM

На устройствах с 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
  • Для устройств с виртуальным разделом A/B сумма максимальных размеров всех групп должна быть не больше:
    BOARD_SUPER_PARTITION_SIZE - overhead
    Подробнее о реализации виртуального раздела A/B…
  • Для устройств с A/B-запуском сумма максимальных размеров всех групп должна быть равна:
    BOARD_SUPER_PARTITION_SIZE / 2 - overhead
  • Для устройств без поддержки A/B и устройств с поддержкой A/B, добавленной позже, сумма максимальных размеров всех групп должна быть равна:
    BOARD_SUPER_PARTITION_SIZE - overhead
  • При сборке сумма размеров образов каждого раздела в группе обновления не должна превышать максимальный размер группы.
  • Накладные расходы необходимы для учета метаданных, выравнивания и т. д. Оптимальный размер дополнительного пространства – 4 МиБ, но вы можете выбрать и больший, если это необходимо для устройства.

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

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

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

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

BOARD_EXT4_SHARE_DUP_BLOCKS := true

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

Пример:

BOARD_PRODUCTIMAGE_PARTITION_RESERVED_SIZE := 52428800

Это приведет к тому, что в файловой системе product.img будет 50 МиБ свободного места.

Изменения в системе как корневом каталоге

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

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

Не устанавливайте 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 и содержит ramdisk восстановления. Ранее загрузчик использовал параметр командной строки ядра skip_initramfs, чтобы определить, в каком режиме загружаться. На устройствах с Android 10 загрузчик НЕ ДОЛЖЕН передавать skip_initramfs в командную строку ядра. Вместо этого загрузчик должен передать androidboot.force_normal_boot=1, чтобы пропустить восстановление и загрузить обычную ОС Android. На устройствах с Android 12 или более поздней версии для передачи androidboot.force_normal_boot=1 необходимо использовать bootconfig.

Изменения конфигурации 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: Support vbmeta blobs in beginning of partition."

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

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

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

Для логических разделов необходимо внести изменения в файл 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 в ramdisk первого этапа.

Изменения в 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 remount

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

Подробнее о том, как включить overlayfs, можно узнать из файла README в 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_partitionIMAGE_PARTITION_SIZE.
  • Сбросьте существующее значение BOARD_partitionIMAGE_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;

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

Подробнее о беспроводном обновлении устройств с разделами A/B без динамических разделов…

Заводские образы

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

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

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

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

Настройка устройства хранения данных Device Mapper

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