A/B-Aktualisierungen implementieren

OEMs und SoC-Anbieter, die A/B-Systemupdates implementieren möchten, müssen dafür sorgen, dass ihr Bootloader die HAL „boot_control“ implementiert und die richtigen Parameter an den Kernel übergibt.

HAL für die Bootsteuerung implementieren

A/B-fähige Bootloader müssen das boot_control HAL unter hardware/libhardware/include/hardware/boot_control.h implementieren. Sie können Implementierungen mit dem Dienstprogramm system/extras/bootctl und system/extras/tests/bootloader/ testen.

Außerdem müssen Sie den unten dargestellten Zustandsautomaten implementieren:

Abbildung 1. Bootloader-Zustandsmaschine

Kernel einrichten

So implementieren Sie A/B-Systemupdates:

  1. Wählen Sie bei Bedarf die folgenden Kernel-Patchreihen aus:
  2. Die Kernel-Befehlszeilenargumente müssen die folgenden zusätzlichen Argumente enthalten:
    skip_initramfs rootwait ro init=/init root="/dev/dm-0 dm=system none ro,0 1 android-verity <public-key-id> <path-to-system-partition>"
    wobei der Wert <public-key-id> die ID des öffentlichen Schlüssels ist, der zum Verifizieren der Signatur der Verity-Tabelle verwendet wird. Weitere Informationen finden Sie unter dm-verity.
  3. Fügen Sie das X509-Zertifikat mit dem öffentlichen Schlüssel dem System-Schlüsselbund hinzu:
    1. Kopieren Sie das im .der-Format formatierte X509-Zertifikat in das Stammverzeichnis des Verzeichnisses kernel. Wenn das X .509-Zertifikat als .pem-Datei formatiert ist, verwenden Sie den folgenden openssl-Befehl, um es von .pem in das .der-Format zu konvertieren:
      openssl x509 -in <x509-pem-certificate> -outform der -out <x509-der-certificate>
    2. Erstellen Sie zImage so, dass das Zertifikat Teil des Systemschlüsselbunds ist. Prüfen Sie den Eintrag procfs. Dazu muss KEYS_CONFIG_DEBUG_PROC_KEYS aktiviert sein:
      angler:/# cat /proc/keys
      
      1c8a217e I------     1 perm 1f010000     0     0 asymmetri
      Android: 7e4333f9bba00adfe0ede979e28ed1920492b40f: X509.RSA 0492b40f []
      2d454e3e I------     1 perm 1f030000     0     0 keyring
      .system_keyring: 1/4
      Die erfolgreiche Einbindung des X509-Zertifikats weist auf das Vorhandensein des öffentlichen Schlüssels im Systemschlüsselbund hin (die Hervorhebung kennzeichnet die öffentliche Schlüssel-ID).
    3. Ersetzen Sie das Leerzeichen durch # und geben Sie es als <public-key-id> in der Kernel-Befehlszeile ein. Geben Sie beispielsweise Android:#7e4333f9bba00adfe0ede979e28ed1920492b40f anstelle von <public-key-id> an.

Build-Variablen festlegen

A/B-fähige Bootloader müssen die folgenden Kriterien für Buildvariablen erfüllen:

Muss für A/B-Ziel definiert sein
  • AB_OTA_UPDATER := true
  • AB_OTA_PARTITIONS := \
      boot \
      system \
      vendor
    und andere Partitionen, die über update_engine aktualisiert wurden (Radio, Bootloader usw.)
  • PRODUCT_PACKAGES += \
      update_engine \
      update_verifier
Ein Beispiel finden Sie unter /device/google/marlin/+/android-7.1.0_r1/device-common.mk. Optional können Sie den unter Kompilieren beschriebenen Schritt „dex2oat“ nach der Installation, aber vor dem Neustart ausführen.
Dringend empfohlen für A/B-Ziel
  • TARGET_NO_RECOVERY := true definieren
  • BOARD_USES_RECOVERY_AS_BOOT := true definieren
  • BOARD_RECOVERYIMAGE_PARTITION_SIZE nicht definieren
Kann nicht für A/B-Ziel definiert werden
  • BOARD_CACHEIMAGE_PARTITION_SIZE
  • BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE
Optional für Debug-Builds PRODUCT_PACKAGES_DEBUG += update_engine_client

Partitionen (Slots) festlegen

A/B-Geräte benötigen keine Wiederherstellungs- oder Cache-Partition, da Android diese Partitionen nicht mehr verwendet. Die Datenpartition wird jetzt für das heruntergeladene OTA-Paket verwendet und der Code des Wiederherstellungs-Images befindet sich auf der Boot-Partition. Alle A/B-Partitionen müssen folgendermaßen benannt werden (Slots haben immer die Namen a, b usw.): boot_a, boot_b, system_a, system_b, vendor_a, vendor_b.

