Implementar partições dinâmicas

O particionamento dinâmico é implementado com o uso do mapeador de dispositivo dm-linear no kernel do Linux. A partição super contém metadados que listam os nomes e intervalos de blocos de cada partição dinâmica em super. Durante o init do primeiro estágio, isso os metadados são analisados e validados, e os dispositivos de bloqueio virtual são criados para para representar cada partição dinâmica.

Ao aplicar uma OTA, as partições dinâmicas são criadas automaticamente, redimensionadas ou excluídas conforme necessário. Para dispositivos A/B, há duas cópias dos os metadados, e as alterações são aplicadas somente à cópia que representa o na posição desejada.

Como as partições dinâmicas são implementadas no espaço do usuário, as partições precisam pelo carregador de inicialização não podem se tornar dinâmicos. Por exemplo, boot, dtbo e vbmeta são lidos pelo carregador de inicialização, e portanto, precisam permanecer como partições físicas.

Cada partição dinâmica pode pertencer a um grupo de atualização. Esses limitam o espaço máximo que as partições deles podem consumir. Por exemplo, system e vendor podem pertencer a um que restringe o tamanho total de system e vendor.

Implementar partições dinâmicas em novos dispositivos

Esta seção detalha como implementar partições dinâmicas em novos dispositivos sendo lançados com o Android 10 e versões mais recentes. Para atualizar outros dispositivos, consulte Como fazer upgrade do Android dispositivos.

Mudanças na partição

Para dispositivos lançados com o Android 10, crie uma partição chamada super. O super a partição processa slots A/B internamente, de modo que dispositivos A/B não precisam separe as partições super_a e super_b. Todas as partições AOSP somente leitura não usadas pelo carregador de inicialização precisam ser dinâmico e deve ser removido da tabela de partição GUID (GPT). As partições específicas do fornecedor não precisam ser dinâmicas e podem ser colocadas na GPT.

Para estimar o tamanho de super, adicione os tamanhos do partições estão sendo excluídas da GPT. Para dispositivos A/B, deve incluir o tamanho de ambos os espaços. A Figura 1 mostra um exemplo de tabela de partições antes e depois da conversão para dinâmica partições diferentes.

Layout da tabela de partições
Figura 1. Novo layout de tabela de partição física quando conversão para partições dinâmicas
.

As partições dinâmicas compatíveis são:

  • Sistema
  • Fornecedor
  • Produto
  • Extensão do sistema
  • ODM

Para dispositivos lançados com o Android 10, os opção de linha de comando do kernel androidboot.super_partition deve estar vazio para que o comando sysprop ro.boot.super_partition está vazio.

Alinhamento de partições

O módulo device-mapper pode operar de forma menos eficiente se o A partição super não está alinhada corretamente. A A partição super PRECISA estar alinhada ao mínimo de E/S tamanho da solicitação, conforme determinado pela camada de blocos. Por padrão, o sistema de build (via lpmake, que gera o super), pressupõe que um alinhamento de 1 MiB é suficiente para todas as partições dinâmicas. No entanto, os fornecedores devem verifique se a partição super está alinhada corretamente.

É possível determinar o tamanho mínimo da solicitação de um dispositivo de transferência por blocos inspecionando sysfs. Exemplo:

# 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

É possível verificar o alinhamento da partição super em uma semelhante:

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

O deslocamento de alinhamento PRECISA ser 0.

Mudanças na configuração do dispositivo

Para ativar o particionamento dinâmico, adicione a seguinte flag em device.mk:

PRODUCT_USE_DYNAMIC_PARTITIONS := true

Mudanças na configuração da placa

É necessário definir o tamanho da partição super:

BOARD_SUPER_PARTITION_SIZE := <size-in-bytes>

Em dispositivos A/B, o sistema de build gera um erro se o tamanho total das imagens de partição dinâmica é mais da metade do super tamanho da partição.

