Servizi e servizi Trasferimento dati

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

Servizi di registrazione

I server di interfaccia HIDL (oggetti che implementano l'interfaccia) possono essere registrati come servizi denominati. Non è necessario che il nome registrato sia correlato all'interfaccia o al nome del pacchetto. Se non viene specificato alcun nome, viene utilizzato il nome "default"; questo dovrebbe essere usato per gli HAL che non necessitano di registrare due implementazioni della stessa interfaccia. Ad esempio, la chiamata C++ per la registrazione del servizio definita in ciascuna 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. Viene automaticamente associato alla registrazione del servizio e può essere recuperato tramite una chiamata al metodo ( android::hardware::IInterface::getInterfaceVersion() ) su ogni interfaccia HIDL. Non è necessario che gli oggetti server siano registrati e possono essere passati tramite parametri del metodo HIDL a un altro processo che effettuerà chiamate al metodo HIDL nel server.

Alla scoperta dei servizi

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

Ciascuna versione di un'interfaccia HIDL viene trattata come un'interfaccia separata. Pertanto, IFooService versione 1.1 e IFooService versione 2.2 possono essere entrambi registrati come "foo_service" e getService("foo_service") su entrambe le interfacce ottiene il servizio registrato per quell'interfaccia. Questo è il motivo per cui, nella maggior parte dei casi, non è necessario fornire alcun parametro relativo al nome per la registrazione o il rilevamento (ovvero nome "predefinito").

Anche l'oggetto interfaccia del fornitore svolge un ruolo 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 proseguire immediatamente anche senza ottenere il servizio. Ciò può accadere (ad esempio) quando un client desidera gestire da solo le notifiche di servizio o in un programma diagnostico (come atrace ) che deve ottenere tutti i servizi hw e recuperarli. In questo caso, vengono fornite API aggiuntive come tryGetService in C++ o getService("instance-name", false) in Java. Anche l'API legacy getService fornita in Java deve essere utilizzata con le notifiche del servizio. L'utilizzo di questa API non evita la race condition in cui un server si registra dopo che il client lo ha richiesto con una di queste API senza tentativi.

Notifiche di morte del servizio

I clienti che desiderano essere avvisati quando un servizio termina può ricevere notifiche di morte fornite dal framework. Per ricevere le notifiche il cliente deve:

  1. Sottoclasse della classe/interfaccia HIDL hidl_death_recipient (nel codice C++, non in HIDL).
  2. Sostituisci il relativo metodo serviceDied() .
  3. Istanzia un oggetto della sottoclasse hidl_death_recipient .
  4. Chiama il metodo linkToDeath() sul servizio da monitorare, passando l'oggetto interfaccia di IDeathRecipient . Tieni presente che questo metodo non assume la proprietà del destinatario della morte o del delegato 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 della morte può essere registrato su più servizi diversi.

Trasferimento dati

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

  • I metodi di blocco attendono finché il server non ha prodotto un risultato.
  • I metodi unidirezionali inviano i dati in una sola direzione e non 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 è comunque bloccante.

Tutti i metodi dichiarati in un'interfaccia HIDL vengono chiamati in un'unica direzione, dall'HAL o nell'HAL. L'interfaccia non specifica in quale direzione verrà chiamata. Le architetture che necessitano che le chiamate provengano dall'HAL dovrebbero fornire due (o più) interfacce nel pacchetto HAL e servire l'interfaccia appropriata da ciascun processo. Le parole client e server vengono utilizzate in relazione alla direzione di chiamata dell'interfaccia (ovvero l'HAL può essere un server di un'interfaccia e un client di un'altra interfaccia).

Richiamate

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

I callback 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.

Le richiamate asincrone consentono al server di un'interfaccia HIDL di originare chiamate. Ciò avviene facendo passare un'istanza di una seconda interfaccia attraverso la prima interfaccia. Il client della prima interfaccia deve fungere da server della seconda. Il server della prima interfaccia può chiamare metodi sul secondo oggetto interfaccia. Ad esempio, un'implementazione HAL può inviare informazioni in modo asincrono al processo che le sta utilizzando chiamando metodi su un oggetto interfaccia creato e servito da quel processo. I metodi nelle interfacce utilizzate per il callback asincrono possono essere bloccanti (e possono restituire valori al chiamante) o oneway . Per un esempio, vedere "callback asincroni" in HIDL C++ .

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

Limiti per transazione

Non vengono imposti limiti per transazione alla quantità di dati inviati nei metodi e callback HIDL. Tuttavia, le chiamate superiori a 4KB per transazione sono considerate eccessive. Se ciò si verifica, si consiglia di riprogettare l'interfaccia HIDL specificata. Un'altra limitazione sono le 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 di più chiamate oneway che non vengono gestite rapidamente dal processo ricevente. Per impostazione predefinita, lo spazio totale massimo disponibile per tutte le transazioni simultanee è 1 MB.

In un'interfaccia ben progettata, non dovrebbe verificarsi il superamento di questi limiti di risorse; in tal caso, la chiamata che li ha superati potrebbe bloccarsi fino a quando le risorse non saranno disponibili o segnalare un errore di trasporto. Ogni evento di superamento dei limiti per transazione o di overflow delle risorse di implementazione HIDL da parte di transazioni in volo aggregate viene registrato per facilitare il debug.

Implementazioni del metodo

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 che per quello 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 passa i dati alle implementazioni dei metodi dello sviluppatore.

Il chiamante di una funzione (metodo HIDL o callback) ha la proprietà delle strutture dati passate alla funzione e mantiene la proprietà dopo la chiamata; in tutti i casi il chiamato non ha bisogno di liberare o rilasciare la memoria.

  • In C++, i dati possono essere di sola lettura (i tentativi di scrittura su di essi possono causare un errore di segmentazione) e sono validi per la durata della chiamata. Il client può copiare in profondità i dati per propagarli oltre la chiamata.
  • In Java, il codice riceve una copia locale dei dati (un normale oggetto Java), che può conservare e modificare o consentire la raccolta dei rifiuti.

Trasferimento dati non RPC

HIDL dispone di due modi per trasferire i dati senza utilizzare una chiamata RPC: memoria condivisa e Fast Message Queue (FMQ), entrambi supportati solo in C++.

  • Memoria condivisa . La memory di tipo HIDL incorporata viene utilizzata per passare un oggetto che rappresenta la memoria condivisa che è stata allocata. Può essere utilizzato in un processo di ricezione per mappare la memoria condivisa.
  • Coda messaggi veloce (FMQ) . HIDL fornisce un tipo di coda di messaggi basata su modello che implementa il passaggio di messaggi senza attesa. Non utilizza il kernel o lo scheduler in modalità passthrough o binderizzata (la comunicazione tra dispositivi non avrà queste proprietà). In genere, l'HAL imposta la fine della coda, creando un oggetto che può essere passato tramite RPC tramite un parametro di tipo HIDL integrato MQDescriptorSync o MQDescriptorUnsync . Questo oggetto può essere utilizzato dal processo ricevente per impostare l'altra estremità della coda.
    • Le code di sincronizzazione non possono sovraccaricarsi e possono avere un solo lettore.
    • Le code non sincronizzate possono traboccare e possono avere molti lettori, ognuno dei quali deve leggere i dati in tempo o perderli.
    A nessuno dei due tipi è consentito l'underflow (la lettura da una coda vuota fallirà) e ogni tipo può avere un solo scrittore.

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