Bootzeit optimieren

Auf dieser Seite finden Sie Tipps zur Verbesserung der Startzeit.

Debug-Symbole aus Modulen entfernen

Ähnlich wie bei der Entfernung von Debug-Symbolen aus dem Kernel auf einem Produktionsgerät sollten Sie auch die Debug-Symbole aus Modulen entfernen. Das Entfernen von Debug-Symbolen aus Modulen trägt zur Verbesserung der Startzeit bei, indem Folgendes reduziert wird:

  • Die Zeit, die zum Lesen der Binärdateien aus dem Flash-Speicher benötigt wird.
  • Die Zeit, die zum Dekomprimieren der RAM-Disk benötigt wird.
  • Die Zeit, die zum Laden der Module benötigt wird.

Durch das Entfernen von Debug-Symbolen aus Modulen können mehrere Sekunden bei der Startzeit eingespart werden.

Das Entfernen von Symbolen ist standardmäßig im Android-Plattform-Build aktiviert. Wenn Sie es explizit aktivieren möchten, legen Sie fest BOARD_DO_NOT_STRIP_VENDOR_RAMDISK_MODULES in der gerätespezifischen Konfiguration unter device/vendor/device.

LZ4-Komprimierung für Kernel und RAM-Disk verwenden

Gzip erzeugt im Vergleich zu LZ4 eine kleinere komprimierte Ausgabe, aber LZ4 dekomprimiert schneller als Gzip. Für den Kernel und die Module ist die absolute Reduzierung der Speichergröße durch die Verwendung von Gzip im Vergleich zum Vorteil der Dekomprimierungszeit von LZ4 nicht so bedeutend.

Unterstützung für die LZ4-RAM-Disk-Komprimierung wurde dem Android-Plattform-Build über BOARD_RAMDISK_USE_LZ4 hinzugefügt. Sie können diese Option in der gerätespezifischen Konfiguration festlegen. Die Kernel-Komprimierung kann über die Kernel-Defconfig festgelegt werden.

Durch den Wechsel zu LZ4 sollte die Startzeit um 500 bis 1.000 ms verkürzt werden.

Übermäßige Protokollierung in Treibern vermeiden

In ARM64 und ARM32 benötigen Funktionsaufrufe, die mehr als eine bestimmte Entfernung vom Aufrufort entfernt sind, eine Sprungtabelle (eine sogenannte Procedure Linking Table oder PLT), um die vollständige Sprungadresse zu codieren. Da Module dynamisch geladen werden, müssen diese Sprungtabellen beim Laden des Moduls korrigiert werden. Die Aufrufe, die eine Verschiebung erfordern, werden als Verschiebungseinträge mit expliziten Addenden (oder kurz RELA) im ELF-Format bezeichnet.

Der Linux-Kernel führt bei der Zuweisung der PLT eine Optimierung der Speichergröße durch (z. B. Cache-Treffer-Optimierung). Mit diesem Upstream Commit, hat das Optimierungsschema eine Komplexität von O(N^2), wobei N die Anzahl der RELAs vom Typ R_AARCH64_JUMP26 oder R_AARCH64_CALL26 ist. Weniger RELAs dieser Typen sind also hilfreich, um die Ladezeit des Moduls zu verkürzen.

Ein häufiges Codierungsmuster, das die Anzahl der R_AARCH64_CALL26- oder R_AARCH64_JUMP26-RELAs erhöht, ist eine übermäßige Protokollierung in einem Treiber. Jeder Aufruf von printk() oder einem anderen Protokollierungsschema fügt in der Regel einen CALL26/JUMP26-RELA-Eintrag hinzu. Im Commit-Text des Upstream- Commits, sehen Sie, dass das Laden der sechs Module trotz der Optimierung etwa 250 ms dauert. Das liegt daran, dass diese sechs Module die sechs Module mit der größten Protokollmenge waren.

