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 .

Registrando serviç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. Os objetos do servidor não precisam ser registrados e podem ser passados ​​através dos parâmetros do método HIDL para outro processo que fará chamadas do método HIDL para o servidor.

Descobrindo serviços

As solicitações por código 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 (ou seja, nome "padrão").

O objeto Vendor Interface 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 notificações de serviço por conta própria 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 após o cliente solicitá-lo com uma dessas APIs sem nova tentativa.

Notificações de morte de serviço

Os clientes que desejam ser notificados quando um serviço morre podem receber notificações de falecimento entregues pela estrutura. Para receber notificações o cliente deverá:

  1. Subclasse a classe/interface HIDL 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 este método não se apropria do destinatário do falecimento 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 beneficiário de óbito pode estar cadastrado em vários serviços diferentes.

Transferência de dados

Os dados podem ser enviados para 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.
  • Os 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 poderão 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 para o HAL. A interface não especifica em qual direção ela será chamada. Arquiteturas que necessitam 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, diferenciados por retorno de chamada síncrono e retorno de chamada assíncrono .

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 objeto da segunda interface. Por exemplo, uma implementação HAL pode enviar informações de forma assíncrona de volta ao processo que a está utilizando, chamando métodos em um objeto de interface criado e servido por esse processo. Os métodos nas interfaces usadas para retorno de chamada assíncrono podem ser bloqueadores (e podem retornar valores para o chamador) ou oneway . Por exemplo, consulte "Retornos de chamada assíncronos" em HIDL C++ .

Para simplificar a propriedade da memória, as chamadas de método e os retornos de chamada aceitam apenas parâmetros in e não oferecem suporte a 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. Contudo, chamadas que excedam 4 KB por transação são consideradas excessivas. Se isso for observado, é recomendável reestruturar a interface HIDL fornecida. Outra limitação são os recursos disponíveis para a infraestrutura HIDL lidar com múltiplas transações simultâneas. Várias transações podem estar em andamento simultaneamente devido a vários threads ou processos enviando chamadas para um processo ou múltiplas chamadas oneway que não são tratadas rapidamente pelo processo receptor. 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 deveria acontecer exceder essas limitações de recursos; se isso acontecer, a chamada que os excedeu poderá ser bloqueada até que os recursos fiquem disponíveis ou sinalizar um erro de transporte. Cada ocorrência de excesso de limites por transação ou transbordamento de recursos de implementação HIDL por transações agregadas em andamento é registrada para facilitar a depuração.

Implementações de método

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 dos 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 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 manté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 gravação neles podem causar uma falha de segmentação) e são válidos durante a chamada. O cliente pode copiar profundamente os 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 sejam coletados como lixo.

Transferência de dados não RPC

O HIDL possui duas maneiras de transferir dados sem usar uma chamada RPC: memória compartilhada e Fast Message Queue (FMQ), ambas suportadas apenas em C++.

  • Memoria 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ápida (FMQ) . HIDL fornece um tipo de fila de mensagens modelo que implementa passagem de mensagens sem espera. Ele não usa o kernel ou o agendador no modo passthrough ou binderizado (a comunicação entre dispositivos não terá essas propriedades). Normalmente, o HAL configura seu fim da fila, criando um objeto que pode ser transmitido por meio de RPC por meio de um parâmetro do tipo HIDL integrado MQDescriptorSync ou MQDescriptorUnsync . Este objeto pode ser usado pelo processo de recebimento para configurar a outra extremidade da fila.
    • As filas de sincronização não podem transbordar e só podem ter um leitor.
    • As filas não sincronizadas podem transbordar e podem ter muitos leitores, cada um dos quais deve ler os dados a tempo ou perdê-los.
    Nenhum dos tipos pode sofrer overflow (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) .