Ottimizzazione dei tempi di avvio

Questo documento fornisce indicazioni ai partner per migliorare i tempi di avvio per dispositivi Android specifici. Il tempo di avvio è un componente importante delle prestazioni del sistema poiché gli utenti devono attendere il completamento dell'avvio prima di poter utilizzare il dispositivo. Per dispositivi come le automobili in cui l'avvio a freddo avviene più frequentemente, avere un tempo di avvio rapido è fondamentale (a nessuno piace aspettare decine di secondi solo per inserire una destinazione di navigazione).

Android 8.0 consente tempi di avvio ridotti supportando numerosi miglioramenti in una gamma di componenti. La tabella seguente riassume questi miglioramenti delle prestazioni (misurati sui dispositivi Google Pixel e Pixel XL).

Componente Miglioramento
Boot loader
  • Risparmiato 1,6 secondi rimuovendo il registro UART
  • Risparmiato 0,4 secondi passando a LZ4 da GZIP
Nucleo del dispositivo
  • Risparmiato 0,3 secondi rimuovendo le configurazioni del kernel inutilizzate e riducendo le dimensioni del driver
  • Risparmiato 0,3 secondi con l'ottimizzazione del precaricamento di dm-verity
  • Risparmiato 0,15 secondi per rimuovere attese/test non necessari nel driver
  • Risparmiato 0,12 s per rimuovere CONFIG_CC_OPTIMIZE_FOR_SIZE
Sintonizzazione I/O
  • 2 secondi risparmiati durante l'avvio normale
  • 25 secondi risparmiati al primo avvio
init.*.rc
  • Risparmiato 1,5 secondi mettendo in parallelo i comandi init
  • Hai risparmiato 0,25 secondi avviando lo zigote in anticipo
  • Risparmiato 0,22 secondi tramite cpuset tune
Animazione di avvio
  • Avviato 2 secondi prima all'avvio senza l'attivazione di fsck, molto più grande all'avvio con l'avvio attivato da fsck
  • 5 secondi risparmiati su Pixel XL con spegnimento immediato dell'animazione di avvio
Politica SELinux Salvato 0,2 secondi da genfscon

Ottimizzazione del bootloader

Per ottimizzare il bootloader per migliorare i tempi di avvio:

  • Per la registrazione:
    • Disabilitare la scrittura del registro su UART poiché può richiedere molto tempo con molte registrazioni. (Sui dispositivi Google Pixel, abbiamo riscontrato che rallenta il bootloader di 1,5 s).
    • Registra solo le situazioni di errore e valuta la possibilità di archiviare altre informazioni in memoria con un meccanismo separato per il recupero.
  • Per la decompressione del kernel, considerare l'utilizzo di LZ4 per l'hardware contemporaneo anziché GZIP (esempio patch ). Tieni presente che diverse opzioni di compressione del kernel possono avere tempi di caricamento e decompressione diversi e alcune opzioni potrebbero funzionare meglio di altre per il tuo hardware specifico.
  • Controllare i tempi di attesa non necessari per l'ingresso in modalità antirimbalzo/speciale e ridurli al minimo.
  • Passa il tempo di avvio trascorso nel bootloader al kernel come cmdline.
  • Controllare l'orologio della CPU e considerare la parallelizzazione (richiede supporto multi-core) per il caricamento del kernel e l'inizializzazione dell'I/O.

Ottimizzazione dell'efficienza I/O

