A/B (nahtlose) Systemaktualisierungen

A/B-Systemupdates, auch nahtlose Updates genannt, stellen sicher, dass während eines Over-the-Air-Updates (OTA) ein funktionsfähiges Bootsystem auf der Festplatte verbleibt. Dieser Ansatz verringert die Wahrscheinlichkeit eines inaktiven Geräts nach einem Update, was zu weniger Geräteaustausch und Geräte-Reflashes in Reparatur- und Garantiezentren führt. Auch andere kommerzielle Betriebssysteme wie ChromeOS nutzen A/B-Updates erfolgreich.

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

A/B-Systemaktualisierungen bieten die folgenden Vorteile:

  • OTA-Updates können bei laufendem System durchgeführt werden, ohne den Benutzer zu unterbrechen. Benutzer können ihre Geräte während eines OTA weiterhin verwenden – die einzige Ausfallzeit während eines Updates entsteht, wenn das Gerät in der aktualisierten Festplattenpartition neu startet.
  • Nach einem Update dauert der Neustart nicht länger als ein normaler Neustart.
  • Wenn ein OTA nicht angewendet wird (z. B. aufgrund eines fehlerhaften Flashs), ist der Benutzer davon nicht betroffen. Der Benutzer führt weiterhin das alte Betriebssystem aus und der Client kann das Update erneut versuchen.
  • Wenn ein OTA-Update angewendet wird, der Start jedoch fehlschlägt, wird das Gerät in der alten Partition neu gestartet und bleibt verwendbar. Es steht dem Kunden frei, das Update erneut zu versuchen.
  • Etwaige Fehler (z. B. E/A-Fehler) betreffen nur den nicht verwendeten Partitionssatz und können erneut versucht werden. Solche Fehler werden auch weniger wahrscheinlich, da die I/O-Last bewusst niedrig gehalten wird, um eine Beeinträchtigung des Benutzererlebnisses 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 zum Speichern des Updatepakets auf /data oder /cache verfügen muss.
  • Die Cache-Partition wird nicht mehr zum Speichern von OTA-Update-Paketen verwendet, sodass nicht sichergestellt werden muss, 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 mit einem alten Image neu gestartet werden. (Für Android Verified Boot sind keine A/B-Updates erforderlich.)

Über A/B-Systemaktualisierungen

A/B-Updates erfordern Änderungen sowohl am Client als auch am System. Der OTA-Paketserver sollte jedoch keine Änderungen erfordern: Update-Pakete werden weiterhin über HTTPS bereitgestellt. Bei Geräten, die die OTA-Infrastruktur von Google nutzen, erfolgen alle Systemänderungen in AOSP und der Client-Code wird von den Google Play-Diensten bereitgestellt. OEMs, die die OTA-Infrastruktur von Google nicht nutzen, können den AOSP-Systemcode wiederverwenden, müssen jedoch 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 erfolgen, werden sie nicht mehr vom Benutzer initiiert. Um Störungen für Benutzer zu vermeiden, wird empfohlen, Updates zu planen, wenn sich das Gerät im Wartungsmodus im Leerlauf befindet, z. B. über Nacht, und über WLAN verfügbar ist. Ihr Client kann jedoch jede gewünschte Heuristik verwenden.
  • Erkundigen 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 identisch sein, mit der Ausnahme, dass Sie signalisieren möchten, dass das Gerät A/B unterstützt. (Der Client von Google enthält außerdem eine Schaltfläche „Jetzt prüfen“ , damit Benutzer nach dem neuesten Update suchen können.)
  • Rufen Sie update_engine mit der HTTPS-URL für Ihr Update-Paket auf, sofern eines verfügbar ist. update_engine aktualisiert die Rohblöcke auf der derzeit nicht verwendeten Partition, während es das Update-Paket streamt.
  • Melden Sie Ihren Servern Installationserfolge oder -fehlschläge basierend auf dem update_engine Ergebniscode. Wenn das Update erfolgreich angewendet wurde, weist update_engine den Bootloader an, beim nächsten Neustart das neue Betriebssystem zu starten. Der Bootloader greift auf das alte Betriebssystem zurück, wenn das neue Betriebssystem nicht booten kann, sodass vom Client keine Arbeit erforderlich ist. Wenn das Update fehlschlägt, muss der Client anhand des detaillierten Fehlercodes entscheiden, wann (und ob) es erneut versucht werden soll. 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 Kunde:

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

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

  • Partitionsauswahl (Slots), der update_engine Daemon und Bootloader-Interaktionen (unten beschrieben)
  • Erstellungsprozess und Generierung von OTA-Updatepaketen (beschrieben unter „Implementieren von A/B-Updates “)

Partitionsauswahl (Slots)

