Otimização do tempo de inicialização

Esta página fornece dicas para melhorar o tempo de inicialização.

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

Semelhante a como os símbolos de depuração são removidos do kernel em uma produção remova os símbolos de depuração dos módulos. Remoção de depuração de módulos ajuda o tempo de inicialização reduzindo:

  • 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.

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

A remoção de símbolos é ativada por padrão no build da plataforma Android, mas para ativá-las, defina BOARD_DO_NOT_STRIP_VENDOR_RAMDISK_MODULES na configuração específica do dispositivo em dispositivo/vendor/device.

Usar 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 rapidamente do que o Gzip. Para o kernel e os módulos, o armazenamento absoluto a redução de tamanho com o Gzip não é tão significativa em comparação com a benefício do tempo de descompactação do LZ4.

Adicionamos suporte à compactação LZ4 do ramdisk na plataforma Android. criar com BOARD_RAMDISK_USE_LZ4. Você pode definir essa opção configuração específica do dispositivo. A compactação do kernel pode ser definida pelo defconfig do kernel.

Mudar para o LZ4 aumenta a velocidade de inicialização de 500 ms a 1.000 ms.

Evite o login excessivo nos seus drivers

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

O kernel do Linux faz alguma otimização do tamanho da memória (como ocorrência em cache otimização) ao alocar o PLT. Com esse upstream fazer commit, o esquema de otimização tem complexidade O(N^2), em que N é o número de RELAs do tipo R_AARCH64_JUMP26 ou R_AARCH64_CALL26. Então, 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 é um registro excessivo em um motorista. Cada chamada para printk() ou qualquer outro esquema de geração de registros normalmente adiciona uma Entrada RELA CALL26/JUMP26. No texto de confirmação do arquivo upstream fazer commit, , observe que, mesmo com a otimização, os seis módulos levam cerca de 250 ms carregar, porque esses seis módulos foram os seis principais com a maior quantidade de registros.

Reduzir a geração de registros pode economizar cerca de 100 a 300 ms de tempo de inicialização, dependendo sobre o excesso de registros.

Ative a sondagem assíncrona, seletivamente

Quando um módulo é carregado, se o dispositivo que ele suporta já foi preenchida pela DT (árvore de dispositivos) e adicionada ao núcleo do driver, o dispositivo a sondagem é feita no contexto da chamada module_init(). Quando uma sondagem do dispositivo é for feito no contexto de module_init(), o módulo não poderá terminar de carregar até que a sondagem é concluída. Como o carregamento do módulo é, na maioria das vezes, serializado, um dispositivo que leva um tempo relativamente longo para verificar, atrasa o tempo de inicialização.

Para evitar tempos de inicialização mais lentos, ative a sondagem assíncrona para módulos que precisam de enquanto testam os dispositivos. Ativar a sondagem assíncrona para todos os módulos pode não ser benéfico, pois o tempo que se leva para bifurcar uma linha e iniciar o pode ser tão alto quanto o tempo necessário para sondar o dispositivo.

Dispositivos conectados por um barramento lento, como I2C, que fazem carregamento de firmware na função de sondagem e dispositivos que usam muito hardware na inicialização pode levar ao problema de tempo. A melhor forma de identificar quando isso é coletar o tempo de sondagem de cada driver e classificá-lo.

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

Ativar a sondagem assíncrona pode economizar de 100 a 500 ms nos tempos de inicialização dependendo do hardware/drivers.

sondar o driver CPUfreq o mais cedo possível

Quanto antes as sondagens do driver da CPUfreq, mais rápido será possível escalonar a CPU frequência máxima (ou algum limite com limitação térmica) durante a inicialização. A mais rápido a CPU, mais rápida será a inicialização. Essa diretriz também se aplica a devfreq que controlam a DRAM, a memória e a frequência de interconexão.

