Gli aggiornamenti di sistema A/B, noti anche come aggiornamenti continui, assicurano che un sistema di avvio funzionante rimanga sul disco durante un aggiornamento over-the-air (OTA) . Questo approccio riduce la probabilità di un dispositivo inattivo dopo un aggiornamento, il che significa meno sostituzioni del dispositivo e riflash del dispositivo presso i centri di riparazione e garanzia. Anche altri sistemi operativi di livello commerciale come ChromeOS utilizzano correttamente gli aggiornamenti A/B.
Per ulteriori informazioni sugli aggiornamenti del sistema A/B e su come funzionano, vedere Selezione delle partizioni (slot) .
Gli aggiornamenti del sistema A/B offrono i seguenti vantaggi:
- Gli aggiornamenti OTA possono avvenire mentre il sistema è in esecuzione, senza interrompere l'utente. Gli utenti possono continuare a utilizzare i propri dispositivi durante un OTA: l'unico tempo di inattività durante un aggiornamento è quando il dispositivo si riavvia nella partizione del disco aggiornata.
- Dopo un aggiornamento, il riavvio non richiede più tempo di un normale riavvio.
- Se un OTA non si applica (ad esempio, a causa di un cattivo flash), l'utente non ne risentirà. L'utente continuerà a eseguire il vecchio sistema operativo e il client è libero di ritentare l'aggiornamento.
- Se viene applicato un aggiornamento OTA ma non si avvia, il dispositivo si riavvierà nella vecchia partizione e rimarrà utilizzabile. Il client è libero di ritentare l'aggiornamento.
- Eventuali errori (come gli errori di I/O) influiscono solo sul set di partizioni inutilizzato e possono essere ritentati. Tali errori diventano anche meno probabili perché il carico di I/O è deliberatamente basso per evitare di degradare l'esperienza dell'utente.
- Gli aggiornamenti possono essere trasmessi ai dispositivi A/B, eliminando la necessità di scaricare il pacchetto prima di installarlo. Streaming significa che non è necessario che l'utente disponga di spazio libero sufficiente per archiviare il pacchetto di aggiornamento in
/data
o/cache
. - La partizione della cache non viene più utilizzata per archiviare i pacchetti di aggiornamento OTA, quindi non è necessario assicurarsi che la partizione della cache sia sufficientemente grande per gli aggiornamenti futuri.
- dm-verity garantisce che un dispositivo avvierà un'immagine non danneggiata. Se un dispositivo non si avvia a causa di un errore OTA o di un problema di dm-verity, il dispositivo può riavviarsi in una vecchia immagine. ( L'avvio verificato Android non richiede aggiornamenti A/B.)
Informazioni sugli aggiornamenti del sistema A/B
Gli aggiornamenti A/B richiedono modifiche sia al client che al sistema. Il server dei pacchetti OTA, tuttavia, non dovrebbe richiedere modifiche: i pacchetti di aggiornamento sono ancora serviti su HTTPS. Per i dispositivi che utilizzano l'infrastruttura OTA di Google, le modifiche al sistema sono tutte in AOSP e il codice client è fornito da Google Play Services. Gli OEM che non utilizzano l'infrastruttura OTA di Google potranno riutilizzare il codice di sistema AOSP ma dovranno fornire il proprio client.
Per gli OEM che forniscono il proprio cliente, il cliente deve:
- Decidi quando eseguire un aggiornamento. Poiché gli aggiornamenti A/B avvengono in background, non vengono più avviati dall'utente. Per evitare di interrompere gli utenti, si consiglia di pianificare gli aggiornamenti quando il dispositivo è in modalità di manutenzione inattiva, ad esempio durante la notte, e su Wi-Fi. Tuttavia, il tuo cliente può utilizzare qualsiasi euristica desideri.
- Effettua il check-in con i server dei pacchetti OTA e determina se è disponibile un aggiornamento. Questo dovrebbe essere per lo più lo stesso del tuo codice client esistente, tranne per il fatto che vorrai segnalare che il dispositivo supporta A/B. (Il client di Google include anche un pulsante Controlla ora per consentire agli utenti di verificare l'ultimo aggiornamento.)
- Chiama
update_engine
con l'URL HTTPS per il tuo pacchetto di aggiornamento, supponendo che ne sia disponibile uno.update_engine
aggiornerà i blocchi grezzi sulla partizione attualmente inutilizzata durante lo streaming del pacchetto di aggiornamento. - Segnala i successi o gli errori di installazione ai tuoi server, in base al codice risultato
update_engine
. Se l'aggiornamento viene applicato correttamente,update_engine
dirà al bootloader di avviarsi nel nuovo sistema operativo al successivo riavvio. Il bootloader tornerà al vecchio sistema operativo se il nuovo sistema operativo non si avvia, quindi non è richiesto alcun intervento da parte del client. Se l'aggiornamento non riesce, il client deve decidere quando (e se) riprovare, in base al codice di errore dettagliato. Ad esempio, un buon cliente potrebbe riconoscere che un pacchetto OTA parziale ("diff") fallisce e provare invece un pacchetto OTA completo.
Facoltativamente, il cliente può:
- Mostra una notifica che chiede all'utente di riavviare. Se desideri implementare una politica in cui l'utente è incoraggiato ad aggiornare regolarmente, questa notifica può essere aggiunta al tuo client. Se il client non richiede agli utenti, gli utenti riceveranno comunque l'aggiornamento al successivo riavvio. (Il client di Google ha un ritardo configurabile per ogni aggiornamento.)
- Mostra una notifica che informa gli utenti se hanno eseguito l'avvio in una nuova versione del sistema operativo o se era previsto che lo facessero ma sono tornati alla vecchia versione del sistema operativo. (Il client di Google in genere non fa nessuno dei due.)
Sul lato del sistema, gli aggiornamenti del sistema A/B influiscono su quanto segue:
- Selezione della partizione (slot), demone
update_engine
e interazioni con il bootloader (descritte di seguito) - Processo di compilazione e generazione del pacchetto di aggiornamento OTA (descritto in Implementazione degli aggiornamenti A/B )
Selezione della partizione (slot)
Gli aggiornamenti di sistema A/B utilizzano due serie di partizioni denominate slot (normalmente slot A e slot B). Il sistema viene eseguito dallo slot corrente mentre le partizioni nello slot inutilizzato non sono accessibili dal sistema in esecuzione durante il normale funzionamento. Questo approccio rende gli aggiornamenti resistenti agli errori mantenendo lo slot inutilizzato come fallback: se si verifica un errore durante o immediatamente dopo un aggiornamento, il sistema può tornare al vecchio slot e continuare ad avere un sistema funzionante. Per raggiungere questo obiettivo, nessuna partizione utilizzata dallo slot corrente deve essere aggiornata come parte dell'aggiornamento OTA (comprese le partizioni per le quali esiste una sola copia).
Ogni slot ha un attributo avviabile che indica se lo slot contiene un sistema corretto da cui il dispositivo può essere avviato. Lo slot corrente è avviabile quando il sistema è in esecuzione, ma l'altro slot potrebbe avere una versione precedente (ancora corretta) del sistema, una versione più recente o dati non validi. Indipendentemente da quale sia lo slot corrente , esiste uno slot che è lo slot attivo (quello da cui il bootloader si avvierà all'avvio successivo) o lo slot preferito .
Ogni slot ha anche un attributo di successo impostato dallo spazio utente, che è rilevante solo se lo slot è anche avviabile. Uno slot di successo dovrebbe essere in grado di avviarsi, funzionare e aggiornarsi da solo. Uno slot avviabile che non è stato contrassegnato come riuscito (dopo diversi tentativi di avvio da esso) dovrebbe essere contrassegnato come non avviabile dal bootloader, inclusa la modifica dello slot attivo in un altro slot avviabile (normalmente nello slot in esecuzione immediatamente prima del tentativo di avvio in quello nuovo, attivo). I dettagli specifici dell'interfaccia sono definiti in boot_control.h
.
Aggiorna il demone del motore
Gli aggiornamenti di sistema A/B utilizzano un demone in background chiamato update_engine
per preparare il sistema all'avvio in una nuova versione aggiornata. Questo demone può eseguire le seguenti azioni:
- Leggi dalle partizioni dello slot A/B corrente e scrivi tutti i dati nelle partizioni dello slot A/B inutilizzate come indicato dal pacchetto OTA.
- Chiama l'interfaccia
boot_control
in un flusso di lavoro predefinito. - Esegui un programma post-installazione dalla nuova partizione dopo aver scritto tutte le partizioni di slot inutilizzate, come indicato dal pacchetto OTA. (Per i dettagli, vedere Post-installazione ).
Poiché il demone update_engine
non è coinvolto nel processo di avvio stesso, è limitato in ciò che può fare durante un aggiornamento dalle policy e funzionalità di SELinux nello slot corrente (tali policy e funzionalità non possono essere aggiornate finché il sistema non si avvia in un nuova versione). Per mantenere un sistema robusto, il processo di aggiornamento non dovrebbe modificare la tabella delle partizioni, il contenuto delle partizioni nello slot corrente o il contenuto delle partizioni non A/B che non possono essere cancellate con un ripristino delle impostazioni di fabbrica.
Aggiorna la fonte del motore
L'origine update_engine
si trova in system/update_engine
. I file dexopt A/B OTA sono divisi tra installd
e un gestore di pacchetti:
-
frameworks/native/cmds/installd/
ota* include lo script postinstall, il binario per chroot, il clone installd che chiama dex2oat, lo script post-OTA move-artifacts e il file rc per lo script move. -
frameworks/base/services/core/java/com/android/server/pm/OtaDexoptService.java
(piùOtaDexoptShellCommand
) è il gestore di pacchetti che prepara i comandi dex2oat per le applicazioni.
Per un esempio funzionante, fai riferimento a /device/google/marlin/device-common.mk
.
Aggiorna i log del motore
Per le versioni di Android 8.x e precedenti, i log update_engine
sono disponibili in logcat
e nella segnalazione di bug. Per rendere disponibili i log update_engine
nel file system, applica le seguenti modifiche alla build:
Queste modifiche salvano una copia del registro update_engine
più recente in /data/misc/update_engine_log/update_engine. YEAR - TIME
. Oltre al registro corrente, i cinque registri più recenti vengono salvati in /data/misc/update_engine_log/
. Gli utenti con l'ID del gruppo di log potranno accedere ai log del file system.
Interazioni con il bootloader
L'HAL boot_control
è usato da update_engine
(e possibilmente altri demoni) per istruire il bootloader da cosa avviare. Gli scenari di esempio comuni e i relativi stati associati includono quanto segue:
- Caso normale : il sistema è in esecuzione dallo slot corrente, slot A o B. Finora non sono stati applicati aggiornamenti. Lo slot corrente del sistema è avviabile, riuscito e lo slot attivo.
- Aggiornamento in corso : il sistema è in esecuzione dallo slot B, quindi lo slot B è lo slot avviabile, riuscito e attivo. Lo slot A è stato contrassegnato come non avviabile poiché i contenuti dello slot A sono in fase di aggiornamento ma non ancora completati. Un riavvio in questo stato dovrebbe continuare l'avvio dallo slot B.
- Aggiornamento applicato, riavvio in sospeso : il sistema è in esecuzione dallo slot B, lo slot B è avviabile e ha esito positivo, ma lo slot A è stato contrassegnato come attivo (e quindi è contrassegnato come avviabile). Lo slot A non è ancora contrassegnato come riuscito e il bootloader dovrebbe effettuare un certo numero di tentativi di avvio dallo slot A.
- Sistema riavviato nel nuovo aggiornamento : il sistema è in esecuzione dallo slot A per la prima volta, lo slot B è ancora avviabile e ha esito positivo mentre lo slot A è solo avviabile e ancora attivo ma non riuscito. Un demone dello spazio utente,
update_verifier
, dovrebbe contrassegnare lo slot A come riuscito dopo che sono stati effettuati alcuni controlli.
Supporto per l'aggiornamento in streaming
I dispositivi degli utenti non dispongono sempre di spazio sufficiente su /data
per scaricare il pacchetto di aggiornamento. Poiché né gli OEM né gli utenti vogliono sprecare spazio su una partizione /cache
, alcuni utenti non hanno aggiornamenti perché il dispositivo non ha un posto dove archiviare il pacchetto di aggiornamento. Per risolvere questo problema, Android 8.0 ha aggiunto il supporto per lo streaming degli aggiornamenti A/B che scrivono i blocchi direttamente nella partizione B mentre vengono scaricati, senza dover memorizzare i blocchi su /data
. Gli aggiornamenti A/B in streaming non richiedono quasi alcuna archiviazione temporanea e richiedono solo spazio di archiviazione sufficiente per circa 100 KiB di metadati.
Per abilitare gli aggiornamenti in streaming in Android 7.1, seleziona le seguenti patch:
- Consenti di annullare una richiesta di risoluzione delega
- Correzione dell'interruzione di un trasferimento durante la risoluzione dei proxy
- Aggiungi unit test per TerminateTransfer tra intervalli
- Pulisci RetryTimeoutCallback()
Queste patch sono necessarie per supportare gli aggiornamenti A/B in streaming in Android 7.1 e versioni successive, sia che si utilizzino Google Mobile Services (GMS) o qualsiasi altro client di aggiornamento.
Vita di un aggiornamento A/B
Il processo di aggiornamento inizia quando un pacchetto OTA (indicato nel codice come payload ) è disponibile per il download. I criteri nel dispositivo possono rinviare il download del payload e l'applicazione in base al livello della batteria, all'attività dell'utente, allo stato di carica o ad altri criteri. Inoltre, poiché l'aggiornamento viene eseguito in background, gli utenti potrebbero non sapere che è in corso un aggiornamento. Tutto ciò significa che il processo di aggiornamento potrebbe essere interrotto in qualsiasi momento a causa di criteri, riavvii imprevisti o azioni dell'utente.
Facoltativamente, i metadati nel pacchetto OTA stesso indicano che l'aggiornamento può essere trasmesso in streaming; lo stesso pacchetto può essere utilizzato anche per l'installazione non in streaming. Il server può utilizzare i metadati per comunicare al client che è in streaming in modo che il client trasferisca correttamente l'OTA a update_engine
. I produttori di dispositivi con il proprio server e client possono abilitare gli aggiornamenti in streaming assicurandosi che il server identifichi l'aggiornamento in streaming (o presuppone che tutti gli aggiornamenti siano in streaming) e che il client effettui la chiamata corretta a update_engine
per lo streaming. I produttori possono utilizzare il fatto che il pacchetto è della variante di streaming per inviare un flag al client per attivare il passaggio al framework come streaming.
Dopo che un payload è disponibile, il processo di aggiornamento è il seguente:
Fare un passo | Attività |
---|---|
1 | Lo slot corrente (o "slot di origine") è contrassegnato come riuscito (se non è già contrassegnato) con markBootSuccessful() . |
2 | Lo slot inutilizzato (o "slot di destinazione") viene contrassegnato come non avviabile chiamando la funzione setSlotAsUnbootable() . Lo slot corrente è sempre contrassegnato come riuscito all'inizio dell'aggiornamento per impedire al bootloader di tornare allo slot inutilizzato, che presto avrà dati non validi. Se il sistema ha raggiunto il punto in cui può iniziare ad applicare un aggiornamento, lo slot corrente viene contrassegnato come corretto anche se altri componenti principali sono danneggiati (come l'interfaccia utente in un ciclo di arresto anomalo) poiché è possibile eseguire il push di nuovo software per risolverli i problemi.Il payload di aggiornamento è un BLOB opaco con le istruzioni per l'aggiornamento alla nuova versione. Il payload di aggiornamento è costituito da quanto segue:
|
3 | I metadati del payload vengono scaricati. |
4 | Per ogni operazione definita nei metadati, nell'ordine, i dati associati (se presenti) vengono scaricati in memoria, l'operazione viene applicata e la memoria associata viene eliminata. |
5 | Le intere partizioni vengono rilette e verificate rispetto all'hash previsto. |
6 | Viene eseguito il passaggio post-installazione (se presente). In caso di errore durante l'esecuzione di qualsiasi passaggio, l'aggiornamento fallisce e viene ritentato con possibilmente un payload diverso. Se tutti i passaggi finora sono riusciti, l'aggiornamento ha esito positivo e viene eseguito l'ultimo passaggio. |
7 | Lo slot inutilizzato viene contrassegnato come attivo chiamando setActiveBootSlot() . Contrassegnare lo slot inutilizzato come attivo non significa che finirà l'avvio. Il bootloader (o il sistema stesso) può ripristinare lo slot attivo se non legge uno stato riuscito. |
8 | La post-installazione (descritta di seguito) comporta l'esecuzione di un programma dalla versione del "nuovo aggiornamento" mentre è ancora in esecuzione nella versione precedente. Se definito nel pacchetto OTA, questo passaggio è obbligatorio e il programma deve tornare con il codice di uscita 0 ; in caso contrario, l'aggiornamento non riesce. | 9 | Dopo che il sistema si è avviato correttamente nel nuovo slot e ha terminato i controlli post-riavvio, lo slot corrente (precedentemente lo "slot di destinazione") viene contrassegnato come riuscito chiamando markBootSuccessful() . |
Post installazione
Per ogni partizione in cui è definito un passaggio post-installazione, update_engine
monta la nuova partizione in una posizione specifica ed esegue il programma specificato nell'OTA relativo alla partizione montata. Ad esempio, se il programma post-installazione è definito come usr/bin/postinstall
nella partizione di sistema, questa partizione dallo slot inutilizzato verrà montata in una posizione fissa (come /postinstall_mount
) e /postinstall_mount/usr/bin/postinstall
viene eseguito il comando /postinstall_mount/usr/bin/postinstall
.
Affinché la post-installazione abbia successo, il vecchio kernel deve essere in grado di:
- Monta il nuovo formato di filesystem . Il tipo di filesystem non può cambiare a meno che non ci sia supporto per esso nel vecchio kernel, inclusi dettagli come l'algoritmo di compressione utilizzato se si usa un filesystem compresso (es. SquashFS).
- Comprendere il formato del programma post-installazione della nuova partizione . Se si utilizza un binario ELF (Executable and Linkable Format), dovrebbe essere compatibile con il vecchio kernel (ad esempio un nuovo programma a 64 bit in esecuzione su un vecchio kernel a 32 bit se l'architettura è passata da build a 32 a 64 bit). A meno che al caricatore (
ld
) non venga richiesto di utilizzare altri percorsi o creare un binario statico, le librerie verranno caricate dalla vecchia immagine di sistema e non da quella nuova.
Ad esempio, potresti utilizzare uno script di shell come programma post-installazione interpretato dal binario di shell del vecchio sistema con un #!
marcatore in alto), quindi impostare i percorsi di libreria dal nuovo ambiente per l'esecuzione di un programma binario post-installazione più complesso. In alternativa, è possibile eseguire il passaggio post-installazione da una partizione più piccola dedicata per consentire l'aggiornamento del formato del filesystem nella partizione di sistema principale senza incorrere in problemi di compatibilità con le versioni precedenti o aggiornamenti preliminari; ciò consentirebbe agli utenti di aggiornare direttamente all'ultima versione da un'immagine di fabbrica.
Il nuovo programma post-installazione è limitato dalle politiche SELinux definite nel vecchio sistema. In quanto tale, la fase post-installazione è adatta per eseguire le attività richieste dalla progettazione su un determinato dispositivo o altre attività più impegnative (ad esempio, l'aggiornamento del firmware o del bootloader compatibile con A/B, la preparazione di copie dei database per la nuova versione, ecc. ). Il passaggio post-installazione non è adatto per correzioni di bug una tantum prima del riavvio che richiedono autorizzazioni impreviste.
Il programma post-installazione selezionato viene eseguito nel contesto SELinux postinstall
. Tutti i file nella nuova partizione montata verranno contrassegnati con postinstall_file
, indipendentemente dai loro attributi dopo il riavvio in quel nuovo sistema. Le modifiche agli attributi di SELinux nel nuovo sistema non influiranno sulla fase post-installazione. Se il programma post-installazione necessita di autorizzazioni aggiuntive, queste devono essere aggiunte al contesto post-installazione.
Dopo il riavvio
Dopo il riavvio, update_verifier
attiva il controllo di integrità utilizzando dm-verity. Questo controllo inizia prima di zygote per evitare che i servizi Java apportino modifiche irreversibili che impedirebbero un rollback sicuro. Durante questo processo, il bootloader e il kernel possono anche attivare un riavvio se l'avvio verificato o il dm-verity rilevano eventuali danneggiamenti. Al termine del controllo, update_verifier
contrassegna l'avvio come riuscito.
update_verifier
leggerà solo i blocchi elencati in /data/ota_package/care_map.txt
, che è incluso in un pacchetto A/B OTA quando si utilizza il codice AOSP. Il client di aggiornamento del sistema Java, come GmsCore, estrae care_map.txt
, imposta l'autorizzazione di accesso prima di riavviare il dispositivo ed elimina il file estratto dopo che il sistema si è avviato correttamente nella nuova versione.