動的パーティションの実装

動的パーティショニングは、Linux カーネルの dm-linear device-mapper モジュールを使用して実装されます。super パーティションには、super にある動的パーティションの名前とブロック範囲を示すメタデータが格納されます。第 1 ステージ init では、このメタデータが解析および検証され、各動的パーティションを表す仮想ブロック デバイスが作成されます。

OTA を適用すると、必要に応じて動的パーティションが自動的に作成、サイズ変更、削除されます。A/B デバイスの場合、メタデータのコピーは 2 つあり、変更はターゲット スロットとなるコピーにのみ適用されます。

動的パーティションはユーザー空間に実装されているため、ブートローダーに必要なパーティションを動的に作成することはできません。たとえば、bootdtbovbmeta はブートローダーが読み取るため、物理パーティションとして残す必要があります。

動的パーティションはそれぞれアップデート グループに属します。アップデート グループは、そのグループ内のパーティションが消費できる最大の容量を制限します。 たとえば systemvendor は、systemvendor の合計サイズを制限するグループに属します。

新しいデバイスへの動的パーティションの実装

このセクションでは、Android 10 以降でリリースする新しいデバイスに動的パーティションを実装する方法について説明します。既存のデバイスをアップグレードする方法については、Android デバイスのアップグレードをご覧ください。

パーティショニングの変更

Android 10 でリリースするデバイスの場合は、super というパーティションを作成します。super パーティションは内部で A/B スロットを処理するため、A/B デバイスは区別された super_asuper_b パーティションを必要としません。 ブートローダーで使用されない読み取り専用の AOSP パーティションはすべて動的である必要があり、GUID パーティション テーブル(GPT)から削除する必要があります。 ベンダー固有のパーティションは動的でなくても GPT に配置できます。

super のサイズを見積もる際には、GPT から削除するパーティションのサイズを追加します。A/B デバイスの場合は、両方のスロットのサイズを含めます。図 1 は、動的パーティションへ変換する前後のパーティション テーブルの例を示しています。

パーティション テーブルのレイアウト
図 1. 動的パーティションに変換する際の新しい物理パーティション テーブルのレイアウト

サポートされている動的パーティションは次のとおりです。

  • System
  • Vendor
  • Product
  • System Ext
  • ODM

Android 10 でリリースするデバイスの場合、カーネル コマンドライン オプション androidboot.super_partition を空にして、コマンド sysprop ro.boot.super_partition が空になるようにします。

パーティションの配置

super パーティションが正しく配置されていないと、device-mapper モジュールが効率的に動作しない場合があります。super パーティションは、ブロックレイヤによって決定される I/O リクエストの最小サイズに従って配置する必要があります。デフォルトでは、super パーティション イメージを生成する lpmake を経由するビルドシステムは、すべての動的パーティションが 1 MiB の配置で十分であると想定しています。ただし、ベンダーは 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 デバイスの場合、グループ名には内部的にスロット名が接尾辞として付されているため、グループの最大サイズは 1 スロットのみになります。

次の例では、すべてのパーティションを 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 に、vendorproductodmgroup_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 - オーバーヘッド
  • A/B 以外のデバイスとレトロフィット A/B デバイスの場合、全グループの最大サイズの合計は次のようになります。
    BOARD_SUPER_PARTITION_SIZE - オーバーヘッド
  • ビルド時、アップデート グループ内の各パーティションに含まれるイメージの合計サイズは、グループの最大サイズを超えることはできません。
  • オーバーヘッドの計算には、メタデータ、配置などを考慮する必要があります。オーバーヘッドは 4 MiB 程度ですが、必要に応じてデバイスのオーバーヘッドを大きくすることもできます。

動的パーティションのサイズ設定

動的パーティションが行われる前は、将来のアップデートに十分な空き領域を確保するためパーティションのサイズが過剰に割り当てられていました。実際のサイズは現在と同じでしたが、ほとんどの読み取り専用パーティションにはファイル システムに空き領域がありました。動的パーティションでは、この空き領域は使用されないため、OTA 中にパーティションを拡張するために使用できます。 パーティションがスペースを浪費せず、最小限のサイズで割り当てられるようにすることが重要です。

読み取り専用の ext4 イメージでは、ハードコーディングされたパーティション サイズが指定されていない場合、最小サイズが自動的に割り当てられます。ビルドシステムは、ファイル システムに未使用の領域ができるだけ少なくなるようにイメージを収めます。これにより、デバイスで OTA に使用できるスペースが無駄になることはありません。

さらに、ブロックレベルでの重複排除を有効にすると、ext4 イメージをさらに圧縮できます。これを有効にするには、次の設定を使用します。

BOARD_EXT4_SHARE_DUP_BLOCKS := true

