Bootzeitoptimierung

Auf dieser Seite finden Sie eine Reihe von Tipps zur Verbesserung der Startzeit, aus denen Sie auswählen können.

Entfernen Sie Debug-Symbole aus Modulen

Ähnlich wie beim Entfernen von Debugsymbolen aus dem Kernel auf einem Produktionsgerät stellen Sie sicher, dass Sie auch die Debugsymbole aus Modulen entfernen. Das Entfernen von Debug-Symbolen aus Modulen verkürzt die Startzeit, indem Folgendes reduziert wird:

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

Das Entfernen des Debug-Symbols von Modulen kann beim Booten mehrere Sekunden einsparen.

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

Verwenden Sie die LZ4-Komprimierung für Kernel und Ramdisk

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 Dekomprimierungszeitvorteil von LZ4 nicht so bedeutend.

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

Der Wechsel zu LZ4 sollte eine um 500 bis 1000 ms schnellere Startzeit ermöglichen.

Vermeiden Sie übermäßiges Einloggen Ihrer Treiber

In ARM64 und ARM32 benötigen Funktionsaufrufe, die mehr als eine bestimmte Entfernung von der Aufrufstelle entfernt sind, eine Sprungtabelle (Prozedur Linking Table oder PLT genannt), um die vollständige Sprungadresse kodieren zu können. Da Module dynamisch geladen werden, müssen diese Sprungtabellen während des Modulladens korrigiert werden. Die Aufrufe, die verschoben werden müssen, werden als Einträge mit expliziten Addends (oder kurz RELA) im ELF-Format bezeichnet.

Der Linux-Kernel führt bei der Zuweisung des PLT eine gewisse Optimierung der Speichergröße durch (z. B. Cache-Trefferoptimierung). 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 Art zu haben, 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 die übermäßige Protokollierung in einem Treiber. Jeder Aufruf von printk() oder einem anderen Protokollierungsschema fügt normalerweise einen CALL26 / JUMP26 RELA-Eintrag hinzu. Beachten Sie im Commit-Text im Upstream-Commit , dass das Laden der sechs Module selbst mit der Optimierung etwa 250 ms dauert – das liegt daran, dass diese sechs Module die sechs Module mit der höchsten Protokollierung waren.

Durch die Reduzierung der Protokollierung können Sie etwa 100 bis 300 ms an Startzeiten einsparen, je nachdem, wie übermäßig die vorhandene Protokollierung ist.

Aktivieren Sie selektiv die asynchrone Prüfung

Wenn ein Modul geladen wird und das von ihm unterstützte Gerät bereits aus dem DT (Gerätebaum) ausgefüllt und zum Treiberkern hinzugefügt wurde, erfolgt die Geräteprüfung im Kontext des Aufrufs module_init() . Wenn eine Geräteprüfung im Kontext von module_init() durchgeführt wird, kann das Modul erst dann vollständig geladen werden, wenn die Prüfung abgeschlossen ist. Da das Laden von Modulen größtenteils seriell erfolgt, verlangsamt ein Gerät, dessen Prüfung relativ lange dauert, die Startzeit.

Um langsamere Startzeiten zu vermeiden, aktivieren Sie die asynchrone Prüfung für Module, deren Geräte eine Weile prüfen. Die Aktivierung der asynchronen Prüfung für alle Module ist möglicherweise nicht vorteilhaft, da die Zeit, die zum Verzweigen eines Threads und zum Starten der Prüfung benötigt wird, möglicherweise genauso lang ist 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 in ihrer Sondenfunktion Firmware laden, und Geräte, die viele Hardware-Initialisierungen durchführen, können zu Zeitproblemen führen. Der beste Weg, um festzustellen, wann dies geschieht, besteht darin, die Testzeit für jeden Treiber zu erfassen und zu sortieren.

Um die asynchrone Prüfung für ein Modul zu ermöglichen, reicht es nicht aus, nur das Flag PROBE_PREFER_ASYNCHRONOUS im Treibercode zu setzen. Für Module müssen Sie außerdem 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 können je nach Hardware/Treiber etwa 100 bis 500 ms Startzeit eingespart werden.