A/B-Systemaktualisierungen verwenden zwei Sätze von Partitionen, die als Steckplätze bezeichnet werden (normalerweise Steckplatz A und Steckplatz B). Das System läuft vom aktuellen Steckplatz aus, während das laufende System im Normalbetrieb nicht auf die Partitionen im ungenutzten Steckplatz zugreift. Dieser Ansatz macht Aktualisierungen fehlersicher, indem der ungenutzte Steckplatz als Ausweichmöglichkeit beibehalten wird: Wenn während oder unmittelbar nach einem Update ein Fehler auftritt, kann das System auf den alten Steckplatz zurücksetzen und weiterhin über ein funktionsfähiges System verfügen. Um dieses Ziel zu erreichen, sollte im Rahmen des OTA-Updates keine vom aktuellen Steckplatz verwendete Partition aktualisiert werden (einschließlich Partitionen, für die nur eine Kopie vorhanden ist).

Jeder Steckplatz verfügt über 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, der andere Steckplatz verfügt jedoch möglicherweise über eine alte (immer noch korrekte) Version des Systems, eine neuere Version oder ungültige Daten. Unabhängig vom aktuellen Steckplatz gibt es einen Steckplatz, der der aktive Steckplatz (derjenige, von dem der Bootloader beim nächsten Start startet) oder der bevorzugte Steckplatz ist.

Jeder Steckplatz verfügt außerdem über ein vom Benutzerbereich festgelegtes Erfolgsattribut , das nur relevant ist, wenn der Steckplatz auch bootfähig ist. Ein erfolgreicher Slot sollte in der Lage sein, sich selbst zu starten, 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 der Änderung des aktiven Steckplatzes in einen anderen bootfähigen Steckplatz (normalerweise in den Steckplatz, der unmittelbar vor dem Startversuch aktiv war). in das neue, aktive). Die spezifischen Details der Schnittstelle sind in boot_control.h definiert.

Engine-Daemon aktualisieren

A/B-Systemaktualisierungen verwenden einen Hintergrund-Daemon namens update_engine , um das System auf den Start einer neuen, aktualisierten Version vorzubereiten. Dieser Daemon kann die folgenden Aktionen ausführen:

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

Da der update_engine Daemon nicht am Startvorgang selbst beteiligt ist, sind seine Möglichkeiten während einer Aktualisierung durch die SELinux- Richtlinien und -Funktionen im aktuellen Steckplatz eingeschränkt (solche Richtlinien und Funktionen können erst aktualisiert werden, wenn das System in einen startet). neue Version). Um ein robustes System aufrechtzuerhalten, sollte der Aktualisierungsprozess die Partitionstabelle, den Inhalt von Partitionen im aktuellen Steckplatz oder den Inhalt von Nicht-A/B-Partitionen, die nicht durch einen Werksreset gelöscht werden können, nicht ändern.

Engine-Quelle aktualisieren

Die update_engine Quelle 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-Versionen 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 /data/misc/update_engine_log/update_engine. YEAR - TIME . Zusätzlich zum aktuellen Protokoll werden die fünf aktuellsten Protokolle unter /data/misc/update_engine_log/ gespeichert. Benutzer mit der Protokollgruppen -ID können auf die Dateisystemprotokolle zugreifen.

Bootloader-Interaktionen

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

  • Normalfall : Das System läuft in seinem aktuellen Steckplatz, entweder Steckplatz A oder B. Bisher wurden keine Updates durchgeführt. Der aktuelle Steckplatz des Systems ist bootfä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 ist. Ein Neustart in diesem Zustand sollte mit dem Booten von Steckplatz B fortfahren.
  • Update angewendet, Neustart ausstehend : Das System wird von Steckplatz B ausgeführt, Steckplatz B ist bootfähig und erfolgreich, aber Steckplatz 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 wurde mit neuem Update neu gestartet : Das System wird zum ersten Mal von Steckplatz A ausgeführt, 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 User-Space-Daemon, update_verifier , sollte Steckplatz A als erfolgreich markieren, nachdem einige Prüfungen durchgeführt wurden.

Unterstützung für Streaming-Updates

Benutzergeräte verfügen nicht immer über genügend Speicherplatz auf /data um das Update-Paket herunterzuladen. Da weder OEMs noch Benutzer Speicherplatz auf einer /cache Partition verschwenden möchten, verzichten einige Benutzer auf Updates, da das Gerät keinen Platz zum Speichern des Update-Pakets hat. Um dieses Problem zu beheben, hat Android 8.0 Unterstützung für das Streamen von A/B-Updates hinzugefügt, bei denen Blöcke beim Herunterladen direkt auf die B-Partition geschrieben werden, ohne dass die Blöcke auf /data gespeichert werden müssen. Streaming-A/B-Updates benötigen fast keinen temporären Speicher und benötigen 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 Streaming-A/B-Updates in Android 7.1 und höher zu unterstützen, unabhängig davon, ob Google Mobile Services (GMS) oder ein anderer Update-Client verwendet wird.

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 den Download und die Anwendung der Nutzlast basierend auf dem Akkustand, der Benutzeraktivität, dem Ladestatus oder anderen Richtlinien verzögern. Da das Update im Hintergrund ausgeführt wird, wissen Benutzer möglicherweise nicht, dass ein Update ausgeführt wird. All dies bedeutet, dass der Aktualisierungsprozess aufgrund von Richtlinien, unerwarteten Neustarts oder Benutzeraktionen jederzeit unterbrochen werden kann.

