A/B (nahtlose) Systemaktualisierungen

A/B-Systemaktualisierungen, auch als nahtlose Aktualisierungen bekannt, stellen sicher, dass während einer Over-the-Air-Aktualisierung (OTA) ein funktionsfähiges Bootsystem auf der Festplatte verbleibt. Dieser Ansatz verringert die Wahrscheinlichkeit eines inaktiven Geräts nach einem Update, was weniger Geräteaustausch und Geräte-Reflashes in Reparatur- und Garantiezentren bedeutet. Andere kommerzielle Betriebssysteme wie ChromeOS verwenden ebenfalls erfolgreich A/B-Updates.

Weitere Informationen zu A/B-Systemaktualisierungen und ihrer Funktionsweise finden Sie unter Partitionsauswahl (Slots) .

A/B-Systemaktualisierungen bieten die folgenden Vorteile:

  • OTA-Updates können während des Betriebs des Systems erfolgen, ohne den Benutzer zu unterbrechen. Benutzer können ihre Geräte während eines OTA weiter verwenden – die einzige Ausfallzeit während eines Updates ist, wenn das Gerät in der aktualisierten Festplattenpartition neu gestartet wird.
  • Nach einem Update dauert der Neustart nicht länger als ein normaler Neustart.
  • Wenn ein OTA nicht angewendet wird (z. B. aufgrund eines schlechten Blitzes), ist der Benutzer nicht betroffen. Der Benutzer führt weiterhin das alte Betriebssystem aus, und der Client kann die Aktualisierung erneut versuchen.
  • Wenn ein OTA-Update angewendet wird, aber nicht gestartet werden kann, wird das Gerät in der alten Partition neu gestartet und bleibt verwendbar. Dem Client steht es frei, die Aktualisierung erneut zu versuchen.
  • Alle Fehler (z. B. E/A-Fehler) wirken sich nur auf den nicht verwendeten Partitionssatz aus und können wiederholt werden. Solche Fehler werden auch weniger wahrscheinlich, da die E/A-Last absichtlich niedrig ist, um eine Beeinträchtigung der Benutzererfahrung zu vermeiden.
  • Updates können auf A/B-Geräte gestreamt werden, sodass das Paket vor der Installation nicht heruntergeladen werden muss. Streaming bedeutet, dass der Benutzer nicht über genügend freien Speicherplatz verfügen muss, um das Aktualisierungspaket unter /data oder /cache zu speichern.
  • Die Cache-Partition wird nicht mehr zum Speichern von OTA-Update-Paketen verwendet, daher muss nicht sichergestellt werden, dass die Cache-Partition groß genug für zukünftige Updates ist.
  • dm-verity garantiert, dass ein Gerät ein unbeschädigtes Image startet. Wenn ein Gerät aufgrund eines fehlerhaften OTA- oder dm-verity-Problems nicht startet, kann das Gerät in einem alten Image neu gestartet werden. (Android Verified Boot erfordert keine A/B-Updates.)

Über A/B-Systemaktualisierungen

A/B-Updates erfordern Änderungen sowohl am Client als auch am System. Der OTA-Paketserver sollte jedoch keine Änderungen erfordern: Aktualisierungspakete werden weiterhin über HTTPS bereitgestellt. Bei Geräten, die die OTA-Infrastruktur von Google verwenden, erfolgen alle Systemänderungen in AOSP, und der Client-Code wird von Google Play-Diensten bereitgestellt. OEMs, die die OTA-Infrastruktur von Google nicht verwenden, können den AOSP-Systemcode wiederverwenden, müssen aber ihren eigenen Client bereitstellen.

