Bootzeit optimieren

Auf dieser Seite finden Sie Tipps zur Verkürzung der Bootzeit.

Debugsymbole aus Modulen entfernen

Ähnlich wie bei Produktionsgeräten müssen Sie auch die Debugging-Symbole aus den Modulen entfernen. Wenn Sie Debugsymbole aus Modulen entfernen, verkürzt sich die Bootzeit, 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. Wenn Sie es explizit aktivieren möchten, legen Sie in Ihrer gerätespezifischen Konfiguration unter „device/vendor/device“ die Option BOARD_DO_NOT_STRIP_VENDOR_RAMDISK_MODULES fest.

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

Gzip generiert 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-Plattform-Build über BOARD_RAMDISK_USE_LZ4 unterstützt jetzt die LZ4-Ramdisk-Komprimierung. Sie können diese Option in der 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

Bei ARM64 und ARM32 benötigen Funktionsaufrufe, die sich weiter als eine bestimmte Entfernung vom Aufrufort befinden, eine Sprungtabelle (Procedure Linking Table, 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 neu angeordnet werden müssen, werden als Relocation-Einträge mit expliziten Addenden (kurz RELA) im ELF-Format bezeichnet.

Der Linux-Kernel führt beim Zuweisen der PLT einige Optimierungen der Speichergröße durch, z. B. die Optimierung von Cache-Treffern. 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 DT (Device Tree) 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 sinnvoll, 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 viel Hardwareinitialisierung ausführen, können zu Timingproblemen 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, nur das Flag PROBE_PREFER_ASYNCHRONOUS im Treibercode festzulegen. Bei Modulen müssen Sie außerdem module_name.async_probe=1 in die Kernel-Befehlszeile einfügen oder async_probe=1 als Modulparameter übergeben, wenn Sie das Modul mit modprobe oder insmod laden.

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 der CPUfreq-Treiber die Prüfung durchführt, desto früher können Sie die CPU-Taktfrequenz beim Starten 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 Ladereihenfolge von der initcall-Ebene und der Kompilierungs- oder Verknüpfungsreihenfolge 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ühzeitigen Laden des Moduls müssen Sie auch dafür sorgen, dass alle Abhängigkeiten für die Prüfung des CPUfreq-Treibers 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 Phase“, „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 Ersteinrichtung benötigt wird, verschieben Sie es in die zweite Phase der Initialisierung, indem Sie es in die Anbieter- oder vendor_dlkm-Partition verschieben.

Bei der Ersteinrichtung müssen nicht mehrere Geräte geprüft werden, 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. Bewahren Sie eine Kopie dieser Module im RAM-Disk der ersten Stufe und in der Anbieter- oder vendor_dlkm-Partition auf. 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 während der normalen Bootphase in der ersten Phase der Initialisierung. 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.

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

  • 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 sparen.

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 geladen werden. In diesem Abschnitt geht es hauptsächlich um die folgenden Untergruppen:

  • BOARD_VENDOR_RAMDISK_KERNEL_MODULES: Liste der Module, die in das RAM-Disk 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: Liste der Module, die geladen werden, wenn im RAM-Disk „recovery“ oder fastbootd ausgewählt wird.
  • BOARD_VENDOR_KERNEL_MODULES: Liste der Module, die in das Verzeichnis /vendor/lib/modules/ in die Anbieter- oder vendor_dlkm-Partition 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 Phase der Initialisierung 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 der Anbieter- oder vendor_dlkm-Partition beanspruchen, solange das Boot-Modulsatz minimiert ist. Die modules.list-Datei des Anbieters muss eine gefilterte Liste der Module in /vendor/lib/modules enthalten. Durch die gefilterte Liste wird sichergestellt, dass die Bootzeiten nicht durch das erneute Laden der Module beeinträchtigt werden, was ein kostspieliger Vorgang 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-Property 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 zu ermöglichen. 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-Eigenschaftssignal). 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 hierfür ist das Warten, bis der Displaytreiber im Hintergrund der Wiederherstellung oder von fastbootd fertig geladen ist, bevor 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 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 des init-Ramdisks erfolgt, bevor der CPUfreq-Treiber geladen werden kann. Wenn die CPU vom Bootloader auf dem unteren Ende ihrer Taktfrequenz belassen wird, kann die Dekomprimierungszeit des RAM-Disks länger dauern als bei einem statisch kompilierten Kernel (nach Anpassung an die RAM-Disk-Größe), da die CPU-Taktfrequenz bei CPU-intensiven Aufgaben (Dekomprimierung) sehr niedrig ist. 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 dürfen die Firmware nicht in der ersten Phase der Initialisierung 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 der Initialisierung laden, insbesondere nicht im Kontext der Geräteprüfung. 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.