Durch die Reduzierung der Protokollierung kann die Startzeit um etwa 100 bis 300 ms verkürzt werden , je nachdem, wie umfangreich die vorhandene Protokollierung ist.

Asynchrone Prüfung selektiv aktivieren

Wenn ein Modul geladen wird und das Gerät, das es unterstützt, bereits aus dem DT (Gerätebaum) gefüllt und dem Treiberkern hinzugefügt wurde, erfolgt die Geräteprüfung im Kontext des module_init()-Aufrufs. Wenn eine Geräteprüfung im Kontext von module_init() erfolgt, kann das Laden des Moduls erst abgeschlossen werden, wenn die Prüfung abgeschlossen ist. Da das Laden von Modulen meist seriell erfolgt, verlangsamt ein Gerät, dessen Prüfung relativ lange dauert, die Startzeit.

Um längere Startzeiten zu vermeiden, aktivieren Sie die asynchrone Prüfung für Module, deren Prüfung einige Zeit in Anspruch nimmt. Die Aktivierung der asynchronen Prüfung für alle Module ist möglicherweise nicht von Vorteil, da die Zeit, die zum Verzweigen eines Threads und zum Starten der Prüfung benötigt wird, genauso lang sein kann wie die Zeit, die zum Prüfen des Geräts benötigt wird.

Geräte, die über einen langsamen Bus wie I2C verbunden sind, Geräte, die Firmware in ihrer Prüffunktion laden, und Geräte, die eine umfangreiche Hardwareinitialisierung durchführen, können zu dem Timing-Problem führen. Am besten lässt sich das feststellen, indem Sie die Prüfzeit für jeden Treiber erfassen und sortieren.

Um die asynchrone Prüfung für ein Modul zu aktivieren, reicht es nicht aus, nur das PROBE_PREFER_ASYNCHRONOUS Flag im Treibercode festzulegen. Für Module müssen Sie auch module_name.async_probe=1 in der Kernel-Befehlszeile hinzufügen oder async_probe=1 als Modulparameter übergeben, wenn Sie das Modul mit modprobe oder insmod laden.

Durch die Aktivierung der asynchronen Prüfung kann die Startzeit je nach Hardware/Treibern um etwa 100 bis 500 ms verkürzt werden.

CPUfreq-Treiber so früh wie möglich prüfen

Je früher der CPUfreq-Treiber geprüft wird, desto früher können Sie die CPU-Frequenz während des Starts auf das Maximum (oder ein thermisch begrenztes Maximum) skalieren. Je schneller die CPU, desto schneller der Start. Diese Richtlinie gilt auch für devfreq-Treiber, die die DRAM-, Speicher- und Interconnect-Frequenz steuern.

Bei Modulen kann die Ladefolge von der initcall-Ebene und der Kompilierungs- oder Linkreihenfolge der Treiber abhängen. Verwenden Sie einen Alias MODULE_SOFTDEP(), um sicherzustellen, dass der cpufreq-Treiber zu den ersten Modulen gehört, die geladen werden.

Neben dem frühen Laden des Moduls müssen Sie auch sicherstellen, dass alle Abhängigkeiten zur Prüfung des CPUfreq-Treibers ebenfalls geprüft wurden. Wenn Sie beispielsweise ein Takt- oder Regler-Handle benötigen, um die Frequenz der CPU zu steuern, müssen diese zuerst geprüft werden. Möglicherweise müssen auch die thermischen Treiber vor dem CPUfreq-Treiber geladen werden, wenn die CPUs während des Starts zu heiß werden können. Tun Sie also alles, um sicherzustellen, dass die CPUfreq- und relevanten devfreq-Treiber so früh wie möglich geprüft werden.

Die Einsparungen durch die frühe Prüfung des CPUfreq-Treibers können sehr gering bis sehr groß sein, je nachdem, wie früh Sie diese prüfen können und mit welcher Frequenz der Bootloader die CPUs verlässt.

