Bei Legacy-A/B-Systemupdates, auch als nahtlose Updates bezeichnet , bleibt während eines OTA-Updates (Over The Air) ein funktionierendes Boot-System auf der Festplatte erhalten. Dieser Ansatz verringert die Wahrscheinlichkeit, dass ein Gerät nach einem Update inaktiv ist. Das bedeutet, dass weniger Geräte in Reparatur- und Garantiezentren ausgetauscht und neu geflasht werden müssen. Auch andere Betriebssysteme für den kommerziellen Einsatz wie ChromeOS verwenden A/B-Updates erfolgreich.
Weitere Informationen zu A/B-Systemupdates und ihrer Funktionsweise finden Sie unter Partitionsauswahl (Slots).
A/B-Systemupdates bieten folgende Vorteile:
- OTA-Updates können während des Betriebs des Systems erfolgen, ohne den Nutzer zu unterbrechen. Nutzer können ihre Geräte während eines OTA-Updates weiterhin verwenden. Die einzige Ausfallzeit während eines Updates ist, wenn das Gerät in die aktualisierte Festplattenpartition neu gestartet wird.
- Nach einem Update dauert ein Neustart nicht länger als ein normaler Neustart.
- Wenn ein OTA nicht angewendet werden kann (z. B. aufgrund eines fehlerhaften Flashs), ist der Nutzer davon nicht betroffen. Der Nutzer verwendet weiterhin das alte Betriebssystem und der Client kann das Update noch einmal versuchen.
- Wenn ein OTA-Update angewendet wird, das Gerät aber nicht startet, wird es in die alte Partition neu gestartet und bleibt nutzbar. Der Client kann das Update noch einmal versuchen.
- Alle Fehler (z. B. E/A-Fehler) wirken sich nur auf die nicht verwendete Partition aus und können wiederholt werden. Solche Fehler werden auch weniger wahrscheinlich, da die I/O-Last bewusst niedrig gehalten wird, um die Nutzerfreundlichkeit nicht zu beeinträchtigen.
-
Updates können auf A/B-Geräte gestreamt werden. Das Paket muss also nicht heruntergeladen werden, bevor es installiert wird. Beim Streaming muss der Nutzer nicht genügend freien Speicherplatz haben, um das Updatepaket auf
/data
oder/cache
zu speichern. - Die Cache-Partition wird nicht mehr zum Speichern von OTA-Updatepaketen verwendet. Es ist also nicht mehr erforderlich, dafür zu sorgen, dass die Cache-Partition für zukünftige Updates groß genug ist.
- dm-verity sorgt dafür, dass auf einem Gerät ein nicht beschädigtes Image gebootet wird. Wenn ein Gerät aufgrund eines fehlerhaften OTA-Updates oder eines dm-verity-Problems nicht startet, kann es in ein altes Image neu gestartet werden. (Für den Bootmodus mit Verifikation von Android sind keine A/B-Updates erforderlich.)
A/B-Systemupdates
Für A/B-Updates sind Änderungen am Client und am System erforderlich. Für den OTA-Paketserver sollten jedoch keine Änderungen erforderlich sein: Updatepakete werden weiterhin über HTTPS bereitgestellt. Bei Geräten, die die OTA-Infrastruktur von Google verwenden, sind die Systemänderungen alle in AOSP enthalten und der Clientcode 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.
Wenn OEMs ihren eigenen Client bereitstellen, muss dieser folgende Anforderungen erfüllen:
- Sie entscheiden, wann Sie ein Update durchführen möchten. Da A/B-Updates im Hintergrund erfolgen, werden sie nicht mehr vom Nutzer initiiert. Damit Nutzer nicht gestört werden, sollten Updates geplant werden, wenn sich das Gerät im Leerlauf-Wartungsmodus befindet, z. B. über Nacht, und mit WLAN verbunden ist. Ihr Client kann jedoch beliebige Heuristiken verwenden.
- Prüfen Sie auf Ihren OTA-Paketservern, ob ein Update verfügbar ist. Dieser sollte größtenteils mit Ihrem vorhandenen Clientcode übereinstimmen. Sie müssen jedoch signalisieren, dass das Gerät A/B unterstützt. Der Client von Google enthält auch die Schaltfläche Jetzt prüfen, über die Nutzer 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 das Updatepaket gestreamt wird. -
Melden Sie erfolgreiche oder fehlgeschlagene Installationen basierend auf dem Ergebniscode
update_engine
an Ihre Server. Wenn das Update erfolgreich angewendet wurde, weistupdate_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 gestartet werden kann. Der Client muss also nichts tun. Wenn das Update fehlschlägt, muss der Client anhand des detaillierten Fehlercodes entscheiden, wann (und ob) er es noch einmal versuchen soll. Ein guter Client könnte beispielsweise erkennen, dass ein partielles („Diff“) OTA-Paket fehlschlägt, und stattdessen ein vollständiges OTA-Paket ausprobieren.
Optional kann der Kunde Folgendes tun:
- Dem Nutzer eine Benachrichtigung anzeigen, in der er zum Neustart aufgefordert wird. Wenn Sie eine Richtlinie implementieren möchten, in der der Nutzer aufgefordert wird, regelmäßig Updates durchzuführen, kann diese Benachrichtigung in Ihren Client aufgenommen werden. Wenn der Client Nutzer nicht auffordert, erhalten sie das Update beim nächsten Neustart. Der Client von Google hat eine pro Update konfigurierbare Verzögerung.
- Zeigen Sie eine Benachrichtigung an, in der Nutzer darüber informiert werden, ob sie in einer neuen Betriebssystemversion gebootet haben oder ob dies erwartet wurde, aber stattdessen die alte Betriebssystemversion verwendet wird. (Der Google-Client macht das normalerweise nicht.)
Auf Systemseite wirken sich A/B-Systemupdates auf Folgendes aus:
-
Partitionierung (Slots), der
update_engine
-Daemon und Bootloader-Interaktionen (siehe unten) - Build-Prozess und Generierung von OTA-Updatepaketen (siehe A/B-Updates implementieren)
Partition auswählen (Slots)
Bei A/B-Systemupdates werden zwei Partitionssätze verwendet, die als Slots bezeichnet werden (normalerweise Slot A und Slot B). Das System wird über den aktuellen Slot ausgeführt, während auf die Partitionen im nicht verwendeten Slot im normalen Betrieb nicht zugegriffen wird. Dieser Ansatz macht Updates fehlertolerant, da der nicht verwendete Slot als Fallback dient: Wenn während oder unmittelbar nach einem Update ein Fehler auftritt, kann das System auf den alten Slot zurückgesetzt werden und weiterhin funktionieren. Dazu darf keine Partition, die vom aktuellen Slot verwendet wird, im Rahmen des OTA-Updates aktualisiert werden. Das gilt auch für Partitionen, von denen es nur eine Kopie gibt.
Jeder Slot hat das Attribut bootable, das angibt, ob der Slot ein korrektes System enthält, von dem das Gerät gebootet werden kann. Der aktuelle Slot ist bootfähig, wenn das System ausgeführt wird. Der andere Slot kann jedoch eine alte (aber immer noch korrekte) Version des Systems, eine neuere Version oder ungültige Daten enthalten. Unabhängig davon, welcher Slot der aktuelle ist, gibt es einen Slot, der der aktive Slot ist (der, von dem der Bootloader beim nächsten Start bootet) oder der bevorzugte Slot.
Jeder Slot hat auch ein Attribut successful, das vom Nutzerbereich festgelegt wird und nur relevant ist, wenn der Slot auch bootfähig ist. Ein erfolgreicher Slot sollte in der Lage sein, zu booten, ausgeführt zu werden und sich selbst zu aktualisieren. Ein bootfähiger Slot, der nicht als erfolgreich markiert wurde (nachdem mehrere Versuche unternommen wurden, von ihm zu booten), sollte vom Bootloader als nicht bootfähig markiert werden. Dazu gehört auch, den aktiven Slot in einen anderen bootfähigen Slot zu ändern (normalerweise in den Slot, der unmittelbar vor dem Versuch ausgeführt wurde, in den neuen, aktiven Slot zu booten). Die genauen Details der Schnittstelle sind in
boot_control.h
definiert.
Engine-Daemon aktualisieren
Bei A/B-Systemupdates wird ein Hintergrund-Daemon namens update_engine
verwendet, um das System für den Start in einer neuen, aktualisierten Version vorzubereiten. Dieser Daemon kann die folgenden Aktionen ausführen:
- Lese aus den aktuellen A/B-Partitionen des Slots und schreibe alle Daten gemäß den Anweisungen des OTA-Pakets in die ungenutzten A/B-Partitionen des Slots.
- Rufen Sie die
boot_control
-Schnittstelle in einem vordefinierten Workflow auf. - Führen Sie ein post-install-Programm über die neue Partition aus, nachdem alle ungenutzten Slot-Partitionen wie im OTA-Paket angegeben geschrieben wurden. Weitere Informationen finden Sie unter Nach der Installation.
Da der update_engine
-Daemon nicht am Bootvorgang selbst beteiligt ist, sind seine Möglichkeiten während eines Updates durch die SELinux-Richtlinien und ‑Funktionen im aktuellen Slot begrenzt. Diese Richtlinien und Funktionen können erst aktualisiert werden, wenn das System in einer neuen Version gebootet wird. Damit das System stabil bleibt, darf die Partitionstabelle, der Inhalt von Partitionen im aktuellen Slot oder der Inhalt von Nicht-A/B-Partitionen, die nicht durch Zurücksetzen auf die Werkseinstellungen gelöscht werden können, durch den Aktualisierungsprozess nicht geändert oder ersetzt werden.
Quell-Engine aktualisieren
Die Quelle update_engine
befindet sich in system/update_engine
. Die A/B-OTA-Dexopt-Dateien werden zwischen installd
und einem Paketmanager aufgeteilt:
-
frameworks/native/cmds/installd/
ota* enthält das Post-Install-Skript, die Binärdatei für chroot, den installd-Klon, der dex2oat aufruft, das Post-OTA-Skript zum Verschieben von Artefakten und die RC-Datei für das Verschiebeskript. -
frameworks/base/services/core/java/com/android/server/pm/OtaDexoptService.java
(plusOtaDexoptShellCommand
) ist der Paketmanager, der dex2oat-Befehle für Anwendungen vorbereitet.
Ein funktionierendes Beispiel finden Sie unter /device/google/marlin/device-common.mk
.
Suchmaschinen-Logs aktualisieren
Bei Android 8.x und früheren Versionen finden Sie die update_engine
-Logs unter logcat
und im Fehlerbericht. Damit die update_engine
-Logs im Dateisystem verfügbar sind, müssen Sie die folgenden Änderungen in Ihren Build einfügen:
Durch diese Änderungen wird eine Kopie des letzten update_engine
-Logs in /data/misc/update_engine_log/update_engine.YEAR-TIME
gespeichert. Zusätzlich zum aktuellen Log werden die fünf letzten Logs unter /data/misc/update_engine_log/
gespeichert. Nutzer mit der Gruppen-ID log können auf die Dateisystemlogs zugreifen.
Bootloader-Interaktionen
Die boot_control
-HAL wird von update_engine
(und möglicherweise anderen Daemons) verwendet, um dem Bootloader mitzuteilen, von wo aus er booten soll. Gängige Beispielszenarien und die zugehörigen Status:
- Normaler Fall: Das System wird vom aktuellen Slot aus ausgeführt, entweder Slot A oder B. Bisher wurden keine Updates angewendet. Der aktuelle Slot des Systems ist bootfähig, erfolgreich und der aktive Slot.
- Update wird ausgeführt: Das System wird von Slot B aus ausgeführt. Slot B ist also der bootfähige, erfolgreiche und aktive Slot. Slot A wurde als nicht bootfähig markiert, da der Inhalt von Slot A aktualisiert wird, aber noch nicht abgeschlossen ist. Bei einem Neustart in diesem Zustand sollte weiterhin von Slot B gebootet werden.
- Update angewendet, Neustart ausstehend: Das System wird von Slot B aus ausgeführt. Slot B ist bootfähig und erfolgreich, aber Slot A wurde als aktiv markiert (und ist daher als bootfähig markiert). Slot A ist noch nicht als erfolgreich markiert und der Bootloader sollte eine bestimmte Anzahl von Versuchen unternehmen, um von Slot A zu starten.
-
System in neues Update neu gestartet: Das System wird zum ersten Mal von Slot A aus ausgeführt. Slot B ist weiterhin bootfähig und erfolgreich, während Slot A nur bootfähig und weiterhin aktiv, aber nicht erfolgreich ist. Ein Nutzerbereichs-Daemon,
update_verifier
, sollte Slot A nach einigen Prüfungen als erfolgreich markieren.
Unterstützung für Streaming-Updates
Auf Nutzergeräten ist nicht immer genügend Speicherplatz auf /data
verfügbar, um das Updatepaket herunterzuladen. Da weder OEMs noch Nutzer Speicherplatz auf einer /cache
-Partition verschwenden möchten, erhalten einige Nutzer keine Updates, weil auf dem Gerät kein Speicherplatz für das Updatepaket vorhanden ist. Um dieses Problem zu beheben, wurde in Android 8.0 die Unterstützung für das Streamen von A/B-Updates hinzugefügt. Dabei werden Blöcke direkt auf die B-Partition geschrieben, während sie heruntergeladen werden, ohne dass sie auf /data
gespeichert werden müssen. Für Streaming-A/B-Updates ist fast kein temporärer Speicher erforderlich. Es wird nur Speicherplatz für etwa 100 KiB an Metadaten benötigt.
Wenn Sie Streaming-Updates in Android 7.1 aktivieren möchten, wählen Sie die folgenden Patches aus:
- Zulassen, dass eine Proxyauflösungsanfrage abgebrochen wird
- Problem beim Beenden einer Übertragung während der Proxyauflösung behoben
- Unittest für „TerminateTransfer“ zwischen Bereichen hinzufügen
- RetryTimeoutCallback() bereinigen
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 Updatevorgang beginnt, wenn ein OTA-Paket (im Code als Nutzlast bezeichnet) zum Herunterladen verfügbar ist. Richtlinien auf dem Gerät können den Download und die Anwendung der Nutzlast basierend auf dem Akkustand, der Nutzeraktivität, dem Ladestatus oder anderen Richtlinien verzögern. Da das Update im Hintergrund ausgeführt wird, wissen Nutzer möglicherweise nicht, dass ein Update läuft. Das bedeutet, dass der Updatevorgang jederzeit durch Richtlinien, unerwartete Neustarts oder Nutzeraktionen unterbrochen werden kann.
Optional können Metadaten im OTA-Paket selbst angeben, dass das Update gestreamt werden kann. Dasselbe Paket kann auch für die Installation ohne Streaming verwendet werden. Der Server kann die Metadaten verwenden, um dem Client mitzuteilen, dass er streamt, damit der Client die OTA-Aktualisierung korrekt an update_engine
übergibt. Gerätehersteller mit eigenem Server und Client können Streaming-Updates aktivieren, indem sie dafür sorgen, 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 ausführt. Hersteller können die Tatsache, dass das Paket die Streaming-Variante ist, nutzen, um ein Flag an den Client zu senden, um die Übergabe an die Framework-Seite als Streaming auszulösen.
Wenn eine Nutzlast verfügbar ist, läuft der Aktualisierungsprozess so ab:
Schritt | Aktivitäten |
---|---|
1 |
Der aktuelle Slot (oder „Quellenslot“) wird mit markBootSuccessful() als erfolgreich markiert, sofern er nicht bereits markiert ist.
|
2 |
Der nicht verwendete Slot (oder „Ziel-Slot“) wird durch Aufrufen der Funktion setSlotAsUnbootable() als nicht bootfähig markiert. Der aktuelle Slot wird zu Beginn des Updates immer als erfolgreich markiert, damit der Bootloader nicht auf den ungenutzten Slot zurückgreift, 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 Slot 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 übertragen. Die Update-Payload ist ein undurchsichtiger Blob mit den Anweisungen zum Aktualisieren auf die neue Version. Die Update-Nutzlast besteht aus Folgendem:
|
3 | Die Nutzlast-Metadaten werden heruntergeladen. |
4 | Für jeden in den Metadaten definierten Vorgang werden die zugehörigen Daten (falls vorhanden) in der angegebenen Reihenfolge in den Arbeitsspeicher heruntergeladen, der Vorgang wird angewendet und der zugehörige Arbeitsspeicher wird verworfen. |
5 | Die gesamten Partitionen werden noch einmal gelesen und mit dem erwarteten Hash verglichen. |
6 | Der Schritt nach der Installation (falls vorhanden) wird ausgeführt. Bei einem Fehler während der Ausführung eines Schritts schlägt das Update fehl und wird möglicherweise mit einer anderen Nutzlast wiederholt. Wenn alle bisherigen Schritte erfolgreich waren, wird das Update erfolgreich abgeschlossen und der letzte Schritt wird ausgeführt. |
7 |
Der nicht verwendete Slot wird durch Aufrufen von setActiveBootSlot() als aktiv markiert.
Wenn Sie den nicht verwendeten Slot als aktiv markieren, wird der Bootvorgang nicht abgeschlossen. Der Bootloader (oder das System selbst) kann den aktiven Slot zurückschalten, wenn kein erfolgreicher Status gelesen wird.
|
8 |
Bei der Nachinstallation (siehe unten) wird ein Programm aus der Version „new update“ ausgeführt, während die alte Version noch aktiv ist. Wenn dieser Schritt im OTA-Paket definiert ist, ist er erforderlich und das Programm muss mit dem Exitcode 0 zurückgegeben werden. Andernfalls schlägt das Update fehl.
|
9 |
Nachdem das System im neuen Slot erfolgreich gestartet wurde und die Prüfungen nach dem Neustart abgeschlossen sind, wird der jetzt aktuelle Slot (früher der „Ziel-Slot“) durch Aufrufen von markBootSuccessful() als erfolgreich markiert.
|
Nach der Installation
Für jede Partition, für die ein Schritt nach der Installation definiert ist, hängt update_engine
die neue Partition an einem bestimmten Ort ein und führt das im OTA angegebene Programm relativ zur eingebundenen Partition aus. Wenn das Programm nach der Installation beispielsweise als usr/bin/postinstall
in der Systempartition definiert ist, wird diese Partition aus dem ungenutzten Slot an einem festen Speicherort (z. B. /postinstall_mount
) bereitgestellt und der Befehl /postinstall_mount/usr/bin/postinstall
wird ausgeführt.
Damit die Nachinstallation erfolgreich ist, muss der alte Kernel Folgendes können:
- Stellen Sie das neue Dateisystemformat bereit. Der Dateisystemtyp kann nur geändert werden, wenn er vom alten Kernel unterstützt wird. Das gilt auch für Details wie den verwendeten Komprimierungsalgorithmus, wenn ein komprimiertes Dateisystem (z. B. SquashFS) verwendet wird.
-
Das Programmformat der neuen Partition nach der Installation Wenn Sie ein ELF-Binärprogramm (Executable and Linkable Format) verwenden, sollte es mit dem alten Kernel kompatibel sein, z.B. ein 64‑Bit-Programm, das auf einem alten 32‑Bit-Kernel ausgeführt wird, wenn die Architektur von 32‑Bit- 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 System-Image und nicht aus dem neuen geladen.
Sie könnten beispielsweise ein Shell-Skript als Post-Install-Programm verwenden, das vom Shell-Binärprogramm des alten Systems mit einem #!
-Marker oben interpretiert wird. Anschließend können Sie Bibliothekspfade aus der neuen Umgebung einrichten, um ein komplexeres binäres Post-Install-Programm auszuführen. Alternativ können Sie den Schritt nach der Installation über eine separate, kleinere Partition ausführen. So kann das Dateisystemformat in der Hauptsystempartition aktualisiert werden, ohne dass Probleme mit der Abwärtskompatibilität oder Zwischenschritte bei Updates auftreten. Nutzer können dann direkt von einem Werksimage auf die neueste Version aktualisieren.
Das neue Programm nach der Installation ist durch die im alten System definierten SELinux-Richtlinien eingeschränkt. Daher eignet sich der Schritt nach der Installation für Aufgaben, die auf einem bestimmten Gerät erforderlich sind, oder für andere Best-Effort-Aufgaben. Der Schritt nach der Installation ist nicht geeignet für einmalige Fehlerkorrekturen vor dem Neustart, für die unvorhergesehene Berechtigungen erforderlich sind.
Das ausgewählte Programm für die Installation wird im SELinux-Kontext postinstall
ausgeführt. Alle Dateien in der neuen gemounteten Partition werden mit postinstall_file
getaggt, unabhängig davon, welche Attribute sie nach dem Neustart in diesem neuen System haben. Änderungen an den SELinux-Attributen im neuen System haben keine Auswirkungen auf den Schritt nach der Installation. Wenn für das Programm nach der Installation zusätzliche Berechtigungen erforderlich sind, müssen diese dem Kontext nach der Installation hinzugefügt werden.
Nach dem Neustart
Nach dem Neustart wird durch update_verifier
die Integritätsprüfung mit dm-verity ausgelöst.
Diese Prüfung beginnt vor dem Zygote-Prozess, um zu verhindern, dass Java-Dienste irreversible Änderungen vornehmen, die ein sicheres Rollback verhindern würden. Während dieses Vorgangs können der Bootloader und der Kernel auch einen Neustart auslösen, wenn beim verifizierten Bootmodus oder bei dm-verity eine Beschädigung erkannt wird. Nach Abschluss des Checks wird der erfolgreiche Start mit update_verifier
gekennzeichnet.
update_verifier
liest nur die in /data/ota_package/care_map.txt
aufgeführten Blöcke, die im AOSP-Code enthalten sind, wenn ein A/B-OTA-Paket verwendet wird. Der Java-Systemupdate-Client, z. B. GmsCore, extrahiert care_map.txt
, richtet die Zugriffsberechtigung vor dem Neustart des Geräts ein und löscht die extrahierte Datei, nachdem das System erfolgreich in der neuen Version gestartet wurde.