Serviços e transferência de dados

Esta seção descreve como registrar e descobrir serviços e como enviar dados para um serviço chamando métodos definidos em interfaces em arquivos .hal .

Serviços de registro

Os servidores de interface HIDL (objetos que implementam a interface) podem ser registrados como serviços nomeados. O nome registrado não precisa estar relacionado à interface ou ao nome do pacote. Se nenhum nome for especificado, o nome "default" será usado; isso deve ser usado para HALs que não precisam registrar duas implementações da mesma interface. Por exemplo, a chamada C++ para registro de serviço definida em cada interface é:

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

A versão de uma interface HIDL está incluída na própria interface. Ele é automaticamente associado ao registro do serviço e pode ser recuperado por meio de uma chamada de método ( android::hardware::IInterface::getInterfaceVersion() ) em cada interface HIDL. Objetos de servidor não precisam ser registrados e podem ser passados ​​por meio de parâmetros de método HIDL para outro processo que fará chamadas de método HIDL no servidor.

Descobrindo serviços

As requisições por código de cliente são feitas para uma determinada interface por nome e por versão, chamando getService na classe HAL desejada:

// 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 versão de uma interface HIDL é tratada como uma interface separada. Assim, IFooService versão 1.1 e IFooService versão 2.2 podem ser registrados como "foo_service" e getService("foo_service") em qualquer interface obtém o serviço registrado para essa interface. É por isso que, na maioria dos casos, nenhum parâmetro de nome precisa ser fornecido para registro ou descoberta (significando nome "padrão").

O Vendor Interface Object também desempenha um papel no método de transporte da interface retornada. Para uma interface IFoo no pacote android.hardware.foo@1.0 , a interface retornada por IFoo::getService sempre usa o método de transporte declarado para android.hardware.foo no manifesto do dispositivo se a entrada existir; e se o método de transporte não estiver disponível, nullptr será retornado.

Em alguns casos, pode ser necessário continuar imediatamente mesmo sem obter o serviço. Isso pode acontecer (por exemplo) quando um cliente deseja gerenciar as próprias notificações de serviço ou em um programa de diagnóstico (como atrace ) que precisa obter todos os hwservices e recuperá-los. Nesse caso, APIs adicionais são fornecidas, como tryGetService em C++ ou getService("instance-name", false) em Java. A API legada getService fornecida em Java também deve ser usada com notificações de serviço. O uso dessa API não evita a condição de corrida em que um servidor se registra depois que o cliente o solicita com uma dessas APIs sem repetição.

Notificações de morte de serviço

Os clientes que desejam ser notificados quando um serviço morre podem receber notificações de óbito entregues pela estrutura. Para receber notificações, o cliente deve:

  1. Subclasse a classe/interface hidl_death_recipient (em código C++, não em HIDL).
  2. Substitua seu método serviceDied() .
  3. Instancie um objeto da subclasse hidl_death_recipient .
  4. Chame o método linkToDeath() no serviço a ser monitorado, passando o objeto de interface do IDeathRecipient . Observe que esse método não se apropria do destinatário da morte ou do proxy no qual é chamado.

Um exemplo de pseudocódigo (C++ e Java são semelhantes):

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

O mesmo destinatário da morte pode ser registrado em vários serviços diferentes.

Transferência de dados

Os dados podem ser enviados a um serviço chamando métodos definidos em interfaces em arquivos .hal . Existem dois tipos de métodos:

  • Os métodos de bloqueio aguardam até que o servidor produza um resultado.
  • Métodos unidirecionais enviam dados em apenas uma direção e não bloqueiam. Se a quantidade de dados em trânsito nas chamadas RPC exceder os limites de implementação, as chamadas podem bloquear ou retornar uma indicação de erro (o comportamento ainda não foi determinado).

Um método que não retorna um valor, mas não é declarado como oneway , ainda está bloqueando.

Todos os métodos declarados em uma interface HIDL são chamados em uma única direção, seja do HAL ou no HAL. A interface não especifica em qual direção ela será chamada. Arquiteturas que precisam de chamadas originadas do HAL devem fornecer duas (ou mais) interfaces no pacote HAL e servir a interface apropriada de cada processo. As palavras cliente e servidor são usadas em relação à direção de chamada da interface (ou seja, o HAL pode ser um servidor de uma interface e um cliente de outra interface).

Retornos de chamada

A palavra retorno de chamada refere-se a dois conceitos diferentes, distinguidos por retorno de chamada síncrono e retorno de chamada assíncrono .

