Les mises à jour du système A/B, également appelées mises à jour transparentes, garantissent qu'un système de démarrage fonctionnel reste sur le disque pendant une mise à jour sans fil (OTA) . Cette approche réduit la probabilité d'un appareil inactif après une mise à jour, ce qui signifie moins de remplacements d'appareils et de reflashages 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 plus d'informations sur les mises à jour du système A/B et leur fonctionnement, voir Sélection de partition (slots) .
Les mises à jour du système A/B offrent les avantages suivants :
- Les mises à jour OTA peuvent se produire pendant que le système est en cours d'exécution, sans interrompre l'utilisateur. Les utilisateurs peuvent continuer à utiliser leurs appareils pendant une OTA - le seul temps d'arrêt pendant une mise à jour est 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 un OTA ne s'applique pas (par exemple, à cause d'un mauvais flash), l'utilisateur ne sera pas affecté. L'utilisateur continuera à exécuter l'ancien système d'exploitation et le client est libre de réessayer la mise à jour.
- Si une mise à jour OTA est appliquée mais ne démarre pas, l'appareil redémarrera dans l'ancienne partition et restera utilisable. Le client est libre de réessayer la mise à jour.
- Toutes les erreurs (telles que les erreurs d'E/S) n'affectent que le jeu de partitions inutilisé et peuvent être réessayées. De telles erreurs deviennent également moins probables 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 sur les appareils A/B, éliminant ainsi le besoin de télécharger le package avant de l'installer. Le streaming signifie qu'il n'est pas nécessaire que l'utilisateur dispose 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 que la partition de cache est suffisamment grande pour les futures mises à jour.
- dm-verity garantit qu'un périphérique démarrera une image non corrompue. Si un appareil ne démarre pas en raison d'un mauvais problème OTA ou dm-verity, l'appareil peut redémarrer dans une ancienne image. (Android Verified Boot 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. Le serveur de packages OTA, cependant, ne devrait pas nécessiter de modifications : les packages de mise à jour sont toujours servis 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 n'utilisant pas l'infrastructure OTA de Google pourront réutiliser le code système AOSP mais devront fournir leur propre client.
Pour les équipementiers fournissant leur propre client, le client doit :
- Décidez quand prendre une mise à jour. Étant donné que les mises à jour A/B se produisent en arrière-plan, elles ne sont plus lancées par l'utilisateur. Pour éviter de perturber les utilisateurs, il est recommandé de planifier les mises à jour lorsque l'appareil est en mode de maintenance inactif, comme la nuit, et sur le Wi-Fi. Cependant, votre client peut utiliser toutes les heuristiques de votre choix.
- Vérifiez auprès de vos serveurs de packages OTA et déterminez si une mise à jour est disponible. Cela devrait être essentiellement le même que votre code client existant, sauf que vous voudrez signaler que l'appareil prend en charge A/B. (Le client de Google comprend également un bouton Vérifier maintenant pour que les utilisateurs vérifient la dernière mise à jour.)
- Appelez
update_engine
avec l'URL HTTPS de votre package de mise à jour, en supposant qu'il en existe un de disponible.update_engine
mettra à 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
indiquera au chargeur de démarrage de démarrer dans le nouveau système d'exploitation au prochain redémarrage. Le chargeur de démarrage reviendra à l'ancien système d'exploitation si le nouveau système d'exploitation ne démarre pas, donc aucun travail n'est requis de la part du client. Si la mise à jour échoue, le client doit décider quand (et s'il) réessayer, en fonction du code d'erreur détaillé. Par exemple, un bon client pourrait reconnaître qu'un package OTA partiel ("diff") échoue et essayer un package OTA complet à la place.
Facultativement, le client peut :
- Afficher une notification demandant à l'utilisateur de redémarrer. Si vous souhaitez implémenter une stratégie dans laquelle l'utilisateur est encouragé à effectuer régulièrement des mises à jour, cette notification peut être ajoutée à votre client. Si le client n'invite pas les utilisateurs, ceux-ci obtiendront quand même la mise à jour la prochaine fois qu'ils redémarreront. (Le client de Google a un délai configurable par mise à jour.)
- Afficher une notification indiquant aux utilisateurs s'ils ont démarré dans une nouvelle version du système d'exploitation ou s'ils étaient censés le faire, mais sont revenus à l'ancienne version du système d'exploitation. (Le client de Google ne fait généralement ni l'un ni l'autre.)
Côté système, les mises à jour du système A/B affectent les éléments suivants :
- Sélection de partition (slots), le démon
update_engine
et les interactions du chargeur de démarrage (décrites ci-dessous) - Processus de génération et génération de package de mise à jour OTA (décrit dans Implémentation des mises à jour A/B )
Sélection de partition (emplacements)
Les mises à jour du système A/B utilisent deux ensembles de partitions appelées logements (généralement le logement A et le logement 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 pendant le fonctionnement normal. Cette approche rend les mises à jour résistantes aux pannes en conservant l'emplacement inutilisé comme solution de secours : si une erreur se produit pendant ou immédiatement après une mise à jour, le système peut revenir à l'ancien emplacement et continuer à avoir un système fonctionnel. Pour atteindre cet objectif, aucune partition utilisée par le slot 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 a un attribut bootable qui indique si l'emplacement contient un système correct à partir duquel le périphérique peut démarrer. L'emplacement actuel est amorçable lorsque le système est en cours d'exécution, mais l'autre emplacement peut avoir 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 y a un emplacement qui est l'emplacement actif (celui à partir duquel le chargeur de démarrage démarrera au prochain démarrage) ou l'emplacement préféré .
Chaque emplacement a également un attribut réussi défini par l'espace utilisateur, qui n'est pertinent que si l'emplacement est également amorçable. Un emplacement réussi 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 de démarrage à partir de celui-ci) doit être marqué comme non amorçable par le chargeur de démarrage, y compris le changement de l'emplacement actif vers un autre emplacement amorçable (normalement vers l'emplacement exécuté immédiatement avant la tentative de démarrage dans le nouveau, actif). Les détails spécifiques de l'interface sont définis dans boot_control.h
.
Mettre à jour le démon du moteur
Les mises à jour du système A/B utilisent un démon d'arrière-plan appelé update_engine
pour préparer le système à démarrer dans une nouvelle version mise à jour. Ce démon peut effectuer les actions suivantes :
- Lisez à partir des partitions A/B de l'emplacement actuel et écrivez toutes les données sur les partitions A/B inutilisées, comme indiqué par le package OTA.
- Appelez l'interface
boot_control
dans un workflow prédéfini. - Exécutez un programme de post-installation à partir de la nouvelle partition après avoir écrit toutes les partitions d'emplacement inutilisées, comme indiqué par le package OTA. (Pour plus de détails, voir 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 politiques et fonctionnalités SELinux dans l'emplacement actuel (ces politiques et fonctionnalités ne peuvent pas être mises à jour tant que le système ne démarre pas dans un nouvelle version). Pour maintenir un système robuste, le processus de mise à jour ne doit pas modifier la table des partitions, le contenu des partitions dans l'emplacement actuel ou le contenu des partitions non A/B qui ne peuvent pas être effacées avec une réinitialisation d'usine.
Mettre à jour la source du moteur
La source update_engine
se trouve dans system/update_engine
. Les fichiers A/B OTA dexopt sont répartis entre installd
et un gestionnaire de packages :
-
frameworks/native/cmds/installd/
ota* inclut le script postinstall, le binaire pour chroot, le clone installd qui appelle dex2oat, le script move-artifacts post-OTA et le fichier rc pour le script move. -
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 un exemple fonctionnel, reportez-vous à /device/google/marlin/device-common.mk
.
Mettre à jour les journaux du moteur
Pour les versions Android 8.x et antérieures, les journaux update_engine
se trouvent dans logcat
et dans le rapport de bogue. Pour rendre les journaux update_engine
disponibles dans le système de fichiers, corrigez les modifications suivantes dans votre build :
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 de journaux pourront accéder aux journaux du système de fichiers.
Interactions avec le chargeur de démarrage
La HAL boot_control
est utilisée par update_engine
(et éventuellement d'autres démons) pour indiquer au chargeur de démarrage à partir de quoi démarrer. Les exemples de scénarios courants et leurs états associés sont les suivants :
- Cas normal : le système fonctionne à partir de son emplacement actuel, soit l'emplacement A ou B. Aucune mise à jour n'a été appliquée jusqu'à présent. L'emplacement actuel du système est amorçable, réussi et l'emplacement actif.
- Mise à jour en cours : Le système s'exécute à partir de l'emplacement B, donc l'emplacement B est l'emplacement amorçable, réussi et actif. L'emplacement A a été marqué comme non amorçable car le contenu de l'emplacement A est en cours de mise à jour mais n'est pas encore terminé. Un redémarrage dans cet état devrait continuer à démarrer à partir de l'emplacement B.
- Mise à jour appliquée, redémarrage en attente : Le système s'exécute à partir de l'emplacement B, l'emplacement B est amorçable et réussi, mais l'emplacement 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 un certain nombre de tentatives de démarrage à partir de l'emplacement A doivent être effectuées par le chargeur de démarrage.
- Système redémarré dans une nouvelle mise à jour : le système s'exécute à partir de l'emplacement A pour la première fois, l'emplacement B est toujours amorçable et réussi tandis que l'emplacement A est uniquement amorçable, et toujours actif mais sans succès. Un démon d'espace utilisateur,
update_verifier
, doit marquer l'emplacement A comme réussi après quelques vérifications.
Prise en charge de la mise à jour en continu
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 perdre d'espace sur une partition /cache
, certains utilisateurs se passent de mises à jour car l'appareil n'a nulle part où 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 sur 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 pratiquement aucun stockage temporaire et nécessitent juste assez de stockage pour environ 100 Ko de métadonnées.
Pour activer les mises à jour en continu dans Android 7.1, sélectionnez les correctifs suivants :
- Autoriser l'annulation d'une demande de résolution de proxy
- Correction de la fin d'un transfert lors de la résolution des proxys
- Ajouter un test unitaire pour TerminateTransfer entre les plages
- Nettoyer le 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 Google Mobile Services (GMS) ou tout autre client de mise à jour.
Durée de vie d'une mise à jour A/B
Le processus de mise à jour démarre lorsqu'un package OTA (appelé charge utile dans le code) est disponible pour le téléchargement. Les politiques de l'appareil peuvent différer le téléchargement et l'application de la charge utile en fonction du niveau de la batterie, de l'activité de l'utilisateur, de l'état de charge ou d'autres politiques. De plus, étant donné que la mise à jour s'exécute en arrière-plan, les utilisateurs peuvent ne pas savoir qu'une mise à jour est en cours. Tout cela signifie que le processus de mise à jour peut être interrompu à tout moment en raison de politiques, de redémarrages inattendus ou d'actions de l'utilisateur.
En option, les métadonnées du package OTA lui-même indiquent que la mise à jour peut être diffusée en continu ; le même package peut également être utilisé pour une installation sans diffusion en continu. Le serveur peut utiliser les métadonnées pour indiquer au client qu'il diffuse afin que le client transmette correctement l'OTA à update_engine
. Les fabricants d'appareils disposant de leurs propres serveur et client peuvent activer les mises à jour en continu en s'assurant que le serveur identifie que la mise à jour est en continu (ou suppose que toutes les mises à jour sont en continu) et que le client appelle correctement update_engine
pour la diffusion en continu. Les fabricants peuvent utiliser le fait que le package est de la variante de diffusion en continu pour envoyer un indicateur au client afin de déclencher le transfert vers le cadre en tant que diffusion en continu.
Une fois qu'une charge utile est disponible, le processus de mise à jour est le suivant :
Marcher | Activités |
---|---|
1 | L'emplacement actuel (ou "emplacement source") est marqué comme réussi (s'il n'est pas déjà marqué) 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 retomber sur l'emplacement inutilisé, qui contiendra bientôt des données invalides. 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 cassés (comme l'interface utilisateur dans une boucle de plantage) car il est possible de pousser un nouveau logiciel pour réparer ces problèmes.La charge utile de mise à jour est un blob opaque avec 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 charge utile sont téléchargées. |
4 | Pour chaque opération définie dans les métadonnées, dans l'ordre, les données associées (le cas échéant) sont téléchargées dans la 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 vérifiées par rapport au hachage attendu. |
6 | L'étape de 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 est réessayée avec éventuellement une charge utile différente. Si toutes les étapes ont réussi jusqu'à présent, 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 finira de démarrer. Le chargeur de démarrage (ou le système lui-même) peut rebasculer l'emplacement actif s'il ne lit pas un état réussi. |
8 | La post-installation (décrite ci-dessous) consiste à exécuter un programme à partir de la "nouvelle version de mise à jour" tout en continuant à fonctionner dans l'ancienne version. Si elle est définie dans le package OTA, cette étape est obligatoire et le programme doit revenir avec 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() . |
Post-installation
Pour chaque partition où une étape de 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 relatif à la partition montée. Par exemple, si le programme de post-installation est défini comme 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 le /postinstall_mount/usr/bin/postinstall
La commande /postinstall_mount/usr/bin/postinstall
est exécutée.
Pour que la post-installation réussisse, l'ancien noyau doit pouvoir :
- Montez le nouveau format de système de fichiers . Le type de système de fichiers ne peut pas changer à moins qu'il ne soit pris en charge dans l'ancien noyau, y compris des détails tels que l'algorithme de compression utilisé si vous utilisez un système de fichiers compressé (c'est-à-dire SquashFS).
- Comprenez le format du programme post-installation de la nouvelle partition . Si vous utilisez un binaire ELF (Executable and Linkable Format), il doit être compatible avec l'ancien noyau (par exemple, un nouveau programme 64 bits s'exécutant sur un ancien noyau 32 bits si l'architecture est passée de versions 32 à 64 bits). À moins que le chargeur (
ld
) ne reçoive l'instruction d'utiliser d'autres chemins ou de 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 de post-installation interprété par le binaire shell de l'ancien système avec un #!
marqueur en haut), puis configurez les chemins de bibliothèque à partir du nouvel environnement pour exécuter un programme de post-installation binaire plus complexe. Alternativement, vous pouvez exécuter l'étape de 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 encourir de problèmes de compatibilité descendante ou de mises à jour intermédiaires ; cela permettrait aux utilisateurs de mettre à jour directement vers la dernière version à partir d'une image d'usine.
Le nouveau programme de post-installation est limité par les politiques SELinux définies dans l'ancien système. En tant que telle, l'étape de post-installation convient à l'exécution de tâches requises par la conception sur un appareil donné ou d'autres tâches de meilleur effort (c'est-à-dire la mise à jour du micrologiciel ou du chargeur de démarrage compatible A/B, la préparation de copies de bases de données pour la nouvelle version, etc. ). L'étape de post-installation ne convient pas aux corrections de bogues ponctuelles avant le redémarrage qui nécessitent des autorisations imprévues.
Le programme de post-installation sélectionné s'exécute dans le contexte postinstall
SELinux. Tous les fichiers de la nouvelle partition montée seront étiqueté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'affecteront pas l'étape de post-installation. Si le programme de post-installation a besoin d'autorisations supplémentaires, celles-ci doivent être ajoutées au contexte de post-installation.
Après le redémarrage
Après le redémarrage, update_verifier
déclenche le contrôle d'intégrité à l'aide de dm-verity. Cette vérification commence avant zygote pour éviter que les services Java n'apportent des modifications irréversibles qui empêcheraient une restauration en toute sécurité. Au cours de ce processus, le chargeur de démarrage et le noyau peuvent également déclencher un redémarrage si le démarrage vérifié ou dm-verity détecte une corruption. Une fois la vérification terminée, update_verifier
marque le démarrage comme réussi.
update_verifier
ne lira que les blocs répertoriés dans /data/ota_package/care_map.txt
, qui est inclus dans un package A/B OTA 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.