Für OEMs, die ihren eigenen Kunden beliefern, muss der Kunde:

  • Entscheiden Sie, wann Sie ein Update durchführen möchten. Da A/B-Updates im Hintergrund stattfinden, werden sie nicht mehr vom Benutzer initiiert. Um eine Unterbrechung der Benutzer zu vermeiden, wird empfohlen, Updates zu planen, wenn sich das Gerät im Wartungsmodus im Leerlauf befindet, z. B. über Nacht, und wenn es über WLAN läuft. Ihr Client kann jedoch jede gewünschte Heuristik verwenden.
  • Melden Sie sich bei Ihren OTA-Paketservern und stellen Sie fest, ob ein Update verfügbar ist. Dies sollte größtenteils mit Ihrem vorhandenen Client-Code übereinstimmen, außer dass Sie signalisieren möchten, dass das Gerät A/B unterstützt. (Der Client von Google enthält auch eine Schaltfläche Jetzt prüfen, mit der Benutzer nach dem neuesten Update suchen können.)
  • Rufen Sie update_engine mit der HTTPS-URL für Ihr Updatepaket auf, sofern eines verfügbar ist. update_engine aktualisiert die Rohblöcke auf der derzeit nicht verwendeten Partition, während sie das Aktualisierungspaket streamt.
  • Melden Sie Installationserfolge oder -fehler basierend auf dem Ergebniscode von update_engine an Ihre Server. Wenn das Update erfolgreich angewendet wird, weist update_engine den Bootloader an, beim nächsten Neustart in das neue Betriebssystem zu booten. Der Bootloader greift auf das alte Betriebssystem zurück, wenn das neue Betriebssystem nicht booten kann, sodass vom Client keine Arbeit erforderlich ist. Wenn die Aktualisierung fehlschlägt, muss der Client anhand des detaillierten Fehlercodes entscheiden, wann (und ob) er es erneut versucht. Ein guter Kunde könnte beispielsweise erkennen, dass ein teilweises („diff“) OTA-Paket fehlschlägt, und stattdessen ein vollständiges OTA-Paket ausprobieren.

Optional kann der Client:

  • Zeigen Sie eine Benachrichtigung an, die den Benutzer zum Neustart auffordert. Wenn Sie eine Richtlinie implementieren möchten, bei der der Benutzer aufgefordert wird, routinemäßig zu aktualisieren, kann diese Benachrichtigung zu Ihrem Client hinzugefügt werden. Wenn der Client die Benutzer nicht auffordert, erhalten die Benutzer das Update trotzdem beim nächsten Neustart. (Der Client von Google hat eine pro Update konfigurierbare Verzögerung.)
  • Zeigen Sie eine Benachrichtigung an, die Benutzern mitteilt, ob sie in eine neue Betriebssystemversion gebootet haben oder ob dies erwartet wurde, aber auf die alte Betriebssystemversion zurückgegriffen wurde. (Der Client von Google tut normalerweise beides nicht.)

Auf der Systemseite wirken sich A/B-Systemaktualisierungen auf Folgendes aus:

  • Partitionsauswahl (Slots), der update_engine Daemon und Bootloader-Interaktionen (unten beschrieben)
  • Build-Prozess und Generierung von OTA-Aktualisierungspaketen (beschrieben in Implementieren von A/B-Aktualisierungen )

Partitionsauswahl (Slots)

A/B-Systemaktualisierungen verwenden zwei Gruppen von Partitionen, die als Steckplätze bezeichnet werden (normalerweise Steckplatz A und Steckplatz B). Das System läuft vom aktuellen Steckplatz, während das laufende System während des normalen Betriebs nicht auf die Partitionen im ungenutzten Steckplatz zugreift. Dieser Ansatz macht Updates fehlerresistent, indem der ungenutzte Steckplatz als Fallback beibehalten wird: Wenn während oder unmittelbar nach einem Update ein Fehler auftritt, kann das System auf den alten Steckplatz zurückkehren und weiterhin ein funktionierendes System haben. Um dieses Ziel zu erreichen, sollte keine vom aktuellen Steckplatz verwendete Partition im Rahmen des OTA-Updates aktualisiert werden (einschließlich Partitionen, für die es nur eine Kopie gibt).

Jeder Steckplatz hat ein bootfähiges Attribut, das angibt, ob der Steckplatz ein korrektes System enthält, von dem das Gerät booten kann. Der aktuelle Steckplatz ist bootfähig, wenn das System läuft, aber der andere Steckplatz kann eine alte (immer noch korrekte) Version des Systems, eine neuere Version oder ungültige Daten enthalten. Unabhängig davon, was der aktuelle Steckplatz ist, gibt es einen Steckplatz, der der aktive Steckplatz (der, von dem der Bootloader beim nächsten Start bootet) oder der bevorzugte Steckplatz ist.