パーティションの最小サイズの自動割り当てが望ましくない場合、パーティション サイズを制御するには 2 つの方法があります。BOARD_partitionIMAGE_PARTITION_RESERVED_SIZE を使用して空き領域の最小サイズを指定するか、または BOARD_partitionIMAGE_PARTITION_SIZE を指定して動的パーティションを特定のサイズになるよう強制します。どちらも、必要のない限りおすすめしません。

例:

BOARD_PRODUCTIMAGE_PARTITION_RESERVED_SIZE := 52428800

これにより、product.img のファイル システムの使用されていない領域が 50 MiB になります。

system-as-root の変更

Android 10 でリリースするデバイスでは、system-as-root を使用しないでください。

動的パーティションを持つ(リリース時からかレトロフィットかにかかわらず)デバイスは、system-as-root を使用しないでください。Linux カーネルは super パーティションを解釈できないため、自身で system をマウントできません。system は RAM ディスクにある第 1 ステージ init によりマウントされます。

BOARD_BUILD_SYSTEM_ROOT_IMAGE は設定しないでください。Android 10 では、BOARD_BUILD_SYSTEM_ROOT_IMAGE フラグが使用されるのは、システムがカーネルによってマウントされるか、RAM ディスクの第 1 ステージ init でマウントされるかを区分するためです。

BOARD_BUILD_SYSTEM_ROOT_IMAGEtrue に設定すると、PRODUCT_USES_DYNAMIC_PARTITIONStrue の場合にビルドエラーが発生します。

BOARD_USES_RECOVERY_AS_BOOT が true に設定されている場合、リカバリ イメージは boot.img として作成され、リカバリの RAM ディスクが含まれます。これまで、ブートローダーはカーネル コマンドライン パラメータ 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 を使用している場合、デバイスがチェーン パーティション記述子を使用していなければ、変更の必要はありません。ただし、チェーン パーティションを使用しており、確認済みのパーティションの 1 つが動的な場合は、変更が必要です。

system パーティションと vendor パーティションの vbmeta をチェーンするデバイスの設定例は次のとおりです。

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

この設定では、ブートローダーは system パーティションと vendor パーティションの最後に vbmeta フッターがあることを想定しています。これらのパーティションは super にあり、ブートローダーから見えなくなったため、以下の 2 つの変更が必要です。

  • vbmeta_system パーティションと vbmeta_vendor パーティションをデバイスのパーティション テーブルに追加します。A/B デバイスの場合は、vbmeta_system_avbmeta_system_bvbmeta_vendor_avbmeta_vendor_b を追加します。1 つ以上のパーティションを追加する場合は、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 blob をサポートします。

カーネルのコマンドラインの変更

新しいパラメータである androidboot.boot_devices をカーネルのコマンドラインに追加する必要があります。これは init/dev/block/by-name シンボリック リンクの有効化に使用します。ueventd により作成された by-name シンボリック リンクへのデバイスパス コンポーネントである必要があります。つまり /dev/block/platform/device-path/by-name/partition-name です。 Android 12 以降でリリースするデバイスでは、bootconfig を使用して androidboot.boot_devicesinit に渡す必要があります。

たとえば、super パーティションの by-name シンボリック リンクが /dev/block/platform/soc/100000.ufshc/by-name/super の場合、BoardConfig.mk ファイルにコマンドライン パラメータを次のように追加できます。

BOARD_KERNEL_CMDLINE += androidboot.boot_devices=soc/100000.ufshc
BoardConfig.mk ファイルに bootconfig パラメータを次のように追加できます。
BOARD_BOOTCONFIG += androidboot.boot_devices=soc/100000.ufshc

fstab の変更

デバイスツリーとデバイスツリー オーバーレイに fstab エントリを含めることはできません。RAM ディスクの一部となる fstab ファイルを使用します。

論理パーティションについて、fstab ファイルに次の変更を行う必要があります。

  • fs_mgr フラグ フィールドに、Android 10 で導入された、パーティションが第 1 ステージでマウントされることを示す logical フラグと first_stage_mount フラグを含める必要があります。
  • 1 つのパーティションは、avb=vbmeta partition namefs_mgr フラグとして指定できます。指定された vbmeta パーティションは、デバイスのマウントを試みるより先に第 1 ステージ 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 ファイルを第 1 ステージ RAM ディスクにコピーします。

SELinux の変更

super パーティション ブロック デバイスには super_block_device のラベルを付ける必要があります。たとえば、super パーティションの by-name シンボリック リンクが /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

ブートローダーやユーザー空間以外のフラッシング ツールは、動的パーティションを認識しないため、フラッシュできません。これに対処するには、fastbootd と呼ばれる fastboot プロトコルのユーザー空間への実装をデバイスが使用する必要があります。

fastbootd の実装方法の詳細については、Fastboot をユーザー空間に移動するをご覧ください。

adb の再マウント

