Ottimizzazione del tempo di avvio

Questa pagina fornisce una serie di suggerimenti tra cui è possibile selezionare per migliorare il tempo di avvio.

Elimina i simboli di debug dai moduli

In modo simile a come i simboli di debug vengono rimossi dal kernel su un dispositivo di produzione, assicurati di rimuovere anche i simboli di debug dai moduli. L'eliminazione dei simboli di debug dai moduli aiuta il tempo di avvio riducendo quanto segue:

  • Il tempo necessario per leggere i file binari dalla 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.

L'eliminazione dei simboli è abilitata per impostazione predefinita nella build della piattaforma Android, ma per abilitarla esplicitamente, imposta BOARD_DO_NOT_STRIP_VENDOR_RAMDISK_MODULES nella configurazione specifica del dispositivo in device/ vendor / device .

Usa la compressione LZ4 per kernel e ramdisk

Gzip genera un output compresso più piccolo rispetto a LZ4, ma LZ4 si decomprime più velocemente di Gzip. Per il kernel e i moduli, la riduzione assoluta delle dimensioni di archiviazione derivante dall'utilizzo di Gzip non è così significativa rispetto al vantaggio in termini di tempo di decompressione di LZ4.

Il supporto per la compressione del ramdisk LZ4 è stato aggiunto alla piattaforma Android creata tramite BOARD_RAMDISK_USE_LZ4 . Puoi impostare questa opzione nella configurazione specifica del dispositivo. La compressione del kernel può essere impostata tramite kernel defconfig.

Il passaggio a LZ4 dovrebbe garantire un tempo di avvio più veloce da 500 ms a 1000 ms.

Evita accessi eccessivi ai tuoi driver

In ARM64 e ARM32, le chiamate di funzione che si trovano a una distanza superiore a quella specifica dal sito di chiamata necessitano di 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 necessitano di rilocazione sono chiamate voci di rilocazione con addendi espliciti (o RELA, in breve) voci nel formato ELF.

