Język definicji interfejsu HAL lub HIDL to język opisu interfejsu (IDL) służący do określania interfejsu między warstwą HAL a jej użytkownikami. HIDL umożliwia określenie typów i wywołań metod, zebranych w interfejsy i pakiety. Mówiąc szerzej, HIDL to system komunikacji między bazami kodu, które mogą być kompilowane niezależnie. Począwszy od Androida 10, HIDL jest przestarzały i Android jest migrowany, aby wszędzie używać AIDL .
HIDL jest przeznaczony do komunikacji między procesami (IPC). Warstwy HAL utworzone za pomocą HDL nazywane są powiązanymi warstwami HAL, ponieważ mogą komunikować się z innymi warstwami architektury za pomocą wywołań komunikacji między procesami spoiwa (IPC). Powiązane warstwy HAL działają w oddzielnym procesie od klienta, który ich używa. W przypadku bibliotek, które muszą być połączone z procesem, dostępny jest także tryb przekazywania (nie jest obsługiwany w Javie).
HIDL określa struktury danych i sygnatury metod zorganizowane w interfejsy (podobne do klasy), które są zbierane w pakiety. Składnia HIDL wygląda znajomo programistom C++ i Java, ale ma inny zestaw słów kluczowych. HIDL używa również adnotacji w stylu Java.
Terminologia
W tej sekcji zastosowano następujące terminy związane z HIDL:
spoiwowe | Wskazuje, że HIDL jest używany do zdalnych wywołań procedur między procesami, zaimplementowanych za pomocą mechanizmu podobnego do Bindera. Zobacz także przejście . |
---|---|
wywołanie zwrotne, asynchroniczne | Interfejs obsługiwany przez użytkownika HAL, przekazywany do HAL (przy użyciu metody HIDL) i wywoływany przez HAL w celu zwrócenia danych w dowolnym momencie. |
wywołanie zwrotne, synchroniczne | Zwraca dane z implementacji metody HIDL serwera do klienta. Nieużywane w przypadku metod zwracających void lub pojedynczą wartość pierwotną. |
klient | Proces wywołujący metody określonego interfejsu. Proces frameworku HAL lub Android może być klientem jednego interfejsu i serwerem innego. Zobacz także przejście . |
rozciąga się | Wskazuje interfejs, który dodaje metody i/lub typy do innego interfejsu. Interfejs może rozszerzać tylko jeden inny interfejs. Można go użyć do mniejszej wersji pakietu o tej samej nazwie lub do nowego pakietu (np. rozszerzenia dostawcy), aby zbudować na starszym pakiecie. |
generuje | Wskazuje metodę interfejsu, która zwraca wartości klientowi. 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 języku C++ lub Java. Wszystkie metody w interfejsie są wywoływane w tym samym kierunku: proces klienta wywołuje metody zaimplementowane przez proces serwera. |
jednokierunkowa | Po zastosowaniu do metody HIDL wskazuje, że metoda nie zwraca żadnych wartości i nie blokuje. |
pakiet | Zbiór interfejsów i typów danych współdzielących wersję. |
Przejść przez | Tryb HIDL, w którym serwer jest biblioteką współdzieloną dlopen przez klienta. W trybie przekazywania klient i serwer to ten sam proces, ale oddzielne bazy kodu. Używany tylko do przenoszenia starszych baz kodu do modelu HIDL. Zobacz także Oprawiony . |
serwer | Proces implementujący metody interfejsu. Zobacz także przejście . |
transport | Infrastruktura HIDL, która przenosi dane pomiędzy serwerem a klientem. |
wersja | Wersja pakietu. Składa się z dwóch liczb całkowitych, większej i mniejszej. Niewielkie przyrosty wersji mogą dodawać (ale nie zmieniać) typy i metody. |
Projekt HIDL
Celem HIDL jest umożliwienie wymiany frameworku Android bez konieczności przebudowywania warstw HAL. Warstwy HAL będą tworzone przez dostawców lub twórców SOC i umieszczane w partycji /vendor
na urządzeniu, co umożliwi zastąpienie platformy Android na jej własnej partycji przez wersję OTA bez konieczności ponownej kompilacji warstw HAL.
Projekt HIDL równoważy następujące obawy:
- Interoperacyjność . Twórz niezawodnie interoperacyjne interfejsy pomiędzy procesami, które można kompilować przy użyciu różnych architektur, łańcuchów narzędzi i konfiguracji kompilacji. Interfejsy HIDL są wersjonowane i nie można ich zmieniać po ich opublikowaniu.
- Efektywność . HIDL stara się minimalizować 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 zapewnia także interfejsy pamięci współdzielonej, a ponieważ procedury RPC są z natury nieco powolne, HIDL obsługuje dwa sposoby przesyłania danych bez użycia wywołania RPC: pamięć współdzielona i szybka kolejka komunikatów (FMQ).
- Intuicyjny . HIDL pozwala uniknąć drażliwych problemów związanych z własnością pamięci, używając tylko
in
dla RPC (patrz Język definicji interfejsu Androida (AIDL) ); wartości, których nie można efektywnie zwrócić z metod, zwracane są za pomocą funkcji wywołania zwrotnego. Ani przekazywanie danych do HIDL w celu przesłania, ani odbieranie danych z HIDL nie zmienia własności danych — własność zawsze pozostaje własnością funkcji wywołującej. Dane muszą trwać tylko przez czas trwania wywoływanej funkcji i mogą zostać zniszczone natychmiast po powrocie wywołanej funkcji.
Korzystanie z trybu przejścia
Aby zaktualizować urządzenia z wcześniejszymi wersjami Androida do Androida O, możesz zawinąć zarówno konwencjonalne (i starsze) warstwy HAL w nowy interfejs HIDL, który obsługuje warstwę HAL w trybach powiązanych i tego samego procesu (przejściowego). To opakowanie jest przezroczyste zarówno dla środowiska HAL, jak i platformy Android.
Tryb przekazywania jest dostępny tylko dla klientów i implementacji języka C++. Urządzenia z wcześniejszymi wersjami Androida nie mają warstw HAL napisanych w języku Java, więc warstwy HAL Java są z natury powiązane.
Przejściowe pliki nagłówkowe
Kiedy kompilowany jest plik .hal
, hidl-gen
tworzy dodatkowy plik nagłówkowy tranzytowy BsFoo.h
oprócz nagłówków używanych do komunikacji w ramach segregatora; ten nagłówek definiuje funkcje, które mają być dlopen
ed. Ponieważ warstwy HAL z przekazywaniem działają w tym samym procesie, w którym są wywoływane, w większości przypadków metody przejściowe są wywoływane przez bezpośrednie wywołanie funkcji (ten sam wątek). Metody oneway
działają we własnym wątku, ponieważ nie mają czekać, aż warstwa HAL je przetworzy (oznacza to, że każda warstwa HAL korzystająca z metod oneway
w trybie przekazywania musi być bezpieczna dla wątków).
Biorąc pod uwagę IFoo.hal
, BsFoo.h
otacza metody wygenerowane przez HIDL, aby zapewnić dodatkowe funkcje (takie jak wykonywanie transakcji oneway
w innym wątku). Plik ten jest podobny do BpFoo.h
, jednak zamiast przekazywać wywołania IPC za pomocą bindera, żądane funkcje są wywoływane bezpośrednio. Przyszłe implementacje warstw HAL mogą zapewniać wiele implementacji, takich jak FooFast HAL i FooAccurate HAL. W takich przypadkach dla każdej dodatkowej implementacji zostanie utworzony plik (np. PTFooFast.cpp
i PTFooAccurate.cpp
).
Binderizujące przejścia HAL
Można powiązać implementacje HAL obsługujące tryb przekazywania. Biorąc pod uwagę interfejs HAL abcd@MN::IFoo
, tworzone są dwa pakiety:
-
abcd@MN::IFoo-impl
. Zawiera implementację warstwy HAL i udostępnia funkcjęIFoo* HIDL_FETCH_IFoo(const char* name)
. Na starszych urządzeniach ten pakiet jestdlopen
i tworzona jest implementacja za pomocąHIDL_FETCH_IFoo
. Możesz wygenerować kod podstawowy za pomocąhidl-gen
i-Lc++-impl
i-Landroidbp-impl
. -
abcd@MN::IFoo-service
. Otwiera tranzytową warstwę HAL i rejestruje się jako usługa powiązana, umożliwiając użycie tej samej implementacji HAL zarówno jako przejściowej, jak i powiązanej.
Biorąc pod uwagę typ IFoo
, możesz wywołać sp<IFoo> IFoo::getService(string name, bool getStub)
, aby uzyskać dostęp do instancji IFoo
. Jeśli getStub
ma wartość true, getService
próbuje otworzyć warstwę HAL tylko w trybie przekazu. Jeśli getStub
ma wartość false, getService
próbuje znaleźć usługę powiązaną; jeśli to się nie powiedzie, próbuje znaleźć usługę przekazywania. Parametr getStub
nie powinien być nigdy używany, z wyjątkiem defaultPassthroughServiceImplementation
. (Urządzenia uruchamiane z systemem Android O są urządzeniami w pełni powiązanymi, więc otwieranie usługi w trybie przekazującym jest niedozwolone).
Gramatyka HIDL
Z założenia język HIDL jest podobny do C (ale nie wykorzystuje preprocesora C). Cała interpunkcja nieopisana poniżej (poza oczywistym użyciem =
i |
) jest częścią gramatyki.
Uwaga: szczegółowe informacje na temat stylu kodu HIDL można znaleźć w Przewodniku po stylu kodu .
-
/** */
wskazuje komentarz do dokumentacji. Można je zastosować tylko do deklaracji typu, metody, pola i wartości wyliczeniowej. -
/* */
oznacza komentarz wielowierszowy. -
//
wskazuje komentarz na końcu linii. Oprócz//
znaki nowej linii są takie same jak inne białe znaki. - W poniższym przykładzie gramatyki tekst od
//
do końca wiersza nie jest częścią gramatyki, ale jest komentarzem do gramatyki. -
[empty]
oznacza, że termin może być pusty. -
?
po literale lub określeniu oznacza, że jest to opcjonalne. -
...
wskazuje sekwencję zawierającą zero lub więcej elementów oddzielających je znakami interpunkcyjnymi, jak wskazano. W języku HIDL nie ma argumentów variadic. - Przecinki oddzielają elementy sekwencji.
- Średniki kończą każdy element, łącznie z ostatnim elementem.
- WIELKIE LITERY nie są terminalami.
-
italics
to rodzina tokenów, taka jakinteger
lubidentifier
(standardowe reguły analizy 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 sekcji Wersjonowanie HIDL . -
words
pisane małymi literami są symbolami dosłownymi.
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