Services et services Transfert de données

Cette section décrit comment enregistrer et découvrir des services et comment envoyer des données à un service en appelant des méthodes définies dans les interfaces des fichiers .hal .

Services d'enregistrement

Les serveurs d'interface HIDL (objets implémentant l'interface) peuvent être enregistrés en tant que services nommés. Le nom enregistré n'a pas besoin d'être lié à l'interface ou au nom du package. Si aucun nom n'est spécifié, le nom « par défaut » est utilisé ; cela doit être utilisé pour les HAL qui n'ont pas besoin d'enregistrer deux implémentations de la même interface. Par exemple, l'appel C++ pour l'enregistrement du service défini dans chaque interface est :

status_t status = myFoo->registerAsService();
status_t anotherStatus = anotherFoo->registerAsService("another_foo_service");  // if needed

La version d'une interface HIDL est incluse dans l'interface elle-même. Il est automatiquement associé à l'enregistrement du service et peut être récupéré via un appel de méthode ( android::hardware::IInterface::getInterfaceVersion() ) sur chaque interface HIDL. Les objets serveur n'ont pas besoin d'être enregistrés et peuvent être transmis via les paramètres de la méthode HIDL à un autre processus qui effectuera des appels de méthode HIDL sur le serveur.

Découvrir les services

Les requêtes par code client sont faites pour une interface donnée par nom et par version, en appelant getService sur la classe HAL souhaitée :

// C++
sp<V1_1::IFooService> service = V1_1::IFooService::getService();
sp<V1_1::IFooService> alternateService = V1_1::IFooService::getService("another_foo_service");
// Java
V1_1.IFooService service = V1_1.IFooService.getService(true /* retry */);
V1_1.IFooService alternateService = V1_1.IFooService.getService("another", true /* retry */);

Chaque version d'une interface HIDL est traitée comme une interface distincte. Ainsi, IFooService version 1.1 et IFooService version 2.2 peuvent tous deux être enregistrés en tant que "foo_service" et getService("foo_service") sur l'une ou l'autre interface obtient le service enregistré pour cette interface. C'est pourquoi, dans la plupart des cas, aucun paramètre de nom ne doit être fourni pour l'enregistrement ou la découverte (c'est-à-dire nom « par défaut »).

L'objet d'interface fournisseur joue également un rôle dans la méthode de transport de l'interface renvoyée. Pour une interface IFoo dans le package android.hardware.foo@1.0 , l'interface renvoyée par IFoo::getService utilise toujours la méthode de transport déclarée pour android.hardware.foo dans le manifeste du périphérique si l'entrée existe ; et si la méthode de transport n'est pas disponible, nullptr est renvoyé.

Dans certains cas, il peut être nécessaire de continuer immédiatement, même sans bénéficier du service. Cela peut se produire (par exemple) lorsqu'un client souhaite gérer les notifications de service lui-même ou dans un programme de diagnostic (tel que atrace ) qui doit récupérer tous les hwservices et les récupérer. Dans ce cas, des API supplémentaires sont fournies telles que tryGetService en C++ ou getService("instance-name", false) en Java. L'ancienne API getService fournie en Java doit également être utilisée avec les notifications de service. L'utilisation de cette API n'évite pas la situation de concurrence critique dans laquelle un serveur s'enregistre après que le client l'a demandé auprès de l'une de ces API sans nouvelle tentative.

Notifications de décès en service

Les clients qui souhaitent être avertis de la mort d'un service peuvent recevoir des notifications de décès fournies par le framework. Pour recevoir des notifications, le client doit :

  1. Sous-classe la classe/interface HIDL hidl_death_recipient (en code C++, pas en HIDL).
  2. Remplacez sa méthode serviceDied() .
  3. Instanciez un objet de la sous-classe hidl_death_recipient .
  4. Appelez la méthode linkToDeath() sur le service à surveiller, en transmettant l'objet d'interface de IDeathRecipient . A noter que cette méthode ne prend pas possession du destinataire du décès ni du mandataire sur lequel elle est appelée.

Un exemple de pseudocode (C++ et Java sont similaires) :

class IMyDeathReceiver : hidl_death_recipient {
  virtual void serviceDied(uint64_t cookie,
                           wp<IBase>& service) override {
    log("RIP service %d!", cookie);  // Cookie should be 42
  }
};
....
IMyDeathReceiver deathReceiver = new IMyDeathReceiver();
m_importantService->linkToDeath(deathReceiver, 42);

Le même destinataire du décès peut être inscrit sur plusieurs services différents.

Transfert de données