Jeder Steckplatz hat auch ein erfolgreiches Attribut, das vom Benutzerbereich gesetzt wird, das nur relevant ist, wenn der Steckplatz auch bootfähig ist. Ein erfolgreicher Steckplatz sollte in der Lage sein, sich selbst zu booten, auszuführen und zu aktualisieren. Ein bootfähiger Steckplatz, der nicht als erfolgreich markiert wurde (nachdem mehrere Versuche unternommen wurden, von ihm zu booten), sollte vom Bootloader als nicht bootfähig markiert werden, einschließlich des Wechselns des aktiven Steckplatzes zu einem anderen bootfähigen Steckplatz (normalerweise zu dem Steckplatz, der unmittelbar vor dem Startversuch ausgeführt wurde). in das neue, aktive). Die spezifischen Details der Schnittstelle werden in boot_control.h definiert.

Engine-Daemon aktualisieren

A/B-Systemaktualisierungen verwenden einen Hintergrund-Daemon namens update_engine , um das System darauf vorzubereiten, in eine neue, aktualisierte Version zu booten. Dieser Daemon kann die folgenden Aktionen ausführen:

  • Lesen Sie aus den aktuellen Slot-A/B-Partitionen und schreiben Sie alle Daten in die unbenutzten Slot-A/B-Partitionen, wie vom OTA-Paket angewiesen.
  • Rufen Sie die Schnittstelle boot_control in einem vordefinierten Workflow auf.
  • Führen Sie ein Nachinstallationsprogramm von der neuen Partition aus, nachdem Sie alle nicht verwendeten Steckplatzpartitionen geschrieben haben, wie vom OTA-Paket angewiesen. (Einzelheiten finden Sie unter Nach der Installation ).

Da der update_engine Daemon nicht am Boot-Prozess selbst beteiligt ist, ist er in seinen Möglichkeiten während eines Updates durch die SELinux -Richtlinien und -Funktionen im aktuellen Steckplatz eingeschränkt (solche Richtlinien und Funktionen können nicht aktualisiert werden, bis das System in eine neue Version). Um ein robustes System aufrechtzuerhalten, sollte der Aktualisierungsprozess nicht die Partitionstabelle, den Inhalt von Partitionen im aktuellen Steckplatz oder den Inhalt von Nicht-A/B-Partitionen ändern, die nicht durch Zurücksetzen auf die Werkseinstellungen gelöscht werden können.

Engine-Quelle aktualisieren

Die Quelle update_engine befindet sich in system/update_engine . Die A/B-OTA-Dexopt-Dateien werden zwischen installd und einem Paketmanager aufgeteilt:

Ein funktionierendes Beispiel finden Sie unter /device/google/marlin/device-common.mk .

Aktualisieren Sie die Engine-Protokolle

Für Android 8.x-Releases und früher sind die update_engine Protokolle in logcat und im Fehlerbericht zu finden. Um die update_engine Protokolle im Dateisystem verfügbar zu machen, patchen Sie die folgenden Änderungen in Ihren Build:

Diese Änderungen speichern eine Kopie des neuesten update_engine Protokolls in /data/misc/update_engine_log/update_engine. YEAR - TIME . Zusätzlich zum aktuellen Log werden die fünf neuesten Logs unter /data/misc/update_engine_log/ . Benutzer mit der Protokollgruppen -ID können auf die Dateisystemprotokolle zugreifen.

Bootloader-Interaktionen

