A/B-Änderungen implementieren

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

Boot-Control-HAL implementieren

A/B-fähige Bootloader müssen die 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 die unten gezeigte Statusmaschine implementieren:

Abbildung 1. Bootloader-Zustandsautomat

Kernel einrichten

So implementieren Sie A/B-Systemupdates:

  1. Wählen Sie die folgenden Kernel-Patch-Serien aus (falls erforderlich):
  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 Überprüfen der Signatur der Verity-Tabelle verwendet wird (weitere Informationen finden Sie unter dm-verity).
  3. Fügen Sie dem System-Keyring das .X509-Zertifikat mit dem öffentlichen Schlüssel hinzu:
    1. Kopieren Sie das im .der-Format formatierte .X509-Zertifikat in das Stammverzeichnis des kernel-Verzeichnisses. 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 das zImage, um das Zertifikat in den Systemschlüsselbund aufzunehmen. Prüfen Sie den Eintrag procfs,um dies zu bestätigen (KEYS_CONFIG_DEBUG_PROC_KEYS muss 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
      Wenn das X .509-Zertifikat erfolgreich eingebunden wurde, ist der öffentliche Schlüssel im Systemschlüsselbund vorhanden (die ID des öffentlichen Schlüssels ist hervorgehoben).
    3. Ersetzen Sie das Leerzeichen durch # und übergeben Sie es als <public-key-id> in der Kernel-Befehlszeile. Übergeben Sie beispielsweise Android:#7e4333f9bba00adfe0ede979e28ed1920492b40f anstelle von <public-key-id>.

Build-Variablen festlegen

A/B-fähige Bootloader müssen die folgenden Build-Variablenkriterien erfüllen:

Muss für A/B-Ziel definiert werden
  • AB_OTA_UPDATER := true
  • AB_OTA_PARTITIONS := \
      boot \
      system \
      vendor
    und andere Partitionen, die über update_engine aktualisiert wurden (Funkmodul, 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 in Kompilieren beschriebenen dex2oat-Schritt nach der Installation, aber vor dem Neustart ausführen.
Dringend für A/B-Zielvorhaben empfohlen
  • TARGET_NO_RECOVERY := true definieren
  • BOARD_USES_RECOVERY_AS_BOOT := true definieren
  • Definieren Sie BOARD_RECOVERYIMAGE_PARTITION_SIZE nicht.
Kann nicht für A/B-Zielvorhaben 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 Recovery-Images befindet sich auf der Boot-Partition. Alle Partitionen, die A/B-getestet werden, sollten so benannt werden (Slots werden immer als a, b usw. bezeichnet): 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 während der Anwendung von Updates vorübergehend zu speichern. Es gab nie eine gute Möglichkeit, die Cache-Partition zu dimensionieren. Wie groß sie sein musste, hing davon ab, welche Updates angewendet werden sollten. 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 in eine Partition geschrieben wird, die derzeit nicht verwendet wird. Bei Streaming-A/B-Updates muss das gesamte OTA-Paket nicht heruntergeladen werden, bevor es angewendet wird.

Recovery

Die Recovery-RAM-Disk ist jetzt in der Datei boot.img enthalten. Beim Aufrufen des Wiederherstellungsmodus darf der Bootloader die Option skip_initramfs nicht in die Kernel-Befehlszeile einfügen.

Bei Updates, die keine A/B-Updates sind, enthält die Wiederherstellungspartition den Code, der zum Anwenden von Updates verwendet wird. A/B-Updates werden von update_engine angewendet, das im regulären gebooteten System-Image ausgeführt wird. Es gibt weiterhin einen Wiederherstellungsmodus, der zum Zurücksetzen auf die Werkseinstellungen und zum Sideloading von Updatepaketen verwendet wird. Daher stammt auch der Name „Wiederherstellung“. Der Code und die Daten für den Wiederherstellungsmodus werden in der regulären Bootpartition in einer RAM-Disk gespeichert. Damit das System-Image gebootet wird, weist der Bootloader den Kernel an, die RAM-Disk zu überspringen. Andernfalls wird das Gerät im Wiederherstellungsmodus gebootet. Der Wiederherstellungsmodus ist klein (und ein Großteil davon war bereits auf der Bootpartition), sodass die Bootpartition nicht größer wird.

Fstab

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

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

Keine Partition sollte den Namen vendor haben. Stattdessen wird die Partition vendor_a oder vendor_b ausgewählt und am Mount-Point /vendor gemountet.

Kernel-Slot-Argumente

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

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

  • --slot SLOT. Überschreibt das Standardverhalten und fordert Fastboot auf, den Slot zu flashen, der als Argument übergeben wird.
  • --set-active [SLOT]. Legen Sie den Slot als aktiv fest. Wenn kein optionales Argument angegeben ist, 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. Dadurch muss auch das Flag „unbootable“ für diesen Slot gelöscht und die Anzahl der Wiederholungsversuche auf die Standardwerte zurückgesetzt werden. Der Bootloader sollte auch die folgenden Variablen unterstützen:

  • has-slot:<partition-base-name-without-suffix>. Gibt „yes“ zurück, falls die angegebene Partition Slots unterstützt, andernfalls „no“.
  • current-slot: Gibt das Slot-Suffix zurück, von dem als Nächstes gebootet wird.
  • slot-count. Gibt eine Ganzzahl zurück, die die Anzahl der verfügbaren Slots angibt. Derzeit werden zwei Slots unterstützt. Der Wert ist also 2.
  • slot-successful:<slot-suffix>. Gibt „yes“ zurück, wenn der angegebene Slot als erfolgreich gebootet markiert wurde, andernfalls „no“.
  • slot-unbootable:<slot-suffix>. Gibt „yes“ zurück, wenn der angegebene Slot als nicht bootfähig markiert ist, andernfalls „no“.
  • slot-retry-count:<slot-suffix>. Anzahl der verbleibenden Wiederholungsversuche, um den angegebenen Slot 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-Partitionen. Die Datei target_files.zip muss generiert werden, indem die Build-Variablen für das A/B-Ziel definiert werden. Die OTA-Pakettools erkennen automatisch Pakete im Format für den A/B-Updater und generieren sie.

Beispiele:

  • So generieren Sie ein vollständiges OTA:
    ./build/make/tools/releasetools/ota_from_target_files \
        dist_output/tardis-target_files.zip \
        ota_update.zip
    
  • So generieren Sie ein inkrementelles OTA:
    ./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

Der update_engine kann jedes Paar von A/B-Partitionen aktualisieren, die auf derselben Festplatte definiert sind. Ein Paar von Partitionen hat ein gemeinsames Präfix (z. B. system oder boot) und ein Suffix pro Slot (z. B. _a). Die Liste der Partitionen, für die der Nutzlastgenerator ein Update definiert, wird durch die Make-Variable AB_OTA_PARTITIONS konfiguriert.

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

AB_OTA_PARTITIONS := \
  boot \
  system \
  bootloader

Alle Partitionen, die von update_engine aktualisiert werden, dürfen nicht vom Rest des Systems geändert werden. Bei inkrementellen oder Delta-Updates werden die Binärdaten aus dem aktuellen Slot verwendet, um die Daten im neuen Slot zu generieren. Bei Änderungen kann es passieren, dass die neuen Slotdaten während des Aktualisierungsvorgangs nicht bestätigt werden und das Update fehlschlägt.

Konfiguration nach der Installation

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

Beispiel: usr/bin/postinst ist system/usr/bin/postinst (wenn keine RAM-Disk verwendet wird). Geben Sie außerdem den Dateisystemtyp an, der an den Systemaufruf mount(2) übergeben werden soll. Fügen Sie den Produkt- oder Geräte-.mk-Dateien (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 im Hintergrund kompiliert werden, bevor das neue System-Image installiert wird. Wenn Sie Apps im Hintergrund kompilieren möchten, fügen Sie der Gerätekonfiguration des Produkts (in device.mk des Produkts) Folgendes hinzu:

  1. Nimm die nativen Komponenten in den Build auf, damit das Kompilierungsskript und die Binärdateien kompiliert und in das Systemimage aufgenommen werden.
      # A/B OTA dexopt package
      PRODUCT_PACKAGES += otapreopt_script
    
  2. Verbinden Sie das Kompilierungsskript mit update_engine, sodass 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
    

Hilfe bei der Installation der voroptimierten Dateien in der ungenutzten zweiten Systempartition finden Sie unter Installation von DEX_PREOPT-Dateien beim ersten Start.