HAL 介面定義語言或 HIDL 是一種介面說明語言 (IDL),用於指定 HAL 和使用者之間的介面。HIDL 可讓您指定在介面和套件中收集的類型和方法呼叫。更廣泛來說,HIDL 是一種系統,用於在可獨立編譯的程式碼集之間進行通訊。
HIDL 的用途是供處理序間通訊 (IPC) 使用,使用 HDL 建立的 HAL 稱為繫結化 HAL,可以透過繫結器處理序間通訊 (IPC) 呼叫與其他架構層通訊。繫結化 HAL 的執行程序與使用繫結的 HAL 不同,如果是必須連結至程序的程式庫,您也可以使用直通模式 (Java 不支援)。
HIDL 會指定資料結構和方法簽章,這些簽章的分類方式是以介面 (類似於類別) 的形式歸類,並收集至套件中。C++ 和 Java 程式設計師雖然對 HIDL 的語法很熟悉,但同時使用一組不同的關鍵字。HIDL 也會使用 Java 樣式的註解。
術語
本節使用下列 HIDL 相關字詞:
繫結 | 表示將 HIDL 用於程序之間的遠端程序呼叫,並透過類似 Binder 的機制實作。另請參閱快速導入。 |
---|---|
回呼、非同步回呼 | 由 HAL 使用者提供的介面、傳遞至 HAL (使用 HIDL 方法),並由 HAL 呼叫以便隨時傳回資料。 |
回呼、同步 | 將伺服器的 HIDL 方法實作所提供的資料傳回用戶端。 不適用於傳回空白或單一原始值的方法。 |
用戶端 | 呼叫特定介面方法的程序。HAL 或 Android 架構程序可能是不同介面的用戶端和另一個介面的伺服器。另請參閱快速導入一節。 |
延伸 | 表示可將方法和/或類型新增至其他介面的介面。一個介面只能擴充另一個介面。可用於在相同的套件名稱中增加次要版本,或是用於在舊版套件上建構的新套件 (例如廠商擴充功能)。 |
產生 | 指出可將值傳回用戶端的介面方法。如要傳回一個非原始值或多個值,系統會產生同步回呼函式。 |
介面 | 方法和類型的集合。在 C++ 或 Java 中轉譯為類別。系統會按照相同方向呼叫介面中的所有方法:用戶端程序叫用由伺服器程序實作的方法。 |
單程 | 套用至 HIDL 方法時,表示該方法未傳回任何值,且不會封鎖。 |
包裹 | 共用版本的介面和資料類型集合。 |
直通 | 伺服器是一個共用程式庫的 HIDL 模式,由用戶端dlopen 使用。在直通模式下,用戶端和伺服器的程序相同,但程式碼集分開。僅適用於將舊版程式碼集帶入 HIDL 模型。另請參閱繫結機制。 |
伺服器 | 實作介面方法的程序。另請參閱快速導入一節。 |
交通 | 在伺服器和用戶端之間移動資料的 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
還會產生額外的直通標頭檔案 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 中沒有變數引數。- 逗號用來分隔序列元素。
- 每個元素都會以半形分號終止,包括最後一個元素。
- UPPERCASE 不成立。
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