Testen Sie Ihren CPUfreq-Treiber so früh wie möglich

Je früher Ihr CPUfreq-Treiber prüft, desto eher können Sie die CPU-Frequenz beim Booten auf das Maximum (oder ein thermisch begrenztes Maximum) skalieren. Je schneller die CPU, desto schneller der Bootvorgang. Diese Richtlinie gilt auch für devfreq Treiber, die DRAM, Speicher und Verbindungsfrequenz 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() um sicherzustellen, dass der cpufreq Treiber zu den ersten Modulen gehört, die geladen werden.

Abgesehen vom frühzeitigen 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 eine Uhr oder einen Regler benötigen, um die Frequenz Ihrer CPU zu steuern, stellen Sie sicher, dass diese zuerst geprüft werden. Oder Sie müssen möglicherweise Wärmetreiber vor dem CPUfreq-Treiber laden, wenn es möglich ist, dass Ihre CPUs beim Hochfahren zu heiß werden. Tun Sie also, was Sie können, um sicherzustellen, dass die CPUfreq- und relevanten Devfreq-Treiber so früh wie möglich geprüft werden.

Die Einsparungen durch die frühzeitige Prüfung Ihres 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 belässt.

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

Da der Initialisierungsprozess der ersten Stufe serialisiert ist, gibt es nicht viele Möglichkeiten, den Startvorgang zu parallelisieren. Wenn ein Modul für den Abschluss der Init-Initialisierung der ersten Stufe nicht benötigt wird, verschieben Sie das Modul in die Init-Initialisierung der zweiten Phase, indem Sie es in der Partition „vendor“ oder vendor_dlkm platzieren.

Die Init-Initialisierung der ersten Stufe erfordert nicht die Prüfung mehrerer Geräte, um zur Init-Initialisierung der zweiten Stufe zu gelangen. Für einen normalen Boot-Ablauf sind nur Konsolen- und Flash-Speicherfunktionen erforderlich.

Laden Sie die folgenden wichtigen Treiber:

  • Wachhund
  • zurücksetzen
  • cpufreq

Für die Wiederherstellung und fastbootd Modus des Benutzerbereichs erfordert die erste Init-Stufe die Prüfung weiterer Geräte (z. B. USB) und die Anzeige. Bewahren Sie eine Kopie dieser Module auf der Ramdisk der ersten Stufe und in der Vendor- oder vendor_dlkm Partition auf. Dadurch können sie in der ersten Stufe von Init für die Wiederherstellung oder fastbootd Boot-Flow geladen werden. Laden Sie die Wiederherstellungsmodusmodule jedoch nicht während des normalen Startvorgangs in der ersten Init-Phase. Module im Wiederherstellungsmodus können auf die zweite Init-Stufe verschoben werden, um die Startzeit zu verkürzen. Alle anderen Module, die in der ersten Init-Phase nicht benötigt werden, sollten in die Partition „vendor“ oder vendor_dlkm verschoben werden.

Anhand einer Liste von Blattgeräten (z. B. UFS oder seriell) findet das Skript dev needs.sh alle Treiber, Geräte und Module, die für die Prüfung von Abhängigkeiten oder Lieferanten (z. B. Uhren, Regler oder gpio ) erforderlich sind.

Durch das Verschieben von Modulen auf die zweite Init-Stufe werden die Startzeiten auf folgende Weise verkürzt:

  • Reduzierung der Ramdisk-Größe.
    • Dies führt zu schnelleren Flash-Lesevorgängen, wenn der Bootloader die Ramdisk lädt (serialisierter Boot-Schritt).
    • Dies führt zu schnelleren Dekomprimierungsgeschwindigkeiten, wenn der Kernel die Ramdisk dekomprimiert (serialisierter Startschritt).
  • Init der zweiten Stufe arbeitet parallel, wodurch die Ladezeit des Moduls mit der Arbeit, die in Init der zweiten Stufe erledigt wird, ausgeblendet wird.