Configure a lista de partições dinâmicas da seguinte maneira: Para dispositivos usando grupos de atualização, liste os grupos no variável BOARD_SUPER_PARTITION_GROUPS. O nome de cada grupo tem um BOARD_group_SIZE e a variável BOARD_group_PARTITION_LIST. Para dispositivos A/B, o tamanho máximo de um grupo deve abranger apenas um já que os nomes dos grupos são sufixados internamente.

Confira um exemplo de dispositivo que coloca todas as partições em um grupo chamado 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

Aqui está um exemplo de dispositivo que coloca serviços de produtos e do sistema group_foo, e vendor, product, e odm em 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
.
  • Para dispositivos de inicialização A/B virtuais, a soma dos tamanhos máximos de todos os grupos precisa ter no máximo:
    BOARD_SUPER_PARTITION_SIZE: sobrecarga
    Consulte Como implementar o A/B virtual.
  • Para dispositivos de inicialização A/B, a soma dos tamanhos máximos de todos os grupos precisa ser:
    BOARD_SUPER_PARTITION_SIZE / 2: sobrecarga
  • Para dispositivos não A/B e aparelhos A/B da Retrofit, a soma do valor máximo os tamanhos de todos os grupos precisam ser:
    BOARD_SUPER_PARTITION_SIZE: sobrecarga
  • No tempo de build, a soma dos tamanhos das imagens de cada partição em um grupo de atualização não deve exceder o tamanho máximo do grupo.
  • Overhead é necessário na computação para contabilizar metadados, alinhamentos e assim por diante. Uma sobrecarga razoável é de 4 MiB, mas você uma sobrecarga maior, conforme a necessidade do dispositivo.

Dimensionar partições dinâmicas

Antes das partições dinâmicas, os tamanhos das partições eram superalocados para garantir que tivesse espaço suficiente para atualizações futuras. O tamanho real foi tomado no estado em que se encontra, e a maioria das partições somente leitura tinha uma quantidade espaço no sistema de arquivos. Em partições dinâmicas, esse espaço livre é inutilizável e possa ser usado para aumentar partições durante uma OTA. É essencial garantir que as partições não desperdam espaço alocada para o tamanho mínimo possível.

Para imagens ext4 somente leitura, o sistema de build aloca automaticamente o tamanho mínimo se nenhum tamanho de partição fixado no código for especificado. A o sistema de build se ajusta à imagem para que o sistema de arquivos tenha o mínimo não utilizado. Isso garante que o dispositivo não desperdice que pode ser usado para OTAs.

Além disso, as imagens ext4 podem ser ainda mais compactadas com a ativação e eliminação de duplicação. Para ativar isso, use a seguinte configuração:

BOARD_EXT4_SHARE_DUP_BLOCKS := true

Se a alocação automática de um tamanho mínimo de partição for indesejável, há duas formas de controlar o tamanho da partição. É possível especificar espaço livre mínimo com BOARD_partitionIMAGE_PARTITION_RESERVED_SIZE, ou especificar BOARD_partitionIMAGE_PARTITION_SIZE para forçar partições dinâmicas para um tamanho específico. Nenhuma das opções é recomendadas, a menos que seja necessário.

Exemplo:

BOARD_PRODUCTIMAGE_PARTITION_RESERVED_SIZE := 52428800

Isso força o sistema de arquivos em product.img a ter 50 MiB de espaço não utilizado.

Alterações no sistema como raiz

Os dispositivos lançados com o Android 10 não podem usar o sistema como raiz.

Dispositivos com partições dinâmicas (lançamentos ou retrofits) partições dinâmicas) não podem usar o sistema como raiz. O kernel do Linux não pode interpretar a partição super e, portanto, não poderá montar system. system agora é montado por init de primeiro estágio, que fica no ramdisk.

Não defina BOARD_BUILD_SYSTEM_ROOT_IMAGE. Em O Android 10, a A sinalização BOARD_BUILD_SYSTEM_ROOT_IMAGE é usada apenas para para diferenciar se o sistema é montado pelo kernel ou pelo init de primeiro estágio no ramdisk.