Optional geben 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, sodass der Client den OTA korrekt an update_engine weitergibt. 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 zum Streamen durchführt. Hersteller können die Tatsache, dass es sich bei dem Paket um eine Streaming-Variante handelt, nutzen, 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, läuft der Aktualisierungsprozess wie folgt ab:

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

Die Update-Nutzlast ist ein undurchsichtiger Blob mit den Anweisungen zum Aktualisieren auf die neue Version. Die Update-Payload besteht aus Folgendem:
  • Metadaten . Die Metadaten sind ein relativ kleiner Teil der Update-Nutzlast und enthalten eine Liste von Vorgängen zum Erstellen und Überprüfen der neuen Version auf dem Zielslot. Beispielsweise könnte ein Vorgang ein bestimmtes Blob dekomprimieren und 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 . Da sie den Großteil der Update-Nutzlast ausmachen, bestehen die mit den Vorgängen verbundenen zusätzlichen Daten in diesen Beispielen aus dem komprimierten Blob oder Binärpatch.
3 Die Payload-Metadaten werden heruntergeladen.
4 Für jede in den Metadaten definierte Operation werden der Reihe nach die zugehörigen Daten (sofern vorhanden) in den Speicher heruntergeladen, die Operation angewendet und der zugehörige Speicher verworfen.
5 Die gesamten Partitionen werden erneut gelesen und anhand des erwarteten Hashs überprüft.
6 Der Schritt nach der Installation (falls vorhanden) wird ausgeführt. Tritt bei der Ausführung eines Schritts ein Fehler auf, schlägt die Aktualisierung fehl und wird möglicherweise mit einer anderen Nutzlast erneut versucht. Wenn alle Schritte bisher erfolgreich waren, ist das Update erfolgreich und der letzte Schritt wird ausgeführt.
7 Der nicht verwendete Steckplatz wird durch den Aufruf setActiveBootSlot() als aktiv markiert. Das Markieren des nicht verwendeten Steckplatzes als aktiv bedeutet nicht, dass der Startvorgang abgeschlossen 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 und die Prüfungen nach dem Neustart abgeschlossen hat, wird der nun aktuelle Steckplatz (früher der „Zielsteckplatz“) durch den Aufruf markBootSuccessful() als erfolgreich markiert.

Nach der Installation

Für jede Partition, für die ein Post-Install-Schritt definiert ist, mountet update_engine die neue Partition an einem bestimmten Ort und führt das im OTA angegebene Programm relativ zur gemounteten Partition aus. Wenn das Post-Install-Programm beispielsweise in der Systempartition als usr/bin/postinstall definiert ist, wird diese Partition aus dem nicht verwendeten Steckplatz an einem festen Speicherort (z. B. /postinstall_mount ) und /postinstall_mount/usr/bin/postinstall gemountet. /postinstall_mount/usr/bin/postinstall Befehl wird ausgeführt.

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

  • Mounten Sie das neue Dateisystemformat . Der Dateisystemtyp kann nicht geändert werden, es sei denn, er wird im alten Kernel unterstützt, 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 diese 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 vom alten Systemabbild und nicht vom neuen geladen.

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

Das neue Post-Install-Programm ist durch die im alten System definierten SELinux-Richtlinien eingeschränkt. Daher eignet sich der Schritt nach der Installation für die Ausführung von Aufgaben, die aufgrund des Designs auf einem bestimmten Gerät erforderlich sind, oder für andere Best-Effort-Aufgaben (z. B. Aktualisieren der A/B-fähigen Firmware oder des Bootloaders, Vorbereiten von Datenbankkopien für die neue Version usw.). ). Der Schritt nach der Installation eignet sich nicht für einmalige Fehlerbehebungen vor dem Neustart, die unvorhergesehene Berechtigungen erfordern.

Das ausgewählte Postinstallationsprogramm wird im postinstall -SELinux-Kontext ausgeführt. Alle Dateien in der neu bereitgestellten Partition werden mit postinstall_file gekennzeichnet, unabhängig von ihren Attributen nach dem Neustart des neuen Systems. Änderungen an den SELinux-Attributen im neuen System haben keine Auswirkungen auf den Schritt nach der Installation. Wenn das Post-Install-Programm zusätzliche Berechtigungen benötigt, müssen diese dem Post-Install-Kontext hinzugefügt werden.

Nach dem Neustart

Nach dem Neustart löst update_verifier die Integritätsprüfung mit dm-verity aus. Diese Prüfung beginnt vor Zygote, um zu verhindern, 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 aufgeführten 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.