本文主要介绍Treble架构下的HAL&HIDL&Binder相关技术原理。Treble的详细资料文档,请参考Treble 官方文档。
1. Treble 简介
Android 8.0 版本的一项新元素是 Project Treble。这是 Android 操作系统框架在架构方面的一项重大改变,旨在让制造商以更低的成本更轻松、更快速地将设备更新到新版 Android 系统。Project Treble 适用于搭载 Android 8.0 及后续版本的所有新设备(这种新的架构已经在 Pixel 手机的开发者预览版中投入使用)。
1.1 系统更新
图 1. Treble 推出前的 Android 更新环境
Android 7.x 及更早版本中没有正式的供应商接口,因此设备制造商必须更新大量 Android 代码才能将设备更新到新版 Android 系统:
图 2. Treble 推出后的 Android 更新环境
Treble 提供了一个稳定的新供应商接口,供设备制造商访问 Android 代码中特定于硬件的部分,这样一来,设备制造商只需更新 Android 操作系统框架,即可跳过芯片制造商直接提供新的 Android 版本:
1.2 Android 经典架构
为了更好的了解Treble 架构里面的HAL,首先了解一下Android的经典架构。
在Android O之前,HAL是一个个的.so库,通过dlopen来进行打开,库和framework位于同一个进程。如图所示:
1.3 Trebe 架构
为了能够让Android O之前的版本升级到Android O,Android设计了Passthrough模式,经过转换,可以方便的使用已经存在代码,不需要重新编写相关的HAL。HIDL分为两种模式:Passthrough和Binderized。
- Binderized: Google官方翻译成绑定试HAL。
- Passthrough:Google官方翻译成直通式HAL。
大致框架图如下,对于Android O之前的设备,对应图1,对于从之前的设备升级到O的版本,对应图2、图3. 对于直接基于Android O开发的设备,对应图4。
新的架构之下,framework和hal运行于不同的进程,所有的HAL采用新的HIDL技术来完成。
2. HIDL 深入理解
HIDL是一种接口定义语言,描述了HAL和它的用户之间的接口。接下来深入分析一下HIDL相关实现。
2.1 hidl-gen工具
在Treble架构中,经常会提到HIDL
,首先介绍和HIDL
相关的一个工具hidl-gen
,系统定义的所有的.hal
接口,都是通过hidl-gen
工具转换成对应的代码。比如hardware/interfaces/power/1.0/IPower.hal
,会通过hidl-gen
转换成out/soong/.intermediates/hardware/interfaces/power/1.0/android.hardware.power@1.0_genc++/gen/android/hardware/power/1.0/PowerAll.cpp
文件,为了深入了解,介绍相关原理,首先分析hidl-gen
。
hidl-gen
源码路径:system/tools/hidl,是在ubuntu上可执行的二进制文件。
使用方法:hidl-gen -o output-path -L language (-r interface-root) fqname
例子:
hidl-gen -Lmakefile -r android.hardware:hardware/interfaces -r android.hidl:system/libhidl/transport android.hardware.power@1.0
参数含义:
- -L: 语言类型,包括c++, c++-headers, c++-sources, export-header, c++-impl, java, java-constants, vts, makefile, androidbp, androidbp-impl, hash等。
hidl-gen
可根据传入的语言类型产生不同的文件。 - fqname: 完全限定名称的输入文件。比如本例中
android.hardware.power@1.0
,要求在源码目录下必须有hardware/interfaces/power/1.0/
目录。
- 对于单个文件来说,格式如下:
package@version::fileName
,比如android.hardware.power@1.0::types.Feature。 - 对于目录来说。格式如下
package@version
,比如android.hardware.power@1.0。
- 对于单个文件来说,格式如下:
- -r: 格式package:path,可选,对fqname对应的文件来说,用来指定包名和文件所在的目录到Android系统源码根目录的路径。如果没有制定,前缀默认是:android.hardware,目录是
Android
源码的根目录。 - -o : 存放hidl-gen产生的中间文件的路径。我们查看hardware/interfaces/power/1.0/Android.bp,可以看到,-o参数都是写的
$(genDir)
,一般都是在out/soong/.intermediates/hardware/interfaces/power/1.0/
下面,根据-L
的不同,后面产生的路径可能不太一样,比如c++
,那么就会就是out/soong/.intermediates/hardware/interfaces/power/1.0/android.hardware.power@1.0_genc++/gen
,如果是c++-headers
,那么就是out/soong/.intermediates/hardware/interfaces/power/1.0/android.hardware.power@1.0_genc++_headers/gen
。
对于实例来说,fqname是:android.hardware.power@1.0
,包名是android.hardware
,文件所在的目录是hardware/interfaces
。例子中的命令会在out/soong/.intermediates/hardware/interfaces/power/1.0/
下面产生对应的c++文件。
2.2 生成子hal的Android.mk
和Android.bp
文件
正如我们所知,所有的HIDL Interface
都是通过一个.hal
文件来描述,为了方便编译生成每一个子hal。Google在系统默认提供了一个脚本update-makefiles.sh
,位于hardware/interfaces/
、frameworks/hardware/interfaces/
、system/hardware/interfaces/
、system/libhidl/
。以hardware/interfaces/
里面的代码为实例做介绍。
#!/bin/bash
source system/tools/hidl/update-makefiles-helper.sh
do_makefiles_update \
"android.hardware:hardware/interfaces" \
"android.hidl:system/libhidl/transport"
这个脚本的主要作用:根据hal文件生成Android.mk(makefile)
和Android.bp(blueprint)
文件。在hardware/interfaces
的子目录里面,存在.hal文件的目录,都会产生Android.bp
和Android.mk
文件。详细分析如下:
a. source system/tools下面的update-makefiles-helper.sh,然后执行do_makefiles_update
b. 解析传入进去的参数。参数android.hardware:hardware/interfaces:
- android.hardware: android.hardware表示包名。
- hardware/interfaces:表示相对于根目录的文件路径。
会输出如下LOG:
Updating makefiles for android.hardware in hardware/interfaces.
Updating ….
c. 获取所有的包名。通过function get_packages()
函数,获取hardware/interfaces
路径下面的所有hal文件
所在的目录路径,比如子目录power里面的hal文件的路径是power/1.0,加上当前的参数包名hardware/interfaces
,通过点的方式连接,将nfc/1.0+hardware/interfaces里面的斜线转换成点,最终获取的包名就是 android.hardware.power@1.0,依次类推获取所有的包名。
d. 执行hidl-gen命令.将c步骤里面获取的参数和包名还有类名传入hidl-gen命令,在hardware/interfaces/power/1.0目录下产生Android.mk
和Android.bp
文件。
Android.mk
: hidl-gen -Lmakefile -r android.hardware:hardware/interfaces -r android.hidl:system/libhidl/transport android.hardware.power@1.0Android.bp
: hidl-gen -Landroidbp -r android.hardware:hardware/interfaces -r android.hidl:system/libhidl/transport android.hardware.power@1.0
关于hidl-gen,后续章节会介绍。
e. 在hardware/interfaces的每个子目录下面产生Android.bp
文件,文件内容主要是subdirs的初始化
,存放当前目录需要包含的子目录。比如hardware/interfaces/power/
下面的Android.bp
文件。
@hardware/interfaces/power/Android.bp
// This is an autogenerated file, do not edit.
subdirs = [
"1.0",
"1.0/default",
"1.0/vts/functional",
]
意思就是说,编译的时候,需要编译hardware/interfaces/power
目录下面的三个子目录。
经过以上步骤,就会在对应的子目录产生Android.mk
和Android.bp
文件。这样以后我们就可以执行正常的编译命令进行编译了。比如mmm hardware/interfaces/power/
,默认情况下,在源码中,Android.mk
和Android.bp
文件已经存在。
2.3 转换.hal 文件为代码
如前面所示,每个接口都是定义在.hal文件里面,比如hardware/interfaces/power/1.0/IPower.hal
,通过hidl-gen
生成的android.bp
文件里面会定义
filegroup {
name: "android.hardware.power@1.0_hal",
srcs: [
"types.hal",
"IPower.hal",
],
}
genrule {
name: "android.hardware.power@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.power@1.0",
srcs: [
":android.hardware.power@1.0_hal",
],
out: [
"android/hardware/power/1.0/types.cpp",
"android/hardware/power/1.0/PowerAll.cpp",
],
}
可以看到在Android.bp
里面,通过hidl-gen
在out下面产生了types.cpp
和PowerAll.cpp
. 实际例子很多,不做详细介绍。
对于生成的PowerAll.cpp来说,我们可以看到,除了IPower.hal
里面定义的函数之外,还生成了很多其他的方法,这个是hidl-gen
默认产生,为了能够支持binder
通信。在IPower.hal
里面定义的setInteractive(bool interactive);
,在PowerAll.cpp
里面对应的是BpHwPower::setInteractive(bool interactive)
。通过命名就可以知道,这个和Binder机制里面的命名一致。代码如下:
::android::hardware::Return<void> BpHwPower::setInteractive(bool interactive) {
atrace_begin(ATRACE_TAG_HAL, "HIDL::IPower::setInteractive::client");
#ifdef __ANDROID_DEBUGGABLE__
if (UNLIKELY(mEnableInstrumentation)) {
std::vector<void *> _hidl_args;
_hidl_args.push_back((void *)&interactive);
for (const auto &callba