Legacy-A/B-Systemupdates, auch als nahtlose Updates bezeichnet, sorgen dafür, dass während eines Over-the-Air-Updates (OTA) ein funktionsfähiges Bootsystem auf dem Laufwerk verbleibt. Dadurch wird die Wahrscheinlichkeit verringert, dass ein Gerät nach einem Update inaktiv ist. Das bedeutet weniger Gerätetausche und weniger Neuflashes in Reparatur- und Garantiezentren. Auch andere kommerzielle Betriebssysteme wie ChromeOS nutzen 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 auch ausgeführt werden, während das System in Betrieb ist, ohne dass der Nutzer unterbrochen wird. Nutzer können ihre Geräte während eines Over-the-Air-Updates weiterhin verwenden. Die einzige Ausfallzeit während eines Updates ist der Neustart des Geräts in der aktualisierten Laufwerkpartition.
- Nach einem Update dauert der Neustart nicht länger als ein normaler Neustart.
- Wenn ein Over-the-air-Update nicht angewendet werden kann (z. B. aufgrund eines fehlerhaften Flashens), ist das für den Nutzer nicht von Bedeutung. Der Nutzer verwendet weiterhin das alte Betriebssystem und kann das Update noch einmal versuchen.
- Wenn ein Over-the-air-Update angewendet wird, aber das Gerät nicht hochfährt, wird es neu gestartet und die alte Partition wird wieder verwendet. Der Kunde kann das Update noch einmal versuchen.
- Alle Fehler (z. B. I/O-Fehler) wirken sich nur auf den nicht verwendeten Partitionssatz aus und können wiederholt werden. Solche Fehler sind auch weniger wahrscheinlich, da die I/O-Auslastung bewusst niedrig gehalten wird, um die Nutzerfreundlichkeit nicht zu beeinträchtigen.
-
Updates können auf A/B-Geräte gestreamt werden, sodass das Paket nicht vor der Installation heruntergeladen werden muss. Beim Streaming muss der Nutzer nicht genügend freien Speicherplatz haben, um das Update-Paket auf
/data
oder/cache
zu speichern. - Die Cache-Partition wird nicht mehr zum Speichern von Over-the-air-Update-Paketen verwendet. Sie muss also nicht groß genug für zukünftige Updates sein.
- dm-verity sorgt dafür, dass ein Gerät ein unverzerrtes Image startet. Wenn ein Gerät aufgrund eines fehlerhaften OTA- oder dm-verity-Problems nicht startet, kann es mit einem alten Image neu gestartet werden. Für den verifizierten Bootmodus von Android sind keine A/B-Updates erforderlich.
A/B-Systemupdates
A/B-Tests erfordern Änderungen sowohl am Client als auch am System. Der OTA-Paketserver sollte jedoch nicht geändert werden müssen: Updatepakete werden weiterhin über HTTPS bereitgestellt. Bei Geräten, die die OTA-Infrastruktur von Google verwenden, werden alle Systemänderungen in AOSP vorgenommen und der Clientcode wird von den 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 Client bereitstellen, gelten folgende Anforderungen:
- Legen Sie fest, wann Sie ein Update ausführen möchten. Da A/B-Updates im Hintergrund erfolgen, werden sie nicht mehr vom Nutzer initiiert. Um Unterbrechungen für Nutzer zu vermeiden, wird empfohlen, Updates zu planen, wenn das Gerät im Inaktivitätsmodus ist, z. B. über Nacht, und eine WLAN-Verbindung besteht. Ihr Kunde kann jedoch beliebige Heuristiken verwenden.
- Prüfen Sie bei Ihren OTA-Paketservern, ob ein Update verfügbar ist. Dieser sollte weitgehend mit Ihrem vorhandenen Clientcode übereinstimmen, mit der Ausnahme, dass Sie angeben müssen, dass das Gerät A/B unterstützt. Der Google-Client enthält außerdem die Schaltfläche Jetzt prüfen, über die Nutzer nach dem neuesten Update suchen können.
-
Rufe
update_engine
mit der HTTPS-URL für dein Update-Paket auf, sofern verfügbar.update_engine
aktualisiert die Rohblöcke auf der derzeit nicht verwendeten Partition, während das Update-Paket gestreamt wird. -
Melden Sie den Erfolg oder Misserfolg der Installation an Ihre Server, basierend auf dem Ergebniscode
update_engine
. Wenn das Update erfolgreich angewendet wurde, weistupdate_engine
den Bootloader an, beim nächsten Neustart das neue Betriebssystem zu starten. Der Bootloader wechselt zum alten Betriebssystem, wenn das neue Betriebssystem nicht gestartet werden kann. Der Kunde muss also nichts weiter tun. Wenn das Update fehlschlägt, muss der Kunde anhand des detaillierten Fehlercodes entscheiden, wann (und ob) er es noch einmal versuchen möchte. Ein guter Client kann beispielsweise erkennen, dass ein teilweises („diff“) OTA-Paket fehlschlägt, und stattdessen ein vollständiges OTA-Paket versuchen.
Optional kann der Kunde Folgendes tun:
- Dem Nutzer wird eine Benachrichtigung angezeigt, in der er zum Neustart aufgefordert wird. Wenn Sie eine Richtlinie implementieren möchten, in der Nutzer dazu aufgefordert werden, regelmäßig Updates auszuführen, kann diese Benachrichtigung Ihrem Client hinzugefügt werden. Wenn der Client die Nutzer nicht auffordert, erhalten sie das Update beim nächsten Neustart trotzdem. (Der Google-Client hat eine pro Update konfigurierbare Verzögerung.)
- Zeigen Sie Nutzern eine Benachrichtigung an, in der sie darüber informiert werden, ob sie mit einer neuen Betriebssystemversion gestartet sind oder ob sie eigentlich dazu hätten wechseln sollen, aber stattdessen zur alten Betriebssystemversion zurückgefallen sind. (Der Google-Client tut dies in der Regel nicht.)
Auf Systemebene wirken sich A/B-Systemupdates auf Folgendes aus:
-
Partitionsauswahl (Slots),
update_engine
-Daemon und Bootloader-Interaktionen (siehe unten) - Build-Prozess und Generierung des OTA-Update-Pakets (siehe A/B-Updates implementieren)
Partitionsauswahl (Zeitfenster)
Bei A/B-Systemupdates werden zwei Partitionen verwendet, die als Slots bezeichnet werden (normalerweise Slot A und Slot B). Das System wird über den aktuellen Steckplatz ausgeführt, während im normalen Betrieb nicht auf die Partitionen im nicht verwendeten Steckplatz zugegriffen wird. Dieser Ansatz macht Updates fehlertolerant, da der nicht verwendete Steckplatz als Fallback verwendet wird: Wenn während oder unmittelbar nach einem Update ein Fehler auftritt, kann das System zu dem alten Steckplatz zurückkehren und weiterhin ein funktionierendes System haben. Dazu darf keine Partition, die vom aktuellen Steckplatz verwendet wird, im Rahmen des Over-the-air-Updates aktualisiert werden. Das gilt auch für Partitionen, von denen nur eine Kopie vorhanden ist.
Jeder Steckplatz hat das Attribut bootable, das angibt, ob der Steckplatz ein ordnungsgemäßes System enthält, von dem das Gerät starten kann. Der aktuelle Steckplatz kann gestartet werden, wenn das System ausgeführt wird. Der andere Steckplatz enthält jedoch möglicherweise eine alte (noch korrekte) Version des Systems, eine neuere Version oder ungültige Daten. Unabhängig vom aktuellen Slot gibt es einen Slot, der der aktive Slot ist (derjenige, von dem aus der Bootloader beim nächsten Starten bootet) oder der bevorzugte Slot.
Jeder Steckplatz hat außerdem das vom Nutzerbereich festgelegte Attribut successful, das nur relevant ist, wenn der Steckplatz auch bootfähig ist. Ein erfolgreicher Slot sollte sich selbst starten, ausführen und aktualisieren können. Ein bootfähiger Steckplatz, der nach mehreren Versuchen, von ihm zu starten, nicht als erfolgreich markiert wurde, sollte vom Bootloader als nicht bootfähig markiert werden. Dazu gehört auch, den aktiven Steckplatz in einen anderen bootfähigen Steckplatz zu ändern (normalerweise in den Steckplatz, der unmittelbar vor dem Versuch zum Starten in den neuen, aktiven Steckplatz ausgeführt wurde). Die spezifischen Details der Benutzeroberfläche sind in
boot_control.h
definiert.
Update-Engine-Daemon
Bei A/B-Systemupdates wird ein Hintergrund-Daemon namens update_engine
verwendet, um das System auf das Booten in einer neuen, aktualisierten Version vorzubereiten. Dieser Daemon kann die folgenden Aktionen ausführen:
- Lesen Sie aus den aktuellen A/B-Partitionen des Slots und schreiben Sie alle Daten gemäß den Anweisungen im OTA-Paket in die nicht verwendeten A/B-Partitionen des Slots.
- Rufen Sie die
boot_control
-Benutzeroberfläche in einem vordefinierten Workflow auf. - Führen Sie ein Programm nach der Installation von der neuen Partition aus, nachdem Sie alle nicht verwendeten Steckplatzpartitionen gemäß der Anleitung im OTA-Paket geschrieben haben. 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 eingeschränkt. Diese Richtlinien und Funktionen können erst aktualisiert werden, wenn das System in einer neuen Version gestartet wird. Um ein stabiles System zu gewährleisten, darf der Aktualisierungsprozess die Partitionstabelle, den Inhalt von Partitionen im aktuellen Steckplatz oder den Inhalt von nicht A/B-Partitionen, die nicht durch Zurücksetzen auf die Werkseinstellungen gelöscht werden können, nicht ändern.
Update-Engine-Quelle
Die update_engine
-Quelle befindet sich unter system/update_engine
. Die A/B OTA-Dexopt-Dateien werden zwischen installd
und einem Paketmanager aufgeteilt:
-
frameworks/native/cmds/installd/
ota* enthält das Postinstallationsskript, 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 Verschiebungsskript. -
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
.
Engine-Logs aktualisieren
Bei Android 8.x und niedrigeren Versionen finden Sie die update_engine
-Protokolle unter logcat
und im Fehlerbericht. Wenn Sie die update_engine
-Protokolle im Dateisystem verfügbar machen möchten, patchen Sie die folgenden Änderungen in Ihren Build ein:
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 Protokoll werden die fünf letzten Protokolle unter /data/misc/update_engine_log/
gespeichert. Nutzer mit der Gruppen-ID log können auf die Dateisystemprotokolle zugreifen.
Bootloader-Interaktionen
Der boot_control
HAL wird von update_engine
(und möglicherweise anderen Daemons) verwendet, um dem Bootloader mitzuteilen, von wo aus er starten soll. Gängige Beispielszenarien und ihre zugehörigen Status:
- Normaler Fall: Das System wird über seinen aktuellen Steckplatz ausgeführt, also entweder über Steckplatz A oder B. Bisher wurden keine Updates angewendet. Der aktuelle Slot des Systems ist bootfähig, erfolgreich und der aktive Slot.
- Update läuft: Das System wird über Steckplatz B ausgeführt. Steckplatz B ist also bootfähig, erfolgreich und aktiv. Steckplatz A wurde als nicht bootfähig gekennzeichnet, da der Inhalt von Steckplatz A aktualisiert wird, die Aktualisierung aber noch nicht abgeschlossen ist. Ein Neustart in diesem Zustand sollte das Booten von Steckplatz B fortsetzen.
- Update angewendet, Neustart ausstehend: Das System wird über Steckplatz B ausgeführt. Steckplatz B ist bootfähig und der Start ist erfolgreich, aber Steckplatz A wurde als aktiv markiert und ist daher auch als bootfähig gekennzeichnet. Steckplatz A ist noch nicht als erfolgreich gekennzeichnet und der Bootloader sollte einige Versuche zum Starten von Steckplatz A ausführen.
-
System neu gestartet, um ein neues Update auszuführen: Das System wird zum ersten Mal über Steckplatz A ausgeführt. Steckplatz B kann noch gestartet werden und der Start ist erfolgreich, während Steckplatz A nur gestartet werden kann und noch aktiv ist, aber der Start nicht erfolgreich ist. Ein User-Space-Daemon,
update_verifier
, sollte Steckplatz A nach einigen Prüfungen als erfolgreich kennzeichnen.
Unterstützung für Streaming-Updates
Auf den Geräten der Nutzer ist nicht immer genügend Speicherplatz auf /data
verfügbar, um das Update-Paket herunterzuladen. Da weder OEMs noch Nutzer Speicherplatz auf einer /cache
-Partition verschwenden möchten, müssen einige Nutzer ohne Updates auskommen, weil das Gerät das Update-Paket nicht speichern kann. Um dieses Problem zu beheben, wurde in Android 8.0 die Unterstützung für das Streaming von A/B-Updates hinzugefügt, bei denen Blöcke beim Herunterladen direkt in die B-Partition geschrieben werden, ohne dass die Blöcke auf /data
gespeichert werden müssen. Für Streaming-A/B-Tests ist fast kein temporärer Speicherplatz erforderlich. Es reicht ein Speicherplatz für etwa 100 KiB Metadaten.
Wenn Sie Streaming-Updates unter Android 7.1 aktivieren möchten, wählen Sie die folgenden Patches aus:
- Proxy-Auflösungsanfrage abbrechen
- Fehler beim Beenden einer Übertragung beim Auflösen von Proxys behoben
- Unittest für TerminateTransfer zwischen Bereichen hinzufügen
- Cleanup the RetryTimeoutCallback()
Diese Patches sind erforderlich, um das Streaming von A/B-Updates unter 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 Download verfügbar ist. Richtlinien auf dem Gerät können den Download und die Anwendung der Nutzlast je nach Akkustand, Nutzeraktivität, Ladestatus oder anderen Richtlinien verzögern. Da das Update im Hintergrund ausgeführt wird, wissen Nutzer möglicherweise nicht, dass es läuft. Das bedeutet, dass der Updatevorgang jederzeit aufgrund von Richtlinien, unerwarteten Neustarts oder Nutzeraktionen 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 Installation ohne Streaming verwendet werden. Der Server kann die Metadaten verwenden, um dem Client mitzuteilen, dass es sich um ein Streaming handelt, damit der Client die OTA-Daten korrekt an update_engine
weitergibt. Gerätehersteller mit eigenem Server und Client können Streaming-Updates aktivieren, indem sie dafür sorgen, dass der Server das Streaming des Updates erkennt (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 es sich bei dem Paket um die Streamingvariante handelt, nutzen, um ein Flag an den Client zu senden, um die Übertragung an die Framework-Seite als Streaming auszulösen.
So läuft das Update ab, wenn eine Nutzlast verfügbar ist:
Schritt | Aktivitäten |
---|---|
1 |
Der aktuelle Slot (oder „Quell-Slot“) wird mit markBootSuccessful() als erfolgreich markiert (falls noch nicht geschehen).
|
2 |
Der nicht verwendete Steckplatz (oder „Ziel-Steckplatz“) wird durch Aufrufen 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 nicht verwendeten Steckplatz 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 Slot als erfolgreich gekennzeichnet, auch wenn andere wichtige Komponenten beschädigt sind (z. B. die Benutzeroberfläche in einem Absturz-Loop), da neue Software zur Behebung dieser Probleme bereitgestellt werden kann. Die Update-Nutzlast ist ein undurchsichtiger Blob mit der Anleitung zum Aktualisieren auf die neue Version. Die Update-Nutzlast besteht aus Folgendem:
|
3 | Die Nutzlastmetadaten werden heruntergeladen. |
4 | Für jeden in den Metadaten definierten Vorgang werden die zugehörigen Daten (falls vorhanden) nacheinander in den Arbeitsspeicher heruntergeladen, der Vorgang angewendet und der zugewiesene Arbeitsspeicher verworfen. |
5 | Die gesamten Partitionen werden noch einmal gelesen und anhand des erwarteten Hashwerts überprüft. |
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 mit möglicherweise einer anderen Nutzlast noch einmal versucht. Wenn alle bisherigen Schritte erfolgreich waren, ist auch die Aktualisierung erfolgreich und der letzte Schritt wird ausgeführt. |
7 |
Der nicht verwendete Steckplatz wird durch Aufrufen von setActiveBootSlot() als aktiv markiert.
Wenn Sie den nicht verwendeten Steckplatz als aktiv markieren, bedeutet das nicht, dass der Bootvorgang abgeschlossen wird. Der Bootloader (oder das System selbst) kann den aktiven Steckplatz zurückschalten, wenn er keinen erfolgreichen Status liest.
|
8 |
Nach der Installation (siehe unten) wird ein Programm aus der „neuen Updateversion“ ausgeführt, während es noch in der alten Version ausgeführt wird. Wenn dieser Schritt im OTA-Paket definiert ist, ist er erforderlich und das Programm muss mit dem Rückgabecode 0 zurückkehren. Andernfalls schlägt das Update fehl.
|
9 |
Nachdem das System weit genug in den neuen Slot gestartet ist und die Prüfungen nach dem Neustart abgeschlossen sind, wird der aktuelle Slot (früher „Ziel-Slot“) durch Aufrufen von markBootSuccessful() als erfolgreich gekennzeichnet.
|
Nach der Installation
Für jede Partition, für die ein Schritt nach der Installation definiert ist, update_engine
mountet die neue Partition an einem bestimmten Speicherort und führt das in der OTA angegebene Programm relativ zur bereitgestellten Partition aus. Wenn das Programm nach der Installation 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
) bereitgestellt und der Befehl /postinstall_mount/usr/bin/postinstall
wird ausgeführt.
Damit die Installation erfolgreich abgeschlossen werden kann, muss der alte Kernel Folgendes tun können:
- Bringen Sie das neue Dateisystemformat an. Der Dateisystemtyp kann sich nur ändern, wenn er im alten Kernel unterstützt wird. Dazu gehören auch Details wie der Komprimierungsalgorithmus, der bei einem komprimierten Dateisystem (z. B. SquashFS) verwendet wird.
-
Informationen zum Programmformat der neuen Partition nach der Installation Wenn Sie ein ELF-Binärprogramm (Executable and Linkable Format) verwenden, muss es mit dem alten Kernel kompatibel sein. Beispiel: Ein neues 64-Bit-Programm, das auf einem alten 32-Bit-Kernel ausgeführt wird, wenn die Architektur von 32-Bit- zu 64-Bit-Builds gewechselt wurde. Sofern der Lader (
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önnen beispielsweise ein Shell-Script als Programm nach der Installation verwenden, das von der Shell-Binärdatei des alten Systems mit einer #!
-Markierung oben interpretiert wird. Anschließend können Sie Bibliothekspfade aus der neuen Umgebung einrichten, um ein komplexeres binäres Programm nach der Installation auszuführen. Alternativ können Sie den Schritt nach der Installation von einer speziellen kleineren Partition aus ausführen, damit das Dateisystemformat in der Hauptsystempartition aktualisiert werden kann, ohne dass Probleme mit der Abwärtskompatibilität oder Zwischenupdates auftreten. So können Nutzer direkt von einem Werks-Image 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 die Ausführung von Aufgaben, die auf einem bestimmten Gerät aufgrund des Designs 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 nach der Installation wird im SELinux-Kontext postinstall
ausgeführt. Alle Dateien in der neu bereitgestellten Partition werden nach dem Neustart in diesem neuen System mit postinstall_file
getaggt, unabhängig von ihren Attributen. Änderungen an den SELinux-Attributen im neuen System wirken sich nicht auf den Schritt nach der Installation aus. Wenn das Programm nach der Installation zusätzliche Berechtigungen benötigt, müssen diese dem Kontext nach der Installation 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 dem 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 der verifizierte Bootmodus oder dm-verity eine Beschädigung erkennen. Nach Abschluss der Prüfung wird der Start von update_verifier
als erfolgreich markiert.
update_verifier
liest nur die in /data/ota_package/care_map.txt
aufgeführten Blöcke, die in einem A/B-OTA-Paket enthalten sind, wenn der AOSP-Code verwendet wird. Der Java-Systemaktualisierungsclient, 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 der neuen Version gestartet wurde.