Definindo BOARD_BUILD_SYSTEM_ROOT_IMAGE como true causa um erro de build quando PRODUCT_USE_DYNAMIC_PARTITIONS também é true.

Quando BOARD_USES_RECOVERY_AS_BOOT é definido como verdadeiro, imagem de recuperação é criada como boot.img, que contém o arquivo no ramdisk. Anteriormente, o carregador de inicialização usava o kernel skip_initramfs parâmetro de linha de comando para decidir o modo de inicialização. Para Android 10, o carregador de inicialização NÃO PODE transmitir skip_initramfs à linha de comando do kernel. Em vez disso, o carregador de inicialização precisa transmitir androidboot.force_normal_boot=1 para pular a recuperação e inicializará o Android normal. Dispositivos lançados com o Android 12 ou mais recente precisa usar o bootconfig para transmitir androidboot.force_normal_boot=1.

Mudanças na configuração do AVB

Ao usar o Android Inicialização verificada 2.0, se o dispositivo não estiver usando partição encadeada descritores, nenhuma alteração é necessária. Se estiver usando encadeado partições diferentes, e uma das partições verificadas é dinâmica, é necessário fazer mudanças.

Este é um exemplo de configuração para um dispositivo que encadeia vbmeta para system e vendor partições.

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

Com essa configuração, o carregador de inicialização espera encontrar um vbmeta no rodapé no fim da system e vendor partições. Como essas partições não são mais visíveis para o carregador de inicialização (eles ficam em super), dois mudanças são necessárias.

  • Adicionar vbmeta_system e vbmeta_vendor para a tabela de partições do dispositivo. Para dispositivos A/B, adicione vbmeta_system_a, vbmeta_system_b. vbmeta_vendor_a e vbmeta_vendor_b. Se adicionando uma ou mais dessas partições, elas devem ter o mesmo tamanho como a partição vbmeta.
  • Renomeie as flags de configuração adicionando VBMETA_ e especifique para quais partições o encadeamento se estende:
    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
    

Um dispositivo pode usar uma, ambas ou nenhuma dessas partições. Alterações são necessárias apenas ao encadear a uma partição lógica.

Mudanças no carregador de inicialização da AVB

Se o carregador de inicialização tiver libavb incorporado, inclua os seguintes patches:

Se estiver usando partições encadeadas, inclua um patch adicional:

  • 49936b4c0109411fdd38bd4ba3a32a01c40439a9 — "libavb: suporte a blobs vbmeta no início da partição."

Mudanças na linha de comando do kernel

Um novo parâmetro, androidboot.boot_devices, precisa ser adicionado à linha de comando do kernel. Ele é usado por init para ativar links simbólicos /dev/block/by-name. Deve ser componente de caminho do dispositivo para o link simbólico subjacente de nome criado pelo ueventd, ou seja, /dev/block/platform/device-path/by-name/partition-name. Os dispositivos lançados com o Android 12 ou versões mais recentes precisam usar o bootconfig para transmitir androidboot.boot_devices para init.

Por exemplo, se o link simbólico de superpartição por nome for /dev/block/platform/soc/100000.ufshc/by-name/super, você pode adicionar o parâmetro de linha de comando no arquivo BoardConfig.mk como da seguinte forma:

BOARD_KERNEL_CMDLINE += androidboot.boot_devices=soc/100000.ufshc
É possível adicionar o parâmetro bootconfig ao arquivo BoardConfig.mk da seguinte maneira:
BOARD_BOOTCONFIG += androidboot.boot_devices=soc/100000.ufshc

mudanças fstab

As sobreposições da árvore de dispositivos e da árvore de dispositivos não podem conter fstab de entradas de registro. Use um arquivo fstab que vai fazer parte do ramdisk.

