Optimierung der Startzeit

Auf dieser Seite finden Sie Tipps zur Verbesserung der Startzeit.

Debugsymbole aus Modulen entfernen

Ähnlich wie bei Produktionsgeräten müssen auch die Debugging-Symbole aus den Modulen entfernt werden. Das Entfernen von Debugging-Symbolen aus Modulen verbessert die Startzeit, da Folgendes reduziert wird:

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

Wenn Sie das Debugsymbol aus den Modulen entfernen, können Sie beim Starten einige Sekunden sparen.

Das Entfernen von Symbolen ist im Android-Plattform-Build standardmäßig aktiviert. Um sie explizit zu aktivieren, legen Sie BOARD_DO_NOT_STRIP_VENDOR_RAMDISK_MODULES in Ihrer gerätespezifischen Konfiguration unter „device/vendor/device“ fest.

LZ4-Komprimierung für Kernel und Ramdisk verwenden

Gzip erzeugt im Vergleich zu LZ4 eine kleinere komprimierte Ausgabe, aber LZ4 dekomprimiert schneller als Gzip. Bei Kernel und Modulen ist die absolute Speicherplatzeinsparung durch die Verwendung von Gzip im Vergleich zum Vorteil bei der Dekomprimierungszeit von LZ4 nicht so groß.

Der Android-Plattformversion ab BOARD_RAMDISK_USE_LZ4 wurde Unterstützung für die LZ4-Ramdisk-Komprimierung hinzugefügt. Sie können diese Option in Ihrer gerätespezifischen Konfiguration festlegen. Die Kernelkomprimierung kann über die Kernel-Defconfig festgelegt werden.

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

Vermeiden Sie übermäßiges Logging in Ihren Treibern

In ARM64 und ARM32 benötigen Funktionsaufrufe, die über eine bestimmte Entfernung von der Aufruf-Website hinausgehen, eine Jump-Table (Prozedurverknüpfungstabelle oder PLT), um die vollständige Jump-Adresse codieren zu können. Da Module dynamisch geladen werden, müssen diese Sprungtabellen beim Laden des Moduls korrigiert werden. Die Aufrufe, die neu angeordnet werden müssen, werden im ELF-Format als Relocation-Einträge mit expliziten Addenden (kurz RELA) bezeichnet.

Der Linux-Kernel führt beim Zuweisen der PLT einige Optimierungen der Speichergröße durch, z. B. eine Optimierung der Cache-Treffer. Mit diesem Upstream-Commit hat das Optimierungsschema eine O(N^2)-Komplexität, wobei N die Anzahl der RELAs vom Typ R_AARCH64_JUMP26 oder R_AARCH64_CALL26 ist. Daher ist es hilfreich, weniger RELAs dieser Typen zu haben, um die Modulladezeit 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. Bei jedem Aufruf von printk() oder einem anderen Protokollschema wird in der Regel ein CALL26/JUMP26-RELA-Eintrag hinzugefügt. Im Commit-Text im Upstream-Commit sehen Sie, dass das Laden der sechs Module selbst nach der Optimierung etwa 250 ms dauert. Das liegt daran, dass diese sechs Module die sechs Module mit den meisten Protokollierungen waren.

Durch eine Verringerung des Loggings können Sie je nach Umfang der vorhandenen Protokollierung etwa 100 bis 300 ms beim Starten sparen.

Asynchrones Probieren selektiv aktivieren

Wenn ein Modul geladen wird und das von ihm unterstützte Gerät bereits aus dem Gerätebaum (Device Tree, DT) ausgefüllt und dem Treiberkern hinzugefügt wurde, erfolgt die Gerätesuche im Kontext des module_init()-Aufrufs. Wenn eine Geräteprüfung im Kontext von module_init() ausgeführt wird, kann das Modul erst geladen werden, wenn die Prüfung abgeschlossen ist. Da das Laden von Modulen größtenteils seriell erfolgt, verlängert sich die Bootzeit bei Geräten, bei denen die Prüfung relativ lange dauert.

Aktivieren Sie die asynchrone Suche für Module, bei denen es etwas dauert, bis die Geräte geprüft sind, um längere Bootzeiten zu vermeiden. Das Aktivieren des asynchronen Prüfens für alle Module ist möglicherweise nicht vorteilhaft, da die Zeit, die zum Forken eines Threads und Starten der Prüfung benötigt wird, genauso lang sein kann wie die Zeit, die für die Prüfung des Geräts benötigt wird.

