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 à forma 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. Remover os símbolos de depuração dos módulos ajuda no 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.

Remover 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 versão da plataforma Android, mas para ativá-los explicitamente, defina BOARD_DO_NOT_STRIP_VENDOR_RAMDISK_MODULES na configuração específica do dispositivo em device/ vendor / device .

Use 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 de armazenamento do uso do Gzip não é tão significativa em comparação com o benefício do tempo de descompactação do LZ4.

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

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

Evite login excessivo em seus drivers

No ARM64 e 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 tabela de ligaçã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 relocação são chamadas de entradas de relocação com entradas explícitas de adendos (ou RELA, para abreviar) no formato ELF.

O kernel do Linux faz alguma otimização do tamanho da memória (como otimização de ocorrências 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 RELAs R_AARCH64_CALL26 ou R_AARCH64_JUMP26 é o registro excessivo em um driver. Cada chamada para printk() ou qualquer outro esquema de registro normalmente adiciona uma entrada CALL26 / JUMP26 RELA. No texto do commit no commit upstream , observe que mesmo com a otimização, os seis módulos levam cerca de 250 ms para carregar - isso porque esses seis módulos foram os seis principais módulos com a maior quantidade de registro.

A redução do registro pode economizar cerca de 100 a 300 ms nos tempos de inicialização , dependendo de quão excessivo é o registro existente.

Habilite a análise 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, então a investigação do dispositivo é feita no contexto da chamada module_init() . Quando uma investigação de dispositivo é realizada no contexto de module_init() , o módulo não pode terminar o carregamento até que a investigação seja concluída. Como o carregamento do módulo é principalmente serializado, um dispositivo que leva um tempo relativamente longo para ser testado retarda 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. Habilitar a sondagem assíncrona para todos os módulos pode não ser benéfico, pois o tempo necessário para bifurcar um thread 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 teste 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 análise de cada driver e classificá-lo.

Para habilitar a investigação assíncrona para um módulo, não é suficiente definir apenas 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 parâmetro do módulo ao carregar o módulo usando modprobe ou insmod .

A ativação da análise assíncrona pode economizar cerca de 100 a 500 ms nos tempos de inicialização , dependendo do hardware/drivers.

Teste seu driver CPUfreq o mais cedo possível

Quanto mais cedo o driver CPUfreq for testado, mais cedo você poderá escalar a frequência da CPU para o máximo (ou algum máximo termicamente limitado) durante a inicialização. Quanto mais rápida a CPU, mais rápida será a inicialização. Esta 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 ordem de carregamento pode depender do nível 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 certificar-se 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 regulador 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 ao testar seu driver CPUfreq antecipadamente pode ser muito pequena a muito grande, dependendo de quão cedo você pode fazer isso e com que frequência o bootloader deixa as CPUs.

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

Como o processo init 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 .

A inicialização do primeiro estágio não requer a verificação de vários dispositivos para chegar à inicialização do segundo estágio. Somente a funcionalidade de console e armazenamento flash é necessária para um fluxo de inicialização normal.

Carregue os seguintes drivers essenciais:

  • cão de guarda
  • reiniciar
  • CPUfreq

Para recuperação e modo fastbootd de espaço do usuário, o primeiro estágio do init 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 primeiro estágio do init para recuperação ou fluxo de inicialização fastbootd . No entanto, não carregue os módulos do modo de recuperação no primeiro estágio de inicialização durante o fluxo de inicialização normal. Os módulos do modo de recuperação podem ser adiados para o segundo estágio de inicialização para diminuir o tempo de inicialização. Todos os outros módulos que não são necessários no primeiro estágio do init devem ser movidos para a partição vendor ou vendor_dlkm .

Dada uma lista de dispositivos folha (por exemplo, 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 serem investigados.

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

  • Redução do tamanho do Ramdisk.
    • Isso produz leituras de flash mais rápidas quando o bootloader 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 no tempo de inicialização , dependendo de quantos módulos você consegue mover para o init do segundo estágio.

Logística de carregamento de módulos

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 recuperação ou fastbootd for selecionado no disco RAM.
  • 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 init do segundo estágio.

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 depuração e coleta de modinfo para relatórios de bugs.

A duplicação deve custar 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 de inicialização em cada fluxo de inicialização.

Você pode usar os arquivos Board.Config.mk do dispositivo para executar essas ações, conforme visto 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 mais fácil de gerenciar de BOOT_KERNEL_MODULES e RECOVERY_KERNEL_MODULES para ser especificado localmente nos arquivos de configuração da placa. O script anterior localiza e preenche cada um dos módulos do subconjunto dos módulos do kernel disponíveis selecionados, deixando os módulos restantes para o segundo estágio de inicialização.

Para a inicialização do segundo estágio, recomendamos executar o carregamento do módulo como um serviço para não bloquear 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 no 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 estágios posteriores do fluxo de inicialização do script init rc para continuar na tela de inicialização. Consulte o 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 módulos no serviço de carregamento de módulos que sejam mais propícios ao carregamento após a tela de inicialização. Por exemplo, você pode carregar explicitamente os módulos para decodificador de vídeo ou wifi depois que o fluxo de inicialização init tiver sido limpo ( sys.boot_complete sinal de propriedade Android, por exemplo). Certifique-se de que os HALs dos 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 entradas selecionadas sysfs para mostrar que os módulos do driver concluíram as operações de investigação. Um exemplo disso é esperar que o driver de vídeo conclua o carregamento em segundo plano de recuperação ou fastbootd , antes de apresentar os gráficos do menu.

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

Nem todos os SoCs/produtos podem ser capazes de 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. Isto é muito importante porque, com um kernel totalmente modular, a descompressão init do ramdisk ocorre antes que o driver CPUfreq possa ser carregado. Portanto, se a CPU for deixada no limite inferior de sua frequência pelo gerenciador de inicialização, o tempo de descompactação do disco ram pode demorar mais do que um kernel compilado estaticamente (após ajustar a diferença de tamanho do disco ram) 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 CPUs grandes no bootloader

Antes do driver CPUfreq ser carregado, o kernel não tem conhecimento das pequenas e grandes frequências da CPU e não dimensiona a capacidade programada 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 que as CPUs pequenas para a frequência em que o gerenciador de inicialização as deixa. Por exemplo, se a CPU grande tiver 2x o desempenho da CPU pequena para a mesma frequência, mas o carregador de inicialização definir o 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ê deverá fazê-lo mesmo que não planeje usá-la explicitamente.

Os drivers não devem carregar firmware no primeiro estágio de inicialização

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 no primeiro estágio de inicialização, especialmente no contexto de investigação do dispositivo. Carregar o 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 ramdisk do primeiro estágio. E mesmo que o firmware esteja presente no ramdisk do primeiro estágio, ainda causa um atraso desnecessário.