Implementierung von A/B-Updates

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

Implementieren der Boot-Steuerung HAL

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

Sie müssen auch die unten gezeigte Zustandsmaschine implementieren:

Abbildung 1. Bootloader-Zustandsmaschine

Einrichten des Kernels

So implementieren Sie A/B-Systemaktualisierungen:

  1. Cherrypick die folgende Kernel-Patch-Serie (falls erforderlich):
  2. Stellen Sie sicher, dass Kernel-Befehlszeilenargumente 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 verwendet wird, um die Signatur der Verity-Tabelle zu verifizieren (für Details siehe dm-verity ) .
  3. Fügen Sie das .X509-Zertifikat mit dem öffentlichen Schlüssel zum Systemschlüsselbund hinzu:
    1. Kopieren Sie das im .der Format formatierte .X509-Zertifikat in das Stammverzeichnis des kernel Verzeichnisses. Wenn das .X509-Zertifikat als .pem -Datei formatiert ist, verwenden Sie den folgenden openssl -Befehl, um vom .pem in das .der -Format zu konvertieren:
      openssl x509 -in <x509-pem-certificate> -outform der -out <x509-der-certificate>
    2. Erstellen Sie das zImage so, dass es das Zertifikat als Teil des Systemschlüsselbunds enthält. Überprüfen Sie zur Überprüfung den procfs -Eintrag (erfordert die Aktivierung von KEYS_CONFIG_DEBUG_PROC_KEYS ):
      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 Aufnahme des .X509-Zertifikats zeigt das Vorhandensein des öffentlichen Schlüssels im Systemschlüsselbund an (hervorgehobene Markierung kennzeichnet die öffentliche Schlüssel-ID).
    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 setzen