Migliorare l'efficienza I/O è fondamentale per velocizzare i tempi di avvio e la lettura di tutto ciò che non è necessario dovrebbe essere rinviata a dopo l'avvio (su un Google Pixel, all'avvio vengono letti circa 1,2 GB di dati).

Ottimizzazione del filesystem

Il kernel Linux read ahead entra in funzione quando un file viene letto dall'inizio o quando i blocchi vengono letti in sequenza, rendendo necessario ottimizzare i parametri dello scheduler I/O specificatamente per l'avvio (che ha una caratterizzazione del carico di lavoro diversa rispetto alle normali applicazioni).

I dispositivi che supportano aggiornamenti continui (A/B) traggono grandi vantaggi dall'ottimizzazione del file system al primo avvio (ad esempio 20s su Google Pixel). Ad esempio, abbiamo ottimizzato i seguenti parametri per Google Pixel:

on late-fs
  # boot time fs tune
    # boot time fs tune
    write /sys/block/sda/queue/iostats 0
    write /sys/block/sda/queue/scheduler cfq
    write /sys/block/sda/queue/iosched/slice_idle 0
    write /sys/block/sda/queue/read_ahead_kb 2048
    write /sys/block/sda/queue/nr_requests 256
    write /sys/block/dm-0/queue/read_ahead_kb 2048
    write /sys/block/dm-1/queue/read_ahead_kb 2048

on property:sys.boot_completed=1
    # end boot time fs tune
    write /sys/block/sda/queue/read_ahead_kb 512
    ...

Varie

  • Attiva la dimensione di precaricamento dell'hash dm-verity utilizzando la configurazione del kernel DM_VERITY_HASH_PREFETCH_MIN_SIZE (la dimensione predefinita è 128).
  • Per una migliore stabilità del file system e un controllo forzato interrotto che si verifica ad ogni avvio, utilizzare il nuovo strumento di generazione ext4 impostando TARGET_USES_MKE2FS in BoardConfig.mk.

Analisi degli I/O

Per comprendere le attività I/O durante l'avvio, utilizzare i dati ftrace del kernel (utilizzati anche da systrace):

trace_event=block,ext4 in BOARD_KERNEL_CMDLINE

Per suddividere l'accesso ai file per ciascun file, apportare le seguenti modifiche al kernel (solo kernel di sviluppo; non utilizzare nei kernel di produzione):

diff --git a/fs/open.c b/fs/open.c
index 1651f35..a808093 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -981,6 +981,25 @@
 }
 EXPORT_SYMBOL(file_open_root);
 