Com os módulos, a ordem de carregamento pode depender do nível da initcall e ordem de compilação ou vinculação dos drivers. Use um alias MODULE_SOFTDEP() para fazer garanta 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 garantir que todos dependências para sondar o driver CPUfreq também foram sondadas. Por exemplo, se você precisar de uma alça de relógio ou regulador para controlar a frequência da CPU, elas são sondadas primeiro. Ou talvez seja necessário usar drivers térmicos para ser carregados antes do driver CPUfreq se as CPUs puderem ficar muito quente durante a inicialização. Faça o possível para garantir que a CPUfreq e as tags os drivers devfreq sondem o mais cedo possível.

As economias na sondagem antecipada do driver CPUfreq podem ser de muito pequena a muito baixa grandes, dependendo da antecedência com que você pode fazer a sondagem e com que frequência o carregador de inicialização deixa as CPUs lá.

Mover módulos para init de segundo estágio, partição de fornecedor ou merchant_dlkm

Como o processo init do primeiro estágio é serializado, não há muitos para carregar o processo de inicialização em paralelo. Se um módulo não for necessário para do init do primeiro estágio para terminar, mova o módulo para o init do segundo estágio colocando-o no fornecedor ou na partição vendor_dlkm.

A inicialização no primeiro estágio não exige a sondagem de vários dispositivos para chegar ao segundo estágio init. Somente os recursos de armazenamento flash e de console são necessários para um fluxo de inicialização normal.

Carregue os seguintes drivers essenciais:

  • watchdog
  • reset
  • cpufreq

Para o modo fastbootd de recuperação e espaço do usuário, a inicialização do primeiro estágio exige mais dispositivos a serem sondados (como USB) e de exibição. Mantenha uma cópia desses módulos na no ramdisk do primeiro estágio e no fornecedor ou na partição vendor_dlkm. Assim, eles podem ser carregados no init do primeiro estágio para recuperação ou no 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 a inicialização normal fluxo Os módulos do modo de recuperação (Recovery mode) podem ser adiados para a inicialização no segundo estágio para diminuir a o tempo de inicialização. Todos os outros módulos que não são necessários no init do primeiro estágio precisam ser movidos para o fornecedor ou a partição vendor_dlkm.

Com uma lista de dispositivos folha (por exemplo, UFS ou serial), 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 a sondagem.

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

  • Redução do tamanho do ramdisk.
    • Isso produz leituras em flash mais rápidas quando o carregador de inicialização carrega o ramdisk. (etapa de inicialização serializada).
    • Isso proporciona velocidades de descompactação mais rápidas quando o kernel 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 sendo feito na inicialização do segundo estágio.

Dependendo do tempo de inicialização, a mudança dos módulos para o segundo estágio pode economizar de 500 a 1.000 ms de quantos módulos é possível migrar para a inicialização no segundo estágio.

Logística de carregamento do módulo

Os recursos de build mais recentes do Android com configurações de placa que controlam quais os módulos são copiados para cada estágio e quais módulos são carregados. Esta seção foca no seguinte subconjunto:

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

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

A duplicação deve custar espaço mínimo do fornecedor ou de vendor_dlkm partição desde que o conjunto de módulos de inicialização seja minimizado. Certifique-se de que o endereço O arquivo modules.list tem 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).

Verifique se os módulos do modo de recuperação são carregados como um grupo. Carregando módulos do modo de recuperação pode ser feita 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 realizar as ações a seguir. 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 a ser especificado localmente na configuração da placa. . O script anterior encontra e preenche cada um dos módulos de subconjuntos selecionados módulos do kernel disponíveis, deixando os módulos restantes por segundo inicialização do estágio.

Para o init no segundo estágio, recomendamos executar o carregamento do módulo como um serviço, para que ele não bloqueia o fluxo de inicialização. Usar um script de shell para gerenciar o carregamento do módulo para que outras logísticas, como tratamento e mitigação de erros ou carregamento de módulo, do projeto, pode ser informado (ou ignorado) se necessário.

