Servizi e trasferimento dei dati

Questa pagina descrive come registrare e rilevare i servizi e come inviare dati a un servizio chiamando i metodi definiti nelle interfacce nei file .hal.

Registra servizi

I server di interfaccia HIDL (oggetti che implementano l'interfaccia) possono essere registrati come servizi denominati. Il nome registrato non deve necessariamente essere correlato al nome dell'interfaccia o del pacchetto. Se non viene specificato alcun nome, viene utilizzato il nome "predefinito". Questo valore deve essere utilizzato per gli HAL che non devono registrare due implementazioni della stessa interfaccia. Ad esempio, la chiamata C++ per la registrazione del servizio definita in ogni interfaccia è:

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

La versione di un'interfaccia HIDL è inclusa nell'interfaccia stessa. È associato automaticamente alla registrazione del servizio e può essere recuperato tramite una chiamata al metodo (android::hardware::IInterface::getInterfaceVersion()) su ogni interfaccia HIDL. Gli oggetti del server non devono essere registrati e possono essere trasmessi tramite i parametri del metodo HIDL a un altro processo che effettua chiamate al metodo HIDL nel server.

Scoprire i servizi

Le richieste tramite codice client vengono effettuate per una determinata interfaccia per nome e per versione, chiamando getService sulla classe HAL desiderata:

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

Ogni versione di un'interfaccia HIDL viene trattata come un'interfaccia separata. Pertanto, IFooService versione 1.1 e IFooService versione 2.2 possono essere registrate come "foo_service" e getService("foo_service") su entrambe le interfacce ottiene il servizio registrato per quella interfaccia. Ecco perché, nella maggior parte dei casi, non è necessario fornire alcun parametro di nome per la registrazione o il rilevamento (ovvero il nome "predefinito").

L'oggetto interfaccia fornitore svolge un ruolo anche nel metodo di trasporto dell'interfaccia restituita. Per un'interfaccia IFoo nel pacchetto android.hardware.foo@1.0, l'interfaccia restituita da IFoo::getService utilizza sempre il metodo di trasporto dichiarato per android.hardware.foo nel manifest del dispositivo, se la voce esiste; e se il metodo di trasporto non è disponibile, viene restituito nullptr.

In alcuni casi, potrebbe essere necessario continuare immediatamente anche senza ricevere il servizio. Questo può accadere, ad esempio, quando un client vuole gestire autonomamente le notifiche dei servizi o in un programma di diagnostica (comeatrace) che deve recuperare tutti gli hwservices. In questo caso, vengono fornite API aggiuntive come tryGetService in C++ o getService("instance-name", false) in Java. L'API legacygetService fornita in Java deve essere utilizzata anche con le notifiche del servizio. L'utilizzo di questa API non evita la condizione di gara in cui un server si registra dopo la richiesta del client con una di queste API senza tentativi.

Notifiche relative al ritiro di un servizio

I clienti che vogliono ricevere una notifica quando un servizio non è più disponibile possono ricevere notifiche di ritiro del servizio inviate dal framework. Per ricevere le notifiche, il cliente deve:

  1. Crea una sottoclasse della classe/interfaccia HIDL hidl_death_recipient (nel codice C++, non in HIDL).
  2. Sostituisci il metodo serviceDied().
  3. Crea un'istanza di un oggetto della sottoclasse hidl_death_recipient.
  4. Chiama il metodo linkToDeath() sul servizio da monitorare, passando l'oggetto dell'interfaccia di IDeathRecipient. Tieni presente che questo metodo non acquisisce la proprietà del destinatario del decesso o del proxy su cui viene chiamato.

Un esempio di pseudocodice (C++ e Java sono simili):

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

Lo stesso destinatario del decesso può essere registrato su più servizi diversi.

Trasferimento di dati

I dati possono essere inviati a un servizio chiamando i metodi definiti nelle interfacce nei file .hal. Esistono due tipi di metodi:

  • I metodi bloccanti aspettano che il server produca un risultato.
  • I metodi Oneway inviano i dati in una sola direzione e non si bloccano. Se la quantità di dati in transito nelle chiamate RPC supera i limiti di implementazione, le chiamate potrebbero bloccarsi o restituire un'indicazione di errore (il comportamento non è ancora stato determinato).

Un metodo che non restituisce un valore, ma non è dichiarato come oneway, continua a bloccarsi.

Tutti i metodi dichiarati in un'interfaccia HIDL vengono chiamati in una sola direzione, dall'HAL o nell'HAL. L'interfaccia non specifica in quale direzione viene chiamata. Le architetture che richiedono chiamate provenienti dall'HAL devono fornire due (o più) interfacce nel pacchetto HAL e fornire l'interfaccia appropriata da ogni processo. Le parole client e server vengono utilizzate in base alla direzione di chiamata dell'interfaccia (ad es. l'HAL può essere un server di un'interfaccia e un client di un'altra interfaccia).

Callback

La parola callback si riferisce a due concetti diversi, distinti da callback sincrono e callback asincrono.