Durch das Verschieben von Modulen auf die zweite Stufe können 500–1000 ms Bootzeit eingespart werden, je nachdem, wie viele Module Sie auf die zweite Init-Stufe verschieben können.

Modulverladelogistik

Der neueste Android-Build verfügt über Board-Konfigurationen, die steuern, welche Module auf jede Stufe kopiert und welche Module geladen werden. Dieser Abschnitt konzentriert sich auf die folgende Teilmenge:

  • BOARD_VENDOR_RAMDISK_KERNEL_MODULES . Diese Liste der Module, die in die Ramdisk kopiert werden sollen.
  • BOARD_VENDOR_RAMDISK_KERNEL_MODULES_LOAD . Diese Liste der Module, die in der ersten Stufe der Init geladen werden sollen.
  • BOARD_VENDOR_RAMDISK_RECOVERY_KERNEL_MODULES_LOAD . Diese Liste der Module, die geladen werden sollen, wenn Recovery oder fastbootd von der Ramdisk ausgewählt wird.
  • BOARD_VENDOR_KERNEL_MODULES . Diese Liste der Module, die in die Vendor- oder vendor_dlkm Partition im Verzeichnis /vendor/lib/modules/ kopiert werden sollen.
  • BOARD_VENDOR_KERNEL_MODULES_LOAD . Diese Liste der Module, die in der zweiten Stufe der Init geladen werden sollen.

Die Boot- und Wiederherstellungsmodule in der Ramdisk müssen auch in die Vendor- oder vendor_dlkm Partition unter /vendor/lib/modules kopiert werden. Durch das Kopieren dieser Module in die Herstellerpartition wird sichergestellt, dass die Module während der Init-Phase der zweiten Stufe nicht unsichtbar sind, was zum Debuggen und Sammeln von modinfo für Fehlerberichte nützlich ist.

Die Duplizierung sollte minimalen Speicherplatz auf der Vendor- oder vendor_dlkm Partition beanspruchen, solange der Boot-Modulsatz minimiert ist. Stellen Sie sicher, dass die Datei modules.list des Anbieters eine gefilterte Liste von Modulen in /vendor/lib/modules enthält. Die gefilterte Liste stellt sicher, dass die Startzeiten nicht durch das erneute Laden der Module beeinträchtigt werden (was ein teurer Prozess ist).

Stellen Sie sicher, dass die Module im Wiederherstellungsmodus als Gruppe geladen werden. Das Laden von Wiederherstellungsmodusmodulen kann entweder im Wiederherstellungsmodus oder zu Beginn der zweiten Init-Stufe in jedem Startvorgang 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)))

Dieses Beispiel zeigt eine einfacher zu verwaltende Teilmenge von BOOT_KERNEL_MODULES und RECOVERY_KERNEL_MODULES , die lokal in den Board-Konfigurationsdateien angegeben werden kann. Das vorhergehende Skript findet und füllt jedes der Teilmengenmodule aus den ausgewählten verfügbaren Kernelmodulen und lässt die Restmodule für die Init-Phase der zweiten Stufe übrig.

Für die Init-Initialisierung der zweiten Stufe empfehlen wir, das Laden des Moduls als Dienst auszuführen, damit der Boot-Flow nicht blockiert wird. Verwenden Sie ein Shell-Skript, um das Laden des Moduls zu verwalten, sodass andere logistische Maßnahmen wie Fehlerbehandlung und -minderung oder der Abschluss des Modulladens bei Bedarf zurückgemeldet (oder ignoriert) werden können.