Module in die zweite Initialisierungsphase, die Anbieter- oder die `vendor_dlkm`-Partition verschieben

Da der Initialisierungsprozess der ersten Phase seriell erfolgt, gibt es nicht viele Möglichkeiten, den Startprozess zu parallelisieren. Wenn ein Modul nicht für den Abschluss der Initialisierung der ersten Phase erforderlich ist, verschieben Sie es in die Initialisierung der zweiten Phase, indem Sie es in die Anbieter- oder vendor_dlkm-Partition verschieben.

Für die Initialisierung der ersten Phase müssen nicht mehrere Geräte geprüft werden, um zur Initialisierung der zweiten Phase zu gelangen. Für einen normalen Startablauf sind nur Konsolen- und Flash-Speicherfunktionen erforderlich.

Laden Sie die folgenden wichtigen Treiber:

  • watchdog
  • reset
  • cpufreq

Für den Wiederherstellungs- und den Userspace-Modus fastbootd müssen in der Initialisierung der ersten Phase mehr Geräte geprüft werden (z. B. USB) und das Display. Bewahren Sie eine Kopie dieser Module auf der RAM-Disk der ersten Phase und auf der Anbieter- oder vendor_dlkm-Partition auf. So können sie in der Initialisierung der ersten Phase für den Wiederherstellungs- oder fastbootd-Startablauf geladen werden. Laden Sie die Module für den Wiederherstellungsmodus jedoch nicht in der Initialisierung der ersten Phase während des normalen Startablaufs. Module für den Wiederherstellungsmodus können auf die Initialisierung der zweiten Phase verschoben werden, um die Startzeit zu verkürzen. Alle anderen Module, die in der Initialisierung der ersten Phase nicht benötigt werden, sollten in die Anbieter- oder vendor_dlkm-Partition verschoben werden.

Anhand einer Liste von Blattgeräten (z. B. UFS oder seriell), dev needs.sh Skript findet alle Treiber, Geräte und Module, die für Abhängigkeiten oder Lieferanten (z. B. Uhren, Regler oder gpio) geprüft werden müssen.

Durch das Verschieben von Modulen in die Initialisierung der zweiten Phase werden die Startzeiten auf folgende Weise verkürzt:

  • Reduzierung der RAM-Disk-Größe.
    • Dies führt zu schnelleren Flash-Lesevorgängen, wenn der Bootloader die RAM-Disk lädt (serieller Startschritt).
    • Dies führt zu schnelleren Dekomprimierungsgeschwindigkeiten, wenn der Kernel die RAM-Disk dekomprimiert (serieller Startschritt).
  • Die Initialisierung der zweiten Phase erfolgt parallel, wodurch die Ladezeit des Moduls durch die Arbeit in der Initialisierung der zweiten Phase verborgen wird.

Durch das Verschieben von Modulen in die zweite Phase können 500 bis 1.000 ms bei der Startzeit eingespart werden , je nachdem, wie viele Module Sie in die Initialisierung der zweiten Phase verschieben können.

Logistik für das Laden von Modulen

Der neueste Android-Build enthält Board-Konfigurationen, mit denen gesteuert wird, welche Module in die einzelnen Phasen kopiert und welche Module geladen werden. In diesem Abschnitt geht es um die folgende Teilmenge:

  • BOARD_VENDOR_RAMDISK_KERNEL_MODULES: Diese Liste enthält die Module, die in die RAM-Disk kopiert werden sollen.
  • BOARD_VENDOR_RAMDISK_KERNEL_MODULES_LOAD: Diese Liste enthält die Module, die in der Initialisierung der ersten Phase geladen werden sollen.
  • BOARD_VENDOR_RAMDISK_RECOVERY_KERNEL_MODULES_LOAD: Diese Liste enthält die Module, die geladen werden sollen, wenn in der RAM-Disk die Wiederherstellung oder fastbootd ausgewählt wird.
  • BOARD_VENDOR_KERNEL_MODULES: Diese Liste enthält die Module, die in die Anbieter- oder vendor_dlkm-Partition im Verzeichnis /vendor/lib/modules/ kopiert werden sollen.
  • BOARD_VENDOR_KERNEL_MODULES_LOAD: Diese Liste enthält die Module, die in der Initialisierung der zweiten Phase geladen werden sollen.