A/B-fähige Bootloader müssen die folgenden Kriterien für Build-Variablen 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 (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 dex2oat -Schritt nach der Installation (aber vor dem Neustart) durchführen.
Für A/B-Ziele dringend empfohlen
  • Definiere TARGET_NO_RECOVERY := true
  • Definiere BOARD_USES_RECOVERY_AS_BOOT := true
  • Definieren Sie nicht BOARD_RECOVERYIMAGE_PARTITION_SIZE
Kann für A/B-Ziel nicht definiert werden
  • BOARD_CACHEIMAGE_PARTITION_SIZE
  • BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE
Optional für Debug-Builds PRODUCT_PACKAGES_DEBUG += update_engine_client

Partitionen (Slots) einstellen

A/B-Geräte benötigen keine Wiederherstellungspartition oder Cache-Partition, da Android diese Partitionen nicht mehr verwendet. Die Datenpartition wird jetzt für das heruntergeladene OTA-Paket verwendet, und der Wiederherstellungs-Image-Code befindet sich auf der Boot-Partition. Alle Partitionen, die A/B-ed sind, sollten wie folgt benannt werden (Slots heißen immer a , b , etc.): boot_a , boot_b , system_a , system_b , vendor_a , vendor_b .

Zwischenspeicher

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 Cache-Partition zu dimensionieren: Wie groß sie sein musste, hing davon ab, welche Updates Sie anwenden wollten. Der schlimmste Fall wäre eine Cache-Partition so groß wie das Systemabbild. Bei A/B-Updates müssen keine Blöcke gespeichert werden (weil Sie immer auf eine Partition schreiben, die derzeit nicht verwendet wird) und beim A/B-Streaming müssen Sie nicht das gesamte OTA-Paket herunterladen, bevor Sie es anwenden.

Wiederherstellung

Die Wiederherstellungs-RAM-Disk ist jetzt in der Datei boot.img enthalten. Beim Wechsel in die Wiederherstellung kann der Bootloader die Option skip_initramfs nicht in die Kernel-Befehlszeile einfügen.

Bei Nicht-A/B-Updates enthält die Wiederherstellungspartition den Code, der zum Anwenden von Updates verwendet wird. A/B-Updates werden von update_engine angewendet, das im normal gestarteten Systemabbild ausgeführt wird. Es gibt immer noch einen Wiederherstellungsmodus, der verwendet wird, um das Zurücksetzen auf die Werkseinstellungen und das Seitenladen von Aktualisierungspaketen zu implementieren (woher der Name „Wiederherstellung“ stammt). Der Code und die Daten für den Wiederherstellungsmodus werden in der regulären Boot-Partition auf einer Ramdisk gespeichert; Um in das Systemabbild zu booten, weist der Bootloader den Kernel an, die Ramdisk zu überspringen (andernfalls bootet das Gerät in den Wiederherstellungsmodus. Der Wiederherstellungsmodus ist klein (und ein Großteil davon befand sich bereits auf der Bootpartition), sodass die Bootpartition nicht vergrößert wird in Größe.

Fstab

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

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

Keine Partition sollte den Namen " vendor " tragen. Stattdessen wird die Partition vendor_a oder vendor_b ausgewählt und am Einhängepunkt /vendor gemountet.

Kernel-Slot-Argumente

Das aktuelle Slot-Suffix sollte entweder über einen bestimmten Device Tree (DT)-Knoten ( /firmware/android/slot_suffix ) oder über die Kernel-Befehlszeile androidboot.slot_suffix oder das bootconfig-Argument übergeben werden.

Standardmäßig flasht Fastboot den aktuellen Steckplatz auf einem A/B-Gerät. Wenn das Update-Paket auch Images für den anderen, nicht aktuellen Steckplatz enthält, flasht Fastboot diese Images ebenfalls. Zu den verfügbaren Optionen gehören:

  • --slot SLOT . Überschreiben Sie das Standardverhalten und fordern Sie Fastboot auf, den Slot zu flashen, der als Argument übergeben wird.
  • --set-active [ SLOT ] . Legen Sie den Steckplatz als aktiv fest. Wenn kein optionales Argument angegeben ist, wird der aktuelle Steckplatz als aktiv festgelegt.
  • fastboot --help . Erhalten Sie Details zu Befehlen.

Wenn der Bootloader Fastboot implementiert, sollte er den Befehl set_active <slot> unterstützen, der den aktuellen aktiven Steckplatz auf den angegebenen Steckplatz setzt (dies muss auch das nicht bootfähige Flag für diesen Steckplatz löschen und die Anzahl der Wiederholungen auf die Standardwerte zurücksetzen). Der Bootloader sollte auch 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 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 darstellt. Derzeit werden zwei Steckplätze unterstützt, daher ist dieser Wert 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 . Anzahl der verbleibenden Versuche, um zu versuchen, den angegebenen Steckplatz zu booten.

Um alle Variablen anzuzeigen, führen fastboot getvar all .

Generieren von OTA-Paketen

Die OTA-Paket-Tools folgen denselben Befehlen wie die Befehle für Nicht-A/B-Geräte. Die Datei target_files.zip muss generiert werden, indem die Build-Variablen für das A/B-Ziel definiert werden. Die OTA-Paket-Tools identifizieren und generieren automatisch Pakete im Format für den A/B-Updater.

Beispiele:

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

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

Wenn beispielsweise ein Partitionspaar bootloader_a und booloader_b enthalten ist ( _a und _b sind die Steckplatz-Suffixe), können Sie diese Partitionen aktualisieren, indem Sie Folgendes in der Produkt- oder Platinenkonfiguration angeben:

AB_OTA_PARTITIONS := \
  boot \
  system \
  bootloader

Alle von update_engine aktualisierten Partitionen dürfen nicht vom Rest des Systems modifiziert werden. Während inkrementeller oder Delta- Aktualisierungen werden die Binärdaten aus dem aktuellen Schlitz verwendet, um die Daten in dem neuen Schlitz zu erzeugen. Jede Änderung kann dazu führen, dass die Überprüfung der neuen Steckplatzdaten während des Aktualisierungsprozesses fehlschlägt und daher die Aktualisierung fehlschlägt.

Nachinstallation konfigurieren

Sie können den Schritt nach der Installation für jede aktualisierte Partition anders konfigurieren, indem Sie einen Satz von Schlüssel-Wert-Paaren verwenden. Um ein Programm auszuführen, das sich unter /system/usr/bin/postinst in einem neuen Image befindet, geben Sie den Pfad relativ zum Stammverzeichnis des Dateisystems in der Systempartition an.

Beispielsweise ist usr/bin/postinst 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 Folgendes zu den Produkt- oder Geräte- .mk Dateien hinzu (falls zutreffend):

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

Kompilieren

Aus Sicherheitsgründen kann system_server keine Just-in-Time-Kompilierung (JIT) verwenden. Das bedeutet, dass Sie mindestens Odex-Dateien für system_server und seine Abhängigkeiten im Voraus kompilieren müssen; alles andere ist optional.

Um Apps im Hintergrund zu kompilieren, müssen Sie Folgendes zur Gerätekonfiguration des Produkts hinzufügen (in der Datei „device.mk“ des Produkts):

  1. Schließen Sie die nativen Komponenten in den Build ein, um sicherzustellen, dass das Kompilierungsskript und die Binärdateien kompiliert und in das Systemabbild 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 voreingestellten Dateien in der ungenutzten zweiten Systempartition finden Sie unter Erststartinstallation von DEX_PREOPT-Dateien .