É preciso fazer alterações no arquivo fstab para partições lógicas:

  • O campo de sinalizações fs_mgr precisa incluir a sinalização logical. e a flag first_stage_mount, introduzidas Android 10, que indica que uma partição precisa ser montado na primeira etapa.
  • Uma partição pode especificar avb=vbmeta partition name como um a sinalização fs_mgr e o vbmeta especificado; é inicializada pelo primeiro estágio init antes de ao tentar montar um dispositivo.
  • O campo dev precisa ser o nome da partição.

As entradas fstab a seguir definem o sistema, o fornecedor e o produto como lógicos partições diferentes seguindo as regras acima.

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

Copie o arquivo fstab no ramdisk da primeira etapa.

Mudanças no SELinux

O dispositivo de bloco de superpartição precisa ser marcado com o rótulo super_block_device: Por exemplo, se o link simbólico de superpartição por nome for /dev/block/platform/soc/100000.ufshc/by-name/super, adicione a seguinte linha a file_contexts:

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

fastbootd

O carregador de inicialização (ou qualquer ferramenta de atualização no espaço do usuário) não entende partições dinâmicas, por isso não pode atualizá-las. Para resolver isso, os dispositivos precisa usar uma implementação no espaço do usuário do protocolo fastboot, chamada pelo fastboot.

Para mais informações sobre como implementar o fastbootd, consulte Como mover o fastboot para o espaço do usuário.

Remontagem do adb

Para desenvolvedores que usam builds eng ou userdebug, adb remount é extremamente útil para iteração rápida. As partições dinâmicas representam uma problema para adb remount porque não há mais livre espaço dentro de cada sistema de arquivos. Para resolver isso, os dispositivos podem ativar overlayfs. Contanto que haja espaço livre na superpartição, adb remount cria automaticamente um objeto dinâmico temporário e usa overlayfs para gravações. A partição temporária é chamado scratch. Não use esse nome para outros partições diferentes.

Para obter mais informações sobre como ativar overlayfs, consulte overlayfs README no AOSP.

Fazer upgrade dos dispositivos Android

Se você fizer upgrade de um dispositivo para o Android 10 e quiser incluir suporte a partições dinâmicas no OTA, não será preciso alterar a tabela de partições integrada. É necessário configurar obrigatórios.

Mudanças na configuração do dispositivo

Para atualizar o particionamento dinâmico, adicione as seguintes flags em device.mk:

PRODUCT_USE_DYNAMIC_PARTITIONS := true
PRODUCT_RETROFIT_DYNAMIC_PARTITIONS := true

Mudanças na configuração da placa

É necessário definir as seguintes variáveis de tabuleiro:

  • Defina BOARD_SUPER_PARTITION_BLOCK_DEVICES como a lista de dispositivos de transferência por blocos usados. para armazenar extensões de partições dinâmicas. Esta é a lista dos nomes das instalações físicas partições diferentes no dispositivo.
  • Defina BOARD_SUPER_PARTITION_partition_DEVICE_SIZE como os tamanhos. de cada dispositivo de bloco em BOARD_SUPER_PARTITION_BLOCK_DEVICES, respectivamente. Esta é a lista de tamanhos das partições físicas existentes no dispositivo. Isso geralmente é BOARD_partitionIMAGE_PARTITION_SIZE no quadro existente personalizadas.
  • Cancelar a configuração de BOARD_partitionIMAGE_PARTITION_SIZE existente para todos partições diferentes em BOARD_SUPER_PARTITION_BLOCK_DEVICES.
  • Defina BOARD_SUPER_PARTITION_SIZE como a soma de BOARD_SUPER_PARTITION_partition_DEVICE_SIZE.
  • Defina BOARD_SUPER_PARTITION_METADATA_DEVICE como o dispositivo de transferência por blocos em que os metadados da partição dinâmica são armazenados. Precisa ser um dos BOARD_SUPER_PARTITION_BLOCK_DEVICES: Normalmente, ela é definida como system:
  • Definir BOARD_SUPER_PARTITION_GROUPS, BOARD_group_SIZE e BOARD_group_PARTITION_LIST, respectivamente. Consulte Mudanças na configuração da placa em novos dispositivos para mais detalhes.

