Utiliser l'IPC de liaison

Cette page décrit les modifications apportées au pilote de liaison dans Android 8, fournit des informations sur l'utilisation de l'IPC de liaison et liste la stratégie SELinux requise.

Modifications apportées au pilote de liaison

À partir d'Android 8, le framework Android et les HAL communiquent désormais entre eux à l'aide d'un liaisonneur. Comme cette communication augmente considérablement le trafic de liaison, Android 8 inclut plusieurs améliorations conçues pour maintenir l'IPC de liaison rapide. Les fournisseurs de SoC et les OEM doivent fusionner directement à partir des branches pertinentes d'android-4.4, android-4.9 et versions ultérieures du projet kernel/common.

Plusieurs domaines de liaison (contextes)

Common-4.4 et versions ultérieures, y compris en amont

Pour diviser de manière claire le trafic du liaisonneur entre le code du framework (indépendant de l'appareil) et le code du fournisseur (spécifique à l'appareil), Android 8 a introduit le concept de contexte de liaisonneur. Chaque contexte de liaison possède son propre nœud d'appareil et son propre gestionnaire de contexte (service). Vous ne pouvez accéder au gestionnaire de contexte que via le nœud de l'appareil auquel il appartient. Lorsque vous transmettez un nœud de liaison via un certain contexte, il n'est accessible à partir de ce même contexte que par un autre processus, isolant ainsi complètement les domaines les uns des autres. Pour en savoir plus sur l'utilisation, consultez vndbinder et vndservicemanager.

Scatter-gather

Common-4.4 et versions ultérieures, y compris en amont

Dans les versions précédentes d'Android, chaque élément de données d'un appel de liaison était copié trois fois:

  • Une fois pour le sérialiser dans un Parcel dans le processus d'appel
  • Une fois dans le pilote du noyau, copiez le Parcel dans le processus cible.
  • Une fois pour désérialiser le Parcel dans le processus cible

Android 8 utilise l'optimisation de la collecte pour réduire le nombre de copies de trois à un. Au lieu de sérialiser d'abord les données dans un Parcel, les données restent dans leur structure et leur mise en page de mémoire d'origine, et le pilote les copie immédiatement dans le processus cible. Une fois les données dans le processus cible, la structure et la mise en page de la mémoire sont les mêmes, et les données peuvent être lues sans nécessiter une autre copie.

Verrouillage précis

Common-4.4 et versions ultérieures, y compris en amont

Dans les versions précédentes d'Android, le pilote de liaison utilisait un verrouillage global pour se protéger contre l'accès simultané aux structures de données critiques. Bien qu'il y ait eu une contention minimale pour le verrouillage, le principal problème était que si un thread de faible priorité obtenait le verrouillage, puis était préempté, il pouvait sérieusement retarder les threads de priorité plus élevée qui devaient obtenir le même verrouillage. Cela a entraîné des à-coups sur la plate-forme.

Les premières tentatives de résolution de ce problème consistaient à désactiver la préemption tout en maintenant le verrouillage global. Cependant, il s'agissait davantage d'un bidouillage que d'une véritable solution. Il a finalement été rejeté en amont et abandonné. Les tentatives ultérieures ont consisté à affiner le verrouillage, dont une version est exécutée sur les appareils Pixel depuis janvier 2017. Bien que la majorité de ces modifications aient été rendues publiques, des améliorations substantielles ont été apportées dans les versions ultérieures.

Après avoir identifié de petits problèmes dans l'implémentation du verrouillage précis, nous avons conçu une solution améliorée avec une architecture de verrouillage différente et envoyé les modifications dans toutes les branches du noyau courantes. Nous continuons de tester cette implémentation sur un grand nombre d'appareils différents. Comme nous ne sommes au courant d'aucun problème, il s'agit de l'implémentation recommandée pour les appareils livrés avec Android 8.

Héritage de priorité en temps réel

Common-4.4 et common-4.9 (en cours de développement)

Le pilote de liaison a toujours accepté l'héritage de priorité. Comme un nombre croissant de processus dans Android s'exécutent en priorité en temps réel, il est désormais logique que, dans certains cas, si un thread en temps réel effectue un appel de liaison, le thread du processus qui gère cet appel s'exécute également en priorité en temps réel. Pour prendre en charge ces cas d'utilisation, Android 8 implémente désormais l'héritage de priorité en temps réel dans le pilote de liaison.

