Gli aggiornamenti di sistema A/B, noti anche come aggiornamenti continui, assicurano che un sistema di avvio funzionante rimanga sul disco durante un aggiornamento OTA (over-the-air) . Questo approccio riduce la probabilità che un dispositivo sia inattivo dopo un aggiornamento, il che significa un minor numero di sostituzioni e reflash dei dispositivi 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 partizione (slot) .
Gli aggiornamenti del sistema A/B offrono i seguenti vantaggi:
- Gli aggiornamenti OTA possono verificarsi 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 viene applicata (ad esempio, a causa di un flash difettoso), 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) interessano solo il set di partizioni inutilizzate e possono essere ripetuti. Tali errori diventano anche meno probabili perché il carico di I/O è deliberatamente basso per evitare di compromettere l'esperienza dell'utente.
- Gli aggiornamenti possono essere trasmessi in streaming ai dispositivi A/B, eliminando la necessità di scaricare il pacchetto prima di installarlo. Lo streaming significa che non è necessario che l'utente disponga di spazio libero sufficiente per archiviare il pacchetto di aggiornamento su
/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 problema di OTA o dm-verity errato, il dispositivo può riavviarsi in una vecchia immagine. (Android Verified Boot 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 vengono comunque serviti tramite HTTPS. Per i dispositivi che utilizzano l'infrastruttura OTA di Google, le modifiche al sistema sono tutte in AOSP e il codice client è fornito dai servizi di Google Play. 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 vengono eseguiti in background, non vengono più avviati dall'utente. Per evitare di disturbare 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 Verifica ora per consentire agli utenti di verificare la presenza dell'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 mentre trasmette il pacchetto di aggiornamento. - Segnala l'esito positivo o negativo dell'installazione sui tuoi server, in base al codice del risultato
update_engine
. Se l'aggiornamento viene applicato correttamente,update_engine
dirà al bootloader di avviare il nuovo sistema operativo al prossimo riavvio. Il bootloader eseguirà il fallback al vecchio sistema operativo se il nuovo sistema operativo non si avvia, quindi non è richiesto alcun lavoro dal 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 client potrebbe riconoscere che un pacchetto OTA parziale ("diff") non riesce 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 avviato una nuova versione del sistema operativo o se avrebbero dovuto farlo ma sono tornati alla versione precedente 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 del bootloader (descritte di seguito) - Processo di compilazione e generazione del pacchetto di aggiornamento OTA (descritto in Implementazione degli aggiornamenti A/B )
Selezione partizione (slot)
Gli aggiornamenti di sistema A/B utilizzano due set 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 ai guasti mantenendo lo slot inutilizzato come fallback: se si verifica un errore durante o immediatamente dopo un aggiornamento, il sistema può tornare allo slot precedente e continuare a disporre di un sistema funzionante. Per raggiungere questo obiettivo, nessuna partizione utilizzata dallo slot corrente deve essere aggiornata come parte dell'aggiornamento OTA (incluse 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 dallo slot corrente , c'è uno slot che è lo slot attivo (quello che il bootloader 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, eseguire e aggiornarsi. Uno slot avviabile che non è stato contrassegnato come riuscito (dopo che sono stati effettuati 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 nel nuovo, attivo). I dettagli specifici dell'interfaccia sono definiti in boot_control.h
.
Aggiorna il demone del motore
Gli aggiornamenti del 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:
- Leggere dalle partizioni dello slot A/B corrente e scrivere 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. - Eseguire un programma di 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 politiche e dalle funzionalità di SELinux nello slot corrente (tali politiche 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 deve 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 post-installazione, il binario per chroot, il clone installd che chiama dex2oat, lo script move-artifacts post-OTA 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, fare riferimento a /device/google/marlin/device-common.mk
.
Aggiorna i log del motore
Per le versioni di Android 8.x e precedenti, i log di 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 tua 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 del bootloader
L'HAL boot_control
viene utilizzato 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, sia lo slot A che 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é il contenuto dello slot A viene aggiornato ma non ancora completato. 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 un certo numero di tentativi di avvio dallo slot A dovrebbe essere effettuato dal bootloader.
- Sistema riavviato con un 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 hanno sempre 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 restano senza 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 di aggiornamenti A/B che scrivono i blocchi direttamente nella partizione B mentre vengono scaricati, senza dover archiviare i blocchi su /data
. Gli aggiornamenti A/B in streaming non richiedono quasi nessuno spazio di archiviazione temporaneo e richiedono 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 del proxy
- Risolto il problema con l'interruzione di un trasferimento durante la risoluzione dei proxy
- Aggiungi unit test per TerminateTransfer tra intervalli
- Pulisci il RetryTimeoutCallback()
Queste patch sono necessarie per supportare lo streaming di aggiornamenti A/B in Android 7.1 e versioni successive, sia che si utilizzi Google Mobile Services (GMS) o qualsiasi altro client di aggiornamento.
Durata 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 posticipare 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 politiche, 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 installazioni non in streaming. Il server può utilizzare i metadati per dire al client che è in streaming in modo che il client passerà correttamente l'OTA ad 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 streaming per inviare un flag al client per attivare il trasferimento al lato framework come streaming.
Dopo che un carico utile è disponibile, il processo di aggiornamento è il seguente:
Fare un passo | Attività |
---|---|
1 | Lo slot corrente (o "slot di origine") viene contrassegnato come riuscito (se non già contrassegnato) con markBootSuccessful() . |
2 | Lo slot non utilizzato (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 evitare che il bootloader ricada nello 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 riuscito anche se altri componenti principali sono danneggiati (come l'interfaccia utente in un ciclo di arresto anomalo) poiché è possibile eseguire il push di un nuovo software per risolverli i problemi.Il payload di aggiornamento è un blob opaco con le istruzioni per l'aggiornamento alla nuova versione. Il carico utile dell'aggiornamento è composto da:
|
3 | I metadati del carico utile vengono scaricati. |
4 | Per ogni operazione definita nei metadati, in 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 successivo all'installazione (se presente). In caso di errore durante l'esecuzione di qualsiasi passaggio, l'aggiornamento non riesce e viene riprovato con un possibile payload diverso. Se tutti i passaggi finora sono riusciti, l'aggiornamento ha esito positivo e viene eseguito l'ultimo passaggio. |
7 | Lo slot non utilizzato 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 corretto. |
8 | La post-installazione (descritta di seguito) implica l'esecuzione di un programma dalla versione "nuovo aggiornamento" mentre è ancora in esecuzione nella versione precedente. Se definito nel pacchetto OTA, questo passaggio è obbligatorio e il programma deve restituire con codice di uscita 0 ; in caso contrario, l'aggiornamento non riesce. | 9 | Dopo che il sistema si avvia correttamente nel nuovo slot e termina i controlli post-riavvio, lo slot ora corrente (in precedenza "slot di destinazione") viene contrassegnato come riuscito chiamando markBootSuccessful() . |
Post installazione
Per ogni partizione in cui è definita una fase di 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 di 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 esito positivo, il vecchio kernel deve essere in grado di:
- Monta il nuovo formato del 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 usato 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 di creare un binario statico, le librerie verranno caricate dalla vecchia immagine di sistema e non da quella nuova.
Ad esempio, potresti usare uno script di shell come programma post-installazione interpretato dal binario della shell del vecchio sistema con un #!
indicatore in alto), quindi impostare i percorsi della 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 intermedi; ciò consentirebbe agli utenti di aggiornare direttamente all'ultima versione da un'immagine di fabbrica.
Il nuovo programma di post-installazione è limitato dalle politiche di SELinux definite nel vecchio sistema. In quanto tale, il passaggio successivo all'installazione è adatto per eseguire attività richieste dalla progettazione su un determinato dispositivo o altre attività di massimo sforzo (ad es. aggiornamento del firmware o del bootloader compatibile con A/B, preparazione di copie dei database per la nuova versione, ecc. ). Il passaggio successivo all'installazione non è adatto per correzioni di bug una tantum prima del riavvio che richiedono autorizzazioni impreviste.
Il programma di post-installazione selezionato viene eseguito nel contesto di postinstall
di post-installazione. 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 sul passaggio successivo all'installazione. Se il programma di post-installazione necessita di autorizzazioni aggiuntive, queste devono essere aggiunte al contesto di 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, bootloader e kernel possono anche attivare un riavvio se l'avvio verificato o dm-verity rilevano un danneggiamento. Al termine del controllo, update_verifier
contrassegna l'avvio con esito positivo.
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 è stato avviato correttamente nella nuova versione.