在 Andoird 8.0 版本框架代码中,加入了 HIDL(HAL 接口定义语言),HIDL 的出现是为了将用户层和 HAL 层分割开,它指定了 HAL 和用户之间的接口,让用户能够替换 Android 框架,而无需重新编译 HAL,以便让厂商能够以更低的成本、更快速地将设备更新到新版 Android 版本中。
通俗的来说,HIDL 设计了一套通过的框架接口,将 HAL 层实现与 Android 操作系统框架分离开来,设备厂商只需要构建一次 HAL,并将其放置在 /vendor 分区中,便能适应大部分 Android 操作系统框架版本的升级。
图:HIDL 设计下的升级方式
在 HIDL 设计理念中,HAL 模块以一个独立的 Service 运行,用户通过 Binder IPC 与 HAL 模块进行通信。因此 在 Android 8.0 以上的版本中查看用户进程会发现很多 HAL 模块 Service 运行,如下:
consule:/ $ ps -A | grep android.hardware
system 219 1 9728 4852 0 0 S [email protected]
audioserver 235 1 21268 8916 0 0 S [email protected]
bluetooth 236 1 7716 3408 0 0 S [email protected]
cameraserver 237 1 28412 11948 0 0 S [email protected]
media 238 1 10232 4312 0 0 S [email protected]
system 239 1 9976 3648 0 0 S [email protected]
这些 Service 提供了设备 HAL 模块具体接口实现(由 HIDL 主导设计),这些接口独立于 Android 平台与设备厂商 HAL 实现。下面会主要以 HIDL 设计下 Android 9.0 composer HAL 实现来插入分析。
HIDL 是一种接口定义语言,描述了 HAL 和它的用户之间的接口,因此首先需要设计一套通用接口实现。HIDL 为每个 HAL 模块设计了不同接口定义 hal 文件,以 .hal 结尾,在 hidl-gen 工具的帮助下即可自动编译生成对应接口 C++ 实现或者 Java 实现。
下面先来理清几个概念。
Google 为每个 HAL 模块设计一个接口软件包,大部分 HIDL 接口软件包位于 hardware/interfaces 下,hardware/interfaces 顶层会直接映射到 android.hardware 软件包命名空间,软件包名称可以具有子级,表示接口软件包的版本,如 HAL Service:[email protected] 的接口软件包可以在 hardware/interfaces/graphics/composer/2.1 目录下找到。
下表列出了 Android 所有软件包前缀和位置:
软件包前缀 | 位置 |
---|---|
android.hardware.* | hardware/interfaces/* |
android.frameworks.* | frameworks/hardware/interfaces/* |
android.system.* | system/hardware/interfaces/* |
android.hidl.* | system/libhidl/transport/* |
软件包是 HIDL 设计的关键所在,每个接口软件包都包含一个 .hal 文件,.hal 文件包含一个指定文件所属的软件包和版本的 package 语句。如路径 hardware/interfaces/graphics/composer/2.1/IComposer.hal 声明。
package [email protected];
interface IComposer {
...
}
.hal 文件中定义了 HAL 模块向用户提供的访问接口及数据类型
interface IComposer {
struct MyStruct {/*...*/};
...
getCapabilities() generates (vec capabilities);
dumpDebugInfo() generates (string debugInfo);
....
}
不含显式 extends 声明的接口会从 [email protected]::IBase 隐式扩展,其他语法不做分析,可以访问 Google 官网 做深入了解。
hidl-gen 编译器会将 .hal 文件编译成一组 .h 和 .cpp 文件,这些自动生成的文件用于编译客户端/服务端实现链接到的共享库,用于编译此共享库的 Android.bp 文件由 hardware/interfaces/update-makefiles.sh 脚本自动生成。每次将新软件包添加到 hardware/interfaces 或在现有软件包中添加/移除 .hal 文件时,都必须重新运行该脚本,以确保生成的共享库是最新的。
在 HIDL 架构中,系统定义的所有的 .hal 接口,都是通过 hidl-gen 工具在编译时转换成对应的代码。比如:
hal 文件:hardware/interfaces/graphics/composer/2.1/IComposer.hal
1. 生成文件路径:
out/soong/.intermediates/hardware/interfaces/graphics/composer/2.1
2. 头文件自动生成在:
[email protected]_genc++_headers
3. C++文件自动生成在:
[email protected]_genc++
hidl-gen 源码路径:system/tools/hidl,是在 ubuntu 上可执行的二进制文件,这里不对工具源码做分析。
hidl-gen 工具以 .hal 文件为输入,自动生成的文件主要以下几个,这些文件会链接到与软件包同名的单个共享库(例如 [email protected])。
图:由编译器生成的文件
其中,
软件包中编译,如 在hardware/interfaces/graphics/composer 目录下 mm 编译将生成:
下面会简单介绍一下这些库及文件的用途,以及一个 HIDL 架构下的 HAL 模块是怎么运转起来的,在下一章的实例分析中会具体分析它的代码实现。
首先,[email protected] 是一个服务,是一个 Hal 模块可执行程序,而 [email protected] 正是它的启动配置脚本:
service vendor.hwcomposer-2-1 /vendor/bin/hw/[email protected]
class hal animation
user system
group graphics drmrpc
capabilities SYS_NICE
writepid /dev/cpuset/system-background/tasks
也就是说,Android HIDL 架构下,所有 HAL 模块实现都以服务的形式运行在独立的进程空间:
$ ps -A
system 245 1 29084 8000 0 0 S [email protected]
...
而 HAL 服务进程会链接 [email protected]。
cc_binary {
name: "[email protected]",
defaults: ["hidl_defaults"],
vendor: true,
relative_install_path: "hw",
srcs: ["service.cpp"],
init_rc: ["[email protected]"],
shared_libs: [
"[email protected]", // 链接 HIDL 生成主要库
"libbinder",
"libhidlbase",
"libhidltransport",
"liblog",
"libsync",
"libutils",
],
}
首先,[email protected] 服务的作用就是向 hwservicemanager 注册 HAL,以便客户端调用,因此需要开机启动。
[email protected] 中包含必要的 binder IPC 通信机制,包含 HAL 对象如 IComposer 对象的客户端和服务器端通信实现及接口调用。而 [email protected] 又会链接 [email protected],这个动态库具体实现了接口逻辑,如在 passthrought 模式下,链接旧版 HAL(hw_moudle_get)实现逻辑。
在最后一节会通过 composer HAL 来具体分析。
Google 的 HIDL 设计目的是让 HAL 与 用户调用在不同的进程中,HAL 被写成 binder service,而用户接口如 frameworks 作为 binder client 通过 IPC 机制实现跨进程接口调用。
但是理想很丰满,现实却很残酷,很多厂商还停留在 Android 老版本,为了给厂商改变时间,同时保持 Android 向前兼容性,Google 另外设计了 Passthrough 类型的 HAL 框架。请看,
图:HAL 的发展历程
HIDL 接口具有客户端和服务器实现:
在从 libhardware HAL 转换为 HIDL HAL 的过程中,HAL 实现成为服务器,而调用 HAL 的进程则成为客户端。
通过 HAL Service 的注册,hwservicemanager 中已经保存了 HAL 模块对象(如 IComposer),因此我们只需如下操作客户端。
首先将 HAL 库添加到 makefile 中:
Make:LOCAL_SHARED_LIBRARIES += [email protected]
Soong:shared_libs: [ …, [email protected] ]
接下来,添加 HAL 头文件:
#include
…
// in code:
sp client = IComposer::getService();
client->doThing();
要创建 HAL 实现,必须具有表示 HAL 的 .hal 文件并已在 hidl-gen 上使用 -Lmakefile 或 -Landroidbp 为 HAL 生成 makefile(./hardware/interfaces/update-makefiles.sh 完成)。
为了让 HAL 在 Passthrough 模式下工作(兼容旧版 HAL),必须具备 HIDL_FETCH_IModuleName 函数(位于 /(system|vendor|…)/lib(64)?/hw/android.hardware.graphics/[email protected] 下)。
接下来,完成服务器端代码并设置守护进程。守护进程代码(支持 Passthrough 模式)示例:
#include
int main(int /* argc */, char* /* argv */ []) {
return defaultPassthroughServiceImplementation("nfc");
}
defaultPassthroughServiceImplementation 将对提供的 -impl 库执行 dlopen() 操作,并将其作为绑定式服务提供。守护进程代码(对于纯绑定式服务)示例:
int main(int /* argc */, char* /* argv */ []) {
// This function must be called before you join to ensure the proper
// number of threads are created. The threadpool will never exceed
// size one because of this call.
::android::hardware::configureRpcThreadpool(1 /*threads*/, true /*willJoin*/);
sp nfc = new Nfc();
const status_t status = nfc->registerAsService();
if (status != ::android::OK) {
return 1; // or handle error
}
// Adds this thread to the threadpool, resulting in one total
// thread in the threadpool. We could also do other things, but
// would have to specify 'false' to willJoin in configureRpcThreadpool.
::android::hardware::joinRpcThreadpool();
return 1; // joinRpcThreadpool should never return
}
此守护进程通常存在于 $PACKAGE + “-service-suffix”(例如 [email protected])中,但也可以位于任何位置。HAL 的特定类的 sepolicy 是属性 hal_(例如 hal_composer))。您必须将此属性应用到运行特定 HAL 的守护进程(如果同一进程提供多个 HAL,则可以将多个属性应用到该进程)。