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 .

Servicios de registro

Los servidores de interfaz HIDL (objetos que implementan la interfaz) se pueden registrar como servicios con nombre. No es necesario que el nombre registrado esté 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 C++ para el registro de servicios 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 está incluida en la propia interfaz. Se asocia automáticamente con el registro del servicio y se puede recuperar mediante una llamada a un método ( android::hardware::IInterface::getInterfaceVersion() ) en cada interfaz HIDL. Los objetos del servidor no necesitan registrarse y pueden pasarse mediante 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 determinada por nombre y versión, llamando 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 independiente. 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 descubrimiento (lo que significa nombre "predeterminado").

El objeto de interfaz del proveedor también desempeña 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 la entrada existe; y si el método de transporte no está disponible, se devuelve nullptr.

En algunos casos, puede ser necesario continuar inmediatamente incluso sin recibir el servicio. Esto puede suceder (por ejemplo) cuando un cliente desea administrar las notificaciones de servicios por sí mismo o en un programa de diagnóstico (como atrace ) que necesita obtener todos los servicios hw 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 solicita con una de estas API sin reintento.

Notificaciones de muerte del servicio

Los clientes que quieran recibir una notificación cuando un servicio falle pueden recibir notificaciones de muerte entregadas por el marco. Para recibir notificaciones, el cliente deberá:

  1. Subclase la clase/interfaz HIDL hidl_death_recipient (en código C++, no en HIDL).
  2. Anule su método serviceDied() .
  3. Cree una instancia de un objeto de la subclase hidl_death_recipient .
  4. Llame al método linkToDeath() en el servicio a monitorear, pasando el objeto de interfaz IDeathRecipient . Tenga en cuenta que este método no asume propiedad del destinatario del fallecimiento ni del proxy al 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 varios servicios diferentes.

Transferencia de datos

Los datos se pueden enviar 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 bloquean. Si la cantidad de datos en tránsito en las llamadas RPC excede los límites de implementación, las llamadas pueden bloquearse o devolver una indicación de error (el comportamiento aún no está determinado).

Un método que no devuelve un valor pero que no está declarado como oneway sigue bloqueando.

Todos los métodos declarados en una interfaz HIDL se llaman en una única 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 servir la interfaz adecuada de cada proceso. Las palabras cliente y servidor se utilizan con respecto a la dirección de llamada de la interfaz (es decir, 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 sincrónica y devolución de llamada asincrónica .

Las devoluciones de llamada síncronas 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 segundo objeto de interfaz. Por ejemplo, una implementación HAL puede enviar información de forma asincrónica 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 utilizadas para la devolución de llamada asincrónica pueden ser de bloqueo (y pueden devolver valores a la persona que llama) o oneway . Para ver un ejemplo, consulte "Devoluciones de llamada asíncronas" en HIDL C++ .

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

Límites por transacción

No se imponen límites por transacción a 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 ocurre, 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. Se pueden realizar múltiples transacciones en curso simultáneamente debido a que múltiples subprocesos o procesos 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 1 MB de forma predeterminada.

En una interfaz bien diseñada, no debería ocurrir exceder estas limitaciones de recursos; si es así, la llamada que los superó puede bloquearse hasta que los recursos estén disponibles o indicar un error de transporte. Cada vez que se exceden los límites por transacción o se desbordan los recursos de implementación HIDL mediante transacciones en curso 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 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 proxy de los métodos en el lado del llamante que organizan los datos para el transporte IPC, y código auxiliar en el lado del llamado que pasa los datos a las implementaciones de los métodos 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 conserva la propiedad después de la llamada; en todos los casos, el destinatario de la llamada 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 mientras dure la llamada. El cliente puede realizar 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 utilizar una llamada RPC: memoria compartida y una cola de mensajes rápida (FMQ), ambas compatibles sólo con C++.

  • Memoria compartida . La memory de tipo HIDL incorporada 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 de mensajes rápida (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 enlazado (la comunicación entre dispositivos no tendrá estas propiedades). Normalmente, 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 integrado MQDescriptorSync o MQDescriptorUnsync . Este objeto puede ser utilizado por el proceso receptor para configurar el otro extremo de la cola.
    • No se permite que las colas de sincronización se desborden 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.
    Ningún tipo puede tener un desbordamiento insuficiente (la lectura desde una cola vacía fallará) y cada tipo solo puede tener un escritor.

Para obtener más detalles sobre FMQ, consulte Cola de mensajes rápida (FMQ) .