Sie können einen Fehler beim Laden eines Debug-Moduls ignorieren, der bei Benutzer-Builds nicht vorhanden ist. Um diesen Fehler zu ignorieren, legen Sie die Eigenschaft vendor.device.modules.ready fest, um spätere Phasen des init rc -Scripting-Bootflows auszulösen und auf dem Startbildschirm fortzufahren. Verweisen Sie auf das folgende Beispielskript, wenn Sie den folgenden Code in /vendor/etc/init.insmod.sh 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 könnte der one shot Dienst wie folgt 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 Übergang der Module von der ersten zur zweiten Stufe können weitere Optimierungen vorgenommen werden. Sie können die Modprobe-Blocklist-Funktion verwenden, um den Boot-Ablauf der zweiten Stufe aufzuteilen, um das verzögerte Laden von Modulen nicht unbedingt erforderlicher Module einzubeziehen. Das Laden von Modulen, die ausschließlich von einer bestimmten HAL verwendet werden, kann verzögert werden, um die Module erst dann zu laden, wenn die HAL gestartet wird.

Um die scheinbaren Startzeiten zu verbessern, können Sie im Modulladedienst gezielt Module auswählen, die das Laden nach dem Startbildschirm erleichtern. Sie können beispielsweise die Module für Videodecoder oder WLAN explizit spät laden, nachdem der Init-Boot-Flow gelöscht wurde (z. B. sys.boot_complete Android-Eigenschaftssignal). Stellen Sie sicher, dass die HALs für die spät ladenden Module lange genug blockieren, wenn die Kernel-Treiber nicht vorhanden sind.

Alternativ können Sie den Befehl wait<file>[<timeout>] von init im Boot-Flow-RC-Skript verwenden, um auf ausgewählte sysfs Einträge zu warten, um anzuzeigen, dass die Treibermodule die Testvorgänge abgeschlossen haben. Ein Beispiel hierfür ist das Warten darauf, dass der Bildschirmtreiber im Hintergrund von Recovery oder fastbootd geladen wird, bevor Menügrafiken angezeigt werden.

Initialisieren Sie die CPU-Frequenz im Bootloader auf einen angemessenen Wert

Möglicherweise sind nicht alle SoCs/Produkte in der Lage, die CPU mit der höchsten Frequenz zu starten, da es während der Boot-Loop-Tests zu thermischen oder strombezogenen Problemen kommt. Stellen Sie jedoch sicher, dass der Bootloader die Frequenz aller Online-CPUs so hoch einstellt, wie es für ein SoC/Produkt sicher möglich ist. Dies ist sehr wichtig, da bei einem vollständig modularen Kernel die Init-Ramdisk-Dekomprimierung stattfindet, bevor der CPUfreq-Treiber geladen werden kann. Wenn die CPU also vom Bootloader am unteren Ende ihrer Frequenz belassen wird, kann die Ramdisk-Dekomprimierungszeit länger dauern als bei einem statisch kompilierten Kernel (nach Anpassung an die Ramdisk-Größendifferenz), da die CPU-Frequenz bei CPU-intensiver Ausführung sehr niedrig wäre Arbeit (Dekompression). Das Gleiche gilt für die Speicher-/Verbindungsfrequenz.

Initialisieren Sie die CPU-Frequenz großer CPUs im Bootloader

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

Stellen Sie sicher, dass die großen CPUs bei der Frequenz, mit der der Bootloader sie einschaltet, 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 jedoch die... Wenn Sie die Frequenz der kleinen CPU auf 1,5 GHz und die Frequenz der großen CPU auf 300 MHz erhöhen, sinkt die Startleistung, 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 booten, sollten Sie dies tun, auch wenn Sie nicht vorhaben, sie explizit zu verwenden.

Treiber sollten in der ersten Initialisierungsphase keine Firmware laden

Es kann unvermeidbare Fälle geben, in denen Firmware in der ersten Init-Stufe geladen werden muss. Aber im Allgemeinen sollten Treiber in der ersten Init-Phase keine Firmware laden, insbesondere im Device-Probe-Kontext. Das Laden der Firmware in der ersten Init-Stufe führt dazu, dass der gesamte Startvorgang blockiert, wenn die Firmware nicht in der Ramdisk der ersten Stufe verfügbar ist. Und selbst wenn die Firmware in der Ramdisk der ersten Stufe vorhanden ist, verursacht sie dennoch eine unnötige Verzögerung.