Die Start- und Wiederherstellungsmodule in der RAM-Disk müssen auch in die Anbieter- oder vendor_dlkm-Partition unter /vendor/lib/modules kopiert werden. Durch das Kopieren dieser Module in die Anbieterpartition wird sichergestellt, dass die Module während der Initialisierung der zweiten Phase nicht unsichtbar sind. Das ist nützlich für das Debugging und das Erfassen von modinfo für Fehlerberichte.

Die Duplizierung sollte nur minimalen Speicherplatz auf der Anbieter- oder vendor_dlkm-Partition beanspruchen, solange die Anzahl der Startmodule minimiert ist. Achten Sie darauf, dass die modules.list-Datei des Anbieters eine gefilterte Liste der Module in /vendor/lib/modules enthält. Durch die gefilterte Liste wird sichergestellt, dass die Startzeiten nicht durch das erneute Laden der Module beeinträchtigt werden (was ein aufwendiger Prozess ist).

Achten Sie darauf, dass die Module für den Wiederherstellungsmodus als Gruppe geladen werden. Das Laden von Modulen für den Wiederherstellungsmodus kann entweder im Wiederherstellungsmodus oder zu Beginn der Initialisierung der zweiten Phase in jedem Startablauf erfolgen.

Sie können die Gerätedateien Board.Config.mk verwenden, um diese Aktionen auszuführen, wie im folgenden Beispiel zu sehen:

# 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)))

In diesem Beispiel wird eine einfacher zu verwaltende Teilmenge von BOOT_KERNEL_MODULES und RECOVERY_KERNEL_MODULES gezeigt, die lokal in den Board-Konfigurationsdateien angegeben werden kann. Das vorherige Skript sucht und füllt jedes der Teilmengenmodule aus den ausgewählten verfügbaren Kernelmodulen. Die restlichen Module werden für die Initialisierung der zweiten Phase verwendet.

Für die Initialisierung der zweiten Phase empfehlen wir, das Laden von Modulen als Dienst auszuführen, damit der Startablauf nicht blockiert wird. Verwenden Sie ein Shell-Skript, um das Laden von Modulen zu verwalten, damit andere Logistikvorgänge wie Fehlerbehandlung und -behebung oder der Abschluss des Ladens von Modulen bei Bedarf gemeldet (oder ignoriert) werden können.

Sie können einen Fehler beim Laden eines Debug-Moduls ignorieren, das in Nutzer-Builds nicht vorhanden ist. Wenn Sie diesen Fehler ignorieren möchten, legen Sie die Eigenschaft vendor.device.modules.ready fest, um spätere Phasen des init rc-Skripting-Startablaufs auszulösen, damit der Startbildschirm angezeigt wird. Verwenden Sie das folgende Beispielskript, wenn Sie den folgenden Code 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

In der Hardware-RC-Datei könnte der one shot-Dienst mit Folgendem angegeben werden:

service insmod-sh /vendor/etc/init.insmod.sh /vendor/etc/init.insmod.<hw>.cfg
    class main
    user root
    group root system
    Disabled
    oneshot

Nachdem Module von der ersten in die zweite Phase verschoben wurden, können weitere Optimierungen vorgenommen werden. Mit der Modprobe-Blocklist-Funktion können Sie den Startablauf der zweiten Phase aufteilen, um das verzögerte Laden von nicht unbedingt erforderlichen Modulen einzubeziehen. Das Laden von Modulen, die ausschließlich von einem bestimmten HAL verwendet werden, kann verzögert werden, sodass die Module erst geladen werden, wenn der HAL gestartet wird.

