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

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

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

Модуль 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 - накладные расходы
    См. раздел «Реализация виртуального A/B-тестирования» .
  • Для пусковых устройств типа A/B сумма максимальных размеров всех групп должна быть равна:
    BOARD_SUPER_PARTITION_SIZE / 2 - overhead
  • Для устройств, не относящихся к классу 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 МиБ неиспользованного пространства.

Изменения, идущие от корня системы

На устройствах, запускаемых с 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.

Если параметр PRODUCT_USE_DYNAMIC_PARTITIONS также имеет true , установка BOARD_BUILD_SYSTEM_ROOT_IMAGE в значение 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 . Он должен представлять собой компонент пути к устройству, соответствующий символической ссылке 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

В файлах `device tree` и `device tree overlays` не должно быть записей в `fstab`. Используйте файл `fstab`, который будет частью виртуального диска в оперативной памяти.

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

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

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

#<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 remount

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

Изображения завода

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

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

Для устройств A/B, запускающихся с динамическими разделами, super.img содержит образы в слоте A. После прямой прошивки образа super.img, перед перезагрузкой устройства отметьте слот 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-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}