Język definiowania interfejsu HAL (HIDL) to język opisywania interfejsu (IDL) służący do określania interfejsu między HAL a jego użytkownikami. HIDL umożliwia określanie typów i wywołań metod zebranych w interfejsach i pakietach. W szerszym znaczeniu HIDL to system komunikacji między bazami kodu, które mogą być kompilowane niezależnie.
HIDL jest przeznaczony do komunikacji między procesami (IPC). Interfejsy HAL utworzone za pomocą HDL są nazywane binderized HALs, ponieważ mogą komunikować się z innymi warstwami architektury za pomocą wywołań interprocess communication (IPC) bindera. Binderized HALs działają w oddzielnym procesie od klienta, który ich używa. W przypadku bibliotek, które muszą być powiązane z procesem, dostępna jest też opcja przepuszczania (passthrough) (nieobsługiwana w języku Java).
HIDL określa struktury danych i nazwy metod zorganizowane w interfejsach (podobnych do klas), które są zbierane w pakiety. Składnia HIDL jest znajoma programistom C++ i Java, ale ma inny zestaw słów kluczowych. HIDL używa też adnotacji w stylu Java.
Terminologia
W tej sekcji używamy następujących terminów związanych z HIDL:
binderized | Wskazuje, że HIDL jest używany do wywoływania procedur zdalnych między procesami, co jest realizowane za pomocą mechanizmu podobnego do Binder. Zobacz też przepuszczanie. |
---|---|
wywołanie zwrotne, asynchroniczne | Interfejs obsługiwany przez użytkownika HAL, przekazywany do HAL (za pomocą metody HIDL) i wywoływany przez HAL w dowolnym momencie w celu zwrócenia danych. |
callback, synchroniczne | Zwraca dane z implementacji metody HIDL serwera do klienta. Nieużywane w przypadku metod, które zwracają wartość void lub pojedynczą wartość prymitywną. |
client | Proces, który wywołuje metody określonego interfejsu. Proces HAL lub Android Framework może być klientem jednego interfejsu i serwerem drugiego. Zobacz też przechodzenie. |
rozszerza | Wskazuje interfejs, który dodaje metody lub typy do innego interfejsu. Interfejs może rozszerzać tylko jeden inny interfejs. Można go użyć do wdrożenia małej aktualizacji wersji w ramach tej samej nazwy pakietu lub do utworzenia nowego pakietu (np. rozszerzenia dostawcy) na podstawie starszego pakietu. |
generuje | Wskazuje metodę interfejsu, która zwraca wartości do klienta. Aby zwrócić jedną wartość niepierwotną lub więcej niż jedną wartość, generowana jest synchroniczna funkcja wywołania zwrotnego. |
interfejs | Zbiór metod i typów. przetłumaczone na klasę w C++ lub Javie. Wszystkie metody w interfejsie są wywoływane w tym samym kierunku: proces klienta wywołuje metody zaimplementowane przez proces serwera. |
w jedną stronę | Gdy jest stosowana do metody HIDL, oznacza, że metoda nie zwraca żadnych wartości i nie blokuje. |
paczka | Zbiór interfejsów i typów danych, które mają tę samą wersję. |
przekazywanie | Tryb HIDL, w którym serwer jest biblioteką wspólną, dlopen edytowaną przez klienta. W trybie przekazywania klient i serwer to ten sam proces, ale z oddzielnymi bazami kodu. Służy tylko do przenoszenia starszych baz kodu do modelu HIDL.
Zobacz też Binderized. |
serwer | Proces, który implementuje metody interfejsu. Zobacz też przechodzenie. |
transport | Infrastruktura HIDL, która przesyła dane między serwerem a klientem. |
Wersja | Wersja pakietu. Składa się z 2 liczb całkowitych: głównej i mniejszej. Mniejsze aktualizacje wersji mogą dodawać (ale nie zmieniać) typów i metod. |
Projekt HIDL
Celem HIDL jest możliwość zastąpienia platformy Android bez konieczności ponownego tworzenia interfejsów HAL. Interfejsy HAL są tworzone przez dostawców lub producentów SOC i przechowywane na partycji /vendor
na urządzeniu. Umożliwia to zastąpienie platformy Androida (na własnej partycji) za pomocą aktualizacji OTA bez ponownego kompilowania interfejsów HAL.
Projekt HIDL zapewnia równowagę w zakresie tych kwestii:
- Interoperacyjność. Tworzenie niezawodnych interfejsów interoperacyjnych pomiędzy procesami, które mogą być kompilowane z różnymi architekturami, zestawami narzędzi i konfiguracjami kompilacji. Interfejce HIDL są wersjonowane i nie można ich zmieniać po opublikowaniu.
- Skuteczność. HIDL stara się zminimalizować liczbę operacji kopiowania. Dane zdefiniowane w HIDL są dostarczane do kodu C++ w standardowych strukturach danych C++, których można używać bez rozpakowywania. HIDL udostępnia też interfejsy pamięci współdzielonej. Ponieważ wywołania RPC są z zasady dość powolne, HIDL obsługuje 2 sposoby przesyłania danych bez użycia wywołania RPC: pamięć współdzielona i szybka kolejka komunikatów (Fast Message Queue, FMQ).
- Intuicyjny. HIDL pozwala uniknąć kłopotliwych problemów z własnością pamięci, ponieważ w przypadku wywołań RPC używa tylko parametrów
in
(patrz język definiowania interfejsów Androida (AIDL)); wartości, których nie można efektywnie zwracać z metod, są zwracane za pomocą funkcji wywołania zwrotnego. Ani przekazywanie danych do HIDL w celu ich przeniesienia, ani odbieranie danych z HIDL nie zmienia ich własności – własność danych zawsze pozostaje przy funkcji wywołującej. Dane muszą być przechowywane tylko przez czas działania wywoływanej funkcji i mogą zostać zniszczone bezpośrednio po jej zakończeniu.
Korzystanie z trybu przekazywania
Aby zaktualizować urządzenia z Androidem w starszych wersjach do Androida O, możesz opakować zarówno konwencjonalne (i starsze) interfejsy HAL w nowym interfejsie HIDL, który obsługuje interfejs HAL w trybie binderized i w tym samym procesie (przesyłającym). Ta otoczka jest przezroczysta zarówno dla HAL, jak i ramy Androida.
Tryb przekazywania jest dostępny tylko w przypadku klientów i implementacji w C++. Urządzenia z wersjami Androida wcześniejszymi niż 10.0 nie mają interfejsów HAL napisanych w języku Java, więc interfejsy HAL w języku Java są z założenia obsługiwane przez Binder.
Przepuszczanie plików nagłówka
Podczas kompilowania pliku .hal
funkcja hidl-gen
generuje dodatkowy plik nagłówka przekazywania dalej BsFoo.h
, który uzupełnia nagłówki używane do komunikacji z binderem. Ten nagłówek definiuje funkcje, które mają być przekazywane dalej. Ponieważ funkcje przekazywania dalej HAL działają w tym samym procesie, w którym są wywoływane, w większości przypadków są one wywoływane przez bezpośrednie wywołanie funkcji (w tym samym wątku).dlopen
Metody oneway
są wykonywane w ramach własnego wątku, ponieważ nie mają czekać na przetworzenie przez HAL (co oznacza, że każdy interfejs HAL, który używa metod oneway
w trybie przekazywania, musi być bezpieczny w zakresie wątków).
Funkcja IFoo.hal
otacza metody wygenerowane przez HIDL, aby udostępnić dodatkowe funkcje (np. uruchamianie transakcji oneway
w innym wątku).BsFoo.h
Plik ten jest podobny do pliku BpFoo.h
, ale zamiast przekazywania wywołań IPC za pomocą bindera wywołuje bezpośrednio żądane funkcje. Przyszłe implementacje HAL mogą zawierać wiele implementacji, np. FooFast HAL i FooAccurate HAL. W takich przypadkach dla każdej dodatkowej implementacji zostanie utworzony plik (np. PTFooFast.cpp
i PTFooAccurate.cpp
).
Binderyzacja interfejsów HAL typu passthrough
Implementacje HAL, które obsługują tryb przekazywania, możesz zbindować. W przypadku interfejsu HAL a.b.c.d@M.N::IFoo
tworzone są 2 pakiety:
a.b.c.d@M.N::IFoo-impl
. Zawiera implementację HAL i funkcjęIFoo* HIDL_FETCH_IFoo(const char* name)
. Na starszych urządzeniach ten pakiet jestdlopen
, a implementacja jest tworzona za pomocąHIDL_FETCH_IFoo
. Kod podstawowy możesz wygenerować, używająchidl-gen
i-Lc++-impl
oraz-Landroidbp-impl
.a.b.c.d@M.N::IFoo-service
. Otwiera interfejs HAL z przepuszczaniem i rejestruje się jako usługa z binderem, co umożliwia użycie tej samej implementacji interfejsu HAL zarówno w trybie przepuszczania, jak i z binderem.
W przypadku typu IFoo
możesz wywołać metodę sp<IFoo>
IFoo::getService(string name, bool getStub)
, aby uzyskać dostęp do instancji IFoo
. Jeśli getStub
ma wartość prawda, getService
próbuje otworzyć HAL tylko w trybie przekazywania. Jeśli getStub
ma wartość false, getService
próbuje znaleźć usługę zbinderem; jeśli się to nie uda, próbuje znaleźć usługę przelotową. Parametru getStub
nie należy używać w żadnym innym przypadku niż w pozycji defaultPassthroughServiceImplementation
. (urządzenia z Androidem O są w pełni zintegrowane z binderem, więc otwieranie usługi w trybie przekazywania nie jest dozwolone).
Gramatyka HIDL
Język HIDL jest z założenia podobny do języka C (ale nie używa preprocesora C). Wszystkie znaki interpunkcyjne, które nie zostały opisane poniżej (z wyjątkiem oczywistego użycia znaków =
i |
), są częścią gramatyki.
Uwaga: szczegółowe informacje o stylu kodu HIDL znajdziesz w przewodniku po stylu kodu.
/** */
oznacza komentarz do dokumentacji. Można ich używać tylko w przypadku deklaracji typu, metody, pola i wartości wyliczenia./* */
oznacza komentarz wielowierszowy.//
oznacza komentarz do końca wiersza. Poza//
nowe wiersze są takie same jak inne odstępy.- W przykładowej gramatyce poniżej tekst od
//
do końca wiersza nie jest częścią gramatyki, ale komentarzem do niej. [empty]
oznacza, że termin może być pusty.?
po literalu lub terminie oznacza, że jest to opcjonalne....
wskazuje sekwencję zawierającą co najmniej 1 element z znakami interpunkcyjnymi w określonej kolejności. W HIDL nie ma argumentów zmiennoargumentowych.- Elementy sekwencji są rozdzielane przecinkami.
- Pomiędzy elementami, w tym po ostatnim, należy umieszczać średniki.
- WIELKIE_LITERY to symbol nieterminalny.
italics
to rodzina tokenów, np.integer
lubidentifier
(standardowe reguły analizowania C).constexpr
to wyrażenie stałe w stylu C (takie jak1 + 1
i1L << 3
).import_name
to nazwa pakietu lub interfejsu, kwalifikowana zgodnie z opisem w wersji sterownika HIDL.- Małe litery
words
to tokeny dosłowne.
Przykład:
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