eng や userdebug ビルドを使用するデベロッパーの場合、adb remount は迅速な反復処理に非常に役立ちます。動的パーティションが原因で adb remount に問題が発生するのは、各ファイル システム内に空き領域がなくなるためです。これに対処するため、デバイスは overlayfs を有効にできます。super パーティション内に空き領域がある限り、adb remount は自動的に一時的な動的パーティションを作成し、overlayfs を書き込みに使用します。一時パーティションには scratch という名前が付きますので、他のパーティションにはこの名前を使用しないでください。

overlayfs を有効にする方法の詳細については、AOSP の overlayfs の README をご覧ください。

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_SIZEBOARD_SUPER_PARTITION_BLOCK_DEVICES の各ブロック デバイスのサイズに設定します。 これは、デバイス上にある既存の物理パーティションのサイズをリストにしたものです。通常既存のボード設定では BOARD_partitionIMAGE_PARTITION_SIZE とされています。
  • BOARD_SUPER_PARTITION_BLOCK_DEVICES のすべてのパーティションに対して既存の BOARD_partitionIMAGE_PARTITION_SIZE の設定を解除します。
  • BOARD_SUPER_PARTITION_SIZEBOARD_SUPER_PARTITION_partition_DEVICE_SIZE の合計に設定します。
  • 動的パーティション メタデータが格納されているブロック デバイスを BOARD_SUPER_PARTITION_METADATA_DEVICE に設定します。BOARD_SUPER_PARTITION_BLOCK_DEVICES のいずれかである必要があります。通常、system に設定されています。
  • BOARD_SUPER_PARTITION_GROUPSBOARD_group_SIZEBOARD_group_PARTITION_LIST をそれぞれ設定します。詳しくは新しいデバイスでのボード設定の変更をご覧ください。

たとえば、デバイスにすでに system パーティションと vendor パーティションがあり、更新中に動的パーティションに転換し、新しく product パーティションを追加する場合は、次のようにボード設定を行います。

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 パーティション ブロック デバイスには super_block_device_type 属性を指定する必要があります。たとえば、デバイスに system パーティションと vendor パーティションがある場合、次のように動的パーティションのエクステントを保存するブロック デバイスとして使用し、by-name シンボリック リンクを 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 デバイスの OTA をご覧ください。

ファクトリー イメージ

動的パーティションをサポートしてリリースするデバイスでは、ユーザー空間 fastboot を使用してファクトリ イメージをフラッシュしないでください。ユーザー空間での起動はほかのフラッシュ方法に比べ速度が遅くなります。

これに対処するため、make dist は追加の super.img イメージをビルドし、super パーティションに直接フラッシュできます。このイメージは論理パーティションのコンテンツを自動的にバンドルします。つまり、super パーティション メタデータに加えて、system.imgvendor.img なども含んでいます。このイメージは、追加のツールや fastbootd を使用せずに、直接 super パーティションにフラッシュできます。ビルド後、super.img${ANDROID_PRODUCT_OUT} に配置されます。

動的パーティションを使用してリリースする A/B デバイスの場合、super.img には A スロットにイメージが含まれています。super イメージを直接フラッシュした後、A スロットを起動可能にしてからデバイスを再起動します。

レトロフィット デバイスの場合、make dist は一連の super_*.img イメージをビルドし、これは対応する物理パーティションに直接フラッシュできます。たとえば、BOARD_SUPER_PARTITION_BLOCK_DEVICES が system vendor のとき、make distsuper_system.imgsuper_vendor.img をビルドします。これらのイメージは、target_files.zip 内の OTA フォルダに置かれます。

デバイス マッパーのストレージとデバイスの調整

動的パーティショニングは、決定性のない各種デバイス マッパー オブジェクトに対応しています。これらは想定どおりにインスタンス化されない場合があるため、すべてのマウントをトラッキングし、関連付けられているすべてのパーティションの Android プロパティで基盤となるストレージ デバイスを更新する必要があります。

init 内のメカニズムはマウントをトラッキングし、Android プロパティを非同期で更新します。更新にかかる時間が所定の時間内に収まることは保証されません。そのため、すべての on property トリガーが応答するのに十分な時間を確保する必要があります。プロパティは dev.mnt.blk.<partition> です(<partition> はたとえば、rootsystemdatavendor)。次の例のように、各プロパティは基本ストレージ デバイス名に関連付けられます。

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

コマンド処理が第 2 ステージ init で開始されると、epoll loop がアクティブになり、値の更新が開始されます。ただし、プロパティのトリガーは後期 init までアクティブでないため、初期ブートステージでの rootsystemvendor の処理には使用できません。init.rc スクリプトが early-fs でオーバーライドするまで(このとき各種デーモンやファシリティが開始される)、カーネル デフォルト read_ahead_kb は十分であると想定されます。このため、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}