+static void _trace_do_sys_open(struct file *filp, int flags, int mode, long fd)
+{
+       char *buf;
+       char *fname;
+
+       buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
+       if (!buf)
+               return;
+       fname = d_path(&filp-<f_path, buf, PAGE_SIZE);
+
+       if (IS_ERR(fname))
+               goto out;
+
+       trace_printk("%s: open(\"%s\", %d, %d) fd = %ld, inode = %ld\n",
+                     current-<comm, fname, flags, mode, fd, filp-<f_inode-<i_ino);
+out:
+       kfree(buf);
+}
+
long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
 {
 	struct open_flags op;
@@ -1003,6 +1022,7 @@
 		} else {
 			fsnotify_open(f);
 			fd_install(fd, f);
+			_trace_do_sys_open(f, flags, mode, fd);

Utilizzare i seguenti script per facilitare l'analisi delle prestazioni di avvio.

  • system/extras/boottime_tools/bootanalyze/bootanalyze.py Misura il tempo di avvio con una suddivisione dei passaggi importanti nel processo di avvio.
  • system/extras/boottime_tools/io_analysis/check_file_read.py boot_trace Fornisce informazioni di accesso per ciascun file.
  • system/extras/boottime_tools/io_analysis/check_io_trace_all.py boot_trace Fornisce un'analisi a livello di sistema.

Ottimizzazione di init.*.rc

Init è il ponte dal kernel fino alla creazione del framework e i dispositivi solitamente trascorrono alcuni secondi in diverse fasi di inizializzazione.

Esecuzione di attività in parallelo

Sebbene l'attuale init di Android sia più o meno un processo a thread singolo, è comunque possibile eseguire alcune attività in parallelo.

  • Esegui comandi lenti in un servizio di script di shell e unisciti a quello in seguito attendendo una proprietà specifica. Android 8.0 supporta questo caso d'uso con un nuovo comando wait_for_property .
  • Identificare le operazioni lente in init. Il sistema registra il comando init exec/wait_for_prop o qualsiasi azione che richiede molto tempo (in Android 8.0, qualsiasi comando che richiede più di 50 ms). Ad esempio:
    init: Command 'wait_for_coldboot_done' action=wait_for_coldboot_done returned 0 took 585.012ms

    L'esame di questo registro può indicare opportunità di miglioramento.

  • Avviare tempestivamente i servizi e abilitare i dispositivi periferici nel percorso critico. Ad esempio, alcuni SOC richiedono l'avvio di servizi relativi alla sicurezza prima di avviare SurfaceFlinger. Esaminare il registro di sistema quando ServiceManager restituisce "attendere il servizio": in genere questo è un segno che è necessario avviare prima un servizio dipendente.
  • Rimuovere eventuali servizi e comandi non utilizzati in init.*.rc. Tutto ciò che non viene utilizzato nella fase iniziale di init dovrebbe essere rimandato al completamento dell'avvio.

Nota: il servizio proprietà fa parte del processo init, quindi chiamare setproperty durante l'avvio può causare un lungo ritardo se init è occupato nei comandi integrati.

Utilizzo dell'ottimizzazione dello scheduler

Utilizza l'ottimizzazione dello scheduler per l'avvio anticipato. Esempio da un Google Pixel:

on init
    # boottime stune
    write /dev/stune/schedtune.prefer_idle 1
    write /dev/stune/schedtune.boost 100
    on property:sys.boot_completed=1
    # reset stune
    write /dev/stune/schedtune.prefer_idle 0
    write /dev/stune/schedtune.boost 0

    # or just disable EAS during boot
    on init
    write /sys/kernel/debug/sched_features NO_ENERGY_AWARE
    on property:sys.boot_completed=1
    write /sys/kernel/debug/sched_features ENERGY_AWARE

Alcuni servizi potrebbero richiedere un aumento di priorità durante l'avvio. Esempio:

init.zygote64.rc:
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
    class main
    priority -20
    user root
...

Iniziare presto lo zigote

I dispositivi con crittografia basata su file possono avviare zygote prima al trigger zygote-start (per impostazione predefinita, zygote viene avviato alla classe main, che è molto più tardi di zygote-start). Quando lo fai, assicurati di consentire l'esecuzione di zygote in tutte le CPU (poiché un'impostazione errata di cpuset potrebbe forzare l'esecuzione di zygote in CPU specifiche).

Disabilita il risparmio energetico

Durante l'avvio del dispositivo, l'impostazione di risparmio energetico per componenti come UFS e/o regolatore della CPU può essere disabilitata.

Attenzione: per ragioni di efficienza, il risparmio energetico deve essere abilitato in modalità caricabatterie.

on init
    # Disable UFS powersaving
    write /sys/devices/soc/${ro.boot.bootdevice}/clkscale_enable 0
    write /sys/devices/soc/${ro.boot.bootdevice}/clkgate_enable 0
    write /sys/devices/soc/${ro.boot.bootdevice}/hibern8_on_idle_enable 0
    write /sys/module/lpm_levels/parameters/sleep_disabled Y
on property:sys.boot_completed=1
    # Enable UFS powersaving
    write /sys/devices/soc/${ro.boot.bootdevice}/clkscale_enable 1
    write /sys/devices/soc/${ro.boot.bootdevice}/clkgate_enable 1
    write /sys/devices/soc/${ro.boot.bootdevice}/hibern8_on_idle_enable 1
    write /sys/module/lpm_levels/parameters/sleep_disabled N
on charger
    # Enable UFS powersaving
    write /sys/devices/soc/${ro.boot.bootdevice}/clkscale_enable 1
    write /sys/devices/soc/${ro.boot.bootdevice}/clkgate_enable 1
    write /sys/devices/soc/${ro.boot.bootdevice}/hibern8_on_idle_enable 1
    write /sys/class/typec/port0/port_type sink
    write /sys/module/lpm_levels/parameters/sleep_disabled N

Rimandare l'inizializzazione non critica

L'inizializzazione non critica come ZRAM può essere rinviata a boot_complete.

on property:sys.boot_completed=1
   # Enable ZRAM on boot_complete
   swapon_all /vendor/etc/fstab.${ro.hardware}

Ottimizzazione dell'animazione di avvio

Utilizzare i seguenti suggerimenti per ottimizzare l'animazione di avvio.

Configurazione dell'avvio anticipato

Android 8.0 consente l'avvio anticipato dell'animazione di avvio, prima del montaggio della partizione dei dati utente. Tuttavia, anche quando si utilizza la nuova catena di strumenti ext4 in Android 8.0, fsck viene comunque attivato periodicamente per motivi di sicurezza, causando un ritardo nell'avvio del servizio bootanimation.

Per far sì che la bootanimation venga avviata in anticipo, dividi il montaggio di fstab in due fasi:

  • Nella fase iniziale, montare solo le partizioni (come system/ e vendor/ ) che non richiedono controlli di esecuzione, quindi avviare i servizi di animazione di avvio e le relative dipendenze (come servicemanager e surfaceflinger).
  • Nella seconda fase, montare le partizioni (come data/ ) che richiedono controlli di esecuzione.

L'animazione di avvio verrà avviata molto più velocemente (e in tempo costante) indipendentemente da fsck.

Finitura pulita

Dopo aver ricevuto il segnale di uscita, la bootanimation riproduce l'ultima parte, la cui durata può rallentare il tempo di avvio. Un sistema che si avvia rapidamente non ha bisogno di lunghe animazioni che potrebbero effettivamente nascondere eventuali miglioramenti apportati. Ti consigliamo di rendere brevi sia il ciclo ripetuto che il finale.

Ottimizzazione di SELinux

Utilizzare i seguenti suggerimenti per ottimizzare SELinux e migliorare i tempi di avvio.

  • Utilizza espressioni regolari pulite (regex) . Una regex mal formata può portare a un sacco di sovraccarico quando si fa corrispondenza con la policy SELinux per sys/devices in file_contexts . Ad esempio, l'espressione regolare /sys/devices/.*abc.*(/.*)? forza erroneamente una scansione di tutte le sottodirectory /sys/devices che contengono "abc", abilitando le corrispondenze sia per /sys/devices/abc che /sys/devices/xyz/abc . Migliorare questa espressione regolare in /sys/devices/[^/]*abc[^/]*(/.*)? abiliterà una corrispondenza solo per /sys/devices/abc .
  • Sposta le etichette in genfscon . Questa funzionalità SELinux esistente passa i prefissi di corrispondenza dei file nel kernel nel binario SELinux, dove il kernel li applica ai filesystem generati dal kernel. Ciò aiuta anche a correggere i file creati dal kernel con etichette errate, prevenendo condizioni di competizione che possono verificarsi tra i processi dello spazio utente che tentano di accedere a questi file prima che avvenga la rietichettatura.

Strumento e metodi

Utilizza i seguenti strumenti per raccogliere dati per gli obiettivi di ottimizzazione.

Grafico di avvio

Bootchart fornisce la ripartizione del carico di CPU e I/O di tutti i processi per l'intero sistema. Non richiede la ricostruzione dell'immagine del sistema e può essere utilizzato come rapido controllo di integrità prima di immergersi in systrace.

Per abilitare il grafico di avvio:

adb shell 'touch /data/bootchart/enabled'
adb reboot

Dopo l'avvio, recupera la tabella di avvio:

$ANDROID_BUILD_TOP/system/core/init/grab-bootchart.sh

Al termine, eliminare /data/bootchart/enabled per evitare di raccogliere i dati ogni volta.

Se bootchart non funziona e ricevi un errore che informa che bootchart.png non esiste, procedi come segue:
  1. Esegui i seguenti comandi:
          sudo apt install python-is-python3
          cd ~/Documents
          git clone https://github.com/xrmx/bootchart.git
          cd bootchart/pybootchartgui
          mv main.py.in main.py
        
  2. Aggiorna $ANDROID_BUILD_TOP/system/core/init/grab-bootchart.sh in modo che punti alla copia locale di pybootchartgui (situata in ~/Documents/bootchart/pybootchartgui.py )

Systrace

Systrace consente di raccogliere sia le tracce del kernel che quelle di Android durante l'avvio. La visualizzazione di systrace può aiutare ad analizzare problemi specifici durante l'avvio. (Tuttavia, per controllare il numero medio o il numero accumulato durante l'intero avvio, è più semplice esaminare direttamente la traccia del kernel).

Per abilitare systrace durante l'avvio:

  • In frameworks/native/cmds/atrace/atrace.rc , modificare:
      write /sys/kernel/debug/tracing/tracing_on 0
      write /sys/kernel/tracing/tracing_on 0

    A:

      #    write /sys/kernel/debug/tracing/tracing_on 0
      #    write /sys/kernel/tracing/tracing_on 0
  • Ciò abilita la traccia (che è disabilitata per impostazione predefinita).

  • Nel file device.mk , aggiungi la seguente riga:
    PRODUCT_PROPERTY_OVERRIDES +=    debug.atrace.tags.enableflags=802922
    PRODUCT_PROPERTY_OVERRIDES +=    persist.traced.enable=0
  • Nel file BoardConfig.mk del dispositivo, aggiungere quanto segue:
    BOARD_KERNEL_CMDLINE := ... trace_buf_size=64M trace_event=sched_wakeup,sched_switch,sched_blocked_reason,sched_cpu_hotplug
  • Per un'analisi I/O dettagliata, aggiungi anche block ed ext4 e f2fs.

  • Nel file init.rc specifico del dispositivo, aggiungere quanto segue:
    on property:sys.boot_completed=1          // This stops tracing on boot complete
    write /d/tracing/tracing_on 0
    write /d/tracing/events/ext4/enable 0
    write /d/tracing/events/f2fs/enable 0
    write /d/tracing/events/block/enable 0
    
  • Dopo l'avvio, recupera la traccia:

    adb root && adb shell atrace --async_stop -z -c -o /data/local/tmp/boot_trace
    adb pull /data/local/tmp/boot_trace
    $ANDROID_BUILD_TOP/external/chromium-trace/systrace.py --from-file=boot_trace