HIDL

Język definicji interfejsu HAL (HIDL) to język opisu interfejsu (IDL) służący do określania interfejsu między interfejsem HAL a jego użytkownikami. HIDL umożliwia określenie typów i wywołań metod zbieranych w interfejsach i pakietach. Ogólnie HIDL to system komunikacji między bazami kodu, które mogą być kompilowane niezależnie.

Protokół HIDL jest przeznaczony do komunikacji między procesami (IPC). Listy HAL utworzone przy użyciu HDL są nazywane bindowanymi kodami HAL, ponieważ mogą komunikować się z innymi warstwami architektury za pomocą wywołań komunikacji międzyprocesowej bindera (IPC). Powiązane poziomy HAL są uruchamiane w procesie innym niż klient, który ich używa. W przypadku bibliotek, które muszą być połączone z procesem, jest też dostępny tryb przekazywania (nieobsługiwany w języku Java).

HIDL określa struktury danych i podpisy metod uporządkowane w interfejsach (podobnych do klas) zbieranych w pakiety. Składnia języka HIDL wygląda znajomo dla programistów w językach C++ i Java, ale zawiera inny zestaw słów kluczowych. HIDL wykorzystuje również adnotacje w stylu Java.

Terminologia

W tej sekcji używane są następujące terminy związane z HIDL:

powiązane Wskazuje, że HIDL jest używany do zdalnych wywołań procedur między procesami zaimplementowanych za pomocą mechanizmu podobnego do Binder. Zobacz też Przekazywanie.
wywołanie zwrotne, asynchroniczne Interfejs udostępniany przez użytkownika HAL, przekazywany do HAL (za pomocą metody HIDL) i wywoływany przez HAL w celu zwrócenia danych w dowolnym momencie.
wywołanie zwrotne, synchroniczne Zwraca do klienta dane z implementacji metody HIDL serwera. Nieużywane w przypadku metod, które zwracają nieważną lub pojedynczą wartość podstawową.
klient Proces, który wywołuje metody określonego interfejsu. Proces HAL lub platformy Androida może być klientem jednego interfejsu, a serwerem innego. Zobacz też informacje o przekazywaniu.
rozciąga się Wskazuje interfejs, który dodaje metody i/lub typy do innego interfejsu. Interfejs może stanowić rozszerzenie tylko jednego innego interfejsu. Można jej używać do niewielkich przyrostów wersji w ramach tej samej nazwy pakietu lub do nowego pakietu (np. rozszerzenia dostawcy) w celu utworzenia starszego pakietu.
generuje Wskazuje metodę interfejsu, która zwraca wartości klientowi. Aby zwrócić jedną lub więcej wartości, generowana jest synchroniczna funkcja wywołania zwrotnego.
interfejs Zbiór metod i typów. Przetłumaczone na zajęcia w C++ lub Java. Wszystkie metody w interfejsie są wywoływane w tym samym kierunku – proces klienta wywołuje metody wdrożone przez proces serwera.
w jedną stronę Po zastosowaniu do metody HIDL oznacza, że metoda nie zwraca żadnych wartości i nie blokuje.
paczka Zbiór interfejsów i typów danych współużytkujących daną wersję.
przekazywanie Tryb HIDL, w którym serwer jest biblioteką współdzieloną dlopenprzez klienta. W trybie przekazywania klient i serwer to ten sam proces, ale istnieją osobne bazy kodu. Służy tylko do przeniesienia starszych baz kodu do modelu HIDL. Zapoznaj się też z sekcją Powiązane.
serwer Proces implementujący metody interfejsu. Zobacz też informacje o przekazywaniu.
transport infrastruktura HIDL, która służy do przenoszenia danych między serwerem a klientem.
Wersja Wersja pakietu. Składa się z 2 liczb całkowitych (dużej i małej). Niewielkie przyrosty wersji mogą dodawać (ale nie zmieniać) typy i metody.

Projekt HIDL

Dzięki HIDL można zastąpić platformę Androida bez konieczności ponownego tworzenia list HAL. Listy HAL będą tworzone przez dostawców lub twórców SOC i umieszczane na urządzeniu w partycji /vendor, dzięki czemu platforma Androida jako osobna partycja może zostać zastąpiona przez OTA bez ponownego kompilowania HAL.

Projekt HIDL obejmuje następujące problemy:

  • Współdziałanie. Twórz niezawodne, współdziałające interfejsy między procesami, które można skompilować z użyciem różnych architektur, łańcuchów narzędzi i konfiguracji kompilacji. Interfejsy HIDL mają różne wersje i po opublikowaniu nie można ich zmienić.
  • Skuteczność. HIDL próbuje zminimalizować liczbę operacji kopiowania. Dane zdefiniowane przez HIDL są przesyłane do kodu C++ w strukturach danych układu standardowego C++, których można używać bez rozpakowywania. HIDL udostępnia również interfejsy pamięci współdzielonej. Ponieważ RPC z natury działają nieco wolno, HIDL obsługuje 2 sposoby przesyłania danych bez użycia wywołania RPC: pamięć współdzieloną i szybkie przesyłanie wiadomości (FMQ).
  • Intuicyjny. HIDL pozwala uniknąć poważnych problemów z własnością pamięci, wykorzystując tylko parametry in dla RPC (patrz Język definiowania interfejsu Androida (AIDL)). Wartości, których nie można skutecznie zwrócić z metod, są zwracane przez funkcje wywołania zwrotnego. Ani przekazywanie danych do HIDL w celu ich przesłania, ani otrzymywanie danych z HIDL nie zmienia własności danych – własność zawsze odpowiada funkcji wywołującej. Dane muszą być przechowywane tylko przez czas trwania wywołanej funkcji i mogą zostać zniszczone natychmiast po zwróceniu wywołanej funkcji.

