Aggiornamenti di sistema A/B (senza interruzioni).

Gli aggiornamenti di sistema A/B, noti anche come aggiornamenti continui, garantiscono che un sistema di avvio funzionante rimanga sul disco durante un aggiornamento over-the-air (OTA) . Questo approccio riduce la probabilità che il dispositivo risulti inattivo dopo un aggiornamento, il che significa meno sostituzioni e interventi di ripristino del dispositivo presso i centri di riparazione e garanzia. Anche altri sistemi operativi di livello commerciale come ChromeOS utilizzano con successo 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 viene applicata (ad esempio a causa di un flash errato), l'utente non sarà interessato. L'utente continuerà a eseguire il vecchio sistema operativo e il client sarà 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 errori 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 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 in /data o /cache .
  • La partizione della cache non viene più utilizzata per archiviare i pacchetti di aggiornamento OTA, quindi non è necessario garantire che la partizione della cache sia sufficientemente grande per gli aggiornamenti futuri.
  • dm-verity garantisce che il dispositivo avvierà un'immagine non corrotta. Se un dispositivo non si avvia a causa di un problema OTA o dm-verity errato, il dispositivo può riavviarsi in una vecchia immagine. ( L'avvio verificato per 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 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 da Google Play Services. Gli OEM che non utilizzano l'infrastruttura OTA di Google potranno riutilizzare il codice del sistema AOSP ma dovranno fornire il proprio client.

Per gli OEM che riforniscono il proprio cliente, il cliente deve:

  • Decidi quando effettuare un aggiornamento. Poiché gli aggiornamenti A/B avvengono in background, non vengono più avviati dall'utente. Per evitare interruzioni per gli utenti, è consigliabile pianificare gli aggiornamenti quando il dispositivo è in modalità di manutenzione inattiva, ad esempio durante la notte, e in modalità Wi-Fi. Tuttavia, il tuo cliente può utilizzare qualsiasi euristica desideri.
  • Controlla con i server dei pacchetti OTA e determina se è disponibile un aggiornamento. Dovrebbe essere sostanzialmente lo stesso del 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 la disponibilità dell'ultimo aggiornamento.)
  • Chiama update_engine con l'URL HTTPS per il tuo pacchetto di aggiornamento, presupponendo che ne sia disponibile uno. update_engine aggiornerà i blocchi grezzi sulla partizione attualmente inutilizzata mentre trasmette il 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 eseguirà il fallback sul vecchio sistema operativo se il nuovo sistema operativo non riesce ad avviarsi, quindi non è richiesto alcun intervento da parte del client. Se l'aggiornamento fallisce, 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 policy in cui l'utente è incoraggiato ad aggiornare regolarmente, questa notifica può essere aggiunta al tuo client. Se il client non lo richiede agli utenti, gli utenti riceveranno comunque l'aggiornamento al successivo riavvio. (Il client di Google ha un ritardo configurabile per aggiornamento.)
  • Mostra una notifica che informa gli utenti se hanno avviato una nuova versione del sistema operativo o se ci si aspettava che lo facessero ma sono tornati alla vecchia versione del sistema operativo. (Il cliente di Google in genere non fa nessuna delle due cose.)

Dal punto di vista del sistema, gli aggiornamenti del sistema A/B incidono su quanto segue:

  • Selezione della partizione (slot), demone update_engine e interazioni del bootloader (descritte di seguito)
  • Processo di creazione e generazione del pacchetto di aggiornamento OTA (descritto in Implementazione degli aggiornamenti A/B )

Selezione della partizione (slot)