Por exemplo, se o dispositivo já tiver partições do sistema e do fornecedor, e você quiser converter para partições dinâmicas e adicionar uma nova partição do produto durante a atualização, defina esta configuração da placa:

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

Mudanças no SELinux

Os dispositivos de blocos de superpartição precisam ser marcados com o atributo super_block_device_type: Por exemplo, se o dispositivo já tiver system e vendor, você quer usá-las como bloco. dispositivos para armazenar as extensões das partições dinâmicas, e os links simbólicos por nome são marcados como 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

Em seguida, adicione a linha abaixo a device.te:

typeattribute system_block_device super_block_device_type;

Para outras configurações, consulte Como implementar partições dinâmicas em dispositivos novos.

Para mais informações sobre as atualizações da Retrofit, consulte OTA para dispositivos A/B sem recursos dinâmicos Partições.

Imagens de fábrica

Para um dispositivo com suporte a partições dinâmicas, evite usar use o fastboot para atualizar imagens de fábrica, já que a inicialização com o espaço do usuário é mais lento do que outros métodos de atualização.

Para resolver isso, o make dist agora cria Imagem super.img que pode ser transferida diretamente para o super partição. Ele agrupa automaticamente o conteúdo de blocos partições diferentes, ou seja, ele contém system.img, vendor.img, e assim por diante, além do super. metadados de partição. Essa imagem pode ser atualizada diretamente no Partição super sem ferramentas adicionais ou uso pelo fastboot. Depois da criação, super.img é colocado em ${ANDROID_PRODUCT_OUT}.

Para dispositivos A/B iniciados com partições dinâmicas, super.img contém imagens no slot A. Depois de realizar a atualização imagem direta, marque o slot A como inicializável antes de reiniciar a dispositivo.

Para dispositivos retrofit, make dist cria um conjunto de super_*.img imagens que podem ser atualizadas diretamente para partições físicas correspondentes. Por exemplo, make dist. builds super_system.img e super_vendor.img quando BOARD_SUPER_PARTITION_BLOCK_DEVICES é o sistema fornecedor. Essas imagens são colocadas na pasta OTA no target_files.zip:

Ajuste de dispositivo de armazenamento do mapeador de dispositivos

O particionamento dinâmico acomoda vários mapeadores de dispositivo não determinísticos objetos. Nem todas elas são instanciadas conforme esperado, portanto, é preciso acompanhar montadas e atualize as propriedades do Android de todas as partições associadas com os dispositivos de armazenamento subjacentes.

Um mecanismo dentro de init rastreia as montagens de forma assíncrona. atualiza as propriedades do Android. O tempo necessário não é garantido em um período específico. Por isso, é preciso dar tempo suficiente para que todos os acionadores on property reajam. As propriedades são dev.mnt.blk.<partition> em que <partition> é root, system, data ou vendor, por exemplo. Cada propriedade está associada ao do dispositivo de armazenamento base, como mostrado nestes exemplos:

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]

A linguagem init.rc permite que as propriedades do Android sejam expandido como parte das regras, e os dispositivos de armazenamento podem ser ajustados pela plataforma conforme necessário, com comandos como:

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

Quando o processamento do comando começar no estágio init do segundo estágio, o epoll loop fica ativo, e os valores começam a ser atualizados. No entanto, Como os acionadores de propriedade só ficam ativos depois de init, eles não pode ser usado nos estágios iniciais de inicialização para lidar com root; system ou vendor. Você pode esperar que o padrão do kernel read_ahead_kb é ser suficiente até que o Os scripts init.rc podem ser substituídos em early-fs (quando vários daemons e instalações são iniciados). Por isso, o Google recomenda que você usa o recurso on property, junto com um propriedade controlada por init.rc, como sys.read_ahead_kb, para lidar com o tempo das operações e evitar disputas, como nestas exemplos:

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}