Um die scheinbare Startzeit zu verbessern, können Sie im Dienst zum Laden von Modulen speziell Module auswählen, die besser nach dem Startbildschirm geladen werden können. Sie können beispielsweise die Module für den Videodecoder oder das WLAN explizit verzögert laden, nachdem der Startablauf abgeschlossen ist (z. B. mit dem Android-Eigenschaftssignal sys.boot_complete). Achten Sie darauf, dass die HALs für die verzögert geladenen Module lange genug blockieren, wenn die Kernel-Treiber nicht vorhanden sind.

Alternativ können Sie den Befehl wait<file>[<timeout>] von init im Start ablauf-RC-Skripting verwenden, um zu warten, bis ausgewählte sysfs Einträge zeigen, dass die Treiber-Module die Prüfvorgänge abgeschlossen haben. Ein Beispiel dafür ist das Warten, bis der Displaytreiber im Hintergrund der Wiederherstellung oder von fastbootd vollständig geladen ist, bevor Menügrafiken angezeigt werden.

CPU-Frequenz im Bootloader auf einen angemessenen Wert initialisieren

Nicht alle SoCs/Produkte können die CPU möglicherweise mit der höchsten Frequenz starten, da bei Tests im Startloop thermische oder Leistungsbedenken bestehen. Achten Sie jedoch darauf, dass der Bootloader die Frequenz aller Online-CPUs so hoch wie möglich für ein SoC oder Produkt festlegt. Das ist sehr wichtig, da bei einem vollständig modularen Kernel die Dekomprimierung der init-RAM-Disk erfolgt, bevor der CPUfreq-Treiber geladen werden kann. Wenn die CPU also vom Bootloader am unteren Ende ihrer Frequenz belassen wird, kann die Dekomprimierungszeit der RAM-Disk länger dauern als bei einem statisch kompilierten Kernel (nach Anpassung der RAM-Disk-Größe), da die CPU-Frequenz bei CPU-intensiven Aufgaben (Dekomprimierung) sehr niedrig wäre. Dasselbe gilt für die Speicher- und Interconnect-Frequenz.

CPU-Frequenz großer CPUs im Bootloader initialisieren

Bevor der CPUfreq-Treiber geladen wird, kennt der Kernel die CPU-Frequenzen nicht und skaliert die CPU-Planungskapazität nicht für die aktuelle Frequenz. Der Kernel migriert Threads möglicherweise zur großen CPU, wenn die Last auf der kleinen CPU ausreichend hoch ist.

Achten Sie darauf, dass die großen CPUs bei der Frequenz, mit der der Bootloader sie verlässt, mindestens so leistungsstark sind wie die kleinen CPUs. Wenn die große CPU beispielsweise bei derselben Frequenz doppelt so leistungsstark ist wie die kleine CPU, der Bootloader aber die Frequenz der kleinen CPU auf 1,5 GHz und die Frequenz der großen CPU auf 300 MHz festlegt, sinkt die Startleistung, wenn der Kernel einen Thread zur großen CPU verschiebt. Wenn es in diesem Beispiel sicher ist, die große CPU mit 750 MHz zu starten, sollten Sie das tun, auch wenn Sie nicht planen, sie explizit zu verwenden.

Treiber sollten in der Initialisierung der ersten Phase keine Firmware laden

Es kann einige unvermeidliche Fälle geben, in denen Firmware in der Initialisierung der ersten Phase geladen werden muss. Im Allgemeinen sollten Treiber jedoch in der Initialisierung der ersten Phase keine Firmware laden, insbesondere nicht im Kontext der Geräteprüfung. Das Laden von Firmware in der Initialisierung der ersten Phase führt dazu, dass der gesamte Startprozess angehalten wird, wenn die Firmware nicht in der RAM-Disk der ersten Phase verfügbar ist. Auch wenn die Firmware in der RAM-Disk der ersten Phase vorhanden ist, führt dies zu einer unnötigen Verzögerung.