Gli aggiornamenti del sistema A/B utilizzano due set di partizioni denominate slot (normalmente slot A e slot B). Il sistema viene eseguito dallo slot corrente mentre il sistema in esecuzione non accede alle partizioni nello slot inutilizzato 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 è possibile avviare il dispositivo. Lo slot corrente è avviabile quando il sistema è in esecuzione, ma l'altro slot potrebbe contenere una versione vecchia (ancora corretta) del sistema, una versione più recente o dati non validi. Indipendentemente da quale sia lo slot corrente , ce n'è uno che è lo slot attivo (quello da cui verrà avviato il bootloader all'avvio successivo) o lo slot preferito .

Ciascuno slot dispone inoltre di 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, eseguirsi 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 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:

  • Leggi dalle partizioni dello slot A/B corrente e scrivi tutti i dati nelle partizioni dello slot A/B non utilizzate 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 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 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 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 l'origine 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:

Per un esempio funzionante, fare riferimento a /device/google/marlin/device-common.mk .

Aggiorna i log del motore

Per le versioni Android 8.x e precedenti, i log update_engine sono disponibili in logcat e nella segnalazione di bug. Per rendere i log update_engine disponibili 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 con il bootloader

L'HAL boot_control viene utilizzato da update_engine (e possibilmente da altri demoni) per istruire il bootloader da cosa eseguire l'avvio. 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 è stato applicato alcun aggiornamento. Lo slot corrente del sistema è avviabile, riuscito e 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 riuscito, ma lo slot A è stato contrassegnato come attivo (e pertanto è 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 con un nuovo aggiornamento : il sistema è in esecuzione dallo slot A per la prima volta, lo slot B è ancora avviabile e riuscito 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 aver effettuato alcuni controlli.

Supporto per l'aggiornamento in streaming

I dispositivi degli utenti non sempre dispongono 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 rimangono 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 blocchi direttamente nella partizione B mentre vengono scaricati, senza dover archiviare i blocchi su /data . Gli aggiornamenti A/B in streaming non necessitano quasi di spazio di archiviazione temporaneo e richiedono spazio di archiviazione appena sufficiente per circa 100 KiB di metadati.

Per abilitare gli aggiornamenti in streaming in Android 7.1, seleziona le seguenti patch:

Queste patch sono necessarie per supportare lo streaming di aggiornamenti A/B in Android 7.1 e versioni successive sia utilizzando Google Mobile Services (GMS) che qualsiasi altro client di aggiornamento.

Durata di un aggiornamento A/B

Il processo di aggiornamento inizia quando un pacchetto OTA (denominato nel codice payload ) è disponibile per il download. I criteri nel dispositivo possono rinviare il download e l'applicazione del payload 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. Tutto ciò significa che il processo di aggiornamento potrebbe essere interrotto in qualsiasi momento a causa di policy, 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 streaming. Il server può utilizzare i metadati per indicare al client che è in streaming in modo che il client passi correttamente l'OTA a update_engine . I produttori di dispositivi con i propri server e client possono abilitare gli aggiornamenti in streaming garantendo che il server identifichi che 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 sfruttare il fatto che il pacchetto appartiene alla variante streaming per inviare un flag al client per attivare la consegna al framework come streaming.

Una volta disponibile un payload, 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 viene sempre contrassegnato come riuscito all'inizio dell'aggiornamento per evitare che il bootloader torni 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 riuscito anche se altri componenti principali sono danneggiati (come l'interfaccia utente in un ciclo di crash) poiché è possibile inviare nuovo software per risolverli i 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:
  • Metadati . I metadati, una parte relativamente piccola del payload dell'aggiornamento, contengono un elenco di operazioni per produrre e verificare la nuova versione nello slot di destinazione. Ad esempio, un'operazione potrebbe decomprimere un determinato BLOB e scriverlo su blocchi specifici in una partizione di destinazione oppure leggere da una partizione di origine, applicare una patch binaria e scrivere su determinati blocchi in una partizione di destinazione.
  • Dati aggiuntivi . Essendo la maggior parte del payload di aggiornamento, i dati aggiuntivi associati alle operazioni sono costituiti dal BLOB compresso o dalla patch binaria in questi esempi.
3 I metadati del payload 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 scartata.
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 eventualmente un payload diverso. Se tutti i passaggi finora hanno avuto esito positivo, 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 terminerà 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) 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 con codice di uscita 0 ; in caso contrario, l'aggiornamento fallisce.
9 Dopo che il sistema si è avviato correttamente nel nuovo slot e ha completato i controlli post-riavvio, lo slot corrente (in precedenza lo "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 postinstallazione è definito come usr/bin/postinstall nella partizione di sistema, questa partizione dallo slot non utilizzato 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:

  • Montare il nuovo formato del filesystem . Il tipo di filesystem non può cambiare a meno che non sia supportato nel vecchio kernel, inclusi dettagli come l'algoritmo di compressione utilizzato se si utilizza un filesystem compresso (ad esempio 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 utilizzare uno script di shell come programma post-installazione interpretato dal binario della shell del vecchio sistema con un #! marcatore in alto), quindi impostare i percorsi delle librerie dal nuovo ambiente per eseguire un programma di post-installazione binario più complesso. In alternativa, è possibile eseguire il passaggio di post-installazione da una partizione dedicata più piccola per consentire l'aggiornamento del formato del file system nella partizione di sistema principale senza incorrere in problemi di compatibilità con le versioni precedenti o aggiornamenti iniziali; ciò consentirebbe agli utenti di aggiornare direttamente alla versione più recente da un'immagine di fabbrica.

Il nuovo programma post-installazione è limitato dalle policy SELinux definite nel vecchio sistema. In quanto tale, la fase di post-installazione è adatta per eseguire le attività richieste dalla progettazione su un determinato dispositivo o altre attività best-effort (ad esempio aggiornamento del firmware o del bootloader con funzionalità A/B, preparazione di copie dei database per la nuova versione, ecc. ). La fase di post-installazione non è adatta 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 da quali siano i loro attributi dopo il riavvio in quel nuovo sistema. Le modifiche agli attributi SELinux nel nuovo sistema non avranno alcun impatto sulla fase di 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 potrebbero anche attivare un riavvio se l'avvio verificato o dm-verity rilevano eventuali danni. Una volta completato il controllo, update_verifier contrassegna l'avvio 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.