Os retornos de chamada síncronos são usados ​​em alguns métodos HIDL que retornam dados. Um método HIDL que retorna mais de um valor (ou retorna um valor de tipo não primitivo) retorna seus resultados por meio de uma função de retorno de chamada. Se apenas um valor for retornado e for um tipo primitivo, um retorno de chamada não será usado e o valor será retornado do método. O servidor implementa os métodos HIDL e o cliente implementa os retornos de chamada.

Os retornos de chamada assíncronos permitem que o servidor de uma interface HIDL origine chamadas. Isso é feito passando uma instância de uma segunda interface pela primeira interface. O cliente da primeira interface deve atuar como servidor da segunda. O servidor da primeira interface pode chamar métodos no segundo objeto de interface. Por exemplo, uma implementação de HAL pode enviar informações de forma assíncrona de volta para o processo que a está usando chamando métodos em um objeto de interface criado e servido por esse processo. Métodos em interfaces usadas para retorno de chamada assíncrono podem ser bloqueantes (e podem retornar valores para o chamador) ou oneway . Para obter um exemplo, consulte "Retornos de chamada assíncronos" em HIDL C++ .

Para simplificar a propriedade da memória, chamadas de método e retornos in chamada aceitam apenas parâmetros e não suportam parâmetros out ou inout .

Limites por transação

Os limites por transação não são impostos à quantidade de dados enviados em métodos HIDL e retornos de chamada. No entanto, as chamadas superiores a 4 KB por transação são consideradas excessivas. Se isso for visto, é recomendável rearquitetar a interface HIDL fornecida. Outra limitação são os recursos disponíveis para a infraestrutura HIDL para lidar com várias transações simultâneas. Várias transações podem estar em andamento simultaneamente devido a vários threads ou processos que enviam chamadas para um processo ou várias chamadas oneway que não são tratadas rapidamente pelo processo de recebimento. O espaço total máximo disponível para todas as transações simultâneas é de 1 MB por padrão.

Em uma interface bem projetada, não deve acontecer exceder essas limitações de recursos; se isso acontecer, a chamada que os excedeu pode bloquear até que os recursos fiquem disponíveis ou sinalizar um erro de transporte. Cada ocorrência de excesso de limites por transação ou estouro de recursos de implementação HIDL por transações agregadas em andamento é registrada para facilitar a depuração.

Implementações de métodos

O HIDL gera arquivos de cabeçalho declarando os tipos, métodos e retornos de chamada necessários na linguagem de destino (C++ ou Java). O protótipo de métodos e retornos de chamada definidos por HIDL é o mesmo para o código do cliente e do servidor. O sistema HIDL fornece implementações de proxy dos métodos no lado do chamador que organizam os dados para o transporte IPC e código stub no lado do receptor que passa os dados para as implementações dos métodos do desenvolvedor.

O chamador de uma função (método HIDL ou retorno de chamada) possui a propriedade das estruturas de dados passadas para a função e retém a propriedade após a chamada; em todos os casos, o receptor não precisa liberar ou liberar o armazenamento.

  • Em C++, os dados podem ser somente leitura (tentativas de gravar neles podem causar uma falha de segmentação) e são válidos durante a chamada. O cliente pode fazer uma cópia profunda dos dados para propagá-los além da chamada.
  • Em Java, o código recebe uma cópia local dos dados (um objeto Java normal), que pode manter e modificar ou permitir que seja coletado como lixo.

Transferência de dados não RPC

O HIDL tem duas maneiras de transferir dados sem usar uma chamada RPC: memória compartilhada e uma fila de mensagens rápidas (FMQ), ambas suportadas apenas em C++.

  • Memória compartilhada . A memory interna do tipo HIDL é usada para passar um objeto que representa a memória compartilhada que foi alocada. Pode ser usado em um processo de recebimento para mapear a memória compartilhada.
  • Fila de Mensagens Rápidas (FMQ) . O HIDL fornece um tipo de fila de mensagens modelo que implementa a passagem de mensagens sem espera. Ele não usa o kernel ou agendador no modo passthrough ou binderized (a comunicação entre dispositivos não terá essas propriedades). Normalmente, o HAL configura seu final da fila, criando um objeto que pode ser passado por meio de RPC por meio de um parâmetro do tipo HIDL integrado MQDescriptorSync ou MQDescriptorUnsync . Esse objeto pode ser usado pelo processo de recebimento para configurar a outra extremidade da fila.
    • As filas de sincronização não têm permissão para estourar e podem ter apenas um leitor.
    • As filas não sincronizadas têm permissão para estourar e podem ter muitos leitores, cada um dos quais deve ler os dados a tempo ou perdê-los.
    Nenhum tipo tem permissão para underflow (a leitura de uma fila vazia falhará) e cada tipo pode ter apenas um gravador.

Para obter mais detalhes sobre FMQ, consulte Fast Message Queue (FMQ) .