最近在學(xué)習(xí)HIDL收叶,有很多的疑惑,在這里記錄一下玄组,加深自己的理解滔驾,以下部分大多來(lái)自官網(wǎng)。
官網(wǎng):https://source.android.com/devices/architecture/hidl
HIDL定義
HAL 接口定義語(yǔ)言(簡(jiǎn)稱 HIDL俄讹,發(fā)音為“hide-l”)是用于指定 HAL 和其用戶之間的接口的一種接口描述語(yǔ)言 (IDL)哆致。HIDL 允許指定類型和方法調(diào)用(會(huì)匯集到接口和軟件包中)。從更廣泛的意義上來(lái)說(shuō)患膛,HIDL 是用于在可以獨(dú)立編譯的代碼庫(kù)之間進(jìn)行通信的系統(tǒng)摊阀。
HIDL 旨在用于進(jìn)程間通信 (IPC)。進(jìn)程之間的通信經(jīng)過(guò) Binder 化。對(duì)于必須與進(jìn)程相關(guān)聯(lián)的代碼庫(kù)胞此,還可以使用直通模式(在 Java 中不受支持)臣咖。
HIDL 可指定數(shù)據(jù)結(jié)構(gòu)和方法簽名,這些內(nèi)容會(huì)整理歸類到接口(與類相似)中漱牵,而接口會(huì)匯集到軟件包中夺蛇。盡管 HIDL 具有一系列不同的關(guān)鍵字,但 C++ 和 Java 程序員對(duì) HIDL 的語(yǔ)法并不陌生酣胀。此外刁赦,HIDL 還使用 Java 樣式的注釋。
HIDL C++
Android O 對(duì) Android 操作系統(tǒng)的架構(gòu)重新進(jìn)行了設(shè)計(jì)闻镶,以在獨(dú)立于設(shè)備的 Android 平臺(tái)與特定于設(shè)備和供應(yīng)商的代碼之間定義清晰的接口甚脉。Android 已經(jīng)以 HAL 接口的形式(在 hardware/libhardware
中定義為 C 標(biāo)頭)定義了許多此類接口。HIDL 將這些 HAL 接口替換為穩(wěn)定的帶版本接口铆农,它們可以是采用 C++(如下所述)或 Java 的客戶端和服務(wù)器端 HIDL 接口牺氨。
本部分中的幾頁(yè)內(nèi)容介紹了 HIDL 接口的 C++ 實(shí)現(xiàn),其中詳細(xì)說(shuō)明了 hidl-gen
編譯器基于 HIDL .hal
文件自動(dòng)生成的文件墩剖,這些文件如何打包猴凹,以及如何將這些文件與使用它們的 C++ 代碼集成。
HIDL 設(shè)計(jì)
HIDL 的目標(biāo)是涛碑,框架可以在無(wú)需重新構(gòu)建 HAL 的情況下進(jìn)行替換精堕。HAL 將由供應(yīng)商或 SOC 制造商構(gòu)建,放置在設(shè)備的 /vendor
分區(qū)中蒲障,這樣一來(lái)歹篓,框架就可以在其自己的分區(qū)中通過(guò) OTA 進(jìn)行替換,而無(wú)需重新編譯 HAL揉阎。
HIDL 設(shè)計(jì)在以下方面之間保持了平衡:
- 互操作性庄撮。在可以使用各種架構(gòu)、工具鏈和編譯配置來(lái)編譯的進(jìn)程之間創(chuàng)建可互操作的可靠接口毙籽。HIDL 接口是分版本的洞斯,發(fā)布后不得再進(jìn)行更改。
- 效率坑赡。HIDL 會(huì)嘗試盡可能減少?gòu)?fù)制操作的次數(shù)烙如。HIDL 定義的數(shù)據(jù)以 C++ 標(biāo)準(zhǔn)布局?jǐn)?shù)據(jù)結(jié)構(gòu)傳遞至 C++ 代碼,無(wú)需解壓毅否,可直接使用亚铁。此外,HIDL 還提供共享內(nèi)存接口螟加;由于 RPC 本身有點(diǎn)慢徘溢,因此 HIDL 支持兩種無(wú)需使用 RPC 調(diào)用的數(shù)據(jù)傳輸方法:共享內(nèi)存和快速消息隊(duì)列 (FMQ)吞琐。
-
直觀。通過(guò)僅針對(duì) RPC 使用
in
參數(shù)然爆,HIDL 避開了內(nèi)存所有權(quán)這一棘手問(wèn)題(請(qǐng)參閱 Android 接口定義語(yǔ)言 (AIDL))站粟;無(wú)法從方法高效返回的值將通過(guò)回調(diào)函數(shù)返回。無(wú)論是將數(shù)據(jù)傳遞到 HIDL 中以進(jìn)行傳輸曾雕,還是從 HIDL 接收數(shù)據(jù)奴烙,都不會(huì)改變數(shù)據(jù)的所有權(quán),也就是說(shuō)翻默,數(shù)據(jù)所有權(quán)始終屬于調(diào)用函數(shù)缸沃。數(shù)據(jù)僅需要在函數(shù)被調(diào)用期間保留,可在被調(diào)用的函數(shù)返回?cái)?shù)據(jù)后立即清除修械。
HIDL的架構(gòu)模式
- Passthrough 模式
- Binder 化的 Passthrough HALs
什么是Binder化?
一直以來(lái)检盼,供應(yīng)商進(jìn)程都使用 Binder 進(jìn)程間通信 (IPC) 技術(shù)進(jìn)行通信肯污。在 Android O 中,/dev/binder 設(shè)備節(jié)點(diǎn)成為了框架進(jìn)程的專屬節(jié)點(diǎn)吨枉,這意味著供應(yīng)商進(jìn)程將無(wú)法再訪問(wèn)該節(jié)點(diǎn)蹦渣。供應(yīng)商進(jìn)程可以訪問(wèn) /dev/hwbinder,但必須將其 AIDL 接口轉(zhuǎn)為使用 HIDL貌亭。
HIDL 語(yǔ)法
根據(jù)設(shè)計(jì)柬唯,HIDL 語(yǔ)言與 C 語(yǔ)言類似(但前者不使用 C 預(yù)處理器)。下面未描述的所有標(biāo)點(diǎn)符號(hào)(用途明顯的 = 和 | 除外)都是語(yǔ)法的一部分圃庭。
-
/** */
表示文檔注釋锄奢。此樣式只能應(yīng)用于類型、方法剧腻、字段和枚舉值聲明拘央。 -
/* */
表示多行注釋。 -
//
表示注釋一直持續(xù)到行結(jié)束书在。除了//
灰伟,換行符與任何其他空白一樣。 - 在以下示例語(yǔ)法中儒旬,從
//
到行結(jié)束的文本不是語(yǔ)法的一部分栏账,而是對(duì)語(yǔ)法的注釋。 -
[empty]
表示該字詞可能為空栈源。 -
?
跟在文本或字詞后挡爵,表示它是可選的。 -
...
表示包含零個(gè)或多個(gè)項(xiàng)凉翻、用指定的分隔符號(hào)分隔的序列了讨。HIDL 中不含可變參數(shù)捻激。 - 逗號(hào)用于分隔序列元素。
- 分號(hào)用于終止各個(gè)元素前计,包括最后的元素胞谭。
- 大寫字母是非終止符。
-
italics 是一個(gè)令牌系列男杈,例如
*integer*
或*identifier*
(標(biāo)準(zhǔn) C 解析規(guī)則)丈屹。 -
constexpr 是 C 樣式的常量表達(dá)式(如
1 + 1
和1L << 3
)。 - import_name 是軟件包或接口名稱伶棒,HIDL 版本編號(hào)中所述的方式加以限定旺垒。
- 小寫
words
是文本令牌。
實(shí)例:
ROOT =
PACKAGE IMPORTS PREAMBLE { ITEM ITEM ... } // not for types.hal
PREAMBLE = interface identifier EXTENDS
| PACKAGE IMPORTS ITEM ITEM... // only for types.hal; no method definitions
ITEM =
ANNOTATIONS? oneway? identifier(FIELD, FIELD ...) GENERATES?;
| 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
| struct identifier { FIELD; FIELD; ...} identifier;
| union identifier { FIELD; FIELD; ...} identifier;
SFIELD =
TYPE identifier
| struct identifier { FIELD; FIELD; ...};
| union identifier { FIELD; FIELD; ...};
| 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
接口描述
HIDL 是圍繞接口進(jìn)行編譯的肤无,接口是面向?qū)ο蟮恼Z(yǔ)言使用的一種用來(lái)定義行為的抽象類型先蒋。每個(gè)接口都是軟件包的一部分。
軟件包
軟件包名稱可以具有子級(jí)宛渐,例如 package.subpackage竞漾。
已發(fā)布的 HIDL 軟件包的根目錄是 hardware/interfaces 或 vendor/vendorName(例如 Pixel 設(shè)備為 vendor/google)。
軟件包名稱在根目錄下形成一個(gè)或多個(gè)子目錄窥翩;定義軟件包的所有文件都位于同一目錄下业岁。
例:
package android.hardware.example.extension.light@2.0
可以在
hardware/interfaces/example/extension/light/2.0
下找到。
軟件包目錄中包含擴(kuò)展名為 .hal 的文件寇蚊。
每個(gè)文件均必須包含一個(gè)指定文件所屬的軟件包和版本的 package 語(yǔ)句笔时。
文件 types.hal(如果存在)并不定義接口,而是定義軟件包中每個(gè)接口可以訪問(wèn)的數(shù)據(jù)類型仗岸。
接口定義
除了 types.hal 之外允耿,其他 .hal 文件均定義一個(gè)接口。
接口通常定義如下:
interface IBar extends IFoo { // IFoo is another interface
// embedded types
struct MyStruct {/*...*/};
// interface methods
create(int32_t id) generates (MyStruct s);
close();
};
不含顯式 extends 聲明的接口會(huì)從 android.hidl.base@1.0::IBase(類似于 Java 中的 java.lang.Object)隱式擴(kuò)展爹梁。
導(dǎo)入
import 語(yǔ)句是用于訪問(wèn)其他軟件包中的軟件包接口和類型的 HIDL 機(jī)制右犹。
import 語(yǔ)句本身涉及兩個(gè)實(shí)體:
導(dǎo)入實(shí)體:可以是軟件包或接口;
被導(dǎo)入實(shí)體:也可以是軟件包或接口姚垃。
導(dǎo)入實(shí)體由 import 語(yǔ)句的位置決定念链。
當(dāng)該語(yǔ)句位于軟件包的 types.hal 中時(shí),導(dǎo)入的內(nèi)容對(duì)整個(gè)軟件包是可見的积糯;這是軟件包級(jí)導(dǎo)入掂墓。
當(dāng)該語(yǔ)句位于接口文件中時(shí),導(dǎo)入實(shí)體是接口本身看成;這是接口級(jí)導(dǎo)入君编。
被導(dǎo)入實(shí)體由 import 關(guān)鍵字后面的值決定。
該值不必是完全限定名稱川慌;如果某個(gè)組成部分被刪除了吃嘿,系統(tǒng)會(huì)自動(dòng)使用當(dāng)前軟件包中的信息填充該組成部分祠乃。
對(duì)于完全限定值,支持的導(dǎo)入情形有以下幾種:
-
完整軟件包導(dǎo)入
如果該值是一個(gè)軟件包名稱和版本(語(yǔ)法見下文)兑燥,則系統(tǒng)會(huì)將整個(gè)軟件包導(dǎo)入至導(dǎo)入實(shí)體
import android.hardware.nfc@1.0; // import a whole package
-
部分導(dǎo)入
如果值為:
1.一個(gè)接口亮瓷,則系統(tǒng)會(huì)將該軟件包的 types.hal 和該接口導(dǎo)入至導(dǎo)入實(shí)體中。
2.在 types.hal 中定義的 UDT降瞳,則系統(tǒng)僅會(huì)將該 UDT 導(dǎo)入至導(dǎo)入實(shí)體中(不導(dǎo)入 types.hal 中的其他類型)嘱支。
import android.hardware.example@1.0::IQuux;
// import an interface and types.hal
-
僅類型導(dǎo)入
如果該值將上文所述的“部分導(dǎo)入”的語(yǔ)法與關(guān)鍵字 types 而不是接口名稱配合使用,則系統(tǒng)僅會(huì)導(dǎo)入指定軟件包的 types.hal 中的 UDT挣饥。
import android.hardware.example@1.0::types; // import just types.hal
接口繼承
接口可以是之前定義的接口的擴(kuò)展除师。
擴(kuò)展可以是以下三種類型中的一種:
1.接口可以向其他接口添加功能,并按原樣納入其 API扔枫。
2.軟件包可以向其他軟件包添加功能汛聚,并按原樣納入其 API。
3.接口可以從軟件包或特定接口導(dǎo)入類型茧吊。
接口只能擴(kuò)展一個(gè)其他接口(不支持多重繼承)贞岭。
接口哈希
哈希是一種旨在防止意外更改接口并確保接口更改經(jīng)過(guò)全面審查的機(jī)制。這種機(jī)制是必需的搓侄,因?yàn)?HIDL 接口帶有版本編號(hào),也就是說(shuō)话速,接口一經(jīng)發(fā)布便不得再更改讶踪,但不會(huì)影響應(yīng)用二進(jìn)制接口 (ABI) 的情況(例如更正備注)除外。
布局
每個(gè)軟件包根目錄(即映射到 hardware/interfaces 的 android.hardware 或映射到 vendor/foo/hardware/interfaces 的 vendor.foo)都必須包含一個(gè)列出所有已發(fā)布 HIDL 接口文件的 current.txt 文件泊交。
# current.txt files support comments starting with a ‘#' character
# this file, for instance, would be vendor/foo/hardware/interfaces/current.txt
# Each line has a SHA-256 hash followed by the name of an interface.
# They have been shortened in this doc for brevity but they are
# 64 characters in length in an actual current.txt file.
d4ed2f0e...995f9ec4 vendor.awesome.foo@1.0::IFoo # comments can also go here
# types.hal files are also noted in types.hal files
c84da9f5...f8ea2648 vendor.awesome.foo@1.0::types
# Multiple hashes can be in the file for the same interface. This can be used
# to note how ABI sustaining changes were made to the interface.
# For instance, here is another hash for IFoo:
# Fixes type where "FooCallback" was misspelled in comment on "FooStruct"
822998d7...74d63b8c vendor.awesome.foo@1.0::IFoo
使用 hidl-gen 添加哈希
什么是hidl-gen
hidl-gen是安卓架構(gòu)HIDL編譯工具乳讥。
可以手動(dòng)將哈希添加到 current.txt 文件中,也可以使用 hidl-gen 添加廓俭。以下代碼段提供了可與 hidl-gen 搭配使用來(lái)管理 current.txt 文件的命令示例(哈希已縮短):
$ hidl-gen -L hash -r vendor.awesome:vendor/awesome/hardware/interfaces -r android.hardware:hardware/interfaces -r android.hidl:system/libhidl/transport vendor.awesome.nfc@1.0::types
9626fd18...f9d298a6 vendor.awesome.nfc@1.0::types
$ hidl-gen -L hash -r vendor.awesome:vendor/awesome/hardware/interfaces -r android.hardware:hardware/interfaces -r android.hidl:system/libhidl/transport vendor.awesome.nfc@1.0::INfc
07ac2dc9...11e3cf57 vendor.awesome.nfc@1.0::INfc
$ hidl-gen -L hash -r vendor.awesome:vendor/awesome/hardware/interfaces -r android.hardware:hardware/interfaces -r android.hidl:system/libhidl/transport vendor.awesome.nfc@1.0
9626fd18...f9d298a6 vendor.awesome.nfc@1.0::types
07ac2dc9...11e3cf57 vendor.awesome.nfc@1.0::INfc
f2fe5442...72655de6 vendor.awesome.nfc@1.0::INfcClientCallback
$ hidl-gen -L hash -r vendor.awesome:vendor/awesome/hardware/interfaces -r android.hardware:hardware/interfaces -r android.hidl:system/libhidl/transport vendor.awesome.nfc@1.0 >> vendor/awesome/hardware/interfaces/current.txt
idl-gen 生成的每個(gè)接口定義庫(kù)都包含哈希云石,通過(guò)調(diào)用 IBase::getHashChain 可檢索這些哈希。
Services & Data Transfer
注冊(cè)Services
HIDL 接口服務(wù)器(實(shí)現(xiàn)接口的對(duì)象)可注冊(cè)為已命名的服務(wù)研乒。
注冊(cè)的名稱不需要與接口或軟件包名稱相關(guān)汹忠。如果沒(méi)有指定名稱,則使用名稱“默認(rèn)”雹熬;這應(yīng)該用于不需要注冊(cè)同一接口的兩個(gè)實(shí)現(xiàn)的 HAL宽菜。
例如,在每個(gè)接口中定義的服務(wù)注冊(cè)的 C++ 調(diào)用是:
status_t status = myFoo->registerAsService();
status_t anotherStatus = anotherFoo->registerAsService("another_foo_service"); // if needed
HIDL 接口的版本包含在接口本身中竿报。
版本自動(dòng)與Service注冊(cè)關(guān)聯(lián)铅乡,并可通過(guò)每個(gè) HIDL 接口上的方法調(diào)用 (android::hardware::IInterface::getInterfaceVersion()) 進(jìn)行檢索。
服務(wù)器對(duì)象不需要注冊(cè)烈菌,并可通過(guò) HIDL 方法參數(shù)傳遞到其他進(jìn)程阵幸,相應(yīng)的接收進(jìn)程會(huì)向服務(wù)器發(fā)送 HIDL 方法調(diào)用花履。
發(fā)現(xiàn)Service
客戶端代碼按名稱和版本請(qǐng)求指定的接口,并對(duì)所需的 HAL 類調(diào)用 getService:
sp<V1_1::IFooService> service = V1_1::IFooService::getService();
sp<V1_1::IFooService> alternateService = V1_1::IFooService::getService("another_foo_service");
Service終止通知
想要在Service終止時(shí)收到通知的客戶端會(huì)接收到框架傳送的終止通知挚赊。
要接收通知诡壁,客戶端必須:
1.將 HIDL 類/接口 hidl_death_recipient(位于 C++ 代碼中,而非 HIDL 中)歸入子類咬腕。
2.替換其 serviceDied() 方法筛峭。
3.實(shí)例化 hidl_death_recipient 子類的對(duì)象尝胆。
4.在要監(jiān)控的服務(wù)上調(diào)用 linkToDeath() 方法,并傳入 IDeathRecipient 的接口對(duì)象。請(qǐng)注意嘀粱,此方法并不具備在其上調(diào)用它的終止接收方或代理的所有權(quán)。
偽代碼示例:
class IMyDeathReceiver : hidl_death_recipient {
virtual void serviceDied(uint64_t cookie,
wp<IBase>& service) override {
log("RIP service %d!", cookie); // Cookie should be 42
}
};
....
IMyDeathReceiver deathReceiver = new IMyDeathReceiver();
m_importantService->linkToDeath(deathReceiver, 42);
PS:
東西很多啊约谈,吐血三升指巡,今天就到這里』鸨牵回去吃大餐室囊。O(∩_∩)O