Otimização do tempo de inicialização

Esta página fornece um conjunto de dicas que você pode selecionar para melhorar o tempo de inicialização.

Retirar símbolos de depuração dos módulos

Semelhante a como os símbolos de depuração são removidos do kernel em um dispositivo de produção, certifique-se de também remover os símbolos de depuração dos módulos. A remoção dos símbolos de depuração dos módulos ajuda o tempo de inicialização reduzindo o seguinte:

  • O tempo que leva para ler os binários do flash.
  • O tempo que leva para descompactar o ramdisk.
  • O tempo que leva para carregar os módulos.

Retirar o símbolo de depuração dos módulos pode economizar vários segundos durante a inicialização.

A remoção de símbolos é habilitada por padrão na compilação da plataforma Android, mas para habilitá-los explicitamente, defina BOARD_DO_NOT_STRIP_VENDOR_RAMDISK_MODULES na configuração específica do seu dispositivo em device/ vendor / device .

Use a compactação LZ4 para kernel e ramdisk

O Gzip gera uma saída compactada menor em comparação com o LZ4, mas o LZ4 descompacta mais rápido que o Gzip. Para o kernel e os módulos, a redução absoluta do tamanho do armazenamento com o uso do Gzip não é tão significativa em comparação com o benefício do tempo de descompressão do LZ4.

O suporte para compactação de ramdisk LZ4 foi adicionado à compilação da plataforma Android por meio de BOARD_RAMDISK_USE_LZ4 . Você pode definir essa opção na configuração específica do seu dispositivo. A compactação do kernel pode ser definida por meio do defconfig do kernel.

Mudar para LZ4 deve dar um tempo de inicialização de 500ms a 1000ms mais rápido.

Evite o login excessivo em seus drivers

No ARM64 e no ARM32, as chamadas de função que estão a mais de uma distância específica do site de chamada precisam de uma tabela de salto (chamada de tabela de vinculação de procedimento ou PLT) para poder codificar o endereço de salto completo. Como os módulos são carregados dinamicamente, essas tabelas de salto precisam ser corrigidas durante o carregamento do módulo. As chamadas que precisam de realocação são chamadas de entradas de realocação com entradas de adendos explícitos (ou RELA, para abreviar) no formato ELF.

O kernel do Linux faz alguma otimização do tamanho da memória (como otimização de acertos de cache) ao alocar o PLT. Com este commit upstream , o esquema de otimização tem uma complexidade O(N^2), onde N é o número de RELAs do tipo R_AARCH64_JUMP26 ou R_AARCH64_CALL26 . Portanto, ter menos RELAs desses tipos é útil para reduzir o tempo de carregamento do módulo.

Um padrão de codificação comum que aumenta o número de R_AARCH64_CALL26 ou R_AARCH64_JUMP26 RELAs é o log excessivo em um driver. Cada chamada para printk() ou qualquer outro esquema de log normalmente adiciona uma CALL26 / JUMP26 RELA. No texto do commit no upstream commit , observe que, mesmo com a otimização, os seis módulos levam cerca de 250ms para carregar - isso porque esses seis módulos foram os seis principais módulos com a maior quantidade de log.

Reduzir o registro pode economizar cerca de 100 a 300 ms em tempos de inicialização, dependendo de quão excessivo o registro existente é.

Ativar sondagem assíncrona, seletivamente

Quando um módulo é carregado, se o dispositivo que ele suporta já foi preenchido a partir do DT (devicetree) e adicionado ao núcleo do driver, o teste do dispositivo é feito no contexto da chamada module_init() . Quando um teste de dispositivo é feito no contexto de module_init() , o módulo não pode terminar de carregar até que o teste seja concluído. Como o carregamento do módulo é principalmente serializado, um dispositivo que leva um tempo relativamente longo para testar diminui o tempo de inicialização.

