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 metodo, raccolti in interfacce e pacchetti. Più in generale, HIDL è un sistema per la comunicazione tra basi di codice che possono essere compilate in modo indipendente. A partire da Android 10, HIDL è obsoleto e Android sta migrando per utilizzare AIDL ovunque.
HIDL è destinato ad essere utilizzato per la comunicazione tra processi (IPC). Gli HAL creati con HDL sono chiamati HAL binderizzati in quanto possono comunicare con altri livelli dell'architettura utilizzando chiamate IPC (binder inter-process communication). Gli HAL binderizzati 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à passthrough (non supportata in Java).
HIDL specifica le strutture dati e le firme dei metodi, organizzate in interfacce (simili a una classe) raccolte in pacchetti. La sintassi di HIDL sembra familiare ai programmatori C++ e Java, ma con un diverso insieme di parole chiave. HIDL utilizza anche annotazioni in stile Java.
Terminologia
Questa sezione utilizza i seguenti termini relativi a HIDL:
legante | Indica che HIDL viene utilizzato per chiamate di procedure remote tra processi, implementate su un meccanismo simile a Binder. Vedere anche passaggio . |
---|---|
richiamata, asincrono | Interfaccia servita da un utente HAL, passata all'HAL (utilizzando un metodo HIDL) e chiamata dall'HAL per restituire dati in qualsiasi momento. |
richiamata, sincrono | Restituisce i dati dall'implementazione del metodo HIDL di un server al client. Non utilizzato per metodi che restituiscono void o un singolo valore primitivo. |
cliente | Processo che chiama i metodi di una particolare interfaccia. Un processo framework HAL o Android può essere un client di un'interfaccia e un server di un'altra. Vedere anche passaggio . |
estende | Indica un'interfaccia che aggiunge metodi e/o tipi a un'altra interfaccia. Un'interfaccia può estendere solo un'altra interfaccia. Può essere utilizzato per un incremento di versione minore nello stesso nome di pacchetto o per un nuovo pacchetto (ad esempio un'estensione del fornitore) da costruire su un pacchetto precedente. |
genera | Indica un metodo di interfaccia che restituisce valori al client. Per restituire un valore non primitivo o più di un valore, viene generata una funzione di callback sincrona. |
interfaccia | Raccolta di metodi e tipi. Tradotto in una classe in C++ o Java. Tutti i metodi in un'interfaccia sono chiamati nella stessa direzione: un processo client invoca metodi implementati da un processo server. |
senso unico | Quando applicato a un metodo HIDL, indica che il metodo non restituisce valori e non si blocca. |
pacchetto | Raccolta di interfacce e tipi di dati che condividono una versione. |
passthrough | Modalità di HIDL in cui il server è una libreria condivisa, dlopen dal client. In modalità passthrough, client e server sono lo stesso processo ma basi di codice separate. Utilizzato solo per portare basi di codice legacy nel modello HIDL. Vedere anche Legato . |
server | Processo che implementa i metodi di un'interfaccia. Vedere anche passaggio . |
trasporto | Infrastruttura HIDL che sposta i dati tra il server e il client. |
versione | Versione di un pacchetto. Consiste di due numeri interi, maggiore e minore. Gli incrementi di versione minori possono aggiungere (ma non modificare) tipi e metodi. |
Progettazione HIDL
L'obiettivo di HIDL è che il framework Android possa essere sostituito senza dover ricostruire gli HAL. Gli HAL verranno creati da fornitori o produttori di SOC e inseriti in una partizione /vendor
sul dispositivo, consentendo al framework Android, nella propria partizione, di essere sostituito con un OTA senza ricompilare gli HAL.
Il design HIDL bilancia le seguenti preoccupazioni:
- Interoperabilità . Crea 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 tenta di ridurre al minimo il numero di operazioni di copia. I dati definiti da HIDL vengono inviati al codice C++ in strutture di dati di layout standard C++ che possono essere utilizzate senza decomprimere. HIDL fornisce anche interfacce di memoria condivisa e, poiché le RPC sono intrinsecamente piuttosto lente, HIDL supporta due modi per trasferire i dati senza utilizzare una chiamata RPC: memoria condivisa e Fast Message Queue (FMQ).
- Intuitivo . HIDL evita spinosi problemi di proprietà della memoria utilizzando solo
in
parametri per RPC (vedi 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 modifica la proprietà dei dati: la proprietà rimane sempre con la funzione chiamante. I dati devono persistere solo per la durata della funzione chiamata e possono essere distrutti immediatamente dopo il ritorno della funzione chiamata.
Utilizzo della modalità passthrough
Per aggiornare i dispositivi che eseguono versioni precedenti di Android ad Android O, puoi eseguire il wrapping di entrambi gli HAL convenzionali (e legacy) in una nuova interfaccia HIDL che serve l'HAL in modalità binderizzata e stesso processo (passthrough). Questo wrapping è trasparente sia per l'HAL che per il framework Android.
La modalità passthrough è disponibile solo per client e implementazioni C++. I dispositivi che eseguono versioni precedenti di Android non hanno HAL scritti in Java, quindi gli HAL Java sono intrinsecamente binderizzati.
File di intestazione passthrough
Quando un file .hal
viene compilato, 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
ed. Poiché gli HAL passthrough vengono eseguiti nello stesso processo in cui vengono chiamati, nella maggior parte dei casi i metodi passthrough vengono richiamati dalla chiamata di funzione diretta (stesso thread). i metodi oneway
vengono eseguiti nel proprio thread poiché non sono destinati ad attendere che l'HAL li elabori (ciò significa che qualsiasi HAL che utilizza metodi oneway
in modalità passthrough deve essere thread-safe).
Dato un IFoo.hal
, BsFoo.h
il wrapping dei metodi generati da oneway
per fornire funzionalità aggiuntive (come l'esecuzione di transazioni unidirezionali in un altro thread). Questo file è simile a BpFoo.h
, tuttavia invece di passare le chiamate IPC utilizzando il raccoglitore, le funzioni desiderate vengono richiamate direttamente. Le future implementazioni degli HAL potrebbero fornire implementazioni multiple, come FooFast HAL e FooAccurate HAL. In tali casi, verrebbe creato un file per ogni implementazione aggiuntiva (ad esempio, PTFooFast.cpp
e PTFooAccurate.cpp
).
Binderizing passthrough HAL
È possibile legare le implementazioni HAL che supportano la modalità passthrough. Data un'interfaccia HAL abcd@MN::IFoo
, vengono creati due pacchetti:
-
abcd@MN::IFoo-impl
. Contiene l'implementazione dell'HAL ed espone la funzioneIFoo* HIDL_FETCH_IFoo(const char* name)
. Sui dispositivi legacy, questo pacchetto vienedlopen
e l'implementazione viene istanziata utilizzandoHIDL_FETCH_IFoo
. Puoi generare il codice di base usandohidl-gen
e-Lc++-impl
impl e-Landroidbp-impl
. -
abcd@MN::IFoo-service
. Apre l'HAL passthrough e si registra come servizio binderizzato, consentendo l'utilizzo della stessa implementazione HAL sia come passthrough che binderized.
Dato il tipo IFoo
, puoi chiamare sp<IFoo> IFoo::getService(string name, bool getStub)
per ottenere l'accesso a un'istanza di IFoo
. Se getStub
è true, getService
tenta di aprire l'HAL solo in modalità passthrough. Se getStub
è false, getService
tenta di trovare un servizio binderizzato; se fallisce, cerca di trovare il servizio passthrough. Il parametro getStub
non deve mai essere utilizzato tranne che in defaultPassthroughServiceImplementation
. (I dispositivi che si avviano con Android O sono dispositivi completamente binderizzati, quindi l'apertura di un servizio in modalità passthrough non è consentita.)
Grammatica HIDL
In base alla progettazione, il linguaggio HIDL è simile al C (ma non utilizza il preprocessore C). Tutta la punteggiatura non descritta di seguito (a parte l'uso ovvio di =
e |
) fa parte della grammatica.
Nota: per i dettagli sullo stile del codice HIDL, consultare la Guida allo stile del codice .
-
/** */
indica un commento di documentazione. Questi possono essere applicati solo a dichiarazioni di tipo, metodo, campo ed enum. -
/* */
indica un commento su più righe. -
//
indica un commento alla fine della riga. A parte//
, le nuove righe sono le stesse di qualsiasi altro spazio bianco. - Nell'esempio di grammatica seguente, il testo da
//
alla fine della riga non fa parte della grammatica ma è invece un commento sulla grammatica. -
[empty]
significa che il termine può essere vuoto. -
?
dopo un letterale o un termine significa che è facoltativo. -
...
indica una sequenza contenente zero o più elementi con punteggiatura di separazione come indicato. Non ci sono argomenti variadici in HIDL. - Le virgole separano gli elementi della sequenza.
- Il punto e virgola termina ogni elemento, compreso l'ultimo elemento.
- MAIUSCOLA è un non terminale.
-
italics
è una famiglia di token comeinteger
oidentifier
(regole di analisi C standard). -
constexpr
è un'espressione costante in stile C (come1 + 1
e1L << 3
). -
import_name
è un pacchetto o un nome di interfaccia, qualificato come descritto in HIDL Versioning . - Le
words
minuscole sono simboli 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