Servicios y transferencia de datos

Esta sección describe cómo registrar y descubrir servicios y cómo enviar datos a un servicio llamando a métodos definidos en interfaces en archivos .hal .

Registro de servicios

Los servidores de interfaz HIDL (objetos que implementan la interfaz) se pueden registrar como servicios con nombre. El nombre registrado no necesita estar relacionado con la interfaz o el nombre del paquete. Si no se especifica ningún nombre, se utiliza el nombre "predeterminado"; esto debe usarse para HAL que no necesitan registrar dos implementaciones de la misma interfaz. Por ejemplo, la llamada de C++ para el registro del servicio definida en cada interfaz es:

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

La versión de una interfaz HIDL se incluye en la propia interfaz. Se asocia automáticamente con el registro del servicio y se puede recuperar a través de una llamada de método ( android::hardware::IInterface::getInterfaceVersion() ) en cada interfaz HIDL. No es necesario registrar los objetos del servidor y se pueden pasar a través de los parámetros del método HIDL a otro proceso que realizará llamadas al método HIDL en el servidor.

Descubriendo servicios

Las solicitudes por código de cliente se realizan para una interfaz dada por nombre y por versión, llamando a getService en la clase HAL deseada:

// 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 */);

Cada versión de una interfaz HIDL se trata como una interfaz separada. Por lo tanto, IFooService versión 1.1 e IFooService versión 2.2 pueden registrarse como "foo_service" y getService("foo_service") en cualquiera de las interfaces obtiene el servicio registrado para esa interfaz. Esta es la razón por la que, en la mayoría de los casos, no es necesario proporcionar ningún parámetro de nombre para el registro o el descubrimiento (lo que significa que el nombre es "predeterminado").

El objeto de interfaz de proveedor también juega un papel en el método de transporte de la interfaz devuelta. Para una interfaz IFoo en el paquete android.hardware.foo@1.0 , la interfaz devuelta por IFoo::getService siempre usa el método de transporte declarado para android.hardware.foo en el manifiesto del dispositivo si existe la entrada; y si el método de transporte no está disponible, se devuelve nullptr.

En algunos casos, puede ser necesario continuar inmediatamente incluso sin obtener el servicio. Esto puede suceder (por ejemplo) cuando un cliente desea administrar las notificaciones de servicio por sí mismo o en un programa de diagnóstico (como atrace ) que necesita obtener todos los hwservices y recuperarlos. En este caso, se proporcionan API adicionales, como tryGetService en C++ o getService("instance-name", false) en Java. La API heredada getService proporcionada en Java también debe usarse con notificaciones de servicio. El uso de esta API no evita la condición de carrera en la que un servidor se registra después de que el cliente lo solicite con una de estas API sin reintentos.

Servicio de notificaciones de muerte

Los clientes que desean recibir una notificación cuando un servicio muere pueden recibir notificaciones de muerte entregadas por el marco. Para recibir notificaciones, el cliente debe:

  1. Subclase la clase/interfaz hidl_death_recipient (en código C++, no en HIDL).
  2. Anule su método serviceDied() .
  3. Crea una instancia de un objeto de la subclase hidl_death_recipient .
  4. Llame al método linkToDeath() en el servicio para monitorear, pasando el objeto de interfaz de IDeathRecipient . Tenga en cuenta que este método no toma posesión del destinatario de la muerte o del proxy en el que se llama.

Un ejemplo de pseudocódigo (C++ y Java son similares):

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);

El mismo destinatario de fallecimiento puede estar registrado en múltiples servicios diferentes.

Transferencia de datos

Los datos pueden enviarse a un servicio llamando a métodos definidos en interfaces en archivos .hal . Hay dos tipos de métodos:

  • Los métodos de bloqueo esperan hasta que el servidor haya producido un resultado.
  • Los métodos unidireccionales envían datos en una sola dirección y no se bloquean. Si la cantidad de datos en tránsito en las llamadas RPC supera los límites de implementación, las llamadas pueden bloquearse o devolver una indicación de error (el comportamiento aún no se ha determinado).

Un método que no devuelve un valor pero que no se declara como oneway todavía está bloqueando.

Todos los métodos declarados en una interfaz HIDL se llaman en una sola dirección, ya sea desde HAL o hacia HAL. La interfaz no especifica en qué dirección se llamará. Las arquitecturas que necesitan que las llamadas se originen desde HAL deben proporcionar dos (o más) interfaces en el paquete HAL y atender la interfaz apropiada de cada proceso. Las palabras cliente y servidor se utilizan con respecto a la dirección de llamada de la interfaz (es decir, la HAL puede ser un servidor de una interfaz y un cliente de otra interfaz).

devoluciones de llamada

La palabra devolución de llamada se refiere a dos conceptos diferentes, que se distinguen por devolución de llamada síncrona y devolución de llamada asíncrona .

