Questa pagina fornisce suggerimenti per migliorare il tempo di avvio.
Rimuovi i simboli di debug dai moduli
Analogamente a come i simboli di debug vengono rimossi dal kernel su un dispositivo di produzione, assicurati di rimuoverli anche dai moduli. La rimozione dei simboli di debug dai moduli contribuisce a ridurre il tempo di avvio riducendo:
- Il tempo necessario per leggere i file binari dalla memoria flash.
- Il tempo necessario per decomprimere il ramdisk.
- Il tempo necessario per caricare i moduli.
La rimozione del simbolo di debug dai moduli può far risparmiare diversi secondi durante l'avvio.
La rimozione dei simboli è attivata per impostazione predefinita nella build della piattaforma Android, ma
per attivarli esplicitamente, imposta
BOARD_DO_NOT_STRIP_VENDOR_RAMDISK_MODULES
nella configurazione specifica del dispositivo
in device/vendor/device.
Utilizzare la compressione LZ4 per il kernel e il ramdisk
Gzip genera un output compresso più piccolo rispetto a LZ4, ma LZ4 decomprime più velocemente di Gzip. Per il kernel e i moduli, la riduzione assoluta delle dimensioni di archiviazione dovuta all'utilizzo di Gzip non è così significativa rispetto al vantaggio in termini di tempo di decompressione di LZ4.
Il supporto della compressione ramdisk LZ4 è stato aggiunto alla build della piattaforma Android tramite BOARD_RAMDISK_USE_LZ4
. Puoi impostare questa opzione nella configurazione specifica del dispositivo. La compressione del kernel può essere impostata tramite la configurazione predefinita del kernel.
Il passaggio a LZ4 dovrebbe garantire un tempo di avvio più rapido di 500 ms-1000 ms.
Evita la registrazione eccessiva nei driver
In ARM64 e ARM32, le chiamate di funzione che si trovano a una distanza superiore a una distanza specifica dal sito di chiamata richiedono una tabella di salto (chiamata tabella di collegamento delle procedure o PLT) per poter codificare l'indirizzo di salto completo. Poiché i moduli vengono caricati dinamicamente, queste tabelle di salto devono essere corrette durante il caricamento del modulo. Le chiamate che devono essere riassegnate sono chiamate voci di riassegnazione con addendi espliciti (o RELA, in breve) nel formato ELF.
Il kernel Linux esegue alcune ottimizzazioni delle dimensioni della memoria (ad esempio l'ottimizzazione
dell'hit della cache) durante l'allocazione della PLT. Con questo commit
upstream,
lo schema di ottimizzazione ha una complessità O(N^2)
, dove N
è il numero di
RELA di tipo R_AARCH64_JUMP26
o R_AARCH64_CALL26
. Pertanto, avere meno RELA
di questi tipi è utile per ridurre il tempo di caricamento del modulo.
Un pattern di codifica comune che aumenta il numero di
R_AARCH64_CALL26
o R_AARCH64_JUMP26
RELA è la registrazione eccessiva in un
driver. Ogni chiamata a printk()
o a qualsiasi altro schema di logging in genere aggiunge una voce RELA CALL26
/JUMP26
. Nel testo del commit nel commit upstream, nota che anche con l'ottimizzazione, il caricamento dei sei moduli richiede circa 250 ms, perché questi sei moduli erano i primi sei con la quantità maggiore di logging.
La riduzione della registrazione può consentire di risparmiare circa 100-300 ms sui tempi di avvio, a seconda dell'eccessività della registrazione esistente.
Attivare il probing asincrono in modo selettivo
Quando viene caricato un modulo, se il dispositivo che supporta è già stato
compilato da DT (devicetree) e aggiunto al driver core, il sondaggio
del dispositivo viene eseguito nel contesto della chiamata module_init()
. Quando una sonda del dispositivo viene eseguita nel contesto di module_init()
, il caricamento del modulo non può essere completato finché la sonda non viene completata. Poiché il caricamento dei moduli è per lo più serializzato, un dispositivo che
impiega un tempo relativamente lungo per il probing rallenta il tempo di avvio.
Per evitare tempi di avvio più lenti, attiva il probing asincrono per i moduli che impiegano un po' di tempo per eseguire il probing dei dispositivi. L'attivazione del probing asincrono per tutti i moduli potrebbe non essere vantaggiosa, poiché il tempo necessario per creare un fork di un thread e avviare il probe potrebbe essere pari al tempo necessario per eseguire il probe del dispositivo.
I dispositivi connessi tramite un bus lento come I2C, i dispositivi che caricano il firmware nella funzione di sonda e i dispositivi che eseguono molta inizializzazione hardware possono causare il problema di temporizzazione. Il modo migliore per identificare quando si verifica è raccogliere e ordinare l'ora di probing di ogni conducente.
Per attivare il probing asincrono per un modulo, non è sufficiente impostare solo il flag PROBE_PREFER_ASYNCHRONOUS
nel codice del driver. Per i moduli, devi anche aggiungere
module_name.async_probe=1
nella riga di comando del kernel
o passare async_probe=1
come parametro del modulo durante il caricamento del modulo utilizzando
modprobe
o insmod
.
L'attivazione del probing asincrono può ridurre i tempi di avvio di circa 100-500 ms, a seconda dell'hardware/dei driver.
Esegui il probe del driver CPUfreq il prima possibile
Prima il driver CPUfreq esegue il probing, prima puoi scalare la frequenza della CPU
al massimo (o a un massimo limitato termicamente) durante l'avvio. Più
veloce è la CPU, più veloce è l'avvio. Questa linea guida si applica anche ai driver devfreq
che controllano la frequenza di DRAM, memoria e interconnessione.
Con i moduli, l'ordine di caricamento può dipendere dal livello initcall
e
dall'ordine di compilazione o collegamento dei driver. Utilizza un alias MODULE_SOFTDEP()
per assicurarti
che il driver cpufreq
sia tra i primi moduli a essere caricati.
Oltre a caricare il modulo in anticipo, devi anche assicurarti che siano state eseguite le probe di tutte le dipendenze per il driver CPUfreq. Ad esempio, se hai bisogno di un orologio o di una maniglia del regolatore per controllare la frequenza della CPU, assicurati che vengano esaminati per primi. In alternativa, potrebbe essere necessario caricare i driver termici prima del driver CPUfreq se è possibile che le CPU si surriscaldino troppo durante l'avvio. Quindi, fai il possibile per assicurarti che i driver CPUfreq e devfreq pertinenti vengano analizzati il prima possibile.
Il risparmio derivante dal probing anticipato del driver CPUfreq può variare da molto piccolo a molto grande a seconda di quanto presto è possibile eseguire il probing e della frequenza con cui il bootloader lascia le CPU.
Spostare i moduli nella partizione init di secondo livello, fornitore o vendor_dlkm
Poiché il processo di inizializzazione della prima fase è serializzato, non ci sono molte
opportunità per parallelizzare il processo di avvio. Se un modulo non è necessario per
il completamento dell'inizializzazione della prima fase, sposta il modulo nell'inizializzazione della seconda fase inserendolo
nella partizione fornitore o vendor_dlkm
.
L'inizializzazione della prima fase non richiede il probing di più dispositivi per passare all'inizializzazione della seconda fase. Per un normale flusso di avvio sono necessarie solo le funzionalità di archiviazione flash e della console.
Carica i seguenti driver essenziali:
watchdog
reset
cpufreq
Per la modalità di recupero e spazio utente fastbootd
, l'inizializzazione della prima fase richiede più
dispositivi da analizzare (ad esempio USB) e display. Conserva una copia di questi moduli nel
ramdisk della prima fase e nella partizione fornitore o vendor_dlkm
. In questo modo possono essere caricati nella prima fase di inizializzazione per il ripristino o il flusso di avvio fastbootd
. Tuttavia,
non caricare i moduli della modalità di recupero nell'inizializzazione della prima fase durante il normale flusso di avvio. I moduli della modalità di ripristino possono essere posticipati all'inizializzazione della seconda fase per ridurre il
tempo di avvio. Tutti gli altri moduli non necessari nella fase iniziale di inizializzazione devono essere
spostati nella partizione del fornitore o vendor_dlkm
.
Dato un elenco di dispositivi foglia (ad esempio UFS o seriale),
dev needs.sh
lo script trova tutti i driver, i dispositivi e i moduli necessari per le dipendenze o
i fornitori (ad esempio orologi, regolatori o gpio
) da analizzare.
Lo spostamento dei moduli all'inizializzazione della seconda fase riduce i tempi di avvio nei seguenti modi:
- Riduzione delle dimensioni del ramdisk.
- In questo modo le letture flash sono più veloci quando il bootloader carica il ramdisk (passaggio di avvio serializzato).
- Ciò consente velocità di decompressione più rapide quando il kernel decomprime il ramdisk (passaggio di avvio serializzato).
- L'inizializzazione della seconda fase funziona in parallelo, il che nasconde il tempo di caricamento del modulo con il lavoro svolto nell'inizializzazione della seconda fase.
Lo spostamento dei moduli nella seconda fase può far risparmiare 500-1000 ms sui tempi di avvio, a seconda di quanti moduli riesci a spostare nell'inizializzazione della seconda fase.
Logistica di caricamento dei moduli
L'ultima build di Android include configurazioni della scheda che controllano quali moduli vengono copiati in ogni fase e quali vengono caricati. Questa sezione si concentra sul seguente sottoinsieme:
BOARD_VENDOR_RAMDISK_KERNEL_MODULES
. Questo elenco di moduli da copiare nel ramdisk.BOARD_VENDOR_RAMDISK_KERNEL_MODULES_LOAD
. Questo elenco di moduli da caricare nella prima fase di inizializzazione.BOARD_VENDOR_RAMDISK_RECOVERY_KERNEL_MODULES_LOAD
. Questo elenco di moduli da caricare quando viene selezionato il ripristino ofastbootd
dal ramdisk.BOARD_VENDOR_KERNEL_MODULES
. Questo elenco di moduli da copiare nella partizione vendor ovendor_dlkm
nella directory/vendor/lib/modules/
.BOARD_VENDOR_KERNEL_MODULES_LOAD
. Questo elenco di moduli da caricare nella seconda fase di inizializzazione.
I moduli di avvio e ripristino in ramdisk devono essere copiati anche nella partizione fornitore o
vendor_dlkm
in /vendor/lib/modules
. La copia di questi moduli nella
partizione del fornitore garantisce che i moduli non siano invisibili durante l'inizializzazione della seconda fase,
il che è utile per il debug e la raccolta di modinfo
per i report sui bug.
La duplicazione dovrebbe occupare uno spazio minimo sul fornitore o sulla vendor_dlkm
partizione
purché il set di moduli di avvio sia ridotto al minimo. Assicurati che il file
modules.list
del fornitore contenga un elenco filtrato di moduli in /vendor/lib/modules
.
L'elenco filtrato garantisce che i tempi di avvio non siano influenzati dal caricamento dei moduli (un processo costoso).
Assicurati che i moduli della modalità di recupero vengano caricati come gruppo. Il caricamento dei moduli della modalità di recupero può essere eseguito in modalità di recupero o all'inizio della seconda fase init in ogni flusso di avvio.
Puoi utilizzare i file del dispositivo Board.Config.mk
per eseguire queste azioni come mostrato
nell'esempio seguente:
# 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)))
Questo esempio mostra un sottoinsieme più facile da gestire di BOOT_KERNEL_MODULES
e
RECOVERY_KERNEL_MODULES
da specificare localmente nei file di configurazione della scheda. Lo script precedente trova e riempie ciascuno dei moduli del sottoinsieme dai moduli kernel disponibili selezionati, lasciando i moduli rimanenti per l'inizializzazione della seconda fase.
Per l'inizializzazione della seconda fase, ti consigliamo di eseguire il caricamento del modulo come servizio in modo che non blocchi il flusso di avvio. Utilizza uno script shell per gestire il caricamento del modulo in modo che altre operazioni logistiche, come la gestione e la mitigazione degli errori o il completamento del caricamento del modulo, possano essere segnalate (o ignorate) se necessario.
Puoi ignorare un errore di caricamento del modulo di debug che non è presente nelle build utente.
Per ignorare questo errore, imposta la proprietà vendor.device.modules.ready
in modo da attivare le fasi successive del bootflow di scripting init rc
per continuare fino alla schermata di avvio. Fai riferimento al seguente script di esempio, se hai il seguente codice
in /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
Nel file rc dell'hardware, il servizio one shot
può essere specificato con:
service insmod-sh /vendor/etc/init.insmod.sh /vendor/etc/init.insmod.<hw>.cfg
class main
user root
group root system
Disabled
oneshot
È possibile apportare ulteriori ottimizzazioni dopo che i moduli passano dalla prima alla seconda fase. Puoi utilizzare la funzionalità di blocco di modprobe per dividere il flusso di avvio della seconda fase in modo da includere il caricamento differito dei moduli non essenziali. Il caricamento dei moduli utilizzati esclusivamente da un HAL specifico può essere rinviato per caricare i moduli solo all'avvio dell'HAL.
Per migliorare i tempi di avvio apparenti, puoi scegliere in modo specifico i moduli nel
servizio di caricamento dei moduli più adatti al caricamento dopo la schermata
di avvio. Ad esempio, puoi caricare in ritardo in modo esplicito i moduli per
il decodificatore video o il Wi-Fi dopo che il flusso di avvio dell'inizializzazione è stato cancellato
(sys.boot_complete
ad esempio, la proprietà Android). Assicurati che gli HAL per i moduli a caricamento tardivo
si blocchino abbastanza a lungo quando i driver del kernel non sono presenti.
In alternativa, puoi utilizzare il comando wait<file>[<timeout>]
di init nello scripting rc del flusso di avvio per attendere che le voci sysfs
selezionate mostrino che i moduli del driver hanno completato le operazioni di probing. Un esempio è l'attesa del
completamento del caricamento del driver di visualizzazione in background del ripristino o di fastbootd
,
prima di presentare la grafica del menu.
Inizializza la frequenza della CPU a un valore ragionevole nel bootloader
Non tutti i SoC/prodotti potrebbero essere in grado di avviare la CPU alla frequenza più alta a causa di problemi termici o di alimentazione durante i test del ciclo di avvio. Tuttavia, assicurati che il bootloader imposti la frequenza di tutte le CPU online al valore più alto possibile in sicurezza per un SoC o un prodotto. Questo è molto importante perché, con un kernel completamente modulare, la decompressione di init ramdisk avviene prima che il driver CPUfreq possa essere caricato. Pertanto, se il bootloader lascia la CPU alla frequenza più bassa, il tempo di decompressione del ramdisk può richiedere più tempo rispetto a un kernel compilato staticamente (dopo aver aggiustato la differenza di dimensioni del ramdisk) perché la frequenza della CPU sarebbe molto bassa durante l'esecuzione di attività che richiedono un uso intensivo della CPU (decompressione). Lo stesso vale per la memoria e la frequenza di interconnessione.
Inizializza la frequenza della CPU delle CPU di grandi dimensioni nel bootloader
Prima del caricamento del driver CPUfreq
, il kernel non è a conoscenza delle
frequenze della CPU e non scala la capacità di pianificazione della CPU per la frequenza
corrente. Il kernel potrebbe eseguire la migrazione dei thread alla CPU grande se il carico è
sufficientemente elevato sulla CPU piccola.
Assicurati che le CPU grandi siano almeno altrettanto performanti delle CPU piccole per la frequenza con cui il bootloader le lascia. Ad esempio, se la CPU grande ha prestazioni doppie rispetto alla CPU piccola per la stessa frequenza, ma il bootloader imposta la frequenza della CPU piccola su 1,5 GHz e quella della CPU grande su 300 MHz, le prestazioni di avvio diminuiranno se il kernel sposta un thread sulla CPU grande. In questo esempio, se è sicuro avviare la CPU grande a 750 MHz, devi farlo anche se non prevedi di utilizzarla esplicitamente.
I driver non devono caricare il firmware nell'inizializzazione della prima fase
Potrebbero verificarsi alcuni casi inevitabili in cui il firmware deve essere caricato nella prima fase di inizializzazione. Ma in generale, i driver non devono caricare alcun firmware nella prima fase di inizializzazione, soprattutto nel contesto del probing del dispositivo. Il caricamento del firmware nella prima fase di inizializzazione causa l'interruzione dell'intero processo di avvio se il firmware non è disponibile nel ramdisk della prima fase. Anche se il firmware è presente nel ramdisk della prima fase, causa comunque un ritardo non necessario.