Il linguaggio di definizione dell'interfaccia HAL o HIDL è un linguaggio di descrizione dell'interfaccia (IDL) per specificare l'interfaccia tra un HAL e i suoi utenti. HIDL consente di specificare tipi e chiamate di metodi, raccolti in interfacce e pacchetti. In termini più generali, HIDL è un sistema per la comunicazione tra codebase che possono essere compilate in modo indipendente.
HIDL è progettato per essere utilizzato per la comunicazione tra processi (IPC). Gli HAL creati con HDL sono chiamati HAL con binder perché possono comunicare con altri livelli dell'architettura utilizzando chiamate di comunicazione inter-processo (IPC) del binder. Gli HAL con binder vengono eseguiti in un processo separato dal client che li utilizza. Per le librerie che devono essere collegate a un processo, è disponibile anche una modalità di passaggio (non supportata in Java).
HIDL specifica strutture di dati e firme di metodi, organizzate in interfacce (simili a una classe) che vengono raccolte in pacchetti. La sintassi di HIDL è familiare ai programmatori C++ e Java, ma con un insieme diverso di parole chiave. HIDL utilizza anche le annotazioni in stile Java.
Terminologia
Questa sezione utilizza i seguenti termini relativi a HIDL:
in formato binderized | Indica che HIDL viene utilizzato per le chiamate di procedura remota tra processi, implementate tramite un meccanismo simile a Binder. Vedi anche passthrough. |
---|---|
callback, asincrono | Interfaccia fornita da un utente HAL, passata all'HAL (utilizzando un metodo HIDL) e chiamata dall'HAL per restituire i dati in qualsiasi momento. |
callback, sincrono | Restituisce al client i dati dall'implementazione del metodo HIDL di un server. Non utilizzato per i metodi che restituiscono un valore void o un singolo valore primitivo. |
client | Processo che chiama i metodi di una determinata interfaccia. Un processo HAL o del framework Android può essere un client di un'interfaccia e un server di un'altra. Vedi anche passthrough. |
si estende | Indica un'interfaccia che aggiunge metodi e/o tipi a un'altra interfaccia. Un'interfaccia può estendere una sola altra interfaccia. Può essere utilizzato per un incremento minore della versione nello stesso nome del pacchetto o per un nuovo pacchetto (ad es. un'estensione del fornitore) da sviluppare su un pacchetto precedente. |
genera | Indica un metodo dell'interfaccia che restituisce valori al client. Per restituire un valore non primitivo o più valori, viene generata una funzione di callback sincrona. |
interfaccia | Raccolta di metodi e tipi. Tradotto in una classe in C++ o Java. Tutti i metodi di un'interfaccia vengono chiamati nella stessa direzione: un processo client invoca metodi implementati da un processo server. |
solo andata | Se applicato a un metodo HIDL, indica che il metodo non restituisce valori e non si blocca. |
Pacco | Raccolta di interfacce e tipi di dati che condividono una versione. |
passthrough | Modalità di HIDL in cui il server è una libreria condivisa, dlopen e
dal client. In modalità passthrough, il client e il server sono lo stesso processo, ma con basi di codice separate. Utilizzato solo per importare le codebase precedenti nel modello HIDL.
Vedi anche Binderized. |
server | Processo che implementa i metodi di un'interfaccia. Vedi anche passthrough. |
trasporto | Infrastruttura HIDL che sposta i dati tra il server e il client. |
versione | Versione di un pacchetto. È composto da due numeri interi, maggiore e minore. Gli incrementi di versioni minori possono aggiungere (ma non modificare) tipi e metodi. |
Progettazione HIDL
Lo scopo di HIDL è che il framework Android possa essere sostituito senza dover ricostruire le HAL. Gli HAL vengono creati da fornitori o produttori di SoC e inseriti in una
/vendor
partizione sul dispositivo, consentendo di sostituire il framework Android, nella sua
partizione, con un aggiornamento OTA senza ricompilare gli HAL.
Il design di HIDL bilancia i seguenti aspetti:
- Interoperabilità. Creare interfacce interoperabili in modo affidabile tra processi che possono essere compilati con varie architetture, toolchain e configurazioni di compilazione. Le interfacce HIDL sono versionate e non possono essere modificate dopo la pubblicazione.
- Efficienza. HIDL cerca di ridurre al minimo il numero di operazioni di copia. I dati definiti da HIDL vengono inviati al codice C++ in strutture di dati con layout standard C++ che possono essere utilizzate senza scompattamento. HIDL fornisce anche interfacce di memoria condivisa e, poiché le RPC sono intrinsecamente un po' lente, HIDL supporta due modi per trasferire dati senza utilizzare una chiamata RPC: la memoria condivisa e una coda di messaggi rapida (FMQ).
- Intuitiva. HIDL evita i problemi spinosi della proprietà della memoria utilizzando solo parametri
in
per l'RPC (consulta Android Interface Definition Language (AIDL)); i valori che non possono essere restituiti in modo efficiente dai metodi vengono restituiti tramite funzioni di callback. Né il passaggio di dati in HIDL per il trasferimento né la ricezione di dati da HIDL modificano la proprietà dei dati: la proprietà rimane sempre della funzione di chiamata. I dati devono essere mantenuti solo per la durata della funzione chiamata e possono essere distrutti immediatamente dopo il ritorno della funzione chiamata.
Usare la modalità passthrough
Per aggiornare i dispositivi con versioni precedenti di Android ad Android O, puoi eseguire il wrapping sia degli HAL convenzionali (che legacy) in una nuova interfaccia HIDL che fornisce l'HAL in modalità binderizzate e nello stesso processo (passthrough). Questo wrapping è trasparente sia per l'HAL sia per il framework Android.
La modalità di passaggio è disponibile solo per i client e le implementazioni C++. I dispositivi con versioni precedenti di Android non dispongono di HAL scritte in Java, pertanto le HAL Java sono intrinsecamente legate.
File di intestazione passthrough
Quando viene compilato un file .hal
, hidl-gen
produce un
file di intestazione passthrough aggiuntivo BsFoo.h
oltre alle intestazioni
utilizzate per la comunicazione del binder. Questa intestazione definisce le funzioni da
dlopen
. Poiché gli HAL passthrough vengono eseguiti nello stesso processo in cui
vengono chiamati, nella maggior parte dei casi i metodi passthrough vengono invocati tramite chiamata diretta
della funzione (stesso thread). I metodi oneway
vengono eseguiti nel proprio thread poiché non sono progettati per attendere l'elaborazione da parte dell'HAL (ciò significa che qualsiasi HAL che utilizza i metodi oneway
in modalità passthrough deve essere a prova di thread).
Dato un IFoo.hal
, BsFoo.h
esegue il wrapping dei metodi generati da HIDL per fornire funzionalità aggiuntive (ad esempio, l'esecuzione di transazioni oneway
in un altro thread). Questo file è simile aBpFoo.h
, ma anziché passare le chiamate IPC utilizzando il binder, le funzioni richieste vengono richiamate direttamente. Le implementazioni future degli HAL
potrebbero fornire più implementazioni, ad esempio HAL FooFast e un
HAL FooAccurate. In questi casi, verrà creato un file per ogni implementazione aggiuntiva (ad es. PTFooFast.cpp
e
PTFooAccurate.cpp
).
Binderizzazione degli HAL passthrough
Puoi eseguire il binding delle implementazioni HAL che supportano la modalità passthrough. Data un'interfaccia HAL a.b.c.d@M.N::IFoo
, vengono creati due pacchetti:
a.b.c.d@M.N::IFoo-impl
. Contiene l'implementazione dell'HAL e espone la funzioneIFoo* HIDL_FETCH_IFoo(const char* name)
. Sui dispositivi legacy, questo pacchetto èdlopen
e l'implementazione viene attivata utilizzandoHIDL_FETCH_IFoo
. Puoi generare il codice di base utilizzandohidl-gen
e-Lc++-impl
e-Landroidbp-impl
.a.b.c.d@M.N::IFoo-service
. Apre l'HAL passthrough e si registra come servizio con binder, consentendo di utilizzare la stessa implementazione HAL sia come passthrough sia come con binder.
Dato il tipo IFoo
, puoi chiamare sp<IFoo>
IFoo::getService(string name, bool getStub)
per accedere a un'istanza di IFoo
. Se getStub
è true, getService
tenta di aprire l'HAL solo in modalità di passthrough. Se getStub
è
false, getService
tenta di trovare un servizio incapsulato; se questo tentativo
non va a buon fine, tenta di trovare il servizio passthrough. Il parametro getStub
non deve mai essere utilizzato tranne che in
defaultPassthroughServiceImplementation
. I dispositivi lanciati con Android O sono completamente incapsulati, pertanto l'apertura di un servizio in modalità passthrough non è consentita.
Grammatica HIDL
Per impostazione predefinita, il linguaggio HIDL è simile al C (ma non utilizza il precompilatore C). Tutti i segni di punteggiatura non descritti di seguito (a parte l'uso evidente di =
e |
) fanno parte della grammatica.
Nota:per informazioni dettagliate sullo stile del codice HIDL, consulta la Guida allo stile del codice.
/** */
indica un commento della documentazione. Possono essere applicati solo alle dichiarazioni di tipo, metodo, campo e valore enum./* */
indica un commento su più righe.//
indica un commento alla fine della riga. A parte//
, le interruzioni di riga sono uguali a qualsiasi altro spazio vuoto.- Nella grammatica di esempio riportata di seguito, il testo da
//
alla fine della linea non fa parte della grammatica, ma è un commento sulla grammatica. [empty]
indica che il termine può essere vuoto.?
dopo un valore letterale o un termine indica che è facoltativo....
indica una sequenza contenente zero o più elementi con la punteggiatura di separazione indicata. In HIDL non sono presenti argomenti variadici.- Le virgole separano gli elementi della sequenza.
- I punti e virgola terminano ogni elemento, incluso l'ultimo.
- UPPERCASE è un non terminato.
italics
è una famiglia di token comeinteger
oidentifier
(regole di analisi sintattica C standard).constexpr
è un'espressione costante in stile C (ad es.1 + 1
e1L << 3
).import_name
è un nome di pacchetto o interfaccia, qualificato come descritto in Versionamento HIDL.words
minuscolo sono token letterali.
Esempio:
ROOT = PACKAGE IMPORTS PREAMBLE { ITEM ITEM ... } // not for types.hal | PACKAGE IMPORTS ITEM ITEM... // only for types.hal; no method definitions ITEM = ANNOTATIONS? oneway? identifier(FIELD, FIELD ...) GENERATES?; | safe_union identifier { UFIELD; UFIELD; ...}; | struct identifier { SFIELD; SFIELD; ...}; // Note - no forward declarations | union identifier { UFIELD; UFIELD; ...}; | enum identifier: TYPE { ENUM_ENTRY, ENUM_ENTRY ... }; // TYPE = enum or scalar | typedef TYPE identifier; VERSION = integer.integer; PACKAGE = package android.hardware.identifier[.identifier[...]]@VERSION; PREAMBLE = interface identifier EXTENDS EXTENDS = <empty> | extends import_name // must be interface, not package GENERATES = generates (FIELD, FIELD ...) // allows the Binder interface to be used as a type // (similar to typedef'ing the final identifier) IMPORTS = [empty] | IMPORTS import import_name; TYPE = uint8_t | int8_t | uint16_t | int16_t | uint32_t | int32_t | uint64_t | int64_t | float | double | bool | string | identifier // must be defined as a typedef, struct, union, enum or import // including those defined later in the file | memory | pointer | vec<TYPE> | bitfield<TYPE> // TYPE is user-defined enum | fmq_sync<TYPE> | fmq_unsync<TYPE> | TYPE[SIZE] FIELD = TYPE identifier UFIELD = TYPE identifier | safe_union identifier { FIELD; FIELD; ...} identifier; | struct identifier { FIELD; FIELD; ...} identifier; | union identifier { FIELD; FIELD; ...} identifier; SFIELD = TYPE identifier | safe_union identifier { FIELD; FIELD; ...}; | struct identifier { FIELD; FIELD; ...}; | union identifier { FIELD; FIELD; ...}; | safe_union identifier { FIELD; FIELD; ...} identifier; | struct identifier { FIELD; FIELD; ...} identifier; | union identifier { FIELD; FIELD; ...} identifier; SIZE = // Must be greater than zero constexpr ANNOTATIONS = [empty] | ANNOTATIONS ANNOTATION ANNOTATION = | @identifier | @identifier(VALUE) | @identifier(ANNO_ENTRY, ANNO_ENTRY ...) ANNO_ENTRY = identifier=VALUE VALUE = "any text including \" and other escapes" | constexpr | {VALUE, VALUE ...} // only in annotations ENUM_ENTRY = identifier | identifier = constexpr