Les données peuvent être envoyées à un service en appelant des méthodes définies dans les interfaces dans les fichiers .hal . Il existe deux types de méthodes :

  • Les méthodes de blocage attendent que le serveur ait produit un résultat.
  • Les méthodes unidirectionnelles envoient les données dans une seule direction et ne bloquent pas. Si la quantité de données en cours dans les appels RPC dépasse les limites d'implémentation, les appels peuvent soit bloquer, soit renvoyer une indication d'erreur (le comportement n'est pas encore déterminé).

Une méthode qui ne renvoie pas de valeur mais qui n'est pas déclarée comme oneway est toujours bloquante.

Toutes les méthodes déclarées dans une interface HIDL sont appelées dans une seule direction, soit depuis la HAL, soit vers la HAL. L'interface ne précise pas dans quelle direction elle sera appelée. Les architectures qui nécessitent que les appels proviennent de HAL doivent fournir deux (ou plus) interfaces dans le package HAL et servir l'interface appropriée de chaque processus. Les mots client et serveur sont utilisés par rapport au sens d'appel de l'interface (c'est-à-dire que la HAL peut être un serveur d'une interface et un client d'une autre interface).

Rappels

Le mot rappel fait référence à deux concepts différents, distingués par le rappel synchrone et le rappel asynchrone .

Les rappels synchrones sont utilisés dans certaines méthodes HIDL qui renvoient des données. Une méthode HIDL qui renvoie plusieurs valeurs (ou renvoie une valeur de type non primitif) renvoie ses résultats via une fonction de rappel. Si une seule valeur est renvoyée et qu'il s'agit d'un type primitif, aucun rappel n'est utilisé et la valeur est renvoyée par la méthode. Le serveur implémente les méthodes HIDL et le client implémente les rappels.

Les rappels asynchrones permettent au serveur d'une interface HIDL d'émettre des appels. Cela se fait en faisant passer une instance d'une deuxième interface via la première interface. Le client de la première interface doit faire office de serveur de la seconde. Le serveur de la première interface peut appeler des méthodes sur le deuxième objet d'interface. Par exemple, une implémentation HAL peut renvoyer des informations de manière asynchrone au processus qui l'utilise en appelant des méthodes sur un objet d'interface créé et servi par ce processus. Les méthodes des interfaces utilisées pour le rappel asynchrone peuvent être bloquantes (et renvoyer des valeurs à l'appelant) ou oneway . Pour un exemple, consultez « Rappels asynchrones » dans HIDL C++ .

Pour simplifier la propriété de la mémoire, les appels de méthode et les rappels prennent uniquement in paramètres et ne prennent pas en charge les paramètres out ou inout .

Limites par transaction

Aucune limite par transaction n'est imposée sur la quantité de données envoyées dans les méthodes et rappels HIDL. Toutefois, les appels dépassant 4 Ko par transaction sont considérés comme excessifs. Si cela se produit, il est recommandé de ré-architecturer l'interface HIDL donnée. Une autre limitation concerne les ressources dont dispose l'infrastructure HIDL pour gérer plusieurs transactions simultanées. Plusieurs transactions peuvent être en cours simultanément en raison de plusieurs threads ou processus envoyant des appels à un processus ou de plusieurs appels oneway qui ne sont pas traités rapidement par le processus de réception. L'espace total maximum disponible pour toutes les transactions simultanées est de 1 Mo par défaut.

Dans une interface bien conçue, il ne devrait pas y avoir de dépassement de ces limitations de ressources ; si c'est le cas, l'appel qui les a dépassés peut soit se bloquer jusqu'à ce que les ressources soient disponibles, soit signaler une erreur de transport. Chaque occurrence de dépassement des limites par transaction ou de dépassement des ressources de mise en œuvre HIDL par des transactions en cours agrégées est enregistrée pour faciliter le débogage.

Implémentations de méthodes

HIDL génère des fichiers d'en-tête déclarant les types, méthodes et rappels nécessaires dans le langage cible (C++ ou Java). Le prototype des méthodes et des rappels définis par HIDL est le même pour le code client et serveur. Le système HIDL fournit des implémentations proxy des méthodes du côté appelant qui organisent les données pour le transport IPC, ainsi qu'un code stub du côté de l'appelé qui transmet les données aux implémentations développeurs des méthodes.

L'appelant d'une fonction (méthode HIDL ou rappel) est propriétaire des structures de données transmises à la fonction et conserve la propriété après l'appel ; dans tous les cas, l'appelé n'a pas besoin de libérer ou de libérer le stockage.

  • En C++, les données peuvent être en lecture seule (les tentatives d'écriture peuvent provoquer une erreur de segmentation) et sont valides pendant toute la durée de l'appel. Le client peut copier en profondeur les données pour les propager au-delà de l'appel.
  • En Java, le code reçoit une copie locale des données (un objet Java normal), qu'il peut conserver et modifier ou permettre le garbage collection.

Transfert de données non RPC

HIDL dispose de deux manières de transférer des données sans utiliser d'appel RPC : la mémoire partagée et une file d'attente de messages rapide (FMQ), toutes deux prises en charge uniquement en C++.

  • La memoire partagée . La memory de type HIDL intégrée est utilisée pour transmettre un objet représentant la mémoire partagée qui a été allouée. Peut être utilisé dans un processus de réception pour mapper la mémoire partagée.
  • File d'attente de messages rapide (FMQ) . HIDL fournit un type de file d'attente de messages modélisé qui implémente la transmission de messages sans attente. Il n'utilise pas le noyau ou le planificateur en mode passthrough ou binderisé (la communication inter-périphériques n'aura pas ces propriétés). Généralement, le HAL configure sa fin de file d'attente, créant un objet qui peut être transmis via RPC via un paramètre de type HIDL intégré MQDescriptorSync ou MQDescriptorUnsync . Cet objet peut être utilisé par le processus de réception pour configurer l'autre extrémité de la file d'attente.
    • Les files d'attente de synchronisation ne sont pas autorisées à déborder et ne peuvent avoir qu'un seul lecteur.
    • Les files d'attente non synchronisées peuvent déborder et peuvent avoir de nombreux lecteurs, dont chacun doit lire les données à temps ou les perdre.
    Aucun des deux types n'est autorisé à déborder (la lecture à partir d'une file d'attente vide échouera) et chaque type ne peut avoir qu'un seul rédacteur.

Pour plus de détails sur FMQ, consultez Fast Message Queue (FMQ) .