Para evitar tempos de inicialização mais lentos, habilite a sondagem assíncrona para módulos que demoram um pouco para sondar seus dispositivos. A ativação da sondagem assíncrona para todos os módulos pode não ser benéfica, pois o tempo necessário para bifurcar um encadeamento e iniciar a sondagem pode ser tão alto quanto o tempo necessário para sondar o dispositivo.

Dispositivos conectados por meio de um barramento lento, como I2C, dispositivos que carregam firmware em sua função de sonda e dispositivos que fazem muita inicialização de hardware podem levar ao problema de temporização. A melhor maneira de identificar quando isso acontece é coletar o tempo de teste para cada driver e classificá-lo.

Para habilitar a sondagem assíncrona para um módulo, não é suficiente apenas definir o sinalizador PROBE_PREFER_ASYNCHRONOUS no código do driver. Para módulos, você também precisa adicionar module_name .async_probe=1 na linha de comando do kernel ou passar async_probe=1 como um parâmetro de módulo ao carregar o módulo usando modprobe ou insmod .

A ativação da sondagem assíncrona pode economizar cerca de 100 a 500 ms em tempos de inicialização, dependendo do seu hardware/drivers.

Teste seu driver CPUfreq o mais cedo possível

Quanto mais cedo o driver CPUfreq testar, mais cedo você poderá dimensionar a frequência da CPU para o máximo (ou algum máximo limitado termicamente) durante a inicialização. Quanto mais rápida a CPU, mais rápida a inicialização. Essa diretriz também se aplica aos drivers devfreq que controlam a DRAM, a memória e a frequência de interconexão.

Com módulos, a ordenação de carga pode depender do nível de initcall e da ordem de compilação ou link dos drivers. Use um alias MODULE_SOFTDEP() para garantir que o driver cpufreq esteja entre os primeiros módulos a serem carregados.

Além de carregar o módulo antecipadamente, você também precisa ter certeza de que todas as dependências para testar o driver CPUfreq também foram testadas. Por exemplo, se você precisar de um relógio ou alça reguladora para controlar a frequência de sua CPU, certifique-se de que eles sejam testados primeiro. Ou você pode precisar que os drivers térmicos sejam carregados antes do driver CPUfreq se for possível que suas CPUs fiquem muito quentes durante a inicialização. Portanto, faça o que puder para garantir que os drivers CPUfreq e devfreq relevantes sejam investigados o mais cedo possível.

A economia de testar seu driver CPUfreq antecipadamente pode ser muito pequena a muito grande, dependendo de quão cedo você pode obtê-los e com que frequência o bootloader deixa as CPUs.

Mova os módulos para a partição init, vendor ou vendor_dlkm do segundo estágio

Como o processo de inicialização do primeiro estágio é serializado, não há muitas oportunidades para paralelizar o processo de inicialização. Se um módulo não for necessário para a conclusão do primeiro estágio, mova o módulo para o segundo estágio colocando-o na partição vendor ou vendor_dlkm .

O init do primeiro estágio não requer a verificação de vários dispositivos para obter o init do segundo estágio. Apenas console e funcionalidade de armazenamento flash são necessários para um fluxo de inicialização normal.

Carregue os seguintes drivers essenciais:

  • cão de guarda
  • Redefinir
  • cpufreq

Para o modo fastbootd de recuperação e espaço do usuário, o init de primeiro estágio requer mais dispositivos para sondar (como USB) e exibir. Mantenha uma cópia desses módulos no ramdisk do primeiro estágio e na partição vendor ou vendor_dlkm . Isso permite que eles sejam carregados no init do primeiro estágio para recuperação ou fluxo de inicialização fastbootd . No entanto, não carregue os módulos do modo de recuperação na inicialização do primeiro estágio durante o fluxo de inicialização normal. Os módulos do modo de recuperação podem ser adiados para o init de segundo estágio para diminuir o tempo de inicialização. Todos os outros módulos que não são necessários no init do primeiro estágio devem ser movidos para a partição vendor ou vendor_dlkm .