Geräte, die über einen langsamen Bus wie I2C verbunden sind, Geräte, die in ihrer Prüffunktion Firmware laden, und Geräte, die eine starke Hardwareinitialisierung durchführen, können zu Zeitproblemen führen. Am besten können Sie feststellen, wann dies geschieht, indem Sie die Probezeit für jeden Treiber erfassen und sortieren.

Um das asynchrone Prüfen für ein Modul zu aktivieren, reicht es nicht aus, nur das Flag PROBE_PREFER_ASYNCHRONOUS im Treibercode festzulegen. Bei Modulen müssen Sie außerdem module_name.async_probe=1 in der Kernel-Befehlszeile hinzufügen oder async_probe=1 als Modulparameter übergeben, wenn das Modul mit modprobe oder insmod geladen wird.

Durch die Aktivierung des asynchronen Prüfens können Sie je nach Hardware/Treibern etwa 100 bis 500 ms beim Starten einsparen.

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

Je früher die CPU-Frequenztreiber geprüft werden, desto eher können Sie die CPU-Frequenz während des Starts auf das Maximum (oder einen thermisch begrenzten Maximalwert) 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 Ladereihenfolge von der initcall-Ebene und der Kompilierungs- oder Linkreihenfolge der Treiber abhängen. Verwenden Sie einen Alias MODULE_SOFTDEP(), damit der cpufreq-Treiber zu den ersten Modulen gehört, die geladen werden.

Neben dem frühen Laden des Moduls müssen Sie auch darauf achten, dass alle Abhängigkeiten zum Prüfen des CPU-Frequenztreibers geprüft wurden. Wenn Sie beispielsweise einen Takt- oder Regler-Handle benötigen, um die Taktfrequenz Ihrer CPU zu steuern, müssen Sie diese zuerst prüfen. Möglicherweise müssen auch thermische Treiber vor dem CPUfreq-Treiber geladen werden, wenn Ihre CPUs beim Starten zu heiß werden können. Achten Sie daher darauf, dass die CPUfreq- und die relevanten devfreq-Treiber so früh wie möglich prüfen.

Die Einsparungen durch ein frühzeitiges Prüfen des CPUfreq-Treibers können sehr gering bis sehr hoch sein, je nachdem, wie früh Sie diese prüfen können und mit welcher Taktfrequenz der Bootloader die CPUs beibehält.

Module in die Partition „init der zweiten Stufe“, „vendor“ oder „vendor_dlkm“ verschieben

Da der Initialisierungsprozess der ersten Phase serialisiert ist, gibt es nicht viele Möglichkeiten, den Bootvorgang zu parallelisieren. Wenn ein Modul nicht für die Fertigstellung der ersten Phase der Initialisierung benötigt wird, verschieben Sie es in die zweite Phase der Initialisierung, indem Sie es in die Anbieter- oder vendor_dlkm-Partition verschieben.

Für die Ersteinrichtung ist es nicht erforderlich, mehrere Geräte zu prüfen, um zur zweiten Phase der Einrichtung zu gelangen. Für einen normalen Bootvorgang sind nur Konsolen- und Flash-Speicherfunktionen erforderlich.

Laden Sie die folgenden wichtigen Treiber:

  • watchdog
  • reset
  • cpufreq

Für den Wiederherstellungs- und Nutzerbereich im fastbootd-Modus sind für die Erstphase der Initialisierung mehr Geräte (z. B. USB) und ein Display erforderlich. Behalten Sie eine Kopie dieser Module im Ramdisk der ersten Stufe und in der Anbieter- oder vendor_dlkm-Partition. So können sie in der ersten Phase der Initialisierung für die Wiederherstellung oder den fastbootd-Bootvorgang geladen werden. Laden Sie die Module für den Wiederherstellungsmodus jedoch nicht in der ersten Phase der Initialisierung während des normalen Bootvorgangs. Module für den Wiederherstellungsmodus können auf die zweite Phase der Initialisierung verschoben werden, um die Bootzeit zu verkürzen. Alle anderen Module, die in der ersten Phase der Initialisierung nicht benötigt werden, sollten in die Anbieter- oder vendor_dlkm-Partition verschoben werden.