Cache

Bei nicht A/B-Updates wurde die Cache-Partition verwendet, um heruntergeladene OTA-Pakete zu speichern und Blöcke vorübergehend zu speichern, während Updates angewendet wurden. Es gab nie eine gute Möglichkeit, die Größe der Cache-Partition festzulegen: Wie groß sie sein musste, hing davon ab, welche Updates Sie anwenden wollten. Im schlimmsten Fall ist die Cache-Partition so groß wie das System-Image. Bei A/B-Updates müssen keine Blöcke zwischengespeichert werden, da immer auf eine Partition geschrieben wird, die derzeit nicht verwendet wird. Beim Streaming von A/B-Updates muss nicht das gesamte OTA-Paket heruntergeladen werden, bevor es angewendet werden kann.

Recovery

Die Wiederherstellungs-RAM-Disk ist jetzt in der Datei boot.img enthalten. Wenn die Wiederherstellung gestartet wird, kann der Bootloader die Option skip_initramfs nicht in die Kernel-Befehlszeile aufnehmen.

Bei nicht A/B-Updates enthält die Wiederherstellungspartition den Code, mit dem Updates angewendet werden. A/B-Updates werden durch update_engine angewendet, das im regulär gestarteten System-Image ausgeführt wird. Es gibt weiterhin einen Wiederherstellungsmodus, mit dem das Zurücksetzen auf die Werkseinstellungen und das Sideloading von Update-Paketen implementiert wird (daher der Name „Wiederherstellung“). Der Code und die Daten für den Wiederherstellungsmodus werden in der regulären Bootpartition in einem RAM-Disk gespeichert. Um das System-Image zu starten, weist der Bootloader den Kernel an, das RAM-Disk zu überspringen. Andernfalls wird das Gerät im Wiederherstellungsmodus gestartet. Der Wiederherstellungsmodus ist klein und ein Großteil davon befand sich bereits auf der Bootpartition. Die Größe der Bootpartition ändert sich daher nicht.

Fstab

Das Argument slotselect muss in der Zeile für die A/B-Test-Partitionen stehen. Beispiel:

<path-to-block-device>/vendor  /vendor  ext4  ro
wait,verify=<path-to-block-device>/metadata,slotselect

Keine Partition darf den Namen vendor haben. Stattdessen wird die Partition vendor_a oder vendor_b ausgewählt und am Bereitstellungspunkt /vendor bereitgestellt.

Kernel-Slot-Argumente

Das aktuelle Slotsuffix sollte entweder über einen bestimmten Gerätebaumknoten (/firmware/android/slot_suffix) oder über die Kernel-Befehlszeile oder das Bootconfig-Argument androidboot.slot_suffix übergeben werden.

Standardmäßig wird mit Fastboot der aktuelle Slot auf einem A/B-Gerät geflasht. Wenn das Update-Paket auch Images für den anderen, nicht aktuellen Steckplatz enthält, werden diese Images ebenfalls mit Fastboot geflasht. Folgende Optionen sind verfügbar:

  • --slot SLOT. Das Standardverhalten überschreiben und Fastboot auffordern, den als Argument übergebenen Steckplatz zu flashen.
  • --set-active [SLOT]. Legen Sie den Steckplatz als „aktiv“ fest. Wenn kein optionales Argument angegeben wird, wird der aktuelle Slot als aktiv festgelegt.
  • fastboot --help. Details zu Befehlen abrufen

Wenn der Bootloader Fastboot implementiert, sollte er den Befehl set_active <slot> unterstützen, mit dem der aktuelle aktive Slot auf den angegebenen Slot festgelegt wird. Dabei muss auch das Flag „Nicht bootbar“ für diesen Slot gelöscht und die Anzahl der Wiederholungen auf die Standardwerte zurückgesetzt werden. Der Bootloader sollte außerdem die folgenden Variablen unterstützen:

  • has-slot:<partition-base-name-without-suffix>. Gibt „yes“ zurück, wenn die angegebene Partition Slots unterstützt, andernfalls „no“.
  • current-slot: Gibt das Suffix des Slots zurück, von dem aus als Nächstes gestartet wird.
  • slot-count. Gibt eine Ganzzahl zurück, die die Anzahl der verfügbaren Slots angibt. Derzeit werden zwei Slots unterstützt. Daher ist dieser Wert 2.
  • slot-successful:<slot-suffix>. Gibt „yes“ zurück, wenn der angegebene Steckplatz als erfolgreich gestartet markiert wurde, andernfalls „no“.
  • slot-unbootable:<slot-suffix>. Gibt „yes“ zurück, wenn der angegebene Steckplatz als nicht bootfähig gekennzeichnet ist, andernfalls „no“.
  • slot-retry-count:<slot-suffix>. Anzahl der verbleibenden Wiederholungen, um den angegebenen Steckplatz zu starten.

