最近在学习HIDL,有很多的疑惑,在这里记录一下,加深自己的理解,以下部分大多来自官网。
官网:https://source.android.com/devices/architecture/hidl
HAL 接口定义语言(简称 HIDL,发音为“hide-l”)是用于指定 HAL 和其用户之间的接口的一种接口描述语言 (IDL)。HIDL 允许指定类型和方法调用(会汇集到接口和软件包中)。从更广泛的意义上来说,HIDL 是用于在可以独立编译的代码库之间进行通信的系统。
HIDL 旨在用于进程间通信 (IPC)。进程之间的通信经过 Binder 化。对于必须与进程相关联的代码库,还可以使用直通模式(在 Java 中不受支持)。
HIDL 可指定数据结构和方法签名,这些内容会整理归类到接口(与类相似)中,而接口会汇集到软件包中。尽管 HIDL 具有一系列不同的关键字,但 C++ 和 Java 程序员对 HIDL 的语法并不陌生。此外,HIDL 还使用 Java 样式的注释。
Android O 对 Android 操作系统的架构重新进行了设计,以在独立于设备的 Android 平台与特定于设备和供应商的代码之间定义清晰的接口。Android 已经以 HAL 接口的形式(在 hardware/libhardware
中定义为 C 标头)定义了许多此类接口。HIDL 将这些 HAL 接口替换为稳定的带版本接口,它们可以是采用 C++(如下所述)或 Java 的客户端和服务器端 HIDL 接口。
本部分中的几页内容介绍了 HIDL 接口的 C++ 实现,其中详细说明了 hidl-gen
编译器基于 HIDL .hal
文件自动生成的文件,这些文件如何打包,以及如何将这些文件与使用它们的 C++ 代码集成。
HIDL 的目标是,框架可以在无需重新构建 HAL 的情况下进行替换。HAL 将由供应商或 SOC 制造商构建,放置在设备的 /vendor
分区中,这样一来,框架就可以在其自己的分区中通过 OTA 进行替换,而无需重新编译 HAL。
HIDL 设计在以下方面之间保持了平衡:
in
参数,HIDL 避开了内存所有权这一棘手问题(请参阅 Android 接口定义语言 (AIDL));无法从方法高效返回的值将通过回调函数返回。无论是将数据传递到 HIDL 中以进行传输,还是从 HIDL 接收数据,都不会改变数据的所有权,也就是说,数据所有权始终属于调用函数。数据仅需要在函数被调用期间保留,可在被调用的函数返回数据后立即清除。一直以来,供应商进程都使用 Binder 进程间通信 (IPC) 技术进行通信。在 Android O 中,/dev/binder 设备节点成为了框架进程的专属节点,这意味着供应商进程将无法再访问该节点。供应商进程可以访问 /dev/hwbinder,但必须将其 AIDL 接口转为使用 HIDL。
根据设计,HIDL 语言与 C 语言类似(但前者不使用 C 预处理器)。下面未描述的所有标点符号(用途明显的 = 和 | 除外)都是语法的一部分。
/** */
表示文档注释。此样式只能应用于类型、方法、字段和枚举值声明。/* */
表示多行注释。//
表示注释一直持续到行结束。除了 //
,换行符与任何其他空白一样。//
到行结束的文本不是语法的一部分,而是对语法的注释。[empty]
表示该字词可能为空。?
跟在文本或字词后,表示它是可选的。...
表示包含零个或多个项、用指定的分隔符号分隔的序列。HIDL 中不含可变参数。*integer*
或 *identifier*
(标准 C 解析规则)。1 + 1
和 1L << 3
)。words
是文本令牌。实例:
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 是围绕接口进行编译的,接口是面向对象的语言使用的一种用来定义行为的抽象类型。每个接口都是软件包的一部分。
软件包名称可以具有子级,例如 package.subpackage。
已发布的 HIDL 软件包的根目录是 hardware/interfaces 或 vendor/vendorName(例如 Pixel 设备为 vendor/google)。
软件包名称在根目录下形成一个或多个子目录;定义软件包的所有文件都位于同一目录下。
例:
package [email protected]
可以在
hardware/interfaces/example/extension/light/2.0
下找到。
软件包目录中包含扩展名为 .hal 的文件。
每个文件均必须包含一个指定文件所属的软件包和版本的 package 语句。
文件 types.hal(如果存在)并不定义接口,而是定义软件包中每个接口可以访问的数据类型。
除了 types.hal 之外,其他 .hal 文件均定义一个接口。
接口通常定义如下:
interface IBar extends IFoo { // IFoo is another interface
// embedded types
struct MyStruct {/*...*/};
// interface methods
create(int32_t id) generates (MyStruct s);
close();
};
不含显式 extends 声明的接口会从 [email protected]::IBase(类似于 Java 中的 java.lang.Object)隐式扩展。
import 语句是用于访问其他软件包中的软件包接口和类型的 HIDL 机制。
import 语句本身涉及两个实体:
导入实体:可以是软件包或接口;
被导入实体:也可以是软件包或接口。
导入实体由 import 语句的位置决定。
当该语句位于软件包的 types.hal 中时,导入的内容对整个软件包是可见的;这是软件包级导入。
当该语句位于接口文件中时,导入实体是接口本身;这是接口级导入。
被导入实体由 import 关键字后面的值决定。
该值不必是完全限定名称;如果某个组成部分被删除了,系统会自动使用当前软件包中的信息填充该组成部分。
对于完全限定值,支持的导入情形有以下几种:
import [email protected]; // import a whole package
import android.hardware.example@1.0::IQuux;
// import an interface and types.hal
import [email protected]::types; // import just types.hal
接口可以是之前定义的接口的扩展。
扩展可以是以下三种类型中的一种:
1.接口可以向其他接口添加功能,并按原样纳入其 API。
2.软件包可以向其他软件包添加功能,并按原样纳入其 API。
3.接口可以从软件包或特定接口导入类型。
接口只能扩展一个其他接口(不支持多重继承)。
哈希是一种旨在防止意外更改接口并确保接口更改经过全面审查的机制。这种机制是必需的,因为 HIDL 接口带有版本编号,也就是说,接口一经发布便不得再更改,但不会影响应用二进制接口 (ABI) 的情况(例如更正备注)除外。
每个软件包根目录(即映射到 hardware/interfaces 的 android.hardware 或映射到 vendor/foo/hardware/interfaces 的 vendor.foo)都必须包含一个列出所有已发布 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 [email protected]::IFoo # comments can also go here
# types.hal files are also noted in types.hal files
c84da9f5...f8ea2648 [email protected]::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 [email protected]::IFoo
hidl-gen是安卓架构HIDL编译工具。
可以手动将哈希添加到 current.txt 文件中,也可以使用 hidl-gen 添加。以下代码段提供了可与 hidl-gen 搭配使用来管理 current.txt 文件的命令示例(哈希已缩短):
$ hidl-gen -L hash -r vendor.awesome:vendor/awesome/hardware/interfaces -r android.hardware:hardware/interfaces -r android.hidl:system/libhidl/transport [email protected]::types
9626fd18...f9d298a6 [email protected]::types
$ hidl-gen -L hash -r vendor.awesome:vendor/awesome/hardware/interfaces -r android.hardware:hardware/interfaces -r android.hidl:system/libhidl/transport [email protected]::INfc
07ac2dc9...11e3cf57 [email protected]::INfc
$ hidl-gen -L hash -r vendor.awesome:vendor/awesome/hardware/interfaces -r android.hardware:hardware/interfaces -r android.hidl:system/libhidl/transport [email protected]
9626fd18...f9d298a6 [email protected]::types
07ac2dc9...11e3cf57 [email protected]::INfc
f2fe5442...72655de6 [email protected]::INfcClientCallback
$ hidl-gen -L hash -r vendor.awesome:vendor/awesome/hardware/interfaces -r android.hardware:hardware/interfaces -r android.hidl:system/libhidl/transport [email protected] >> vendor/awesome/hardware/interfaces/current.txt
idl-gen 生成的每个接口定义库都包含哈希,通过调用 IBase::getHashChain 可检索这些哈希。
HIDL 接口服务器(实现接口的对象)可注册为已命名的服务。
注册的名称不需要与接口或软件包名称相关。如果没有指定名称,则使用名称“默认”;这应该用于不需要注册同一接口的两个实现的 HAL。
例如,在每个接口中定义的服务注册的 C++ 调用是:
status_t status = myFoo->registerAsService();
status_t anotherStatus = anotherFoo->registerAsService("another_foo_service"); // if needed
HIDL 接口的版本包含在接口本身中。
版本自动与Service注册关联,并可通过每个 HIDL 接口上的方法调用 (android::hardware::IInterface::getInterfaceVersion()) 进行检索。
服务器对象不需要注册,并可通过 HIDL 方法参数传递到其他进程,相应的接收进程会向服务器发送 HIDL 方法调用。
客户端代码按名称和版本请求指定的接口,并对所需的 HAL 类调用 getService:
sp service = V1_1::IFooService::getService();
sp alternateService = V1_1::IFooService::getService("another_foo_service");
想要在Service终止时收到通知的客户端会接收到框架传送的终止通知。
要接收通知,客户端必须:
1.将 HIDL 类/接口 hidl_death_recipient(位于 C++ 代码中,而非 HIDL 中)归入子类。
2.替换其 serviceDied() 方法。
3.实例化 hidl_death_recipient 子类的对象。
4.在要监控的服务上调用 linkToDeath() 方法,并传入 IDeathRecipient 的接口对象。请注意,此方法并不具备在其上调用它的终止接收方或代理的所有权。
伪代码示例:
class IMyDeathReceiver : hidl_death_recipient {
virtual void serviceDied(uint64_t cookie,
wp& service) override {
log("RIP service %d!", cookie); // Cookie should be 42
}
};
....
IMyDeathReceiver deathReceiver = new IMyDeathReceiver();
m_importantService->linkToDeath(deathReceiver, 42);
东西很多啊,吐血三升,今天就到这里。回去吃大餐。O(∩_∩)O