Die HAL boot_control wird von update_engine (und möglicherweise anderen Daemons) verwendet, um den Bootloader anzuweisen, wovon er booten soll. Zu den gängigen Beispielszenarien und den zugehörigen Zuständen gehören die folgenden:

  • Normalfall : Das System läuft von seinem aktuellen Steckplatz, entweder Steckplatz A oder B. Bisher wurden keine Updates angewendet. Der aktuelle Steckplatz des Systems ist startfähig, erfolgreich und der aktive Steckplatz.
  • Aktualisierung läuft: Das System wird von Steckplatz B ausgeführt, daher ist Steckplatz B der bootfähige, erfolgreiche und aktive Steckplatz. Steckplatz A wurde als nicht bootfähig markiert, da der Inhalt von Steckplatz A aktualisiert, aber noch nicht abgeschlossen wird. Bei einem Neustart in diesem Zustand sollte das Booten von Steckplatz B fortgesetzt werden.
  • Update angewendet, Neustart ausstehend : Das System läuft von Slot B, Slot B ist bootfähig und erfolgreich, aber Slot A wurde als aktiv markiert (und ist daher als bootfähig markiert). Steckplatz A ist noch nicht als erfolgreich markiert und der Bootloader sollte einige Versuche unternehmen, von Steckplatz A zu booten.
  • System in neuem Update neu gestartet : Das System läuft zum ersten Mal von Steckplatz A, Steckplatz B ist immer noch bootfähig und erfolgreich, während Steckplatz A nur bootfähig und immer noch aktiv, aber nicht erfolgreich ist. Ein Userspace-Daemon, update_verifier , sollte Steckplatz A als erfolgreich markieren, nachdem einige Überprüfungen durchgeführt wurden.

Unterstützung für Streaming-Updates

Benutzergeräte haben nicht immer genügend Speicherplatz auf /data , um das Aktualisierungspaket herunterzuladen. Da weder OEMs noch Benutzer Speicherplatz auf einer /cache -Partition verschwenden möchten, verzichten einige Benutzer auf Updates, da das Gerät das Update-Paket nirgendwo speichern kann. Um dieses Problem zu beheben, hat Android 8.0 Unterstützung für das Streamen von A/B-Updates hinzugefügt, die Blöcke beim Herunterladen direkt in die B-Partition schreiben, ohne dass die Blöcke auf /data müssen. Das Streamen von A/B-Updates benötigt fast keinen temporären Speicher und benötigt gerade genug Speicherplatz für etwa 100 KiB an Metadaten.

Um Streaming-Updates in Android 7.1 zu aktivieren, wählen Sie die folgenden Patches aus:

Diese Patches sind erforderlich, um das Streaming von A/B-Updates in Android 7.1 und höher zu unterstützen, unabhängig davon, ob Sie Google Mobile Services (GMS) oder einen anderen Update-Client verwenden.

Lebensdauer eines A/B-Updates

Der Aktualisierungsprozess beginnt, wenn ein OTA-Paket (im Code als Payload bezeichnet) zum Herunterladen verfügbar ist. Richtlinien im Gerät können das Herunterladen der Nutzlast und die Anwendung auf der Grundlage von Batteriestand, Benutzeraktivität, Ladezustand oder anderen Richtlinien verschieben. Da das Update im Hintergrund ausgeführt wird, wissen Benutzer außerdem möglicherweise nicht, dass ein Update ausgeführt wird. All dies bedeutet, dass der Aktualisierungsprozess jederzeit aufgrund von Richtlinien, unerwarteten Neustarts oder Benutzeraktionen unterbrochen werden kann.

Optional zeigen Metadaten im OTA-Paket selbst an, dass das Update gestreamt werden kann; Das gleiche Paket kann auch für die Nicht-Streaming-Installation verwendet werden. Der Server kann die Metadaten verwenden, um dem Client mitzuteilen, dass er streamt, damit der Client das OTA korrekt an update_engine . Gerätehersteller mit eigenem Server und Client können Streaming-Updates aktivieren, indem sie sicherstellen, dass der Server erkennt, dass das Update gestreamt wird (oder davon ausgeht, dass alle Updates gestreamt werden) und der Client den richtigen Aufruf an update_engine für das Streaming durchführt. Hersteller können die Tatsache nutzen, dass das Paket von der Streaming-Variante ist, um ein Flag an den Client zu senden, um die Übergabe an die Framework-Seite als Streaming auszulösen.

Nachdem eine Nutzlast verfügbar ist, ist der Aktualisierungsprozess wie folgt:

Schritt Aktivitäten
1 Der aktuelle Slot (oder "Quellslot") wird mit markBootSuccessful() .
2 Der ungenutzte Steckplatz (oder „Zielsteckplatz“) wird durch Aufrufen der Funktion setSlotAsUnbootable() als nicht bootfähig markiert. Der aktuelle Slot wird zu Beginn des Updates immer als erfolgreich markiert, um zu verhindern, dass der Bootloader auf den ungenutzten Slot zurückfällt, der bald ungültige Daten enthält. Wenn das System den Punkt erreicht hat, an dem es mit der Anwendung eines Updates beginnen kann, wird der aktuelle Steckplatz als erfolgreich markiert, selbst wenn andere Hauptkomponenten defekt sind (z. B. die Benutzeroberfläche in einer Absturzschleife), da es möglich ist, neue Software zu pushen, um diese zu beheben Probleme.

Die Update-Payload ist ein undurchsichtiger Blob mit den Anweisungen zum Update auf die neue Version. Die Aktualisierungsnutzlast besteht aus Folgendem:
  • Metadaten . Die Metadaten, ein relativ kleiner Teil der Update-Nutzdaten, enthalten eine Liste von Vorgängen zum Erstellen und Verifizieren der neuen Version auf dem Ziel-Slot. Beispielsweise könnte eine Operation ein bestimmtes Blob dekomprimieren und es in bestimmte Blöcke in einer Zielpartition schreiben oder von einer Quellpartition lesen, einen binären Patch anwenden und in bestimmte Blöcke in einer Zielpartition schreiben.
  • Zusätzliche Daten . Als Hauptteil der Aktualisierungsnutzdaten bestehen die mit den Vorgängen verbundenen zusätzlichen Daten in diesen Beispielen aus dem komprimierten Blob oder binären Patch.
3 Die Payload-Metadaten werden heruntergeladen.
4 Für jede in den Metadaten definierte Operation werden der Reihe nach die zugeordneten Daten (falls vorhanden) in den Speicher heruntergeladen, die Operation wird angewendet und der zugeordnete Speicher wird verworfen.
5 Die gesamten Partitionen werden erneut gelesen und mit dem erwarteten Hash verglichen.
6 Der Schritt nach der Installation (falls vorhanden) wird ausgeführt. Im Falle eines Fehlers während der Ausführung eines Schritts schlägt die Aktualisierung fehl und es wird mit möglicherweise einer anderen Nutzlast erneut versucht. Wenn alle bisherigen Schritte erfolgreich waren, ist das Update erfolgreich und der letzte Schritt wird ausgeführt.
7 Der ungenutzte Steckplatz wird durch den Aufruf von setActiveBootSlot() als aktiv markiert. Das Markieren des unbenutzten Steckplatzes als aktiv bedeutet nicht, dass der Bootvorgang beendet wird. Der Bootloader (oder das System selbst) kann den aktiven Steckplatz zurückschalten, wenn er keinen erfolgreichen Status liest.
8 Bei der Nachinstallation (unten beschrieben) wird ein Programm von der „neuen Update“-Version ausgeführt, während es noch in der alten Version ausgeführt wird. Wenn im OTA-Paket definiert, ist dieser Schritt obligatorisch und das Programm muss mit dem Exit-Code 0 zurückkehren; andernfalls schlägt die Aktualisierung fehl.
9 Nachdem das System erfolgreich weit genug in den neuen Steckplatz gebootet hat und die Überprüfungen nach dem Neustart abgeschlossen sind, wird der jetzt aktuelle Steckplatz (früher der "Zielsteckplatz") durch Aufrufen von markBootSuccessful() als erfolgreich markiert.

Nach der Installation

Für jede Partition, für die ein Nachinstallationsschritt definiert ist, update_engine die neue Partition an einem bestimmten Ort ein und führt das im OTA angegebene Programm relativ zur bereitgestellten Partition aus. Wenn das Nachinstallationsprogramm beispielsweise als usr/bin/postinstall in der Systempartition definiert ist, wird diese Partition aus dem unbenutzten Steckplatz an einem festen Ort (z. B. /postinstall_mount ) und /postinstall_mount/usr/bin/postinstall Befehl wird ausgeführt.

