HIDL

HAL 介面定義語言 (HIDL) 是一種介面描述語言 (IDL),可用來指定 HAL 與其使用者之間的介面。HIDL 可讓您指定類型和方法呼叫,並收集至介面和套件。更廣義來說,HIDL 是一種系統,可用於在可獨立編譯的程式碼集之間進行通訊。

HIDL 的用途是用於程序間通訊 (IPC)。使用 HDL 建立的 HAL 稱為繫結 HAL,因為這些 HAL 可使用繫結器的跨程序通訊 (IPC) 呼叫,與其他架構層進行通訊。繫結 HAL 會在使用者的不同程序中執行。如果程式庫必須連結至程序,也可以使用直通模式 (Java 不支援)。

HIDL 會指定資料結構和方法簽名,並以介面 (類似類別) 的形式進行整理,這些介面會收集到套件中。HIDL 的語法對 C++ 和 Java 程式設計師來說相當熟悉,但使用不同的關鍵字。HIDL 也使用 Java 樣式的註解。

術語

本節會使用下列 HIDL 相關術語:

繫結 表示 HIDL 用於程序之間的遠端程序呼叫,透過類似 Binder 的機制實作。另請參閱「passthrough」。
回呼、非同步 由 HAL 使用者提供的介面,會傳遞至 HAL (使用 HIDL 方法),並由 HAL 隨時呼叫以傳回資料。
回呼,同步 將伺服器的 HIDL 方法實作項目傳回至用戶端。不適用於傳回空值或單一基本值的方法。
client 呼叫特定介面方法的程序。HAL 或 Android 架構程序可能會是某個介面的用戶端,以及另一個介面的伺服器。另請參閱「passthrough」。
extends 表示將方法和/或類型新增至其他介面的介面。一個介面只能擴充一個其他介面。可用於同一個套件名稱中的次要版本升級,或是用於新套件 (例如供應商擴充功能),以便在舊套件上建構。
產生 表示傳回值給用戶端的介面方法。如要傳回一個非基本值或多個值,系統會產生同步回呼函式。
介面 方法和類型的集合。轉譯為 C++ 或 Java 中的類別。介面中的所有方法都會以相同方向呼叫:用戶端程序會叫用由伺服器程序實作的各項方法。
單程 套用至 HIDL 方法時,表示該方法不會傳回任何值,也不會封鎖。
包裹 共用版本的介面和資料類型集合。
直通 HIDL 模式,其中伺服器是用戶端所dlopen的共用程式庫。在傳送模式中,用戶端和伺服器是相同的程序,但程式碼集是分開的。僅用於將舊版程式碼基底帶入 HIDL 模型。另請參閱「Binderized」。
伺服器 實作介面方法的程序。另請參閱「passthrough」。
交通 在伺服器和用戶端之間移動資料的 HIDL 基礎架構。
version 套件的版本。包含兩個整數:主要和次要。次要版本增量可能會新增 (但不會變更) 類型和方法。

HIDL 設計

HIDL 的目標是讓 Android 架構可以替換,而不需要重建 HAL。HAL 是由供應商或 SoC 製造商建構,並放置在裝置上的 /vendor 分區,讓 Android 架構可在其專屬分區中,透過 OTA 取代,而無需重新編譯 HAL。

HIDL 設計可平衡以下疑慮:

  • 互通性。在可使用各種架構、工具鍊和建構設定編譯的程序之間,建立可靠的互通介面。HIDL 介面會加上版本號碼,且發布後就無法變更。
  • 效率。HIDL 會盡量減少複製作業的數量。HIDL 定義的資料會以 C++ 標準版面配置資料結構的形式傳送至 C++ 程式碼,可在不解開的情況下使用。HIDL 也提供共用記憶體介面,由於 RPC 本身速度較慢,因此 HIDL 支援兩種不使用 RPC 呼叫的資料傳輸方式:共用記憶體和快速訊息佇列 (FMQ)。
  • 直覺:HIDL 只使用 RPC 的 in 參數 (請參閱「Android 介面定義語言 (AIDL)」),避免記憶體擁有權的棘手問題;無法從方法有效傳回的值會透過回呼函式傳回。無論是將資料傳遞至 HIDL 以便轉移,或是從 HIDL 接收資料,都不會變更資料的擁有權,因為擁有權一律會保留在呼叫函式中。資料只需在呼叫函式期間保留,並可在呼叫函式傳回後立即銷毀。