I richiami sincroni vengono utilizzati in alcuni metodi HIDL che restituiscono dati. Un metodo HIDL che restituisce più di un valore (o restituisce un valore di tipo non primitivo) restituisce i risultati tramite una funzione di callback. Se viene restituito un solo valore ed è di tipo primitivo, non viene utilizzato un callback e il valore viene restituito dal metodo. Il server implementa i metodi HIDL e il client implementa i callback.

I callback asincroni consentono al server di un'interfaccia HIDL di eseguire chiamate. Ciò viene fatto passando un'istanza di una seconda interfaccia tramite la prima interfaccia. Il client della prima interfaccia deve fungere da server della seconda. Il server della prima interfaccia può chiamare i metodi sull'oggetto della seconda interfaccia. Ad esempio, un'implementazione HAL può inviare informazioni in modo asincrono al processo che le utilizza chiamando metodi su un oggetto interfaccia creato e servito da quel processo. I metodi nelle interfacce utilizzati per il callback asincrono potrebbero essere bloccanti (e restituire valori all'autore della chiamata) o oneway. Per un esempio, consulta "Richiami asincroni" in HIDL C++.

Per semplificare la proprietà della memoria, le chiamate ai metodi e i callback accettano solo parametri in e non supportano i parametri out o inout.

Limiti per transazione

I limiti per transazione non vengono imposti sulla quantità di dati inviati nei metodi e nei callback HIDL. Tuttavia, le chiamate che superano i 4 KB per transazione sono considerate eccessive. In questo caso, è consigliabile riprogettare l'interfaccia HIDL indicata. Un altro limite è costituito dalle risorse disponibili per l'infrastruttura HIDL per gestire più transazioni simultanee. Più transazioni possono essere in corso contemporaneamente a causa di più thread o processi che inviano chiamate a un processo o a più chiamate oneway che non vengono gestite rapidamente dal processo di ricezione. Per impostazione predefinita, lo spazio totale massimo disponibile per tutte le transazioni simultanee è 1 MB.

In un'interfaccia ben progettata, il superamento di queste limitazioni delle risorse non dovrebbe verificarsi. In caso contrario, la chiamata che le ha superate può bloccarsi fino a quando le risorse non diventano disponibili o segnalare un errore di trasporto. Ogni occorrenza di superamento dei limiti per transazione o di overflow delle risorse di implementazione HIDL da parte delle transazioni in-flight aggregate viene registrata per facilitare il debug.

Implementazioni dei metodi

HIDL genera file di intestazione che dichiarano i tipi, i metodi e i callback necessari nella lingua di destinazione (C++ o Java). Il prototipo dei metodi e dei callback definiti da HIDL è lo stesso sia per il codice client sia per quello del server. Il sistema HIDL fornisce implementazioni proxy dei metodi sul lato chiamante che organizzano i dati per il trasporto IPC e codice stub sul lato chiamato che li passa alle implementazioni dei metodi per gli sviluppatori.

L'autore di una chiamata a una funzione (metodo HIDL o callback) è il proprietario delle strutture di dati passate alla funzione e ne mantiene la proprietà dopo la chiamata. In tutti i casi, il chiamato non deve liberare o rilasciare lo spazio di archiviazione.

  • In C++, i dati potrebbero essere di sola lettura (i tentativi di scrittura possono causare un errore di segmentazione) e sono validi per la durata della chiamata. Il client può eseguire una copia approfondita dei dati per propagarli oltre la chiamata.
  • In Java, il codice riceve una copia locale dei dati (un normale oggetto Java), che potrebbe conservare e modificare o consentire di essere sottoposto a garbage collection.

Trasferimento di dati non RPC

HIDL offre due modi per trasferire dati senza utilizzare una chiamata RPC: memoria condivisa e coda di messaggi rapida (FMQ), entrambe supportate solo in C++.

  • Memoria condivisa. Il tipo HIDL integrato memory viene utilizzato per passare un oggetto che rappresenta la memoria condivisa allocata. Può essere utilizzato in un processo di ricezione per mappare la memoria condivisa.
  • Fast Message Queue (FMQ). HIDL fornisce un tipo di coda di messaggi basata su modelli che implementa la trasmissione di messaggi senza attesa. Non utilizza il kernel o lo schedulatore in modalità passthrough o con binder (la comunicazione tra dispositivi non ha queste proprietà). In genere, l'HAL configura la fine della coda, creando un oggetto che può essere passato tramite RPC tramite un parametro di tipo HIDL integratoMQDescriptorSync o MQDescriptorUnsync. Questo oggetto può essere utilizzato dal processo di ricezione per configurare l'altra estremità della coda.
    • Le code di sincronizzazione non possono superare il limite e possono avere un solo lettore.
    • Le code Disaccoppia possono superare il limite e possono avere molti lettori, ciascuno dei quali deve leggere i dati in tempo o perderli.
    Per nessuno dei due tipi è consentito il sottoflusso (la lettura da una coda vuota non va a buon fine) e ciascun tipo può avere un solo scrittore.

Per ulteriori dettagli su FMQ, consulta Fast Message Queue (FMQ).