Il kernel Linux esegue alcune ottimizzazioni delle dimensioni della memoria (come l'ottimizzazione dei colpi della cache) durante l'allocazione del 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 . Quindi avere meno RELA di questi tipi è utile per ridurre il tempo di caricamento del modulo.

Uno schema di codifica comune che aumenta il numero di RELA R_AARCH64_CALL26 o R_AARCH64_JUMP26 è la registrazione eccessiva in un driver. Ogni chiamata a printk() o qualsiasi altro schema di registrazione in genere aggiunge una voce CALL26 / JUMP26 RELA. Nel testo del commit nel commit upstream , si nota che anche con l'ottimizzazione, i sei moduli impiegano circa 250 ms per caricarsi, questo perché quei sei moduli erano i primi sei moduli con la maggior quantità di logging.

La riduzione della registrazione può far risparmiare circa 100 - 300 ms sui tempi di avvio a seconda di quanto sia eccessiva la registrazione esistente.

Abilita il sondaggio asincrono, in modo selettivo

Quando un modulo viene caricato, se il dispositivo che supporta è già stato popolato dal DT (devicetree) e aggiunto al core del driver, la verifica del dispositivo viene eseguita nel contesto della chiamata module_init() . Quando viene eseguita un'analisi del dispositivo nel contesto di module_init() , il modulo non può terminare il caricamento finché l'analisi non viene completata. Poiché il caricamento del modulo è per lo più serializzato, un dispositivo che impiega un tempo relativamente lungo per il rilevamento rallenta il tempo di avvio.

Per evitare tempi di avvio più lenti, abilitare il sondaggio asincrono per i moduli che impiegano un po' di tempo per sondare i propri dispositivi. Abilitare il sondaggio asincrono per tutti i moduli potrebbe non essere vantaggioso in quanto il tempo necessario per biforcare un thread e avviare il sondaggio potrebbe essere pari al tempo necessario per sondare il dispositivo.

I dispositivi collegati tramite un bus lento come I2C, i dispositivi che eseguono il caricamento del firmware nella funzione di sonda e i dispositivi che eseguono molta inizializzazione dell'hardware possono causare problemi di temporizzazione. Il modo migliore per identificare quando ciò accade è raccogliere il tempo della sonda per ogni conducente e ordinarlo.

Per abilitare il sondaggio 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 quando carichi il modulo utilizzando modprobe o insmod .

L'abilitazione del sondaggio asincrono può far risparmiare circa 100 - 500 ms sui tempi di avvio a seconda dell'hardware/driver.

Controlla il tuo driver CPUfreq il prima possibile

Prima viene rilevato il driver CPUfreq, prima sarà possibile scalare la frequenza della CPU al massimo (o ad un massimo limitato termicamente) durante l'avvio. Più veloce è la CPU, più veloce sarà l'avvio. Questa linea guida si applica anche ai driver devfreq che controllano la DRAM, la memoria e la frequenza di interconnessione.

Con i moduli, l'ordine di caricamento può dipendere dal livello initcall e dall'ordine di compilazione o collegamento dei driver. Utilizzare un alias MODULE_SOFTDEP() per assicurarsi che il driver cpufreq sia tra i primi moduli da caricare.

Oltre a caricare il modulo in anticipo, è necessario anche assicurarsi che siano state verificate anche tutte le dipendenze per sondare il driver CPUfreq. Ad esempio, se hai bisogno di un orologio o di un regolatore per controllare la frequenza della tua CPU, assicurati che vengano prima controllati. Oppure potresti aver bisogno che i driver termici siano caricati prima del driver CPUfreq se è possibile che le tue CPU si surriscaldino troppo durante l'avvio. Quindi, fai quello che puoi per assicurarti che CPUfreq e i relativi driver devfreq vengano rilevati il ​​prima possibile.

Il risparmio derivante dal sondaggio anticipato del driver CPUfreq può essere da molto piccolo a molto grande a seconda di quanto tempo riesci a farli sondare e con quale frequenza il bootloader lascia le CPU in funzione.

Spostare i moduli nella partizione init della seconda fase, fornitore o fornitore_dlkm

Poiché il processo di avvio della prima fase è serializzato, non esistono molte opportunità per parallelizzare il processo di avvio. Se un modulo non è necessario per il completamento della prima fase di init, spostare il modulo nella seconda fase di init inserendolo nella partizione fornitore o vendor_dlkm .

L'inizializzazione della prima fase non richiede il rilevamento di diversi dispositivi per arrivare alla seconda fase. Per un normale flusso di avvio sono necessarie solo la console e la funzionalità di archiviazione flash.

Carica i seguenti driver essenziali:

  • cane da guardia
  • Ripristina
  • cpufreq

Per la modalità fastbootd di ripristino e spazio utente, la prima fase di init richiede più dispositivi da rilevare (come USB) e visualizzare. Conservare una copia di questi moduli nel ramdisk della prima fase e nella partizione fornitore o vendor_dlkm . Ciò consente loro di essere caricati nella prima fase di init per il ripristino o il flusso di avvio fastbootd . Tuttavia, non caricare i moduli della modalità di ripristino nella prima fase di init durante il normale flusso di avvio. I moduli in modalità ripristino possono essere rinviati alla seconda fase di inizializzazione per ridurre il tempo di avvio. Tutti gli altri moduli che non sono necessari nella prima fase di init dovrebbero essere spostati nella partizione fornitore o vendor_dlkm .

Dato un elenco di dispositivi foglia (ad esempio, UFS o seriale), lo script dev needs.sh trova tutti i driver, i dispositivi e i moduli necessari per le dipendenze o i fornitori (ad esempio, orologi, regolatori o gpio ) da sondare.

Lo spostamento dei moduli nella seconda fase init riduce i tempi di avvio nei seguenti modi:

  • Riduzione delle dimensioni del ramdisk.
    • Ciò produce letture flash più veloci quando il bootloader carica il ramdisk (passaggio di avvio serializzato).
    • Ciò produce velocità di decompressione più elevate quando il kernel decomprime il ramdisk (fase di avvio serializzato).
  • L'init della seconda fase funziona in parallelo, il che nasconde il tempo di caricamento del modulo con il lavoro svolto nella seconda fase dell'init.

Lo spostamento dei moduli nella seconda fase può far risparmiare 500 - 1000 ms sui tempi di avvio a seconda di quanti moduli sei in grado di spostare nella seconda fase init.

Logistica di caricamento dei moduli

L'ultima build di Android presenta configurazioni della scheda che controllano quali moduli vengono copiati in ciascuna fase e quali moduli 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 init.
  • BOARD_VENDOR_RAMDISK_RECOVERY_KERNEL_MODULES_LOAD . Questo elenco di moduli da caricare quando si seleziona il ripristino o fastbootd dal ramdisk.
  • BOARD_VENDOR_KERNEL_MODULES . Questo elenco di moduli da copiare nella partizione fornitore o vendor_dlkm nella directory /vendor/lib/modules/ .
  • BOARD_VENDOR_KERNEL_MODULES_LOAD . Questo elenco di moduli da caricare nella seconda fase init.

I moduli di avvio e ripristino in ramdisk devono essere copiati anche nella partizione fornitore o vendor_dlkm in /vendor/lib/modules . Copiare questi moduli nella partizione del fornitore garantisce che i moduli non siano invisibili durante la seconda fase di inizializzazione, il che è utile per il debug e la raccolta modinfo per le segnalazioni di bug.

La duplicazione dovrebbe costare uno spazio minimo sulla partizione fornitore o vendor_dlkm purché il set di moduli di avvio sia ridotto al minimo. Assicurati che il file modules.list del fornitore abbia un elenco filtrato di moduli in /vendor/lib/modules . L'elenco filtrato garantisce che i tempi di avvio non siano influenzati dal nuovo caricamento dei moduli (che è un processo costoso).

Assicurarsi che i moduli della modalità di ripristino vengano caricati come gruppo. Il caricamento dei moduli in modalità di ripristino può essere effettuato in modalità di ripristino o all'inizio della seconda fase di init in ciascun flusso di avvio.

È possibile utilizzare i file Board.Config.mk del dispositivo 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ù semplice 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 del kernel disponibili selezionati, lasciando i moduli rimanenti per la seconda fase di init.

Per l'inizializzazione della seconda fase, consigliamo di eseguire il caricamento del modulo come servizio in modo che non blocchi il flusso di avvio. Utilizzare uno script di shell per gestire il caricamento del modulo in modo che altri aspetti logistici, come la gestione e la mitigazione degli errori o il completamento del caricamento del modulo, possano essere segnalati (o ignorati) se necessario.

Puoi ignorare un errore di caricamento del modulo di debug che non è presente nelle build dell'utente. Per ignorare questo errore, impostare la proprietà vendor.device.modules.ready per attivare le fasi successive del flusso di avvio dello scripting init rc per continuare nella 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 potrebbe 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 sono passati dalla prima alla seconda fase. È possibile utilizzare la funzione blocklist modprobe per suddividere il flusso di avvio della seconda fase per includere il caricamento differito dei moduli non essenziali. Il caricamento dei moduli utilizzati esclusivamente da un HAL specifico può essere posticipato 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 che sono più favorevoli al caricamento dopo la schermata di avvio. Ad esempio, puoi caricare esplicitamente in ritardo i moduli per il decodificatore video o il Wi-Fi dopo che il flusso di avvio init è stato cancellato (segnale della proprietà Android sys.boot_complete , ad esempio). 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, è possibile 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 driver hanno completato le operazioni di sonda. Un esempio di ciò è l'attesa che il driver video completi il ​​caricamento in background di ripristino o fastbootd , prima di presentare la grafica del menu.

Inizializza la frequenza della CPU su 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 livello più alto possibile in sicurezza per un SoC/prodotto. Questo è molto importante perché, con un kernel completamente modulare, la decompressione del ramdisk init avviene prima che il driver CPUfreq possa essere caricato. Quindi, se la CPU viene lasciata all'estremità inferiore della sua frequenza dal bootloader, il tempo di decompressione del ramdisk può richiedere più tempo di un kernel compilato staticamente (dopo aver regolato la differenza di dimensione del ramdisk) perché la frequenza della CPU sarebbe molto bassa quando si esegue un utilizzo intensivo della CPU lavoro (decompressione). Lo stesso vale per la frequenza di memoria/interconnessione.

Inizializza la frequenza della CPU delle CPU di grandi dimensioni nel bootloader

Prima che il driver CPUfreq venga caricato, il kernel non è a conoscenza delle frequenze piccole e grandi della CPU e non scala la capacità programmata delle CPU per la loro frequenza attuale. Il kernel potrebbe migrare i thread sulla CPU grande se il carico è sufficientemente elevato sulla CPU piccola.

Assicurati che le CPU grandi abbiano prestazioni almeno pari a quelle piccole per la frequenza alla quale il bootloader le lascia. Ad esempio, se la CPU grande è 2 volte più performante della CPU piccola per la stessa frequenza, ma il bootloader imposta la la frequenza della piccola CPU a 1,5 GHz e la frequenza della grande CPU a 300 MHz, le prestazioni di avvio diminuiranno se il kernel sposta un thread sulla grande CPU. In questo esempio, se è sicuro avviare la grande CPU a 750 MHz, dovresti farlo anche se non prevedi di utilizzarla esplicitamente.

I driver non dovrebbero caricare il firmware nella prima fase di inizializzazione

Potrebbero esserci alcuni casi inevitabili in cui il firmware deve essere caricato nella prima fase di init. Ma in generale, i driver non dovrebbero caricare alcun firmware nella prima fase di init, specialmente nel contesto del test del dispositivo. Il caricamento del firmware nella prima fase init provoca lo stallo dell'intero processo di avvio se il firmware non è disponibile nel ramdisk della prima fase. E anche se il firmware è presente nel ramdisk del primo stadio, provoca comunque un ritardo inutile.