Mises à jour du système A/B (discrètes)

Les anciennes mises à jour du système A/B, également appelées mises à jour fluides , garantissent qu'un système de démarrage opérationnel reste sur le disque lors d'une mise à jour Over The Air (OTA). Cette approche réduit la probabilité qu'un appareil soit inactif après une mise à jour, ce qui signifie moins de remplacements et de flashages 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.

Pour en savoir plus sur les mises à jour système A/B et leur fonctionnement, consultez la section Sélection de partitions (emplacements).

Les mises à jour du système A/B offrent les avantages suivants:

  • Les mises à jour OTA peuvent se produire pendant l'exécution du système, sans interrompre l'utilisateur. Les utilisateurs peuvent continuer à utiliser leurs appareils lors d'une mise à jour OTA. Le seul temps d'arrêt lors d'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 une mise à jour OTA ne s'applique pas (par exemple, en raison d'un flash incorrect), l'utilisateur n'en sera pas affecté. L'utilisateur continuera d'exécuter l'ancien OS, et le client pourra réessayer la mise à jour.
  • Si une mise à jour OTA est appliquée, mais ne démarre pas, l'appareil redémarre dans l'ancienne partition et reste utilisable. Le client peut réessayer la mise à jour.
  • Toutes les erreurs (telles que les erreurs d'E/S) n'affectent que l'ensemble de partitions inutilisé et peuvent être réessayées. De telles 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 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 d'espace libre suffisant 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 appareil démarre une image non corrompue. Si un appareil ne démarre pas en raison d'un problème d'OTA ou d'un problème dm-verity, il peut redémarrer avec une ancienne image. (Le démarrage validé 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 au niveau du client et du système. Le serveur de packages OTA ne devrait toutefois 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, le client 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 déclenchées par l'utilisateur. Pour éviter de perturber les utilisateurs, nous vous recommandons de planifier les mises à jour lorsque l'appareil est en mode maintenance d'inactivité, par exemple pendant la nuit, et sur un réseau Wi-Fi. Toutefois, votre client peut utiliser toutes les heuristiques de votre choix.
  • Contactez vos serveurs de packages OTA et déterminez si une mise à jour est disponible. Il devrait être à peu près identique à votre code client existant, sauf que vous devrez indiquer que l'appareil est compatible avec le test A/B. (Le client 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, si disponible. update_engine met à jour les blocs bruts de la partition actuellement inutilisée pendant le streaming du package de mise à jour.
  • Signalez les succès 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 revient à l'ancien OS si le nouveau ne démarre pas. Aucune tâche n'est donc requise du client. Si la mise à jour échoue, le client doit décider quand (et s'il doit) 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. Si vous souhaitez implémenter une règle dans laquelle l'utilisateur est encouragé à 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. (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 d'OS ou s'ils étaient censés le faire, mais qu'ils sont revenus à l'ancienne version. (Le client de Google ne fait généralement ni l'un ni l'autre.)

Du côté du système, les mises à jour système A/B affectent les éléments suivants:

  • Sélection de partition (emplacements), daemon update_engine et interactions du bootloader (décrites ci-dessous)
  • Processus de compilation et génération du package de mise à jour OTA (décrit dans la section Implémenter des mises à jour A/B)

Sélection de la partition (emplacements)

Les mises à jour système A/B utilisent deux ensembles de partitions appelées emplacements (généralement emplacement A et 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 pendant son 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 à 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 peut être démarré 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 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 réussi doit pouvoir démarrer, s'exécuter et se mettre à jour. Un emplacement de démarrage 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 démarrable par le bootloader, y compris en remplaçant l'emplacement actif par un autre emplacement de démarrage (normalement celui qui s'exécute immédiatement avant la tentative de démarrage dans le nouvel emplacement actif). Les détails spécifiques de l'interface sont définis dans boot_control.h.

Mettre à jour le daemon du moteur

Les mises à jour système A/B utilisent un daemon en arrière-plan appelé update_engine pour préparer le système à démarrer dans une nouvelle version mise à jour. Ce daemon peut effectuer les actions suivantes:

  • Lire à partir des partitions de l'emplacement A/B actuel et écrire toutes les données dans les partitions de l'emplacement 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 en savoir plus, consultez la section Post-installation).

Comme le démon update_engine n'est pas impliqué dans le processus de démarrage lui-même, ses actions sont limitées lors d'une mise à jour par les règles et fonctionnalités SELinux de l'emplacement actuel (ces règles et fonctionnalités ne peuvent pas être mises à jour tant que le système ne démarre pas dans une nouvelle version). Pour maintenir un système robuste, le processus de mise à jour ne doit pas modifier la table de partitionnement, le contenu des partitions de l'emplacement actuel ni le contenu des partitions autres que A/B qui ne peuvent pas être effacées en rétablissant la configuration 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 paquets:

Pour obtenir un exemple fonctionnel, consultez /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 bug. 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 log pourront accéder aux journaux du système de fichiers.

Interactions avec le bootloader

Le HAL boot_control est utilisé par update_engine (et éventuellement d'autres daemons) pour indiquer au bootloader à partir de quoi démarrer. Voici quelques exemples courants de scénarios et les états associés:

  • Cas normal: le système s'exécute à partir de son emplacement actuel, soit l'emplacement A, soit l'emplacement B. Aucune mise à jour n'a été appliquée jusqu'à présent. 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. L'emplacement B est donc l'emplacement de démarrage, opérationnel et actif. Le slot A a été marqué comme non amorçable, car le contenu du slot A est en cours de mise à jour, mais n'est pas encore terminé. Un redémarrage dans cet état doit 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 fonctionne correctement, 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 le bootloader doit effectuer un certain nombre de tentatives de démarrage à partir de l'emplacement A.
  • Le système a redémarré vers une nouvelle mise à jour: le système s'exécute à partir du slot A pour la première fois. Le slot B est toujours amorçable et fonctionne, tandis que le slot A n'est amorçable que, et est toujours actif, mais ne fonctionne pas. Un démon d'espace utilisateur, update_verifier, doit marquer l'emplacement A comme réussi après quelques vérifications.

Prise en charge des mises à jour en continu

Les appareils des utilisateurs n'ont pas toujours suffisamment d'espace sur /data pour télécharger le package de mise à jour. Étant donné que ni les OEM ni les utilisateurs ne souhaitent gaspiller de l'espace sur une partition /cache, certains utilisateurs ne reçoivent pas 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 compatibilité avec les 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 ne nécessitent qu'un espace de stockage suffisant pour environ 100 ko de métadonnées.

Pour activer les mises à jour en streaming dans Android 7.1, sélectionnez les correctifs suivants:

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 en 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 la batterie, de l'activité de l'utilisateur, de l'état de recharge ou d'autres règles. De plus, comme 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 règles, de redémarrages inattendus ou d'actions de l'utilisateur.

Les métadonnées du package OTA lui-même indiquent si la mise à jour peut être lue 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 streaming en s'assurant que le serveur identifie la mise à jour en streaming (ou suppose que toutes les mises à jour sont en streaming) et que le client effectue l'appel approprié à update_engine pour le streaming. Les fabricants peuvent utiliser le fait que le package appartient à la variante de streaming pour envoyer un indicateur au client afin de déclencher le transfert vers le côté du framework en tant que streaming.

Une fois la charge utile disponible, le processus de mise à jour se déroule comme suit:

Étape 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 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éfectueux (par exemple, l'UI dans une boucle de plantage), car il est possible d'envoyer de nouveaux logiciels 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 comprend les éléments suivants :
  • Métadonnées Les métadonnées, qui constituent une partie relativement faible de la charge utile de mise à jour, contiennent une liste d'opérations permettant de générer et de valider la nouvelle version sur l'emplacement cible. Par exemple, une opération peut décompresser un certain blob et l'écrire dans des blocs spécifiques d'une partition cible, ou lire à partir d'une partition source, appliquer un correctif binaire et écrire dans certains blocs d'une partition cible.
  • Données supplémentaires Dans ces exemples, les données supplémentaires associées aux opérations consistent en un blob compressé ou en un correctif binaire, qui constituent l'essentiel de la charge utile de mise à jour.
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, 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 L'intégralité des partitions est lue à nouveau et comparée 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 est réessayée avec une charge utile différente. Si toutes les étapes précédentes ont réussi, la mise à jour aboutit 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 d'état réussi.
8 L'après-installation (décrite ci-dessous) consiste à exécuter un programme à partir de la version "nouvelle mise à jour" tout en exécutant l'ancienne version. Si elle est définie dans le package OTA, cette étape 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 dans le nouvel emplacement et terminé les vérifications post-démarrage, l'emplacement actuel (anciennement "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 à 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 installée à un emplacement fixe (tel que /postinstall_mount) et la commande /postinstall_mount/usr/bin/postinstall sera exécutée.

Pour que la post-installation soit réussie, l'ancien noyau doit pouvoir:

  • Installez 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 des détails tels que l'algorithme de compression utilisé si vous utilisez un système de fichiers compressé (par exemple, SquashFS).
  • Comprendre le format du programme post-installation de la nouvelle partition Si vous utilisez un binaire au format 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 des builds 32 bits aux builds 64 bits). Sauf si le chargeur (ld) est invité à utiliser d'autres chemins d'accès ou à créer un binaire statique, les bibliothèques sont 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 repère #! en haut, puis configurer des 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 petite partition dédiée pour permettre la mise à jour du format de système de fichiers dans la partition système principale sans entraîner de problèmes de rétrocompatibilité ni de mises à niveau par étapes. Cela permettrait aux utilisateurs de 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. Par conséquent, l'étape post-installation est 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 n'est pas adaptée 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 nécessite des 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 zygote pour éviter que les services Java ne modifient de manière irréversible ce qui empêcherait un rollback sécurisé. Au cours de ce processus, le bootloader et le kernel 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 lit que les blocs listés dans /data/ota_package/care_map.txt, qui est inclus dans un package OTA A/B lorsque vous utilisez le 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é dans la nouvelle version.