Dada uma lista de dispositivos folha (por exemplo, o UFS ou serial), o script dev needs.sh encontra todos os drivers, dispositivos e módulos necessários para dependências ou fornecedores (por exemplo, relógios, reguladores ou gpio ) para investigar.

Mover módulos para o init do segundo estágio diminui os tempos de inicialização das seguintes maneiras:

  • Redução do tamanho do Ramdisk.
    • Isso produz leituras flash mais rápidas quando o carregador de inicialização carrega o ramdisk (etapa de inicialização serializada).
    • Isso produz velocidades de descompactação mais rápidas quando o kernel descompacta o ramdisk (etapa de inicialização serializada).
  • O init do segundo estágio funciona em paralelo, o que oculta o tempo de carregamento do módulo com o trabalho que está sendo feito no init do segundo estágio.

Mover módulos para o segundo estágio pode economizar de 500 a 1000 ms em tempos de inicialização, dependendo de quantos módulos você pode mover para o init do segundo estágio.

Logística de carregamento do módulo

A versão mais recente do Android apresenta configurações de placa que controlam quais módulos são copiados para cada estágio e quais módulos são carregados. Esta seção se concentra no seguinte subconjunto:

  • BOARD_VENDOR_RAMDISK_KERNEL_MODULES . Esta lista de módulos a serem copiados para o ramdisk.
  • BOARD_VENDOR_RAMDISK_KERNEL_MODULES_LOAD . Esta lista de módulos a serem carregados no primeiro estágio init.
  • BOARD_VENDOR_RAMDISK_RECOVERY_KERNEL_MODULES_LOAD . Esta lista de módulos a serem carregados quando recovery ou fastbootd é selecionado no ramdisk.
  • BOARD_VENDOR_KERNEL_MODULES . Esta lista de módulos a serem copiados para a partição vendor ou vendor_dlkm no diretório /vendor/lib/modules/ .
  • BOARD_VENDOR_KERNEL_MODULES_LOAD . Esta lista de módulos a serem carregados no segundo estágio init.

Os módulos de inicialização e recuperação no ramdisk também devem ser copiados para a partição vendor ou vendor_dlkm em /vendor/lib/modules . Copiar esses módulos para a partição do fornecedor garante que os módulos não fiquem invisíveis durante a inicialização do segundo estágio, o que é útil para depurar e coletar modinfo para relatórios de bugs.

A duplicação deve custar um espaço mínimo na partição vendor ou vendor_dlkm , desde que o conjunto de módulos de inicialização seja minimizado. Certifique-se de que o arquivo modules.list do fornecedor tenha uma lista filtrada de módulos em /vendor/lib/modules . A lista filtrada garante que os tempos de inicialização não sejam afetados pelo carregamento dos módulos novamente (o que é um processo caro).

Certifique-se de que os módulos do modo de recuperação sejam carregados como um grupo. O carregamento dos módulos do modo de recuperação pode ser feito no modo de recuperação ou no início do segundo estágio init em cada fluxo de inicialização.

Você pode usar os arquivos Board.Config.mk do dispositivo para executar essas ações, conforme mostrado no exemplo a seguir:

# All kernel modules
KERNEL_MODULES := $(wildcard $(KERNEL_MODULE_DIR)/*.ko)
KERNEL_MODULES_LOAD := $(strip $(shell cat $(KERNEL_MODULE_DIR)/modules.load)

# First stage ramdisk modules
BOOT_KERNEL_MODULES_FILTER := $(foreach m,$(BOOT_KERNEL_MODULES),%/$(m))

# Recovery ramdisk modules
RECOVERY_KERNEL_MODULES_FILTER := $(foreach m,$(RECOVERY_KERNEL_MODULES),%/$(m))
BOARD_VENDOR_RAMDISK_KERNEL_MODULES += \
     $(filter $(BOOT_KERNEL_MODULES_FILTER) \
                $(RECOVERY_KERNEL_MODULES_FILTER),$(KERNEL_MODULES))

# ALL modules land in /vendor/lib/modules so they could be rmmod/insmod'd,
# and modules.list actually limits us to the ones we intend to load.
BOARD_VENDOR_KERNEL_MODULES := $(KERNEL_MODULES)
# To limit /vendor/lib/modules to just the ones loaded, use:
# BOARD_VENDOR_KERNEL_MODULES := $(filter-out \
#     $(BOOT_KERNEL_MODULES_FILTER),$(KERNEL_MODULES))

# Group set of /vendor/lib/modules loading order to recovery modules first,
# then remainder, subtracting both recovery and boot modules which are loaded
# already.
BOARD_VENDOR_KERNEL_MODULES_LOAD := \
        $(filter-out $(BOOT_KERNEL_MODULES_FILTER), \
        $(filter $(RECOVERY_KERNEL_MODULES_FILTER),$(KERNEL_MODULES_LOAD)))
BOARD_VENDOR_KERNEL_MODULES_LOAD += \
        $(filter-out $(BOOT_KERNEL_MODULES_FILTER) \
            $(RECOVERY_KERNEL_MODULES_FILTER),$(KERNEL_MODULES_LOAD))

# NB: Load order governed by modules.load and not by $(BOOT_KERNEL_MODULES)
BOARD_VENDOR_RAMDISK_KERNEL_MODULES_LOAD := \
        $(filter $(BOOT_KERNEL_MODULES_FILTER),$(KERNEL_MODULES_LOAD))

# Group set of /vendor/lib/modules loading order to boot modules first,
# then the remainder of recovery modules.
BOARD_VENDOR_RAMDISK_RECOVERY_KERNEL_MODULES_LOAD := \
    $(filter $(BOOT_KERNEL_MODULES_FILTER),$(KERNEL_MODULES_LOAD))
BOARD_VENDOR_RAMDISK_RECOVERY_KERNEL_MODULES_LOAD += \
    $(filter-out $(BOOT_KERNEL_MODULES_FILTER), \
    $(filter $(RECOVERY_KERNEL_MODULES_FILTER),$(KERNEL_MODULES_LOAD)))

Este exemplo mostra um subconjunto de BOOT_KERNEL_MODULES e RECOVERY_KERNEL_MODULES mais fácil de gerenciar a ser especificado localmente nos arquivos de configuração da placa. O script anterior encontra e preenche cada um dos módulos de subconjunto dos módulos de kernel disponíveis selecionados, deixando os módulos restantes para a inicialização do segundo estágio.

Para a inicialização do segundo estágio, recomendamos executar o carregamento do módulo como um serviço para que não bloqueie o fluxo de inicialização. Use um script de shell para gerenciar o carregamento do módulo para que outras logísticas, como tratamento e mitigação de erros ou conclusão do carregamento do módulo, possam ser relatadas (ou ignoradas) se necessário.

Você pode ignorar uma falha de carregamento do módulo de depuração que não está presente nas compilações do usuário. Para ignorar essa falha, defina a propriedade vendor.device.modules.ready para acionar os estágios posteriores do fluxo de inicialização do script init rc para continuar na tela de inicialização. Faça referência ao script de exemplo a seguir, se você tiver o seguinte código em /vendor/etc/init.insmod.sh :

#!/vendor/bin/sh
. . .
if [ $# -eq 1 ]; then
  cfg_file=$1
else
  # Set property even if there is no insmod config
  # to unblock early-boot trigger
  setprop vendor.common.modules.ready
  setprop vendor.device.modules.ready
  exit 1
fi

if [ -f $cfg_file ]; then
  while IFS="|" read -r action arg
  do
    case $action in
      "insmod") insmod $arg ;;
      "setprop") setprop $arg 1 ;;
      "enable") echo 1 > $arg ;;
      "modprobe") modprobe -a -d /vendor/lib/modules $arg ;;
     . . .
    esac
  done < $cfg_file
fi

No arquivo rc de hardware, o serviço one shot pode ser especificado com:

service insmod-sh /vendor/etc/init.insmod.sh /vendor/etc/init.insmod.<hw>.cfg
    class main
    user root
    group root system
    Disabled
    oneshot

Otimizações adicionais podem ser feitas após os módulos passarem do primeiro para o segundo estágio. Você pode usar o recurso de lista de bloqueio modprobe para dividir o fluxo de inicialização do segundo estágio para incluir o carregamento de módulo adiado de módulos não essenciais. O carregamento de módulos usados ​​exclusivamente por um HAL específico pode ser adiado para carregar os módulos somente quando o HAL for iniciado.

Para melhorar os tempos de inicialização aparentes, você pode escolher especificamente os módulos no serviço de carregamento de módulos que são mais propícios ao carregamento após a tela de inicialização. Por exemplo, você pode carregar explicitamente os módulos para o decodificador de vídeo ou Wi-Fi após a limpeza do fluxo de inicialização init ( sys.boot_complete sinal de propriedade do Android, por exemplo). Certifique-se de que os HALs para os módulos de carregamento tardio sejam bloqueados por tempo suficiente quando os drivers do kernel não estiverem presentes.

Como alternativa, você pode usar o comando wait<file>[<timeout>] do init no script rc do fluxo de inicialização para aguardar as entradas sysfs selecionadas para mostrar que os módulos do driver concluíram as operações do probe. Um exemplo disso é esperar que o driver de vídeo complete o carregamento em segundo plano de recovery ou fastbootd , antes de apresentar os gráficos do menu.

Inicialize a frequência da CPU para um valor razoável no bootloader

Nem todos os SoCs/produtos podem inicializar a CPU na frequência mais alta devido a problemas térmicos ou de energia durante os testes de loop de inicialização. No entanto, certifique-se de que o bootloader defina a frequência de todas as CPUs online para o mais alto possível para um SoC/produto. Isso é muito importante porque, com um kernel totalmente modular, a descompressão do init ramdisk ocorre antes que o driver CPUfreq possa ser carregado. Portanto, se a CPU for deixada na extremidade inferior de sua frequência pelo bootloader, o tempo de descompactação do ramdisk pode demorar mais do que um kernel compilado estaticamente (após ajustar a diferença de tamanho do ramdisk) porque a frequência da CPU seria muito baixa ao fazer uso intensivo da CPU trabalho (descompressão). O mesmo se aplica à frequência de memória/interconexão.

Inicialize a frequência da CPU de grandes CPUs no bootloader

Antes que o driver CPUfreq seja carregado, o kernel desconhece as frequências pequenas e grandes da CPU e não dimensiona a capacidade sched das CPUs para sua frequência atual. O kernel pode migrar threads para a CPU grande se a carga for suficientemente alta na CPU pequena.

Certifique-se de que as CPUs grandes tenham pelo menos o mesmo desempenho das CPUs pequenas para a frequência em que o carregador de inicialização as deixa. a frequência da CPU pequena para 1,5 GHz e a frequência da CPU grande para 300 MHz, então o desempenho de inicialização cairá se o kernel mover um thread para a CPU grande. Neste exemplo, se for seguro inicializar a CPU grande a 750 MHz, você deve fazê-lo mesmo que não planeje usá-la explicitamente.

Os drivers não devem carregar o firmware na inicialização do primeiro estágio

Pode haver alguns casos inevitáveis ​​em que o firmware precisa ser carregado no primeiro estágio de inicialização. Mas, em geral, os drivers não devem carregar nenhum firmware na inicialização do primeiro estágio, especialmente no contexto de sondagem do dispositivo. O carregamento do firmware no init do primeiro estágio faz com que todo o processo de inicialização seja interrompido se o firmware não estiver disponível no disco ram do primeiro estágio. E mesmo que o firmware esteja presente no ramdisk do primeiro estágio, ainda causa um atraso desnecessário.