Les anciennes mises à jour du système A/B, également appelées mises à jour continues , garantissent qu'un système de démarrage fonctionnel reste sur le disque lors d'une mise à jour OTA (Over The Air). Cette approche réduit la probabilité qu'un appareil devienne inactif après une mise à jour, ce qui signifie moins de remplacements et de réinstallations d'appareils dans les centres de réparation et de garantie. D'autres systèmes d'exploitation de qualité commerciale, tels que ChromeOS, utilisent également les mises à jour A/B avec succès.
Pour en savoir plus sur les mises à jour du système A/B et leur fonctionnement, consultez Sélection de partition (emplacements).
Les mises à jour du système A/B offrent les avantages suivants :
- Les mises à jour OTA peuvent avoir lieu pendant que le système est en cours d'exécution, sans interrompre l'utilisateur. Les utilisateurs peuvent continuer à utiliser leurs appareils pendant une mise à jour OTA. La seule indisponibilité pendant une mise à jour se produit lorsque l'appareil redémarre dans la partition de disque mise à jour.
- Après une mise à jour, le redémarrage ne prend pas plus de temps qu'un redémarrage normal.
- Si une mise à jour OTA ne peut pas être appliquée (par exemple, en raison d'un flashage incorrect), l'utilisateur ne sera pas affecté. L'utilisateur continuera à exécuter l'ancien OS et le client pourra réessayer la mise à jour.
- Si une mise à jour OTA est appliquée, mais que le démarrage échoue, l'appareil redémarre sur l'ancienne partition et reste utilisable. Le client est libre de réessayer la mise à jour.
- Toute erreur (par exemple, une erreur d'E/S) n'affecte que l'ensemble de partitions inutilisé et peut faire l'objet d'une nouvelle tentative. Ces erreurs sont également moins susceptibles de se produire, car la charge d'E/S est délibérément faible pour éviter de dégrader l'expérience utilisateur.
-
Les mises à jour peuvent être diffusées en streaming sur les appareils A/B, ce qui évite de télécharger le package avant de l'installer. Le streaming signifie que l'utilisateur n'a pas besoin de disposer de suffisamment d'espace libre pour stocker le package de mise à jour sur
/data
ou/cache
. - La partition de cache n'est plus utilisée pour stocker les packages de mise à jour OTA. Il n'est donc pas nécessaire de s'assurer qu'elle est suffisamment grande pour les futures mises à jour.
- dm-verity garantit qu'un appareil démarrera sur une image non corrompue. Si un appareil ne démarre pas en raison d'un problème lié à la mise à jour OTA ou à dm-verity, il peut redémarrer avec une ancienne image. (La vérification du démarrage Android ne nécessite pas de mises à jour A/B.)
À propos des mises à jour du système A/B
Les mises à jour A/B nécessitent des modifications à la fois du client et du système. Toutefois, le serveur de packages OTA ne devrait pas nécessiter de modifications : les packages de mise à jour sont toujours diffusés via HTTPS. Pour les appareils utilisant l'infrastructure OTA de Google, les modifications du système sont toutes dans AOSP, et le code client est fourni par les services Google Play. Les OEM qui n'utilisent pas l'infrastructure OTA de Google pourront réutiliser le code système AOSP, mais devront fournir leur propre client.
Pour les OEM qui fournissent leur propre client, celui-ci doit :
- Décidez quand effectuer une mise à jour. Étant donné que les mises à jour A/B s'effectuent en arrière-plan, elles ne sont plus initiées par l'utilisateur. Pour éviter de perturber les utilisateurs, il est recommandé de planifier les mises à jour lorsque l'appareil est en mode maintenance inactive, par exemple la nuit et en Wi-Fi. Toutefois, votre client peut utiliser les heuristiques de votre choix.
- Vérifiez auprès de vos serveurs de packages OTA si une mise à jour est disponible. Il doit être presque identique à votre code client existant, sauf que vous devez indiquer que l'appareil est compatible avec A/B. (Le client de Google inclut également un bouton Vérifier maintenant permettant aux utilisateurs de rechercher la dernière mise à jour.)
-
Appelez
update_engine
avec l'URL HTTPS de votre package de mise à jour, en supposant qu'il soit disponible.update_engine
met à jour les blocs bruts sur la partition actuellement inutilisée lors de la diffusion du package de mise à jour. -
Signalez les réussites ou les échecs d'installation à vos serveurs, en fonction du code de résultat
update_engine
. Si la mise à jour est appliquée avec succès,update_engine
indique au bootloader de démarrer le nouvel OS lors du prochain redémarrage. Le bootloader reviendra à l'ancien OS si le nouvel OS ne parvient pas à démarrer. Aucune action n'est donc requise de la part du client. Si la mise à jour échoue, le client doit décider quand (et si) réessayer, en fonction du code d'erreur détaillé. Par exemple, un bon client peut reconnaître qu'un package OTA partiel ("diff") échoue et essayer un package OTA complet à la place.
Le client peut également :
- Afficher une notification demandant à l'utilisateur de redémarrer l'appareil. Si vous souhaitez implémenter une règle qui encourage l'utilisateur à effectuer des mises à jour régulières, vous pouvez ajouter cette notification à votre client. Si le client n'invite pas les utilisateurs, ils recevront la mise à jour la prochaine fois qu'ils redémarreront l'appareil. (Le client de Google dispose d'un délai configurable par mise à jour.)
- Afficher une notification indiquant aux utilisateurs s'ils ont démarré une nouvelle version de l'OS ou s'ils étaient censés le faire, mais sont revenus à l'ancienne version de l'OS. (Le client de Google ne fait généralement ni l'un ni l'autre.)
Du côté du système, les mises à jour du système A/B affectent les éléments suivants :
-
Sélection de la partition (emplacements), du daemon
update_engine
et des interactions du bootloader (décrits ci-dessous) - Processus de compilation et génération du package de mise à jour OTA (décrits dans Implémenter les mises à jour A/B)
Sélection de la partition (emplacements)
Les mises à jour du système A/B utilisent deux ensembles de partitions appelés emplacements (normalement l'emplacement A et l'emplacement B). Le système s'exécute à partir de l'emplacement actuel, tandis que les partitions de l'emplacement inutilisé ne sont pas accessibles par le système en cours d'exécution lors du fonctionnement normal. Cette approche rend les mises à jour résistantes aux pannes en conservant l'emplacement inutilisé comme solution de repli : si une erreur se produit pendant ou immédiatement après une mise à jour, le système peut revenir à l'ancien emplacement et continuer à fonctionner. Pour atteindre cet objectif, aucune partition utilisée par l'emplacement actuel ne doit être mise à jour dans le cadre de la mise à jour OTA (y compris les partitions pour lesquelles il n'existe qu'une seule copie).
Chaque emplacement possède un attribut bootable qui indique si l'emplacement contient un système correct à partir duquel l'appareil peut démarrer. L'emplacement actuel est amorçable lorsque le système est en cours d'exécution, mais l'autre emplacement peut contenir une ancienne version (toujours correcte) du système, une version plus récente ou des données non valides. Quel que soit l'emplacement actuel, il existe un emplacement actif (celui à partir duquel le bootloader démarrera au prochain démarrage) ou un emplacement préféré.
Chaque emplacement dispose également d'un attribut successful défini par l'espace utilisateur, qui n'est pertinent que si l'emplacement est également amorçable. Un emplacement fonctionnel doit pouvoir démarrer, s'exécuter et se mettre à jour. Un emplacement amorçable qui n'a pas été marqué comme réussi (après plusieurs tentatives d'amorçage) doit être marqué comme non amorçable par le bootloader, y compris en changeant l'emplacement actif pour un autre emplacement amorçable (normalement pour l'emplacement exécuté immédiatement avant la tentative d'amorçage dans le nouvel emplacement actif). Les détails spécifiques de l'interface sont définis dans
boot_control.h
.
Daemon du moteur de mise à jour
Les mises à jour du système A/B utilisent un daemon en arrière-plan appelé update_engine
pour préparer le système à démarrer sur une nouvelle version mise à jour. Ce démon peut effectuer les actions suivantes :
- Lire les partitions A/B de l'emplacement actuel et écrire les données dans les partitions A/B de l'emplacement inutilisé, comme indiqué par le package OTA.
- Appelez l'interface
boot_control
dans un workflow prédéfini. - Exécutez un programme post-install à partir de la nouvelle partition après avoir écrit toutes les partitions de slot inutilisées, comme indiqué par le package OTA. (Pour en savoir plus, consultez Post-installation.)
Comme le démon update_engine
n'est pas impliqué dans le processus de démarrage lui-même, il est limité dans ce qu'il peut faire lors d'une mise à jour par les règles et fonctionnalités SELinux dans l'emplacement actuel (ces règles et fonctionnalités ne peuvent pas être mises à jour tant que le système n'a pas démarré dans une nouvelle version). Pour maintenir un système robuste, le processus de mise à jour ne doit pas modifier la table de partition, le contenu des partitions dans l'emplacement actuel ni le contenu des partitions non A/B qui ne peuvent pas être effacées avec une réinitialisation des paramètres d'usine.
Mettre à jour la source du moteur
La source update_engine
se trouve dans system/update_engine
. Les fichiers dexopt OTA A/B sont répartis entre installd
et un gestionnaire de packages :
-
frameworks/native/cmds/installd/
ota* inclut le script post-installation, le binaire pour chroot, le clone installd qui appelle dex2oat, le script de déplacement des artefacts post-OTA et le fichier rc pour le script de déplacement. -
frameworks/base/services/core/java/com/android/server/pm/OtaDexoptService.java
(plusOtaDexoptShellCommand
) est le gestionnaire de packages qui prépare les commandes dex2oat pour les applications.
Pour obtenir un exemple fonctionnel, consultez /device/google/marlin/device-common.mk
.
Journaux du moteur de mise à jour
Pour les versions 8.x d'Android et antérieures, les journaux update_engine
se trouvent dans logcat
et dans le rapport de bug. Pour rendre les journaux update_engine
disponibles dans le système de fichiers, appliquez les modifications suivantes à votre compilation :
Ces modifications enregistrent une copie du journal update_engine
le plus récent dans /data/misc/update_engine_log/update_engine.YEAR-TIME
. En plus du journal actuel, les cinq journaux les plus récents sont enregistrés sous /data/misc/update_engine_log/
. Les utilisateurs disposant de l'ID de groupe log pourront accéder aux journaux du système de fichiers.
Interactions avec le bootloader
La HAL boot_control
est utilisée par update_engine
(et éventuellement d'autres daemons) pour indiquer au bootloader à partir de quoi démarrer. Voici quelques exemples de scénarios courants et de leurs états associés :
- Cas normal : le système s'exécute à partir de son emplacement actuel, emplacement A ou B. Aucune mise à jour n'a été appliquée pour le moment. L'emplacement actuel du système est amorçable, réussi et actif.
- Mise à jour en cours : le système s'exécute à partir de l'emplacement B, qui est donc l'emplacement amorçable, réussi et actif. L'emplacement A a été marqué comme non amorçable, car son contenu est en cours de mise à jour, mais n'est pas encore terminé. Dans cet état, un redémarrage doit continuer à démarrer à partir du slot B.
- Mise à jour appliquée, redémarrage en attente : le système s'exécute à partir du slot B, le slot B est amorçable et réussi, mais le slot A a été marqué comme actif (et est donc marqué comme amorçable). L'emplacement A n'est pas encore marqué comme réussi et le bootloader doit effectuer un certain nombre de tentatives de démarrage à partir de l'emplacement A.
-
Le système a redémarré avec la nouvelle mise à jour : le système s'exécute à partir du slot A pour la première fois. Le slot B est toujours bootable et opérationnel, tandis que le slot A est uniquement bootable et toujours actif, mais pas opérationnel. Un démon d'espace utilisateur,
update_verifier
, doit marquer l'emplacement A comme réussi après avoir effectué certaines vérifications.
Prise en charge des mises à jour en streaming
Les appareils des utilisateurs ne disposent pas toujours de suffisamment d'espace sur /data
pour télécharger le package de mise à jour. Comme ni les OEM ni les utilisateurs ne veulent gaspiller d'espace sur une partition /cache
, certains utilisateurs ne reçoivent pas de mises à jour, car l'appareil n'a pas d'espace pour stocker le package de mise à jour. Pour résoudre ce problème, Android 8.0 a ajouté la prise en charge des mises à jour A/B en streaming qui écrivent des blocs directement dans la partition B au fur et à mesure de leur téléchargement, sans avoir à stocker les blocs sur /data
. Les mises à jour A/B en streaming ne nécessitent presque pas de stockage temporaire et juste assez d'espace pour environ 100 Kio de métadonnées.
Pour activer les mises à jour en streaming dans Android 7.1, sélectionnez les correctifs suivants :
- Autoriser l'annulation d'une demande de résolution de proxy
- Correction de l'arrêt d'un transfert lors de la résolution des proxys
- Add unit test for TerminateTransfer between ranges
- Cleanup the RetryTimeoutCallback()
Ces correctifs sont nécessaires pour prendre en charge les mises à jour A/B en streaming dans Android 7.1 et versions ultérieures, que vous utilisiez les services Google Mobile (GMS) ou tout autre client de mise à jour.
Cycle de vie d'une mise à jour A/B
Le processus de mise à jour commence lorsqu'un package OTA (appelé charge utile dans le code) est disponible au téléchargement. Les règles de l'appareil peuvent différer le téléchargement et l'application de la charge utile en fonction du niveau de batterie, de l'activité de l'utilisateur, de l'état de charge ou d'autres règles. De plus, comme la mise à jour s'exécute en arrière-plan, les utilisateurs ne savent peut-être pas qu'une mise à jour est en cours. Cela signifie que le processus de mise à jour peut être interrompu à tout moment en raison de règles, de redémarrages inattendus ou d'actions de l'utilisateur.
Éventuellement, les métadonnées du package OTA lui-même indiquent que la mise à jour peut être diffusée en streaming. Le même package peut également être utilisé pour une installation non en streaming. Le serveur peut utiliser les métadonnées pour indiquer au client qu'il est en streaming, afin que le client transmette correctement l'OTA à update_engine
. Les fabricants d'appareils disposant de leur propre serveur et client peuvent activer les mises à jour en flux continu en s'assurant que le serveur identifie la mise à jour comme étant en flux continu (ou en supposant que toutes les mises à jour le sont) et que le client effectue l'appel correct à update_engine
pour le flux continu. Les fabricants peuvent utiliser le fait que le package est de la variante de streaming pour envoyer un indicateur au client afin de déclencher le transfert côté framework en tant que streaming.
Une fois une charge utile disponible, le processus de mise à jour est le suivant :
Étape | Activités |
---|---|
1 |
L'emplacement actuel (ou "emplacement source") est marqué comme réussi (s'il ne l'est pas déjà) avec markBootSuccessful() .
|
2 |
L'emplacement inutilisé (ou "emplacement cible") est marqué comme non amorçable en appelant la fonction setSlotAsUnbootable() . L'emplacement actuel est toujours marqué comme réussi au début de la mise à jour pour empêcher le bootloader de revenir à l'emplacement inutilisé, qui contiendra bientôt des données non valides. Si le système a atteint le point où il peut commencer à appliquer une mise à jour, l'emplacement actuel est marqué comme réussi, même si d'autres composants majeurs sont défaillants (comme l'UI dans une boucle de plantage), car il est possible de déployer un nouveau logiciel pour résoudre ces problèmes. La charge utile de mise à jour est un blob opaque contenant les instructions de mise à jour vers la nouvelle version. La charge utile de mise à jour se compose des éléments suivants :
|
3 | Les métadonnées de la charge utile sont téléchargées. |
4 | Pour chaque opération définie dans les métadonnées, les données associées (le cas échéant) sont téléchargées en mémoire, l'opération est appliquée et la mémoire associée est supprimée. |
5 | Les partitions entières sont relues et comparées au hachage attendu. |
6 | L'étape post-installation (le cas échéant) est exécutée. En cas d'erreur lors de l'exécution d'une étape, la mise à jour échoue et une nouvelle tentative est effectuée avec une charge utile potentiellement différente. Si toutes les étapes précédentes ont réussi, la mise à jour réussit et la dernière étape est exécutée. |
7 |
L'emplacement inutilisé est marqué comme actif en appelant setActiveBootSlot() .
Marquer l'emplacement inutilisé comme actif ne signifie pas qu'il terminera le démarrage. Le bootloader (ou le système lui-même) peut rétablir l'emplacement actif s'il ne lit pas un état de réussite.
|
8 |
La post-installation (décrite ci-dessous) consiste à exécuter un programme à partir de la version "nouvelle mise à jour" tout en continuant à l'exécuter dans l'ancienne version. Si cette étape est définie dans le package OTA, elle est obligatoire et le programme doit renvoyer le code de sortie 0 . Sinon, la mise à jour échoue.
|
9 |
Une fois que le système a démarré suffisamment loin dans le nouvel emplacement et terminé les vérifications post-redémarrage, l'emplacement actuel (anciennement l'emplacement cible) est marqué comme réussi en appelant markBootSuccessful() .
|
Après l'installation
Pour chaque partition où une étape post-installation est définie, update_engine
monte la nouvelle partition dans un emplacement spécifique et exécute le programme spécifié dans l'OTA par rapport à la partition montée. Par exemple, si le programme post-installation est défini sur usr/bin/postinstall
dans la partition système, cette partition de l'emplacement inutilisé sera montée dans un emplacement fixe (tel que /postinstall_mount
) et la commande /postinstall_mount/usr/bin/postinstall
sera exécutée.
Pour que la post-installation réussisse, l'ancien noyau doit être capable de :
- Montez le nouveau format de système de fichiers. Le type de système de fichiers ne peut pas changer, sauf s'il est compatible avec l'ancien noyau, y compris les détails tels que l'algorithme de compression utilisé si vous utilisez un système de fichiers compressé (par exemple, SquashFS).
-
Comprenez le format du programme post-installation de la nouvelle partition. Si vous utilisez un fichier binaire ELF (Executable and Linkable Format), il doit être compatible avec l'ancien noyau (par exemple, un nouveau programme 64 bits exécuté sur un ancien noyau 32 bits si l'architecture est passée de versions 32 bits à des versions 64 bits). À moins que le chargeur (
ld
) ne soit invité à utiliser d'autres chemins ou à créer un binaire statique, les bibliothèques seront chargées à partir de l'ancienne image système et non de la nouvelle.
Par exemple, vous pouvez utiliser un script shell comme programme post-installation interprété par le binaire shell de l'ancien système avec un marqueur #!
en haut, puis configurer les chemins de bibliothèque à partir du nouvel environnement pour exécuter un programme post-installation binaire plus complexe. Vous pouvez également exécuter l'étape post-installation à partir d'une partition plus petite dédiée pour permettre la mise à jour du format du système de fichiers dans la partition système principale sans entraîner de problèmes de rétrocompatibilité ni de mises à jour intermédiaires. Les utilisateurs pourront ainsi passer directement à la dernière version à partir d'une image d'usine.
Le nouveau programme post-installation est limité par les règles SELinux définies dans l'ancien système. L'étape post-installation est donc adaptée à l'exécution des tâches requises par la conception sur un appareil donné ou d'autres tâches au mieux. L'étape post-installation ne convient pas aux corrections de bugs ponctuelles avant le redémarrage qui nécessitent des autorisations imprévues.
Le programme post-installation sélectionné s'exécute dans le contexte SELinux postinstall
. Tous les fichiers de la nouvelle partition montée seront tagués avec postinstall_file
, quels que soient leurs attributs après le redémarrage dans ce nouveau système. Les modifications apportées aux attributs SELinux dans le nouveau système n'auront aucune incidence sur l'étape post-installation. Si le programme post-installation a besoin d'autorisations supplémentaires, celles-ci doivent être ajoutées au contexte post-installation.
Après le redémarrage
Après le redémarrage, update_verifier
déclenche la vérification de l'intégrité à l'aide de dm-verity.
Cette vérification commence avant le zygote pour éviter que les services Java n'apportent des modifications irréversibles qui empêcheraient une restauration sûre. Au cours de ce processus, le bootloader et le noyau peuvent également déclencher un redémarrage si le démarrage validé ou dm-verity détectent une corruption. Une fois la vérification terminée, update_verifier
indique que le démarrage a réussi.
update_verifier
ne lira que les blocs listés dans /data/ota_package/care_map.txt
, qui est inclus dans un package OTA A/B lors de l'utilisation du code AOSP. Le client de mise à jour du système Java, tel que GmsCore, extrait care_map.txt
, configure l'autorisation d'accès avant de redémarrer l'appareil et supprime le fichier extrait une fois que le système a démarré avec succès dans la nouvelle version.