Damit die Nachinstallation erfolgreich ist, muss der alte Kernel in der Lage sein:

  • Hängen Sie das neue Dateisystemformat ein . Der Dateisystemtyp kann sich nicht ändern, es sei denn, der alte Kernel unterstützt ihn, einschließlich Details wie dem verwendeten Komprimierungsalgorithmus, wenn ein komprimiertes Dateisystem (z. B. SquashFS) verwendet wird.
  • Machen Sie sich mit dem Programmformat nach der Installation der neuen Partition vertraut . Wenn Sie eine ELF-Binärdatei (Executable and Linkable Format) verwenden, sollte sie mit dem alten Kernel kompatibel sein (z. B. ein neues 64-Bit-Programm, das auf einem alten 32-Bit-Kernel ausgeführt wird, wenn die Architektur von 32- auf 64-Bit-Builds umgestellt wurde). Sofern der Loader ( ld ) nicht angewiesen wird, andere Pfade zu verwenden oder eine statische Binärdatei zu erstellen, werden Bibliotheken aus dem alten Systemabbild geladen und nicht aus dem neuen.

Beispielsweise könnten Sie ein Shell-Skript als Nachinstallationsprogramm verwenden, das von der Shell-Binärdatei des alten Systems mit einem #! Markierung oben), dann richten Sie Bibliothekspfade aus der neuen Umgebung ein, um ein komplexeres binäres Nachinstallationsprogramm auszuführen. Alternativ können Sie den Schritt nach der Installation von einer dedizierten kleineren Partition ausführen, um das Dateisystemformat in der Hauptsystempartition zu aktualisieren, ohne dass es zu Abwärtskompatibilitätsproblemen oder Sprungbrett-Updates kommt. Dies würde es Benutzern ermöglichen, direkt von einem Factory-Image auf die neueste Version zu aktualisieren.

Das neue Nachinstallationsprogramm wird durch die im alten System definierten SELinux-Richtlinien eingeschränkt. Daher ist der Post-Install-Schritt geeignet, um Aufgaben auszuführen, die vom Design her auf einem bestimmten Gerät erforderlich sind, oder andere Best-Effort-Aufgaben (z. B. Aktualisieren der A/B-fähigen Firmware oder des Bootloaders, Erstellen von Kopien von Datenbanken für die neue Version usw.). ). Der Schritt nach der Installation ist nicht geeignet für einmalige Fehlerbehebungen vor dem Neustart, die unvorhergesehene Berechtigungen erfordern.

Das ausgewählte Nachinstallationsprogramm wird im postinstall -SELinux-Kontext ausgeführt. Alle Dateien in der neu gemounteten Partition werden mit postinstall_file , unabhängig davon, welche Attribute sie nach dem Neustart des neuen Systems haben. Änderungen an den SELinux-Attributen im neuen System wirken sich nicht auf den Schritt nach der Installation aus. Wenn das Nachinstallationsprogramm zusätzliche Berechtigungen benötigt, müssen diese dem Nachinstallationskontext hinzugefügt werden.

Nach Neustart

Nach dem Neustart löst update_verifier die Integritätsprüfung mit dm-verity aus. Diese Prüfung beginnt vor zygote, um zu vermeiden, dass Java-Dienste irreversible Änderungen vornehmen, die ein sicheres Rollback verhindern würden. Während dieses Vorgangs können Bootloader und Kernel auch einen Neustart auslösen, wenn verifizierter Bootvorgang oder dm-verity eine Beschädigung feststellen. Nachdem die Prüfung abgeschlossen ist, markiert update_verifier den Start als erfolgreich.

update_verifier liest nur die in /data/ota_package/care_map.txt aufgelisteten Blöcke, die bei Verwendung des AOSP-Codes in einem A/B-OTA-Paket enthalten sind. Der Java-Systemaktualisierungsclient, wie z. B. GmsCore, extrahiert care_map.txt , richtet die Zugriffsberechtigung ein, bevor das Gerät neu gestartet wird, und löscht die extrahierte Datei, nachdem das System erfolgreich in die neue Version gestartet wurde.