Wenn eine Liste von Endgeräten (z. B. UFS oder seriell) angegeben ist, ermittelt das dev needs.sh-Script alle Treiber, Geräte und Module, die für Abhängigkeiten oder Lieferanten (z. B. Taktgeber, Regler oder gpio) geprüft werden müssen.

Das Verschieben von Modulen zur Init-Instanz der zweiten Phase verkürzt die Startzeiten auf folgende Weise:

  • Reduzierung der Größe des RAM-Laufwerks.
    • Dadurch werden schnellere Flash-Lesevorgänge erzielt, wenn der Bootloader das RAM-Disk lädt (sequenzieller Bootschritt).
    • Dies führt zu einer schnelleren Dekomprimierung, wenn der Kernel das Ramdisk dekomprimiert (sequenzieller Bootschritt).
  • Die zweite Phase der Initialisierung wird parallel ausgeführt, wodurch die Ladezeit des Moduls durch die Arbeit in der zweiten Phase der Initialisierung ausgeblendet wird.

Wenn Sie Module in die zweite Phase verschieben, können Sie je nach Anzahl der Module, die Sie in die zweite Phase der Initialisierung verschieben können, 500 bis 1.000 ms beim Starten einsparen.

Logistik für das Laden von Modulen

Die neuesten Android-Build-Funktionen steuern Boardkonfigurationen, die steuern, welche Module in die einzelnen Phasen kopiert und welche Module geladen werden. In diesem Abschnitt geht es hauptsächlich um die folgenden Untergruppen:

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

Die Boot- und Wiederherstellungsmodule im RAM-Disk müssen ebenfalls in die Anbieter- oder vendor_dlkm-Partition unter /vendor/lib/modules kopiert werden. Wenn Sie diese Module in die Anbieterpartition kopieren, sind sie während der zweiten Initialisierungsphase nicht unsichtbar. Das ist nützlich für die Fehlerbehebung und das Erfassen von modinfo für Fehlerberichte.

Die Duplizierung sollte nur minimalen Speicherplatz auf dem Anbieter oder der vendor_dlkm-Partition kosten, solange der Bootmodulsatz minimiert ist. Die modules.list-Datei des Anbieters muss eine gefilterte Liste der Module in /vendor/lib/modules enthalten. Die gefilterte Liste sorgt dafür, dass die Startzeiten nicht durch das erneute Laden der Module beeinträchtigt werden (was ein teurer Prozess ist).

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

Sie können die Board.Config.mk-Dateien des Geräts verwenden, um diese Aktionen auszuführen, wie im folgenden Beispiel gezeigt:

# 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 leichter zu verwaltende Teilmenge von BOOT_KERNEL_MODULES und RECOVERY_KERNEL_MODULES gezeigt, die lokal in den Board-Konfigurationsdateien angegeben werden kann. Das vorherige Script sucht und füllt jedes der Teilmodule aus den ausgewählten verfügbaren Kernelmodulen und lässt die verbleibenden Module für die zweite Phase der Initialisierung übrig.

Für die zweite Phase der Initialisierung empfehlen wir, das Laden des Moduls als Dienst auszuführen, damit der Bootvorgang nicht blockiert wird. Verwalten Sie das Laden des Moduls mit einem Shell-Script, damit andere Logistikaufgaben wie Fehlerbehandlung und -behebung oder das Ende des Modulladevorgangs bei Bedarf gemeldet (oder ignoriert) werden können.

Sie können einen Fehler beim Laden des Debug-Moduls ignorieren, der bei Nutzerbuilds nicht auftritt. Wenn Sie diesen Fehler ignorieren möchten, legen Sie die vendor.device.modules.ready-Eigenschaft so fest, dass spätere Phasen des init rc-Script-Boot-Flows ausgelöst werden, um mit dem Startbildschirm fortzufahren. Verwenden Sie das folgende Beispielskript, wenn Sie in /vendor/etc/init.insmod.sh den folgenden Code haben:

#!/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 kann der Dienst one shot so 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