Las devoluciones de llamada sincrónicas se utilizan en algunos métodos HIDL que devuelven datos. Un método HIDL que devuelve más de un valor (o devuelve un valor de tipo no primitivo) devuelve sus resultados a través de una función de devolución de llamada. Si solo se devuelve un valor y es un tipo primitivo, no se utiliza una devolución de llamada y el valor se devuelve desde el método. El servidor implementa los métodos HIDL y el cliente implementa las devoluciones de llamada.

Las devoluciones de llamada asincrónicas permiten que el servidor de una interfaz HIDL origine llamadas. Esto se hace pasando una instancia de una segunda interfaz a través de la primera interfaz. El cliente de la primera interfaz debe actuar como servidor de la segunda. El servidor de la primera interfaz puede llamar a métodos en el objeto de la segunda interfaz. Por ejemplo, una implementación de HAL puede enviar información de forma asíncrona al proceso que la está utilizando llamando a métodos en un objeto de interfaz creado y servido por ese proceso. Los métodos en las interfaces que se utilizan para la devolución de llamada asíncrona pueden ser de bloqueo (y pueden devolver valores a la persona que llama) o oneway . Para ver un ejemplo, consulte "Devoluciones de llamada asincrónicas" en HIDL C++ .

Para simplificar la propiedad de la memoria, las llamadas a métodos y las devoluciones in llamadas solo aceptan parámetros y no inout parámetros de entrada o out .

Límites por transacción

No se imponen límites por transacción en la cantidad de datos enviados en métodos HIDL y devoluciones de llamada. Sin embargo, las llamadas que superan los 4 KB por transacción se consideran excesivas. Si esto se ve, se recomienda rediseñar la interfaz HIDL dada. Otra limitación son los recursos disponibles para la infraestructura HIDL para manejar múltiples transacciones simultáneas. Varias transacciones pueden estar en tránsito simultáneamente debido a múltiples subprocesos o procesos que envían llamadas a un proceso o múltiples llamadas oneway que el proceso receptor no maneja rápidamente. El espacio total máximo disponible para todas las transacciones simultáneas es de 1 MB de forma predeterminada.

En una interfaz bien diseñada, no deberían excederse estas limitaciones de recursos; si lo hace, la llamada que los excedió puede bloquearse hasta que los recursos estén disponibles o señalar un error de transporte. Cada vez que se exceden los límites por transacción o se desbordan los recursos de implementación de HIDL por transacciones en tránsito agregadas, se registra para facilitar la depuración.

implementaciones de métodos

HIDL genera archivos de encabezado que declaran los tipos, métodos y devoluciones de llamada necesarios en el idioma de destino (C++ o Java). El prototipo de los métodos y devoluciones de llamada definidos por HIDL es el mismo para el código del cliente y del servidor. El sistema HIDL proporciona implementaciones de proxy de los métodos en el lado de la persona que llama que organizan los datos para el transporte de IPC, y código auxiliar en el lado del receptor de la llamada que pasa los datos a las implementaciones de los métodos por parte del desarrollador.

La persona que llama a una función (método HIDL o devolución de llamada) tiene la propiedad de las estructuras de datos pasadas a la función y retiene la propiedad después de la llamada; en todos los casos, el destinatario no necesita liberar o liberar el almacenamiento.

  • En C++, los datos pueden ser de solo lectura (los intentos de escribir en ellos pueden provocar un error de segmentación) y son válidos durante la duración de la llamada. El cliente puede hacer una copia profunda de los datos para propagarlos más allá de la llamada.
  • En Java, el código recibe una copia local de los datos (un objeto Java normal), que puede conservar y modificar o permitir que se recolecte basura.

Transferencia de datos sin RPC

HIDL tiene dos formas de transferir datos sin usar una llamada RPC: memoria compartida y Fast Message Queue (FMQ), ambas admitidas solo en C++.

  • Memoria compartida . La memory de tipo HIDL integrada se utiliza para pasar un objeto que representa la memoria compartida que se ha asignado. Se puede utilizar en un proceso de recepción para mapear la memoria compartida.
  • Cola rápida de mensajes (FMQ) . HIDL proporciona un tipo de cola de mensajes con plantilla que implementa el paso de mensajes sin espera. No utiliza el kernel ni el programador en modo passthrough o binderized (la comunicación entre dispositivos no tendrá estas propiedades). Por lo general, HAL configura su final de la cola, creando un objeto que se puede pasar a través de RPC a través de un parámetro de tipo HIDL incorporado MQDescriptorSync o MQDescriptorUnsync . Este objeto puede ser utilizado por el proceso de recepción para configurar el otro extremo de la cola.
    • Las colas de sincronización no pueden desbordarse y solo pueden tener un lector.
    • Las colas no sincronizadas pueden desbordarse y pueden tener muchos lectores, cada uno de los cuales debe leer los datos a tiempo o perderlos.
    No se permite el desbordamiento de ningún tipo (la lectura de una cola vacía fallará), y cada tipo solo puede tener un escritor.

Para obtener más detalles sobre FMQ, consulte Fast Message Queue (FMQ) .