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.cpp
和 PTFooAccurate.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
是符記組合,例如integer
或identifier
(標準 C 剖析規則)。constexpr
是 C 樣式常數運算式 (例如1 + 1
和1L << 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