使用直通模式

如要將搭載舊版 Android 的裝置更新為 Android O,您可以在新的 HIDL 介面中包裝傳統 (和舊版) HAL,以綁定和相同程序 (傳遞) 模式提供 HAL。這個包裝函式對 HAL 和 Android 架構都透明。

轉送模式僅適用於 C++ 用戶端和實作項目。搭載舊版 Android 的裝置沒有以 Java 編寫的 HAL,因此 Java HAL 本質上是綁定器。

編譯 .hal 檔案時,hidl-gen 除了產生用於 Binder 通訊的標頭外,還會產生額外的傳送標頭檔案 BsFoo.h;這個標頭會定義要 dlopen 的函式。由於傳送 HAL 會在呼叫時所在的相同程序中執行,因此在大多數情況下,傳送方法會透過直接函式呼叫 (相同執行緒) 叫用。oneway 方法會在自己的執行緒中執行,因為它們不必等待 HAL 處理 (也就是說,任何在傳送模式中使用 oneway 方法的 HAL 都必須支援執行緒安全)。

IFoo.hal 中,BsFoo.h 會包裝 HIDL 產生的函式,以提供其他功能 (例如讓 oneway 交易在另一個執行緒中執行)。這個檔案與 BpFoo.h 類似,但會直接叫用所需函式,而非使用繫結器傳遞呼叫 IPC。未來的 HAL 實作可能會提供多個實作項目,例如 FooFast HAL 和 FooAccurate HAL。在這種情況下,系統會為每個額外實作項目建立檔案 (例如PTFooFast.cppPTFooAccurate.cpp)。

將穿透式 HAL 繫結

您可以將支援直通模式的 HAL 實作項目綁定。在 HAL 介面 a.b.c.d@M.N::IFoo 中,會建立兩個套件:

  • a.b.c.d@M.N::IFoo-impl:包含 HAL 的實作項目,並公開 IFoo* HIDL_FETCH_IFoo(const char* name) 函式。在舊版裝置上,這個套件會遭到 dlopen,實作項目則會使用 HIDL_FETCH_IFoo 例項化。您可以使用 hidl-gen-Lc++-impl-Landroidbp-impl 產生基本程式碼。
  • a.b.c.d@M.N::IFoo-service。開啟直通 HAL,並將自身註冊為繫結服務,讓相同的 HAL 實作可同時用於直通和繫結。

在指定 IFoo 類型後,您可以呼叫 sp<IFoo> IFoo::getService(string name, bool getStub) 來取得 IFoo 例項的存取權。如果 getStub 為 true,getService 會嘗試只在穿透模式下開啟 HAL。如果 getStub 為 false,getService 會嘗試尋找繫結服務;如果失敗,則會嘗試尋找傳送服務。除了在 defaultPassthroughServiceImplementation 中使用外,請勿在其他地方使用 getStub 參數。(使用 Android O 啟動的裝置是完全繫結的裝置,因此不允許在傳遞模式下開啟服務)。

HIDL 文法

根據設計,HIDL 語言與 C 相似 (但不會使用 C 前置處理器)。除了下文明確提及的 =| 以外,所有未說明的標點符號都屬於文法。

注意:如要進一步瞭解 HIDL 程式碼樣式,請參閱 程式碼樣式指南

  • /** */ 表示說明文件註解。這些標記只能套用至型別、方法、欄位和列舉值宣告。
  • /* */ 表示多行註解。
  • // 表示註解到行尾。除了 // 之外,新行字元與其他空白字元相同。
  • 在下方的語法範例中,從 // 到行尾的文字並非語法的一部分,而是語法的註解。
  • [empty] 表示該字詞可能為空白。
  • ? 後面接著常值或字詞,表示該常值或字詞為選用項目。
  • ... 表示包含零或多個項目的序列,並以指定的符號分隔。HIDL 中沒有變化引數。
  • 以半形逗號分隔序列元素。
  • 半形分號可用於結束每個元素,包括最後一個元素。
  • 大寫是終端。
  • italics 是符記組合,例如 integeridentifier (標準 C 剖析規則)。
  • constexpr 是 C 樣式常數運算式 (例如 1 + 11L << 3)。
  • import_name 是套件或介面名稱,如 HIDL 版本管理一文所述,必須符合特定條件。
  • 小寫 words 是常值符記。

例子:

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