Você pode ignorar uma falha de carregamento do módulo de depuração que não está presente nos builds do usuário. Para ignorar essa falha, defina a propriedade vendor.device.modules.ready como acionar estágios posteriores do fluxo de inicialização de scripts init rc para continuar na inicialização tela. Consulte o script de exemplo a seguir, se você tiver o 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 do 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

Outras otimizações podem ser feitas depois que os módulos forem movidos do primeiro para o na segunda etapa. Use o recurso de lista de bloqueio modprobe para dividir a segunda fluxo de inicialização do estágio para incluir o carregamento de módulo adiado de módulos não essenciais. O carregamento de módulos usados exclusivamente por uma HAL específica pode ser adiado para carregar os módulos somente quando a HAL for iniciada.

Para melhorar os tempos aparentes de inicialização, escolha especificamente os módulos na serviço de carregamento de módulo que sejam mais propícios para o carregamento após o lançamento tela. Por exemplo, é possível carregar os módulos explicitamente para decodificador de vídeo ou Wi-Fi depois que o fluxo de inicialização init tiver sido limpo (sys.boot_complete) indicador de propriedade do Android, por exemplo). Verifique se as HALs para carregamento tardio os módulos são bloqueados por tempo suficiente quando os drivers do kernel não estão presentes.

Também é possível usar o comando wait<file>[<timeout>] do init na inicialização o script rc do fluxo espera que as entradas sysfs selecionadas mostrem que os módulos do driver concluiu as operações de sondagem. Um exemplo disso é aguardar o o driver de exibição para concluir o carregamento em segundo plano de recuperação ou fastbootd, antes de apresentar os gráficos do menu.

Inicializar a frequência da CPU com um valor razoável no carregador de inicialização

Nem todos os SoCs/produtos podem inicializar a CPU na frequência mais alta devido a problemas de temperatura ou energia durante testes de loop de inicialização. No entanto, verifique se o carregador de inicialização define a frequência de todas as CPUs on-line como a mais segura possível para um SoC ou produto. Isso é muito importante porque, com uma kernel modular, a descompactação do ramdisk init ocorre antes que o CPUfreq driver pode ser carregado. Portanto, se a CPU for deixada na extremidade inferior de sua frequência, pelo carregador de inicialização, o tempo de descompactação do ramdisk pode demorar Kernel compilado estaticamente (depois de ajustar a diferença de tamanho do ramdisk) porque a frequência da CPU é muito baixa durante o trabalho intensivo (descompactação). O mesmo se aplica à memória e à frequência de interconexão.

Inicializar a frequência de CPUs grandes no carregador de inicialização

Antes que o driver CPUfreq seja carregado, o kernel não tem conhecimento da frequências da CPU e não escalona a capacidade programada da CPU para o frequência. O kernel pode migrar linhas de execução para a CPU grande se a carga for o suficiente na CPU pequena.

As CPUs grandes precisam ter pelo menos o mesmo desempenho das CPUs pequenas em que o carregador de inicialização os deixa. Por exemplo, se a CPU grande tem desempenho duas vezes maior do que uma CPU pequena para a mesma frequência, mas a o carregador de inicialização define a frequência da CPU pequena como 1,5 GHz e o para 300 MHz, o desempenho da inicialização diminuirá se o kernel move uma linha de execução para a CPU grande. Neste exemplo, se for seguro inicializar a VM CPU a 750 MHz, faça isso mesmo que não pretenda usá-lo explicitamente.

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

Pode haver alguns casos inevitáveis em que é necessário carregar o firmware primeiro inicialização do estágio. Mas, em geral, os drivers não devem carregar nenhum firmware no primeiro estágio init, especialmente no contexto de sondagem do dispositivo. Carregando firmware no init primeiro estágio interrompe todo o processo de inicialização se o firmware não estiver disponível no no ramdisk de primeiro estágio. E mesmo que o firmware esteja presente no primeiro estágio no ramdisk, isso ainda causa um atraso desnecessário.