Korzystanie z trybu przekazywania

Aby zaktualizować urządzenia z wcześniejszymi wersjami Androida O, możesz umieścić zarówno konwencjonalne (jak i starsze) listy HAL, w nowym interfejsie HIDL, który obsługuje HAL w trybach powiązania i tego samego procesu (przekazywania). To opakowanie jest przejrzyste zarówno dla HAL, jak i platformy Androida.

Tryb przekazywania jest dostępny tylko w przypadku klientów i implementacji C++. Urządzenia z wcześniejszymi wersjami Androida nie mają takich interfejsów, więc są one z natury powiązane.

Podczas skompilowania pliku .hal oprócz nagłówków używanych do komunikacji powiązań hidl-gen tworzy dodatkowy przekazujący plik nagłówka BsFoo.h. Ten nagłówek definiuje funkcje, których można dlopenwymieniać. Ponieważ przekazujące HAL działają w tym samym procesie, w którym są wywoływane, w większości przypadków metody przekazywania są wywoływane przez wywołanie funkcji bezpośredniej (ten sam wątek). Metody oneway są uruchamiane w osobnym wątku, ponieważ nie są przeznaczone do czekania na przetworzenie przez HAL (oznacza to, że wszystkie metody HAL korzystające z metod oneway w trybie przekazywania muszą być bezpieczne w przypadku wątków).

Biorąc pod uwagę wartość IFoo.hal, BsFoo.h opakowuje metody wygenerowane przez HIDL, aby udostępnić dodatkowe funkcje (np. uruchamianie transakcji oneway w innym wątku). Ten plik jest podobny do pliku BpFoo.h, ale zamiast przekazywać wywołania IPC za pomocą bindera, odpowiednie funkcje są wywoływane bezpośrednio. W przyszłości wdrożenia HAL mogą obejmować wiele implementacji, takich jak FooFast HAL i FooEXACT HAL. W takich przypadkach tworzony jest plik dla każdej dodatkowej implementacji (np. PTFooFast.cpp i PTFooAccurate.cpp).

Powiązanie przekazujących HAL

Możesz powiązać implementacje HAL, które obsługują tryb przekazywania. Interfejs HAL a.b.c.d@M.N::IFoo tworzy 2 pakiety:

  • a.b.c.d@M.N::IFoo-impl. Zawiera implementację HAL i udostępnia funkcję IFoo* HIDL_FETCH_IFoo(const char* name). Na starszych urządzeniach ten pakiet jest dlopen, a implementacja jest inicjowana za pomocą protokołu HIDL_FETCH_IFoo. Kod podstawowy możesz wygenerować za pomocą właściwości hidl-gen, -Lc++-impl i -Landroidbp-impl.
  • a.b.c.d@M.N::IFoo-service. Otwiera przekazywanie HAL i rejestruje się jako usługa powiązana, co umożliwia używanie tej samej implementacji HAL zarówno jako przekazującej, 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ść prawda, getService próbuje otworzyć listę HAL tylko w trybie przekazywania. Jeśli getStub ma wartość fałsz, getService próbuje znaleźć usługę powiązaną. Jeśli to się nie uda, próbuje znaleźć usługę przekazywania. Parametru getStub nie należy używać nigdy poza defaultPassthroughServiceImplementation. (Urządzenia uruchamiane z Androidem O są urządzeniami w pełni powiązanymi, więc otwarcie usługi w trybie przekazywania jest niedozwolone).

Gramatyka HIDL

Z założenia język HIDL jest podobny do języka C (ale nie korzysta z preprocesora C). Wszystkie znaki interpunkcyjne, które nie zostały opisane poniżej (z wyjątkiem oczywistego zastosowania = i |), stanowią część gramatyki.

Uwaga: szczegółowe informacje o stylu kodu HIDL znajdziesz w poradniku dotyczącym stylów kodu.

  • /** */ oznacza komentarz do dokumentacji. Można je stosować tylko do deklaracji typu, metody, pól i wartości wyliczeniowych.
  • /* */ oznacza komentarz wielowierszowy.
  • // oznacza komentarz na końcu wiersza. Oprócz // znaki nowego wiersza są takie same jak wszystkie pozostałe odstępy.
  • W tej przykładowej gramatyce tekst od // do końca wiersza nie jest częścią gramatyki, ale stanowi komentarz do niej.
  • [empty] oznacza, że hasło może być puste.
  • ? po literału lub pojęciu oznacza, że element jest opcjonalny.
  • ... oznacza sekwencję zawierającą 0 lub więcej elementów, rozdzielając je znakami interpunkcyjnymi. W HIDL nie ma żadnych argumentów zmiennych.
  • Elementy sekwencji są rozdzielane przecinkami.
  • Średniki kończą każdy element, w tym ostatni.
  • WIELKIE LITERY to nieterminal.
  • italics to rodzina tokenów, taka jak integer lub identifier (standardowe reguły analizy C).
  • constexpr to wyrażenie stałe stylu C (np. 1 + 1 i 1L << 3).
  • import_name to nazwa pakietu lub interfejsu kwalifikowana zgodnie z opisem w sekcji Obsługa wersji 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