Nach dem Wechsel von der ersten in die zweite Phase können weitere Optimierungen vorgenommen werden. Mit der Blocklist-Funktion von modprobe können Sie den Bootvorgang der zweiten Phase aufteilen, um das verzögerte Laden nicht erforderlicher Module einzubeziehen. Das Laden von Modulen, die ausschließlich von einer bestimmten HAL verwendet werden, kann verschoben werden, sodass die Module erst beim Starten der HAL geladen werden.

Um die scheinbaren Bootzeiten zu verbessern, können Sie im Modulladedienst bestimmte Module auswählen, die sich besser für das Laden nach dem Startbildschirm eignen. So können Sie beispielsweise die Module für den Videodecoder oder das WLAN explizit verzögert laden, nachdem der Bootvorgang abgeschlossen wurde (z. B. sys.boot_complete-Android-Property-Signal). Achten Sie darauf, dass die HALs für die Module, die später geladen werden, lange genug blockieren, wenn die Kerneltreiber nicht vorhanden sind.

Alternativ können Sie den Befehl wait<file>[<timeout>] von init im Boot-Flow-RC-Script verwenden, um auf ausgewählte sysfs-Einträge zu warten, die anzeigen, dass die Treibermodule die Prüfvorgänge abgeschlossen haben. Ein Beispiel dafür ist das Warten auf das vollständige Laden des Anzeigetreibers im Hintergrund der Wiederherstellung oder auf fastbootd, bevor die Menügrafiken angezeigt werden.

CPU-Taktfrequenz im Bootloader auf einen angemessenen Wert initialisieren

Nicht alle SoCs/Produkte können die CPU aufgrund von thermischen oder Leistungsproblemen während der Boot-Loop-Tests möglicherweise mit der höchsten Taktfrequenz starten. Achten Sie jedoch darauf, dass der Bootloader die Taktfrequenz aller Online-CPUs so hoch wie möglich für ein SoC oder Produkt festlegt. Dies ist sehr wichtig, da bei einem vollständig modularen Kernel die Init-RAMdisk-Dekomprimierung stattfindet, bevor der CPUfreq-Treiber geladen werden kann. Wenn also die CPU vom Bootloader am unteren Ende ihrer Frequenz belassen wird, kann die Ramdisk-Dekomprimierungszeit länger dauern als ein statisch kompilierter Kernel (nach Ausgleich der RAM-Disk-Größenunterschiede), da die CPU-Frequenz bei CPU-intensiver Arbeit (Dekomprimierung) sehr niedrig wäre. Dasselbe gilt für Arbeitsspeicher und Interconnect-Frequenz.

CPU-Frequenz von großen CPUs im Bootloader initialisieren

Bevor der CPUfreq-Treiber geladen wird, kennt der Kernel die CPU-Taktfrequenzen nicht und skaliert die CPU-Planungskapazität nicht auf die aktuelle Taktfrequenz. Der Kernel kann Threads zur großen CPU migrieren, wenn die Auslastung auf der kleinen CPU ausreichend hoch ist.

Achten Sie darauf, dass die großen CPUs bei der Taktfrequenz, mit der der Bootloader sie aktiviert, mindestens so leistungsfähig sind wie die kleinen CPUs. Wenn die große CPU beispielsweise bei gleicher Taktfrequenz doppelt so leistungsfähig ist wie die kleine CPU, der Bootloader aber die Taktfrequenz der kleinen CPU auf 1,5 GHz und die der großen CPU auf 300 MHz festlegt, sinkt die Bootleistung, wenn der Kernel einen Thread auf die große CPU verschiebt. Wenn es in diesem Beispiel sicher ist, die große CPU mit 750 MHz zu starten, sollten Sie dies tun, auch wenn Sie sie nicht explizit verwenden möchten.

Treiber sollten in der ersten Initialisierungsphase keine Firmware laden

Es kann unvermeidliche Fälle geben, in denen die Firmware in der ersten Phase der Initialisierung geladen werden muss. Im Allgemeinen sollten Treiber jedoch keine Firmware in der ersten Phase in der Init-Phase laden, insbesondere im Kontext von Geräteprüfungen. Wenn die Firmware in der ersten Phase der Initialisierung geladen wird, kommt der gesamte Bootvorgang zum Stillstand, wenn die Firmware nicht im RAM-Disk der ersten Phase verfügbar ist. Selbst wenn die Firmware im ersten RAM-Disk vorhanden ist, führt dies zu einer unnötigen Verzögerung.