Esta página oferece dicas para melhorar o tempo de inicialização.
Remover símbolos de depuração dos módulos
Assim como os símbolos de depuração são removidos do kernel em um dispositivo de produção, remova também os símbolos de depuração dos módulos. A remoção dos símbolos de depuração dos módulos ajuda a reduzir o tempo de inicialização:
- O tempo que leva para ler os binários do Flash.
- O tempo que leva para descompactar o ramdisk.
- O tempo necessário 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á-la explicitamente, defina
BOARD_DO_NOT_STRIP_VENDOR_RAMDISK_MODULES
na configuração específica do dispositivo
em device/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 rápido que o Gzip. Para o kernel e os módulos, a redução absoluta do tamanho de armazenamento do Gzip não é tão significativa em comparação com o benefício do tempo de descompactação do LZ4.
O suporte à compactação de ramdisk LZ4 foi adicionado ao build da plataforma
Android por meio de BOARD_RAMDISK_USE_LZ4
. É possível definir essa opção na
configuração específica do dispositivo. A compactação do kernel pode ser definida pelo defconfig do kernel.
A mudança para LZ4 deve proporcionar um tempo de inicialização de 500 a 1.000 ms mais rápido.
Evite o registro excessivo nos drivers
Em 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 de tabela de vinculação de procedimentos ou PLT) para 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 relocalização são chamadas de entradas de relocalização com adições explícitas (ou RELA, abreviação de "relocalização") no formato ELF.
O kernel do Linux faz algumas otimizações de tamanho de memória (como a otimização de acerto
de cache) ao alocar o PLT. Com este commit
upstream,
o esquema de otimização tem uma complexidade O(N^2)
, em que N
é o número de
RELAs do tipo R_AARCH64_JUMP26
ou R_AARCH64_CALL26
. Portanto, ter menos RELAs
desses tipos ajuda a reduzir o tempo de carregamento do módulo.
Um padrão de programação comum que aumenta o número de
R_AARCH64_CALL26
ou R_AARCH64_JUMP26
RELAs é o registro excessivo em um
driver. Cada chamada para printk()
ou qualquer outro esquema de registro geralmente adiciona uma
entrada RELA CALL26
/JUMP26
. No texto de confirmação no commit
upstream,
observe que, mesmo com a otimização, os seis módulos levam cerca de 250ms
para carregar. Isso ocorre porque esses seis módulos foram os seis principais com
a maior quantidade de registros.
Reduzir o registro pode economizar de 100 a 300 ms no tempo de inicialização, dependendo da quantidade de registros.
Ativar a sondagem assíncrona de forma seletiva
Quando um módulo é carregado, se o dispositivo compatível já foi
preenchido na árvore de dispositivos (DT, na sigla em inglês) e adicionado ao núcleo do driver, a detecção
do dispositivo é feita no contexto da chamada module_init()
. Quando uma sondagem de dispositivo é
feita no contexto de module_init()
, o módulo não pode terminar de carregar até
que a sondagem seja concluída. Como o carregamento do módulo é, na maioria das vezes, serializado, um dispositivo que
leva um tempo relativamente longo para a sondagem diminui o tempo de inicialização.
Para evitar tempos de inicialização mais lentos, ative a sondagem assíncrona para módulos que demoram um pouco para sondar os dispositivos. Ativar a sondagem assíncrona para todos os módulos pode não ser benéfico, já que o tempo necessário para bifurcar uma linha de execução e iniciar a sondagem pode ser tão alto quanto o tempo necessário para sondar o dispositivo.
Dispositivos conectados por um barramento lento, como o I2C, dispositivos que fazem o carregamento de firmware na função de sonda e dispositivos que fazem muita inicialização de hardware podem causar problemas de tempo. A melhor maneira de identificar quando isso acontece é coletar o tempo de sondagem de cada driver e classificá-lo.
Para ativar a sondagem assíncrona de um módulo, não é suficiente
definir apenas a flag PROBE_PREFER_ASYNCHRONOUS
no código do driver. Para módulos, também é necessário adicionar
module_name.async_probe=1
na linha de comando do kernel
ou transmitir async_probe=1
como um parâmetro de módulo 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.
Analise o driver CPUfreq o mais cedo possível
Quanto mais cedo o driver CPUfreq for analisado, 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 a 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 vinculação 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 garantir que todas as dependências para sondar o driver CPUfreq também tenham sido verificadas. Por exemplo, se você precisar de um relógio ou regulador para controlar a frequência da CPU, verifique se eles foram testados primeiro. Ou talvez seja necessário carregar drivers térmicos antes do driver CPUfreq se as CPUs ficarem muito quentes durante a inicialização. Portanto, faça o possível para garantir que os drivers CPUfreq e devfreq relevantes sejam detectados o mais cedo possível.
A economia com a sondagem do driver CPUfreq pode ser muito pequena ou muito grande, dependendo de quão cedo você consegue fazer a sondagem e em qual frequência o carregador de inicialização deixa as CPUs.
Mover módulos para a segunda etapa de inicialização, fornecedor ou partição vendor_dlkm
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
concluir a inicialização da primeira fase, mova o módulo para a inicialização da segunda fase, colocando-o
na partição do fornecedor ou vendor_dlkm
.
A inicialização no primeiro estágio não precisa da sondagem de vários dispositivos para chegar à inicialização no segundo estágio. Apenas os recursos de console e armazenamento flash 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 requer mais
dispositivos para sondagem (como USB) e exibição. Mantenha uma cópia desses módulos no
ramdisk da primeira etapa e no fornecedor ou na partição vendor_dlkm
. Isso permite que eles
sejam carregados na inicialização da primeira etapa 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 a inicialização da segunda fase para diminuir o
tempo de inicialização. Todos os outros módulos que não são necessários na inicialização da primeira fase precisam ser
movidos para a partição do fornecedor ou vendor_dlkm
.
Dada uma lista de dispositivos de folha (por exemplo, UFS ou serial),
o script dev needs.sh
encontra todos os drivers, dispositivos e módulos necessários para que dependências ou
fornecedores (por exemplo, relógios, reguladores ou gpio
) sejam investigados.
Mover módulos para a inicialização da segunda fase diminui os tempos de inicialização das seguintes maneiras:
- Redução do tamanho do ramdisk.
- Isso gera leituras de flash mais rápidas quando o carregador de inicialização carrega o ramdisk (etapa de inicialização serializada).
- Isso gera velocidades de descompressão mais rápidas quando o kernel descompacta o ramdisk (etapa de inicialização serializada).
- A inicialização da segunda etapa funciona em paralelo, o que oculta o tempo de carregamento do módulo com o trabalho sendo feito na inicialização da segunda etapa.
Mover módulos para a segunda fase pode economizar 500 a 1.000 ms nos tempos de inicialização, dependendo de quantos módulos você pode mover para a inicialização da segunda fase.
Logística de carregamento de módulos
O build mais recente do Android tem configurações de placa que controlam quais módulos são copiados para cada etapa e quais são carregados. Esta seção se concentra neste subconjunto:
BOARD_VENDOR_RAMDISK_KERNEL_MODULES
: esta lista de módulos a serem copiados para o ramdisk.BOARD_VENDOR_RAMDISK_KERNEL_MODULES_LOAD
: lista de módulos a serem carregados na inicialização do primeiro estágio.BOARD_VENDOR_RAMDISK_RECOVERY_KERNEL_MODULES_LOAD
. Esta lista de módulos a ser carregados quando a recuperação oufastbootd
for selecionada no ramdisk.BOARD_VENDOR_KERNEL_MODULES
: lista de módulos a serem copiados para a partição do fornecedor ouvendor_dlkm
no diretório/vendor/lib/modules/
.BOARD_VENDOR_KERNEL_MODULES_LOAD
: lista de módulos a serem carregados na inicialização da segunda fase.
Os módulos de inicialização e recuperação no ramdisk também precisam ser copiados para a partição do fornecedor ou
vendor_dlkm
em /vendor/lib/modules
. Copiar esses módulos para a
partição do fornecedor garante que eles 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 ocupar um espaço mínimo no fornecedor ou na partição vendor_dlkm
,
desde que o conjunto de módulos de inicialização seja minimizado. Verifique se o arquivo modules.list
do fornecedor 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 de módulos
novamente, o que é um processo caro.
Os módulos do modo de recuperação são carregados como um grupo. O carregamento de módulos do modo de recuperação pode ser feito no modo de recuperação ou no início da inicialização do segundo estágio em cada fluxo de inicialização.
É possível usar os arquivos Board.Config.mk
do dispositivo para realizar essas ações, conforme mostrado
neste exemplo:
# 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 encontra e preenche cada um dos módulos de subconjunto dos
módulos do kernel disponíveis selecionados, deixando os módulos remanescentes para a inicialização do segundo
estágio.
Para a inicialização de segundo estágio, recomendamos executar o carregamento do módulo como um serviço para que ele 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 de erros e mitigação ou a conclusão do carregamento do módulo, possam ser relatadas (ou ignoradas) se necessário.
É possível 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
para
acionar estágios posteriores do fluxo de inicialização do script init rc
para continuar na tela
de início. Consulte o seguinte script de exemplo, 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
Outras otimizações podem ser feitas depois que os módulos são movidos do primeiro para o segundo estágio. Você pode usar o recurso de lista de bloqueio modprobe para dividir o fluxo de inicialização de segundo estágio e incluir o carregamento de módulos não essenciais adiado. 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, escolha especificamente os módulos no
serviço de carregamento de módulo que são mais propícios para o carregamento após a tela
de inicialização. Por exemplo, é possível carregar explicitamente os módulos para
decodificador de vídeo ou Wi-Fi depois que o fluxo de inicialização init for apagado
(indicador de propriedade sys.boot_complete
do Android, por exemplo). Verifique se os HALs para os módulos de carregamento
tardio bloqueiam 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 no script
de rc do fluxo de inicialização para aguardar entradas sysfs
selecionadas para mostrar que os módulos do driver
concluíram as operações de sondagem. Um exemplo disso é esperar que o
driver de exibição conclua o carregamento em segundo plano da recuperação ou fastbootd
,
antes de apresentar os gráficos do menu.
Inicialize a frequência da CPU para 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 preocupações térmicas ou de energia durante testes de loop de inicialização. No entanto, verifique se o bootloader define a frequência de todas as CPUs on-line o mais alto possível para um SoC ou produto. Isso é muito importante porque, com um kernel totalmente modular, a descompactação do ramdisk init ocorre antes que o driver CPUfreq possa ser carregado. Portanto, se a CPU for deixada na extremidade inferior da frequência pelo carregador de inicialização, o tempo de descompressão do ramdisk poderá levar mais tempo do que um kernel compilado estaticamente (após o ajuste para a diferença de tamanho do ramdisk) porque a frequência da CPU será muito baixa ao fazer trabalhos intensos (descompressão). O mesmo se aplica à memória e à frequência de interconexão.
Inicializar a frequência da CPU de CPUs grandes no carregador de inicialização
Antes que o driver CPUfreq
seja carregado, o kernel não tem conhecimento das
frequências da CPU e não dimensiona a capacidade da programação da CPU para a frequência
atual. O kernel pode migrar linhas de execução para a CPU grande se a carga for
suficientemente alta na CPU pequena.
Verifique se as CPUs grandes têm pelo menos o mesmo desempenho que as CPUs pequenas para a frequência em que o carregador de inicialização as deixa. Por exemplo, se a CPU grande tiver o dobro de desempenho da CPU pequena para a mesma frequência, mas o bootloader definir a frequência da CPU pequena como 1,5 GHz e a frequência da CPU grande como 300 MHz, o desempenho de inicialização vai cair se o kernel mover uma linha de execução para a CPU grande. Neste exemplo, se for seguro inicializar a CPU grande a 750 MHz, faça isso mesmo que você não pretenda usá-la explicitamente.
Os drivers não podem carregar o firmware na inicialização do primeiro estágio
Pode haver alguns casos inevitáveis em que o firmware precisa ser carregado na inicialização da primeira etapa. No entanto, 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 na inicialização 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 primeiro estágio do ramdisk, ele ainda causa um atraso desnecessário.