En plus de l'héritage de priorité au niveau de la transaction, l'héritage de priorité de nœud permet à un nœud (objet de service de liaison) de spécifier une priorité minimale à laquelle les appels vers ce nœud doivent être exécutés. Les versions précédentes d'Android acceptaient déjà l'héritage de priorité de nœud avec des valeurs agréables, mais Android 8 prend en charge l'héritage de nœud des stratégies de planification en temps réel.

Modifications apportées à l'espace utilisateur

Android 8 inclut toutes les modifications de l'espace utilisateur requises pour fonctionner avec le pilote de liaison actuel dans le noyau commun, à une exception près: l'implémentation d'origine pour désactiver l'héritage de priorité en temps réel pour /dev/binder utilisait un ioctl. Le développement ultérieur a remplacé le contrôle de l'héritage de priorité par une méthode plus précise par mode de liaison (et non par contexte). Par conséquent, l'ioctl ne se trouve pas dans la branche commune d'Android, mais est envoyé dans nos noyaux communs.

L'héritage de priorité en temps réel est désactivé par défaut pour tous les nœuds. L'équipe chargée des performances Android a trouvé utile d'activer l'héritage de priorité en temps réel pour tous les nœuds du domaine hwbinder. Pour obtenir le même effet, sélectionnez cette modification dans l'espace utilisateur.

SHA pour les noyaux courants

Pour obtenir les modifications nécessaires au pilote de liaison, synchronisez-vous avec le SHA approprié:

  • Common-3.18
    cc8b90c121de ANDROID: liaison: ne vérifiez pas les autorisations de priorité lors de la restauration.
  • Common-4.4
    76b376eac7a2 ANDROID: binder: ne vérifiez pas les autorisations de priorité lors de la restauration.
  • Common-4.9
    ecd972d4f9b5 ANDROID: binder: ne vérifiez pas les autorisations de priorité lors de la restauration.

Utiliser l'IPC de liaison

Historiquement, les processus du fournisseur utilisaient la communication interprocessus (IPC) du liaisonneur pour communiquer. Dans Android 8, le nœud d'appareil /dev/binder devient exclusif aux processus de framework, ce qui signifie que les processus du fournisseur n'y ont plus accès. Les processus du fournisseur peuvent accéder à /dev/hwbinder, mais doivent convertir leurs interfaces AIDL pour utiliser HIDL. Pour les fournisseurs qui souhaitent continuer à utiliser des interfaces AIDL entre les processus du fournisseur, Android est compatible avec l'IPC de liaison, comme décrit ci-dessous. Dans Android 10, AIDL stable permet à tous les processus d'utiliser /dev/binder, tout en assurant les garanties de stabilité de HIDL et de /dev/hwbinder. Pour savoir comment utiliser AIDL stable, consultez la section AIDL pour les HAL.

vndbinder

Android 8 prend en charge un nouveau domaine de liaison à utiliser par les services du fournisseur, accessible à l'aide de /dev/vndbinder au lieu de /dev/binder. Avec l'ajout de /dev/vndbinder, Android dispose désormais des trois domaines IPC suivants:

Domaine IPC Description
/dev/binder Communication inter-processus entre les processus de framework/application avec des interfaces AIDL
/dev/hwbinder Communication inter-processus entre les processus du framework/du fournisseur avec des interfaces HIDL
Communication inter-processus entre les processus du fournisseur avec des interfaces HIDL
/dev/vndbinder Communication interprocessus entre les fournisseurs et les processus avec des interfaces AIDL