Führen Sie fastboot getvar all aus, um alle Variablen aufzurufen.

OTA-Pakete generieren

Für die OTA-Pakettools gelten dieselben Befehle wie für Geräte ohne A/B-Partition. Die target_files.zip-Datei muss generiert werden, indem die Build-Variablen für das A/B-Ziel definiert werden. Die OTA-Pakettools erkennen und generieren automatisch Pakete im Format für den A/B-Updater.

Beispiele:

  • So generieren Sie eine vollständige OTA-Datei:
    ./build/make/tools/releasetools/ota_from_target_files \
        dist_output/tardis-target_files.zip \
        ota_update.zip
    
  • So generieren Sie eine inkrementelle OTA-Datei:
    ./build/make/tools/releasetools/ota_from_target_files \
        -i PREVIOUS-tardis-target_files.zip \
        dist_output/tardis-target_files.zip \
        incremental_ota_update.zip
    

Partitionen konfigurieren

Mit dem update_engine können alle A/B-Partitionspaare aktualisiert werden, die auf demselben Laufwerk definiert sind. Ein Partitionspaar hat ein gemeinsames Präfix (z. B. system oder boot) und ein Slot-spezifisches Suffix (z. B. _a). Die Liste der Partitionen, für die der Nutzlastgenerator ein Update definiert, wird über die Variable AB_OTA_PARTITIONS festgelegt.

Wenn beispielsweise die Partitionen bootloader_a und booloader_b enthalten sind (_a und _b sind die Suffixe des Slots), können Sie diese Partitionen aktualisieren, indem Sie in der Produkt- oder Boardskonfiguration Folgendes angeben:

AB_OTA_PARTITIONS := \
  boot \
  system \
  bootloader

Alle von update_engine aktualisierten Partitionen dürfen vom Rest des Systems nicht geändert werden. Bei inkrementellen oder Delta-Aktualisierungen werden die Binärdaten aus dem aktuellen Steckplatz verwendet, um die Daten im neuen Steckplatz zu generieren. Jede Änderung kann dazu führen, dass die neuen Slot-Daten während des Aktualisierungsvorgangs nicht bestätigt werden und das Update daher fehlschlägt.

Nach der Installation konfigurieren

Sie können den Schritt nach der Installation mithilfe von Schlüssel/Wert-Paaren für jede aktualisierte Partition unterschiedlich konfigurieren. Wenn Sie ein Programm unter /system/usr/bin/postinst in einem neuen Image ausführen möchten, geben Sie den Pfad relativ zum Stammverzeichnis des Dateisystems in der Systempartition an.

Beispiel: usr/bin/postinst ist system/usr/bin/postinst (ohne RAM-Disk) Geben Sie außerdem den Dateisystemtyp an, der an den Systemaufruf mount(2) übergeben werden soll. Fügen Sie den .mk-Dateien des Produkts oder Geräts (falls zutreffend) Folgendes hinzu:

AB_OTA_POSTINSTALL_CONFIG += \
  RUN_POSTINSTALL_system=true \
  POSTINSTALL_PATH_system=usr/bin/postinst \
  FILESYSTEM_TYPE_system=ext4

Apps kompilieren

Apps können vor dem Neustart im Hintergrund mit dem neuen System-Image kompiliert werden. Wenn Sie Apps im Hintergrund kompilieren möchten, fügen Sie der Gerätekonfiguration des Produkts (in der device.mk des Produkts) Folgendes hinzu:

  1. Fügen Sie die nativen Komponenten in den Build ein, damit das Kompilierungsskript und die Binärdateien kompiliert und in das Systemimage aufgenommen werden.
      # A/B OTA dexopt package
      PRODUCT_PACKAGES += otapreopt_script
    
  2. Verknüpfen Sie das Kompilierungsskript mit update_engine, damit es als Schritt nach der Installation ausgeführt wird.
      # A/B OTA dexopt update_engine hookup
      AB_OTA_POSTINSTALL_CONFIG += \
        RUN_POSTINSTALL_system=true \
        POSTINSTALL_PATH_system=system/bin/otapreopt_script \
        FILESYSTEM_TYPE_system=ext4 \
        POSTINSTALL_OPTIONAL_system=true
    

Informationen zum Installieren der optimierten Dateien in der nicht verwendeten zweiten Systempartition finden Sie unter Installation von DEX_PREOPT-Dateien beim ersten Start.