Gli aggiornamenti di sistema A/B legacy, noti anche come aggiornamenti senza interruzioni , assicurano che un sistema di avvio operativo 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 e riflash dei dispositivi nei centri di riparazione e garanzia. Anche altri sistemi operativi di livello commerciale, come ChromeOS, utilizzano gli aggiornamenti A/B con successo.
Per ulteriori informazioni sugli aggiornamenti del sistema A/B e sul loro funzionamento, consulta Selezione della partizione (slot).
Gli aggiornamenti di sistema A/B offrono i seguenti vantaggi:
- Gli aggiornamenti OTA possono avvenire durante il funzionamento del sistema, senza interrompere l'utente. Gli utenti possono continuare a utilizzare i propri dispositivi durante un aggiornamento OTA. L'unico tempo di riposo durante un aggiornamento avviene quando il dispositivo si riavvia nella partizione del disco aggiornata.
- Dopo un aggiornamento, il riavvio non richiede più tempo di un riavvio normale.
- Se l'applicazione di un aggiornamento OTA non va a buon fine (ad esempio a causa di un flash errato), l'utente non sarà coinvolto. L'utente continuerà a utilizzare il vecchio sistema operativo e il cliente potrà riprovare a eseguire l'aggiornamento.
- Se viene applicato un aggiornamento OTA, ma non riesce ad avviarsi, il dispositivo si riavvia nella vecchia partizione e rimane utilizzabile. Il cliente è libero di riprovare l'aggiornamento.
- Eventuali errori (ad esempio errori di I/O) interessano solo il set di partizioni non utilizzato e possono essere riprovati. Inoltre, questi errori sono meno probabili perché il carico I/O è deliberatamente ridotto per evitare di peggiorare l'esperienza 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 memorizzare il pacchetto di aggiornamento su
/data
o/cache
. - La partizione della cache non viene più utilizzata per memorizzare 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 avvii un'immagine non danneggiata. Se un dispositivo non si avvia a causa di un problema con un aggiornamento OTA o con dm-verity, può riavviarsi in una vecchia immagine. ( Avvio verificato di Android non richiede aggiornamenti A/B.)
Informazioni sugli aggiornamenti di sistema A/B
Gli aggiornamenti A/B richiedono modifiche sia al client sia al sistema. Tuttavia, il server dei pacchetti OTA non dovrebbe richiedere modifiche: i pacchetti di aggiornamento vengono comunque pubblicati tramite HTTPS. Per i dispositivi che utilizzano l'infrastruttura OTA di Google, le modifiche di 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 client, il client deve:
- Decidi quando eseguire un aggiornamento. Poiché gli aggiornamenti A/B vengono eseguiti in background, non sono più avviati dall'utente. Per evitare di disturbare gli utenti, è consigliabile pianificare gli aggiornamenti quando il dispositivo è in modalità di manutenzione inattiva, ad esempio di notte, e connesso al Wi-Fi. Tuttavia, il client può utilizzare qualsiasi euristica.
- Controlla i server dei pacchetti OTA e determina se è disponibile un aggiornamento. Questo dovrebbe essere simile al codice client esistente, tranne per il fatto che dovrai indicare che il dispositivo supporta A/B. Il client di Google include anche un pulsante Verifica ora per consentire agli utenti di controllare se è disponibile l'aggiornamento più recente.
-
Chiama
update_engine
con l'URL HTTPS del pacchetto di aggiornamento, se disponibile.update_engine
aggiornerà i blocchi non elaborati della partizione attualmente non utilizzata durante lo streaming del pacchetto di aggiornamento. -
Segnala i successi o gli errori di installazione ai tuoi server in base al
update_engine
codice di risultato. Se l'aggiornamento viene applicato correttamente,update_engine
dirà al bootloader di avviare il nuovo sistema operativo al riavvio successivo. Il bootloader tornerà al vecchio sistema operativo se il nuovo sistema operativo non riesce ad avviarsi, quindi non è richiesta alcuna operazione da parte del cliente. Se l'aggiornamento non va a buon fine, il cliente 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 va a buon fine e provare un pacchetto OTA completo.
Facoltativamente, il cliente può:
- Mostra una notifica che chiede all'utente di riavviare il dispositivo. Se vuoi implementare un criterio in cui l'utente viene incoraggiato a eseguire regolarmente l'aggiornamento, questa notifica può essere aggiunta al tuo client. Se il client non chiede agli utenti, questi riceveranno comunque l'aggiornamento al successivo riavvio. (il client di Google ha un ritardo configurabile per aggiornamento).
- Mostra una notifica che indica agli utenti se hanno avviato una nuova versione del sistema operativo o se era previsto che lo facessero, ma è stata ripristinata la versione precedente del sistema operativo. (in genere il cliente di Google non lo fa).
A livello di sistema, gli aggiornamenti A/B influiscono su quanto segue:
-
Selezione della partizione (slot), del daemon
update_engine
e delle 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 insiemi di partizioni denominate slot (in genere slot A e slot B). Il sistema viene eseguito dallo slot attuale, mentre le partizioni nello slot inutilizzato non sono accessibili al 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ò eseguire il rollback al vecchio slot e continuare ad avere un sistema funzionante. Per raggiungere questo obiettivo, nessuna partizione utilizzata dall'slot attuale deve essere aggiornata nell'ambito dell'aggiornamento OTA (incluse le partizioni per le quali esiste una sola copia).
Ogni slot ha un attributo bootable che indica se lo slot contiene un sistema corretto da cui il dispositivo può avviarsi. 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 attuale, esiste uno slot che è lo slot attivo (da cui verrà avviato il bootloader al successivo avvio) o lo slot preferito.
Ogni slot ha anche un attributo successful impostato dallo spazio utente, che è pertinente
solo se lo slot è avviabile. Uno slot riuscito dovrebbe essere in grado di avviarsi, eseguire e aggiornarsi. Uno slot avviabile che non è stato contrassegnato come riuscito (dopo diversi tentativi di avviarlo) deve essere contrassegnato come non avviabile dal bootloader, inclusa la modifica dello slot attivo in un altro avviabile (di solito quello in esecuzione immediatamente prima del tentativo di avviare quello nuovo e attivo). I dettagli specifici dell'interfaccia sono definiti in
boot_control.h
.
Aggiorna il daemon 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 daemon può eseguire le seguenti azioni:
- Leggi dalle partizioni A/B dello slot corrente e scrivi tutti i dati nelle partizioni A/B dello slot non utilizzato come indicato dal pacchetto OTA.
- Chiama l'interfaccia
boot_control
in un flusso di lavoro predefinito. - Esegui un programma di post-installazione dalla nuova partizione dopo aver scritto tutte le partizioni dello slot inutilizzate, come indicato dal pacchetto OTA. Per maggiori dettagli, consulta Post-installazione.
Poiché il daemon update_engine
non è coinvolto nel processo di avvio stesso, le sue funzionalità durante un aggiornamento sono limitate dai criteri e dalle funzionalità di SELinux nello slot attuale (questi criteri e funzionalità non possono essere aggiornati fino a quando il sistema non avvia una nuova versione). Per mantenere un sistema affidabile, la procedura di aggiornamento
non deve modificare la tabella delle partizioni, i contenuti delle partizioni nello
slot corrente o i contenuti delle partizioni non A/B che non possono essere cancellati con un ripristino dei dati di fabbrica.
Aggiorna l'origine del motore
L'origine update_engine
si trova in
system/update_engine
. I file dexopt OTA A/B sono suddivisi tra installd
e un gestore pacchetti:
-
frameworks/native/cmds/installd/
ota* include lo script postinstall, il file binario per chroot, il clone di 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 dei pacchetti che prepara i comandi dex2oat per le applicazioni.
Per un esempio funzionante, consulta
/device/google/marlin/device-common.mk
.
Aggiorna i log del motore
Per le release di Android 8.x e precedenti, i log update_engine
sono disponibili in
logcat
e nel report del bug. Per rendere disponibili i log update_engine
nel file system, applica le seguenti modifiche alla build:
Queste modifiche salvano una copia del log update_engine
più recente in
/data/misc/update_engine_log/update_engine.YEAR-TIME
. Oltre al log corrente, i cinque log più recenti vengono salvati in
/data/misc/update_engine_log/
. Gli utenti con l'ID gruppo log potranno accedere ai log del file system.
Interazioni con il bootloader
L'HAL boot_control
viene utilizzato da update_engine
(e possibilmente da altri daemon) per indicare al bootloader da dove eseguire l'avvio. Ecco alcuni esempi di scenari comuni e i relativi stati associati:
- Caso normale: il sistema è in esecuzione dallo slot corrente, A o B. Finora non sono stati applicati aggiornamenti. Lo slot corrente del sistema è avviabile, corretto e attivo.
- Aggiornamento in corso: il sistema è in esecuzione dallo slot B, quindi lo slot B è avviabile, corretto e attivo. Lo slot A è stato contrassegnato come non avviabile perché i contenuti dello stesso sono in fase di aggiornamento, ma non sono ancora stati completati. Un riavvio in questo stato dovrebbe continuare l'avvio dallo slot B.
- Aggiornamento applicato, riavvio in attesa: il sistema è in esecuzione dallo slot B, lo slot B è avviabile e funzionante, ma lo slot A è stato contrassegnato come attivo (e quindi come avviabile). Lo slot A non è ancora contrassegnato come riuscito e il bootloader dovrebbe eseguire un certo numero di tentativi di avvio da questo slot.
-
Il sistema è stato riavviato in un nuovo aggiornamento: il sistema è in esecuzione dallo slot A per la prima volta, lo slot B è ancora avviabile e funzionante, mentre lo slot A è avviabile e ancora attivo, ma non funzionante. Un daemon dello spazio utente,
update_verifier
, deve contrassegnare lo slot A come riuscito dopo alcuni controlli.
Supporto per gli aggiornamenti al flusso di dati
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 non ricevono aggiornamenti perché il dispositivo non ha spazio per archiviare il pacchetto di aggiornamento. Per risolvere questo problema, Android 8.0 ha aggiunto il supporto per gli aggiornamenti A/B in streaming che scrivono i blocchi direttamente nella partizione B durante il download, senza doverli memorizzare su /data
. Gli aggiornamenti A/B in streaming non richiedono quasi spazio di archiviazione temporaneo e richiedono solo sufficiente spazio per circa 100 KiB di metadati.
Per abilitare gli aggiornamenti in streaming in Android 7.1, scegli le seguenti patch:
- Consentire l'annullamento di una richiesta di risoluzione del proxy
- Correzione dell'interruzione di un trasferimento durante la risoluzione dei proxy
- Aggiungi il test di unità 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, indipendentemente dall'utilizzo di Google Mobile Services (GMS) o di qualsiasi altro client di aggiornamento.
Durata di un aggiornamento A/B
La procedura di aggiornamento inizia quando un pacchetto OTA (chiamato nel codice payload) è disponibile per il download. I criteri nel dispositivo possono posticipare il download e l'applicazione del payload in base al livello della batteria, all'attività utente, allo stato di ricarica o ad altri criteri. Inoltre, poiché l'aggiornamento viene eseguito in background, gli utenti potrebbero non sapere che è in corso. Ciò significa che la procedura di aggiornamento potrebbe essere interrotta 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 potrebbe utilizzare i metadati per informare il client che è in streaming, in modo che il client trasferisca correttamente l'OTA a update_engine
. I produttori di dispositivi con server e client propri
possono attivare gli aggiornamenti in streaming assicurandosi che il server identifichi l'aggiornamento in streaming (o
assuma 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 appartiene alla variante di streaming per inviare un flag al client per attivare il trasferimento al framework come streaming.
Una volta disponibile un payload, la procedura di aggiornamento è la seguente:
Passaggio | Attività |
---|---|
1 |
Lo slot corrente (o "slot di origine") viene contrassegnato come riuscito (se non lo è già) con
markBootSuccessful() .
|
2 |
Lo slot inutilizzato (o "slot target") viene contrassegnato come non avviabile chiamando la funzione
setSlotAsUnbootable() . Lo slot corrente viene sempre contrassegnato come riuscito all'inizio dell'aggiornamento per impedire al bootloader di tornare allo slot inutilizzato, che a breve 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 (ad esempio l'interfaccia utente in un loop di arresto anomalo), poiché è possibile inviare nuovo
software per risolvere questi problemi. Il payload dell'aggiornamento è un blob opaco con le istruzioni per l'aggiornamento alla nuova versione. Il payload dell'aggiornamento è costituito da quanto segue:
|
3 | I metadati del payload vengono scaricati. |
4 | Per ogni operazione definita nei metadati, in ordine, i dati associati (se presenti) vengono scaricati nella memoria, l'operazione viene applicata e la memoria associata viene eliminata. |
5 | Tutte le partizioni vengono rilette e verificate in base all'hash previsto. |
6 | Viene eseguito il passaggio post-installazione (se presente). In caso di errore durante l'esecuzione di qualsiasi passaggio, l'aggiornamento non va a buon fine e viene riprovato con un possibile payload diverso. Se tutti i passaggi finora sono andati a buon fine, l'aggiornamento viene eseguito correttamente e viene eseguito l'ultimo passaggio. |
7 |
Lo slot inutilizzato viene contrassegnato come attivo chiamando setActiveBootSlot() .
Il fatto che lo slot inutilizzato sia contrassegnato come attivo non significa che l'avvio verrà completato. Il bootloader (o
il sistema stesso) può ripristinare lo slot attivo se non legge uno stato di esito positivo.
|
8 |
Il post-installazione (descritto di seguito) prevede 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 il codice di uscita 0 ;
in caso contrario, l'aggiornamento non va a buon fine.
|
9 |
Dopo che il sistema ha eseguito correttamente l'avvio nel nuovo slot e ha completato i controlli post-riavvio, lo slot corrente (in precedenza "slot di destinazione") viene contrassegnato come corretto chiamando markBootSuccessful() .
|
Post-installazione
Per ogni partizione in cui è definito un passaggio di post-installazione,
update_engine
monta la nuova partizione in una posizione specifica ed esegue il
programma specificato nell'OTA in base alla partizione montata. Ad esempio, se il
programma di post-installazione è definito come usr/bin/postinstall
nella partizione di sistema,
questa partizione dello slot inutilizzato verrà montata in una posizione fissa (ad esempio
/postinstall_mount
) e verrà eseguito il
comando /postinstall_mount/usr/bin/postinstall
.
Affinché il post-installazione vada a buon fine, il vecchio kernel deve essere in grado di:
- Monta il nuovo formato del file system. Il tipo di file system non può essere modificato a meno che non sia supportato nel vecchio kernel, inclusi dettagli come l'algoritmo di compressione utilizzato se si utilizza un file system compresso (ad es. SquashFS).
-
Comprendi il formato del programma post-installazione della nuova partizione. Se utilizzi un file binario ELF (Executable and Linkable Format), deve essere compatibile con il vecchio kernel (ad es. un nuovo programma a 64 bit in esecuzione su un vecchio kernel a 32 bit se l'architettura è passata dalle build a 32 a quelle a 64 bit). A meno che al caricatore (
ld
) non venga chiesto di utilizzare altri percorsi o di creare un file binario statico, le librerie verranno caricate dalla vecchia immagine di sistema e non dalla nuova.
Ad esempio, puoi utilizzare uno script shell come programma di post-installazione interpretato dal codice binario della shell del vecchio sistema con un indicatore #!
in alto, quindi configurare i percorsi della libreria dal nuovo ambiente per eseguire un programma di post-installazione binario più complesso. In alternativa, puoi eseguire il passaggio post-installazione da una
partizione più piccola dedicata per consentire l'aggiornamento del formato del file system nella partizione di sistema principale senza riscontrare problemi di compatibilità con le versioni precedenti o aggiornamenti intermedi; in questo modo, gli utenti potranno eseguire l'aggiornamento direttamente all'ultima versione da un'immagine di fabbrica.
Il nuovo programma di post-installazione è limitato dai criteri SELinux definiti nel vecchio sistema. Pertanto, il passaggio post-installazione è adatto per eseguire attività richieste dalla progettazione su un determinato dispositivo o altre attività di tipo best effort. Il passaggio post-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
postinstall
contesto SELinux. Tutti i file nella nuova partizione montata verranno помеченыpostinstall_file
, indipendentemente dai relativi attributi dopo il riavvio nel nuovo sistema. Le modifiche agli attributi SELinux nel nuovo sistema non influiranno sul passaggio post-installazione. Se il programma di post-installazione richiede autorizzazioni aggiuntive, queste devono essere aggiunte al contesto di post-installazione.
Dopo il riavvio
Dopo il riavvio, update_verifier
attiva il controllo dell'integrità utilizzando dm-verity.
Questo controllo viene avviato prima di zygote per evitare che i servizi Java apportino modifiche irreversibili che impediranno un rollback sicuro. Durante questo processo, il bootloader e il kernel possono anche attivare un riavvio se l'avvio verificato o dm-verity rilevano una corruzione. 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 OTA A/B quando
si utilizza il codice AOSP. Il client di aggiornamento di sistema Java, come GmsCore, estrae
care_map.txt
, configura l'autorizzazione di accesso prima di riavviare il dispositivo e
elimina il file estratto dopo che il sistema ha avviato correttamente la nuova versione.