Pour que /dev/vndbinder s'affiche, assurez-vous que l'élément de configuration du kernel CONFIG_ANDROID_BINDER_DEVICES est défini sur "binder,hwbinder,vndbinder" (valeur par défaut dans les arborescences de kernel courantes d'Android).

Normalement, les processus du fournisseur n'ouvrent pas directement le pilote de liaison, mais se lient à la bibliothèque d'espace utilisateur libbinder, qui ouvre le pilote de liaison. L'ajout d'une méthode pour ::android::ProcessState() sélectionne le pilote de liaison pour libbinder. Les processus du fournisseur doivent appeler cette méthode avant d'appeler IPCThreadState dans ProcessState, ou avant d'effectuer des appels de liaison en général. Pour l'utiliser, placez l'appel suivant après le main() d'un processus du fournisseur (client et serveur):

ProcessState::initWithDriver("/dev/vndbinder");

vndservicemanager

Auparavant, les services de liaison étaient enregistrés auprès de servicemanager, où ils pouvaient être récupérés par d'autres processus. Dans Android 8, servicemanager est désormais utilisé exclusivement par les processus de framework et d'application, et les processus du fournisseur ne peuvent plus y accéder.

Toutefois, les services du fournisseur peuvent désormais utiliser vndservicemanager, une nouvelle instance de servicemanager qui utilise /dev/vndbinder au lieu de /dev/binder et qui est compilée à partir des mêmes sources que le framework servicemanager. Les processus du fournisseur n'ont pas besoin d'apporter de modifications pour communiquer avec vndservicemanager. Lorsqu'un processus du fournisseur ouvre /dev/vndbinder, les recherches de services sont automatiquement redirigées vers vndservicemanager.

Le binaire vndservicemanager est inclus dans les fichiers de compilation de l'appareil par défaut d'Android.

Règle SELinux

Les processus du fournisseur qui souhaitent utiliser la fonctionnalité de liaison pour communiquer entre eux doivent disposer des éléments suivants:

  1. Accès à /dev/vndbinder.
  2. Le {transfer, call} de liaison s'associe à vndservicemanager.
  3. binder_call(A, B) pour tout domaine fournisseur A qui souhaite appeler le domaine fournisseur B via l'interface de liaison du fournisseur.
  4. Autorisation pour les services {add, find} dans vndservicemanager.

Pour répondre aux exigences 1 et 2, utilisez la macro vndbinder_use():

vndbinder_use(some_vendor_process_domain);

Pour répondre à l'exigence 3, les binder_call(A, B) des processus de fournisseurs A et B qui doivent communiquer via le liaison peuvent rester en place et ne doivent pas être renommés.

Pour répondre à l'exigence 4, vous devez modifier la façon dont les noms, les libellés et les règles de service sont gérés.

Pour en savoir plus sur SELinux, consultez Security-Enhanced Linux dans Android. Pour en savoir plus sur SELinux dans Android 8.0, consultez SELinux pour Android 8.0.

Noms des services

Auparavant, le fournisseur traitait les noms de service enregistrés dans un fichier service_contexts et ajoutait les règles correspondantes pour accéder à ce fichier. Exemple de fichier service_contexts à partir de device/google/marlin/sepolicy:

AtCmdFwd                              u:object_r:atfwd_service:s0
cneservice                            u:object_r:cne_service:s0
qti.ims.connectionmanagerservice      u:object_r:imscm_service:s0
rcs                                   u:object_r:radio_service:s0
uce                                   u:object_r:uce_service:s0
vendor.qcom.PeripheralManager         u:object_r:per_mgr_service:s0

Dans Android 8, vndservicemanager charge le fichier vndservice_contexts à la place. Les services des fournisseurs qui migrent vers vndservicemanager (et qui se trouvent déjà dans l'ancien fichier service_contexts) doivent être ajoutés au nouveau fichier vndservice_contexts.

Libellés de service

Auparavant, les libellés de service tels que u:object_r:atfwd_service:s0 étaient définis dans un fichier service.te. Exemple :

type atfwd_service,      service_manager_type;

Sous Android 8, vous devez remplacer le type par vndservice_manager_type et déplacer la règle vers le fichier vndservice.te. Exemple :

type atfwd_service,      vndservice_manager_type;

règles servicemanager

Auparavant, les règles accordaient aux domaines l'accès pour ajouter ou rechercher des services à partir de servicemanager. Exemple :

allow atfwd atfwd_service:service_manager find;
allow some_vendor_app atfwd_service:service_manager add;

Sous Android 8, ces règles peuvent rester en place et utiliser la même classe. Exemple :

allow atfwd atfwd_service:service_manager find;
allow some_vendor_app atfwd_service:service_manager add;