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

Les mises à jour 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 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 d'appareils et de reflashers 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 avec succès les mises à jour A/B.

Pour plus d'informations 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 le fonctionnement du système, sans interrompre l'utilisateur. Les utilisateurs peuvent continuer à utiliser leurs appareils pendant une OTA : le seul temps d'arrêt lors d'une mise à jour survient lorsque l'appareil redémarre sur 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 parvient pas à s'appliquer (par exemple, en raison d'un mauvais flash), l'utilisateur ne sera pas affecté. L'utilisateur continuera à exécuter l'ancien système d'exploitation et le client sera libre de réessayer la mise à jour.
  • Si une mise à jour OTA est appliquée mais ne parvient pas à démarrer, 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) affectent uniquement l'ensemble 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 problème OTA ou dm-verity, l'appareil peut redémarrer avec 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 ne devrait cependant 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 en 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 du système AOSP mais devront fournir leur propre client.

Pour les OEM fournissant 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 de maintenance inactif, par exemple la nuit, et en Wi-Fi. Cependant, votre client peut utiliser toutes les heuristiques de son choix.
  • Enregistrez-vous 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 souhaiterez signaler que l'appareil prend en charge 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’une soit 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 succès ou les échecs de l'installation à vos serveurs, en fonction du code de résultat update_engine . Si la mise à jour est appliquée avec succès, update_engine demandera 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 parvient pas à démarrer, aucun travail n'est donc requis de la part du client. Si la mise à jour échoue, le client doit décider quand (et si) il doit 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.

En option, le client peut :

  • Afficher une notification demandant à l'utilisateur de redémarrer. Si vous souhaitez mettre en œuvre une politique 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 recevront de toute façon la mise à jour au prochain redémarrage. (Le client de Google a un délai configurable par mise à jour.)
  • Afficher une notification indiquant aux utilisateurs s'ils ont démarré sur 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.)

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

  • Sélection de partition (emplacements), démon update_engine et interactions avec le chargeur de démarrage (décrites ci-dessous)
  • Processus de génération et génération du 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 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 au 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 à 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 de démarrage 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 contenir une ancienne version (toujours correcte) du système, une version plus récente ou des données invalides. Quel que soit l'emplacement actuel , il existe 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 possède également un attribut de réussite défini par l'espace utilisateur, qui n'est pertinent que si l'emplacement est également amorçable. Un emplacement réussi doit être capable de démarrer, de s'exécuter et de 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 ne pouvant pas démarrer par le chargeur de démarrage, y compris en changeant l'emplacement actif vers un autre emplacement de démarrage (normalement vers l'emplacement en cours d'exécution juste 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 .

Démon du moteur de mise à jour

Les mises à jour du système A/B utilisent un démon en 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 de slot A/B actuelles et écrivez toutes les données sur les partitions de slot 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 emplacement actuel). 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 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 dexopt A/B OTA sont répartis entre installd et un gestionnaire de packages :

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

Le boot_control HAL est utilisé par update_engine (et éventuellement d'autres démons) pour indiquer au chargeur de démarrage à partir duquel démarrer. Les exemples de scénarios courants et leurs états associés sont les suivants :

  • Cas normal : Le système fonctionne depuis 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 l'emplacement actif.
  • Mise à jour en cours : le système s'exécute à partir de l'emplacement B, l'emplacement B est donc l'emplacement amorçable, réussi et actif. L'emplacement A a été marqué comme ne pouvant pas démarrer 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 le chargeur de démarrage doit effectuer un certain nombre de tentatives de démarrage à partir de l'emplacement A.
  • Système redémarré avec 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 de l'espace utilisateur, update_verifier , doit marquer l'emplacement A comme réussi après quelques 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 souhaitent perdre de l'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 du streaming des mises à jour A/B qui écrivent les 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 presque aucun stockage temporaire et nécessitent juste assez de stockage 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 requis 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é dans le code payload ) est disponible pour le téléchargement. Les politiques de l'appareil peuvent différer le téléchargement de la charge utile et l'application en fonction du niveau de la batterie, de l'activité de l'utilisateur, de l'état de charge ou d'autres politiques. 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 politiques, de redémarrages inattendus ou d'actions de l'utilisateur.

Facultativement, 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 streaming. 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 leur propre serveur et client peuvent activer les mises à jour en streaming en s'assurant que le serveur identifie que la mise à jour est en streaming (ou suppose que toutes les mises à jour sont en streaming) et que le client appelle correctement update_engine pour le streaming. Les fabricants peuvent utiliser le fait que le package est de la variante streaming pour envoyer un indicateur au client afin de déclencher le transfert vers le côté framework en tant que streaming.

Une fois qu'une charge utile est 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 n'est pas déjà marqué) avec markBootSuccessful() .
2 L'emplacement inutilisé (ou "emplacement cible") est marqué comme ne pouvant pas démarrer 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 chargeur de démarrage 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 crash), car il est possible de pousser un nouveau logiciel pour les réparer. 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 la mise à jour se compose des éléments suivants :
  • Métadonnées . Partie relativement petite de la charge utile de la mise à jour, les métadonnées contiennent une liste d'opérations pour produire et vérifier la nouvelle version sur l'emplacement cible. Par exemple, une opération pourrait 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 . Constituant la majeure partie de la charge utile de la mise à jour, les données supplémentaires associées aux opérations sont constituées du blob compressé ou du correctif binaire dans ces exemples.
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 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 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 terminera le démarrage. Le chargeur de démarrage (ou le système lui-même) peut rétablir l'emplacement actif s'il ne lit pas un état réussi.
8 La post-installation (décrite ci-dessous) implique l'exécution d'un programme à partir de la version « nouvelle mise à jour » tout en continuant à s'exécuter dans l'ancienne version. Si 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 après le redémarrage, l'emplacement actuel (anciennement « emplacement cible ») est marqué comme réussi en appelant markBootSuccessful() .

Post-installation

Pour chaque partition pour laquelle 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 être capable de :

  • Montez le nouveau format du système de fichiers . Le type du 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).
  • Comprendre le format du programme de 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 fonctionnant sur un ancien noyau 32 bits si l'architecture est passée de versions 32 à 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 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. Vous pouvez également exécuter l'étape de post-installation à partir d'une partition dédiée plus petite 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é ascendante ou de mises à jour préliminaires ; 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 des tâches requises par la conception sur un appareil donné ou à d'autres tâches nécessitant un effort optimal (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 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 SELinux postinstall . 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 sur ce nouveau système. Les modifications apportées aux attributs SELinux dans le nouveau système n'auront pas d'impact sur l'étape post-installation. Si le programme de post-installation nécessite des 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 la vérification d'intégrité à l'aide de dm-verity. Cette vérification commence avant zygote pour éviter que les services Java n'effectuent 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 lira uniquement 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.