1、Android HIDL 官方介绍
HAL 接口定义语言(简称 HIDL,发音为“hide-l”)是用于指定 HAL 和其用户之间的接口的一种接口描述语言 (IDL)。HIDL 允许指定类型和方法调用(会汇集到接口和软件包中)。从更广泛的意义上来说,HIDL 是用于在可以独立编译的代码库之间进行通信的系统。
HIDL 旨在用于进程间通信 (IPC)。进程之间的通信经过 Binder 化。对于必须与进程相关联的代码库,还可以使用直通模式(在 Java 中不受支持)。
HIDL 可指定数据结构和方法签名,这些内容会整理归类到接口(与类相似)中,而接口会汇集到软件包中。尽管 HIDL 具有一系列不同的关键字,但 C++ 和 Java 程序员对 HIDL 的语法并不陌生。此外,HIDL 还使用 Java 样式的注释。
2、HIDL design
HIDL 的目标是,框架可以在无需重新构建 HAL 的情况下进行替换。HAL 将由供应商或 SOC 制造商构建,放置在设备的 /vendor 分区中,这样一来,框架就可以在其自己的分区中通过 OTA 进行替换,而无需重新编译 HAL。
HIDL 设计在以下方面之间保持了平衡:
互操作性。在可以使用各种架构、工具链和编译配置来编译的进程之间创建可互操作的可靠接口。HIDL 接口是分版本的,发布后不得再进行更改。
效率。HIDL 会尝试尽可能减少复制操作的次数。HIDL 定义的数据以 C++ 标准布局数据结构传递至 C++ 代码,无需解压,可直接使用。此外,HIDL 还提供共享内存接口;由于 RPC 本身有点慢,因此 HIDL 支持两种无需使用 RPC 调用的数据传输方法:共享内存和快速消息队列 (FMQ)。
直观。通过仅针对 RPC 使用 in 参数,HIDL 避开了内存所有权这一棘手问题(请参阅 Android 接口定义语言 (AIDL));无法从方法高效返回的值将通过回调函数返回。无论是将数据传递到 HIDL 中以进行传输,还是从 HIDL 接收数据,都不会改变数据的所有权,也就是说,数据所有权始终属于调用函数。数据仅需要在函数被调用期间保留,可在被调用的函数返回数据后立即清除。
2.1 使用直通模式
要将运行早期版本的 Android 的设备更新为使用 Android O,您可以将惯用的(和旧版)HAL 封装在一个新 HIDL 接口中,该接口将在绑定式模式和同进程(直通)模式提供 HAL。这种封装对于 HAL 和 Android 框架来说都是透明的。
直通模式仅适用于 C++ 客户端和实现。运行早期版本的 Android 的设备没有用 Java 编写的 HAL,因此 Java HAL 自然而然经过 Binder 化。
直通式标头文件
编译 .hal 文件时,除了用于 Binder 通信的标头之外,hidl-gen 还会生成一个额外的直通标头文件 BsFoo.h;此标头定义了会被执行 dlopen 操作的函数。由于直通式 HAL 在它们被调用的同一进程中运行,因此在大多数情况下,直通方法由直接函数调用(同一线程)来调用。oneway 方法在各自的线程中运行,因为它们不需要等待 HAL 来处理它们(这意味着,在直通模式下使用 oneway 方法的所有 HAL 对于线程必须是安全的)。
如果有一个 IFoo.hal,BsFoo.h 会封装 HIDL 生成的方法,以提供额外的功能(例如使 oneway 事务在其他线程中运行)。该文件类似于 BpFoo.h,不过,所需函数是直接调用的,并未使用 Binder 传递调用 IPC。未来,HAL 的实现可能提供多种实现结果,例如 FooFast HAL 和 FooAccurate HAL。在这种情况下,系统会针对每个额外的实现结果创建一个文件(例如 PTFooFast.cpp 和 PTFooAccurate.cpp)。
Binder 化直通式 HAL
您可以将支持直通模式的 HAL 实现 Binder 化。如果有一个 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,并将其自身注册为 Binder 化服务,从而使同一 HAL 实现能够同时以直通模式和 Binder 化模式使用。
如果有一个 IFoo,您可以调用 sp<IFoo> IFoo::getService(string name, bool getStub),以获取对 IFoo 实例的访问权限。如果 getStub 为 True,则 getService 会尝试仅在直通模式下打开 HAL。如果 getStub 为 False,则 getService 会尝试找到 Binder 化服务;如果未找到,则它会尝试找到直通式服务。除了在 defaultPassthroughServiceImplementation 中,其余情况一律不得使用 getStub 参数。(搭载 Android O 的设备是完全 Binder 化的设备,因此不得在直通模式下打开服务。)
3、HIDL 语法
在HIDL里,与AIDL比较类似,底层也是基于binder机制。但是也有稍微不一样的地方。为了支持HIDL,Android 对BInder做了一定程度的修改。
HIDL的基本语法:
1)定义接口 文件IsimpleTest.hal:
interface ISimpleTest {
//定义成员
enum SomeBaseEnum : uint8_t {
bar = 66
};
struct Goober {
int32_t q;
string name;
string address;
};
//定义成员函数
getCookie() generates (int32_t cookie);
customVecInt() generates (vec<int32_t> chain);
customVecStr() generates (vec<string> chain);
mystr() generates (string str);
myhandle() generates (handle str);
};
在这里,我们定义一个新的HIDL接口,取名叫做 ISimpleTest, 从语法上看有点像JAVA的语法。interface是关键字,代表要创建一个HIDL的接口。我们把上述接口保存成 IsimpleTest.hal文件存放在hardware/interfaces/tests/foo/1.0/ISimpleTest.hal,其实我们完全可以新建一个新目录,使用一个新的package名,而不使用android.hardware.tests.foo,
2)HIDL编译
在根目录执行./hardware/interfaces/update-makefiles.sh,我们能够看到会把Android 在hardware/interfaces下的所有package都会更新一遍,我们看下hardware/interfaces/tests/foo/1.0/Android.bp,在Android O当中,貌似使用了 Android.bp来替代Android.mk来作为编译管理工具。至于Android.bp的东西可以后续在研究,这里我们只关注于HIDL。
filegroup {
name: "android.hardware.tests.foo@1.0_hal",
srcs: [
"types.hal",
"IFoo.hal",
"IFooCallback.hal",
"IMyTypes.hal",
"ISimple.hal",
"ISimpleTest.hal",
"ITheirTypes.hal",
],
}
而在生成的C++文件:
genrule {
name: "android.hardware.tests.foo@1.0_genc++",
tools: ["hidl-gen"],
cmd: "$(location hidl-gen) -o $(genDir) -Lc++-sources -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport android.hardware.tests.foo@1.0",
srcs: [
":android.hardware.tests.foo@1.0_hal",
],
out: [
"android/hardware/tests/foo/1.0/types.cpp",
"android/hardware/tests/foo/1.0/FooAll.cpp",
"android/hardware/tests/foo/1.0/FooCallbackAll.cpp",
"android/hardware/tests/foo/1.0/MyTypesAll.cpp",
"android/hardware/tests/foo/1.0/SimpleAll.cpp",
"android/hardware/tests/foo/1.0/SimpleTestAll.cpp",
"android/hardware/tests/foo/1.0/TheirTypesAll.cpp",
],
}
我们可以看到这段逻辑是利用 hidl-gen工具来生成.cpp文件。命令是: cmd: “(genDir) -Lc++-sources -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport android.hardware.tests.foo@1.0”,
.hal源码是:
srcs: [
":android.hardware.tests.foo@1.0_hal",
],
而这部分就是上面所定义的各种.hal文件。最终输出就是各种.cpp文件。我们比较关注的就是 SimpleTestAll.cpp文件。
同时会生成以下一些头文件:
genrule {
name: "android.hardware.tests.foo@1.0_genc++_headers",
tools: ["hidl-gen"],
cmd: "$(location hidl-gen) -o $(genDir) -Lc++-headers -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport android.hardware.tests.foo@1 .0",
srcs: [
":android.hardware.tests.foo@1.0_hal",
],
out: [
……………………….
"android/hardware/tests/foo/1.0/ISimpleTest.h",
"android/hardware/tests/foo/1.0/IHwSimpleTest.h",
"android/hardware/tests/foo/1.0/BnHwSimpleTest.h",
"android/hardware/tests/foo/1.0/BpHwSimpleTest.h",
"android/hardware/tests/foo/1.0/BsSimpleTest.h",
………………………...
],
}
从生成的头文件里看,我们看到有 ISimpleTest.h, BnHwSimpleTest.h, BpHwSimpleTest.h,Bnxxxxx与Bpxxxxx这两个东西我们是不是看起来很眼熟?在Binder里, Ixxxxx.h定义了client与service统一的通用接口,而Bnxxxxx.h 派生自 Ixxxxx.h,做为service端实现的头文件,Bpxxxxx.h同样派生自 Ixxxxx.h做为client端的头文件。这样调用Bpxxxxx.h定义的接口,就自动利用binder机制跨进程由service端实现了Bnxxxxx.h定义函数。
由编译器生成的文件。
IFoo.h - 描述 C++ 类中的纯 IFoo 接口;它包含 IFoo.hal 文件中的 IFoo 接口中所定义的方法和类型,必要时会转换为 C++ 类型。不包含与用于实现此接口的 RPC 机制(例如 HwBinder)相关的详细信息。类的命名空间包含软件包名称和版本号,例如 ::android::hardware::samples::IFoo::V1_0。客户端和服务器都包含此标头:客户端用它来调用方法,服务器用它来实现这些方法。
IHwFoo.h - 头文件,其中包含用于对接口中使用的数据类型进行序列化的函数的声明。开发者不得直接包含其标头(它不包含任何类)。
BpFoo.h - 从 IFoo 继承的类,可描述接口的 HwBinder 代理(客户端)实现。开发者不得直接引用此类。
BnFoo.h - 保存对 IFoo 实现的引用的类,可描述接口的 HwBinder 存根(服务器端)实现。开发者不得直接引用此类。
FooAll.cpp - 包含 HwBinder 代理和 HwBinder 存根的实现的类。当客户端调用接口方法时,代理会自动从客户端封送参数,并将事务发送到绑定内核驱动程序,该内核驱动程序会将事务传送到另一端的存根(该存根随后会调用实际的服务器实现)。
这些文件的结构类似于由 aidl-cpp 生成的文件(有关详细信息,请参见 HIDL 概览中的“直通模式”)。独立于 HIDL 使用的 RPC 机制的唯一一个自动生成的文件是 IFoo.h,其他所有文件都与 HIDL 使用的 HwBinder RPC 机制相关联。因此,客户端和服务器实现不得直接引用除 IFoo 之外的任何内容。为了满足这项要求,请只包含 IFoo.h 并链接到生成的共享库。
注意:HwBinder 只是一种可能的传输机制,未来可能会添加新的传输机制。
.hal最终编译出来的结果是:
cc_library_shared {
name: "android.hardware.tests.foo@1.0",
defaults: ["hidl-module-defaults"],
generated_sources: ["android.hardware.tests.foo@1.0_genc++"],
generated_headers: ["android.hardware.tests.foo@1.0_genc++_headers"],
export_generated_headers: ["android.hardware.tests.foo@1.0_genc++_headers"],
vendor_available: true,
shared_libs: [
"libhidlbase",
"libhidltransport",
"libhwbinder",
"liblog",
"libutils",
"libcutils",
"android.hidl.base@1.0",
],
export_shared_lib_headers: [
"libhidlbase",
"libhidltransport",
"libhwbinder",
"libutils",
"android.hidl.base@1.0",
],
}
从上面看得很清楚,.hal文件被编译后会生成一个动态库文件 android.hardware.tests.foo@1.0.so
3)HIDL的使用
HIDL的使用,其实就是指怎么在service端实现,怎么在client端调用。其实也挺简单,基本流程就是service端往系统里注册,client从系统里拿到service 的proxy,然后调用。跟AIDL的Binder一样一样的。
Client端拿service的proxy:
foo = IFoo::getService(“foo”, mode == PASSTHROUGH /* getStub */);
使用的是Ixxx里自动生成的 getService函数,拿到之后就能使用.hal定义的接口了。
Service端往系统注册:
int main() {
return defaultPassthroughServiceImplementation();
}