Android HIDL概述与绑定模式的实现
Android HIDL 介绍与示例
一、前言
Android O(8.0) 版本之后,底层实现有了比较大的变化,最显著的一个方面就是 HIDL 机制的全面实施。本文对于理解系统源码中 Gnss、Usb、Camera 等模块的工作原理有极大帮助。
二、HIDL 设计目的
在 Android O(8.0) 之前系统的升级牵扯多方协作,极为麻烦,HIDL机制的推出就是将 framework 与 hal 层分开,使得框架部分可以直接被覆盖、更新,而不需要重新对 HAL 进行编译,这样在系统升级时,OEM 厂商 跳过 SoC 厂商,先对 framework 进行升级。
2.1、8.0 之前
framework 与 hal 紧紧耦合存在于 system.img 中,因此在版本升级时需要: OEM 厂商适配 framework ,SoC厂商 适配 hal, 之后将修改打包到 system.img,生成 OTA 升级包,推送到手机进行 OTA 升级
2.2、8.0 之后
framework 与 hal 进行了解耦, framework 存在于 system.img,hal 存在于vendor.img,进行版本升级时,分为两次升级:
- framework升级 : OEM 厂商适配 framework,将修改打包到 system.img, 生成OTA 升级包,推送到手机进行 OTA 升级(framework 发生改变,hal 层未变)。
- hal升级 :SoC 厂商适配 hal, 将修改打包到 vendor.img, 生成OTA 升级包,推送到手机进行OTA升级(framework发生改变,hal 层发生改变)。
三、HIDL机制演进
3.1 老版本 Framework 与 HAL 的通信框架
正如上述所言,旧版的系统架构中, Android Framework 层与 Hal 层是打包成一个 system.img 的,且 Framework 与 hal 层之间是紧密耦合的,通过链接的方式使用相应的硬件 so 库。它们之间的架构一般有如下两种方式:
3.2 HIDL 类型介绍
为了解决两者之间这种紧耦合所带来的弊端,google 引入 HIDL 来定义 Framework 与 HAL 之间的接口,可以用下图来描述:
事实上虽然 google 推出了这种机制,但是很多厂商没有很快的跟上节奏,因此为了向前兼容, google 定义了三种类型:
- ① 是 Treble Project 之前使用的实现架构,使用的是传统 HAL 和旧版 HAL
- ② 直通模式,passthrough mode。如图所示,Framework 和 HAL 层工作在同一个进程当中,下面的 HAL 是使用 HIDL 封装后的库,是直通式 HAL。这些库文件也可用于 ③ 绑定模式
- ③ 绑定模式,binderized mode。是直通式 HAL binder 化,变为绑定式 HAL。Framework 和 HAL 层工作在不同的进程,之间通过 Binder 进行 IPC
- ④ 纯绑定式。相对于 ③ 来说,绑定式 HAL 中并不包含直通式 HAL,因此称为纯绑定式
上述可总结为:
四、HIDL实现
绑定模式 是 google 为了向前兼容而定义的一种类型,且 Android 8.0 及后续版本的设备都必须只支持这种模式。这种模式下 Framework 与 Hal 分别位于不同的进程中,其实从具体实现来讲这种模式也更应该被称为 Binder 化的直通式。下面将通过这种方式实现一个具有加减乘除功能的 HIDL 服务,该服务的名称为 MyTest
4.1 创建IMyTest.hal文件
在系统源码中的 hardware/interfaces 目录下有很多的 HIDL,我们仿照其他 HIDL 来创建自己的目录,在源码根目录执行以下命令:
mkdir -p hardware/interfaces/my_test/1.0/default
之后创建 IMyTest.hal 文件:
touch hardware/interfaces/my_test/1.0/IMyTest.hal
这里定义了四种基本的运算:加、减、乘、除,这是上层调用 HAL 的入口,该文件内容如下:
package android.hardware.my_test@1.0;
interface IMyTest{
//加法
add(uint32_t a,uint32_t b) generates (uint32_t result);
//减法
sub(uint32_t a,uint32_t b) generates (uint32_t result);
//乘法
mul(uint32_t a,uint32_t b) generates (uint32_t result);
//除法
div(uint32_t a,uint32_t b) generates (uint32_t result);
};
4.2 使用hidl-gen生成HAL相关文件
Google帮我们提供了 hidl-gen 工具来生成 HAL 层相关的代码框架和代码实例,这样子我们只需要关心实现部分。在源码根目录执行以下命令:
source build/envsetup.sh
lunch xxx
PACKAGE=android.hardware.my_test@1.0
LOC=hardware/interfaces/my_test/1.0/default/
make hidl-gen
hidl-gen -o $LOC -Lc++-impl -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport $PACKAGE
hidl-gen -o $LOC -Landroidbp-impl -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport $PACKAGE
之后执行 update-makefiles.sh 脚本来为 HIDL 生成对应的 Android.bp 文件,源码根目录执行:
./hardware/interfaces/update-makefiles.sh
现在我们的工程目录结构如下:
接下来我们需要再创建两个文件:
touch hardware/interfaces/my_test/1.0/default/android.hardware.my_test@1.0-service.rc
touch hardware/interfaces/my_test/1.0/default/service.cpp
最终我们的工程目录结构如下:
4.3 调用流程分析
上述过程已经将 HIDL 服务所需要的文件创建完成,虽然其中很多文件还没有具体实现,我们先放在一边,先来对整体的调用流程及各个文件的作用略作说明:
Application | 上层应用 |
JNI | 指 framework 层,getService 获取 hal 层 service |
android.hardware.my_test@1.0.so | 接口库,由hardware/interfaces/my_test/1.0/Android.bp 通过 IMyTest.hal 生成,这样只要这个接口库不变,那么 framework 的更新和 hal 层就隔绝开了 |
android.hardware.my_test@1.0-impl.so | 实现库,上层应用的最终调用 |
mytest-hal-service | service的名称 |
android.hardware.my_test@1.0-service.rc | 设备开机时通过解析 rc 文件启动此服务 |
4.4 接口库生成
android.hardware.my_test@1.0.so 由hardware/interfaces/my_test/1.0/Android.bp 通过 IMyTest.hal 生成,该 Android.bp 文件是在上面一系列命令执行之后生成,而接口库是当我们最终执行编译模块时生成,可以说这个过程不需要我们手动参与。
4.5 实现库生成
由 hardware/interfaces/galaxy_one/1.0/default/Android.bp 在最后模块编译时通过 GalaxyOne.cpp 生成,
Android.bp 内容如下:
// FIXME: your file license if you have one
cc_library_shared {
// FIXME: this should only be -impl for a passthrough hal.
// In most cases, to convert this to a binderized implementation, you should:
// - change '-impl' to '-service' here and make it a cc_binary instead of a
// cc_library_shared.
// - add a *.rc file for this module.
// - delete HIDL_FETCH_I* functions.
// - call configureRpcThreadpool and registerAsService on the instance.
// You may also want to append '-impl/-service' with a specific identifier like
// '-vendor' or '-<hardware identifier>' etc to distinguish it.
name: "android.hardware.my_test@1.0-impl",
relative_install_path: "hw",
// FIXME: this should be 'vendor: true' for modules that will eventually be
// on AOSP.
proprietary: true,
srcs: [
"MyTest.cpp",
],
shared_libs: [ //可以添加需要的库
"liblog",
"libhidlbase",
"libhidltransport",
"libhwbinder",
"libutils",
"android.hardware.my_test@1.0",
],
}
4.6 MyTest.cpp的实现
实现库是通过 MyTest.cpp 编译生成的,现在完善 MyTest.h 和 MyTest.cpp
MyTest.h
Binder化直通式,同样需要将 HIDL_FETCH_XXX 打开
// FIXME: your file license if you have one
#pragma once
#include <android/hardware/my_test/1.0/IMyTest.h>
#include <hidl/MQDescriptor.h>
#include <hidl/Status.h>
#include <log/log.h>
namespace android::hardware::my_test::V1_0::implementation {
using ::android::hardware::hidl_array;
using ::android::hardware::hidl_memory;
using ::android::hardware::hidl_string;
using ::android::hardware::hidl_vec;
using ::android::hardware::Return;
using ::android::hardware::Void;
using ::android::sp;
struct MyTest : public V1_0::IMyTest {
// Methods from ::android::hardware::my_test::V1_0::IMyTest follow.
Return<uint32_t> add(uint32_t a, uint32_t b) override;
Return<uint32_t> sub(uint32_t a, uint32_t b) override;
Return<uint32_t> mul(uint32_t a, uint32_t b) override;
Return<uint32_t> div(uint32_t a, uint32_t b) override;
// Methods from ::android::hidl::base::V1_0::IBase follow.
};
// FIXME: most likely delete, this is only for passthrough implementations
extern "C" IMyTest* HIDL_FETCH_IMyTest(const char* name);
} // namespace android::hardware::my_test::V1_0::implementation
注意,hidl-gen生成的 MyTest.h 的代码中,命名空间不对;将android::hardware::my_test::implementation 改成 android::hardware::my_test::V1_0::implementation
MyTest.cpp
// FIXME: your file license if you have one
#include "MyTest.h"
namespace android::hardware::my_test::V1_0::implementation {
// Methods from ::android::hardware::my_test::V1_0::IMyTest follow.
Return<uint32_t> MyTest::add(uint32_t a, uint32_t b) {
// TODO implement
uint32_t result = a + b;
ALOGE("MyTest::add a = %d,b = %d,result = %d",a,b,result);
return uint32_t {};
}
Return<uint32_t> MyTest::sub(uint32_t a, uint32_t b) {
// TODO implement
uint32_t result = a - b;
ALOGE("MyTest::sub a = %d,b = %d,result = %d",a,b,result);
return uint32_t {};
}
Return<uint32_t> MyTest::mul(uint32_t a, uint32_t b) {
// TODO implement
uint32_t result = a * b;
ALOGE("MyTest::mul a = %d,b = %d,result = %d",a,b,result);
return uint32_t {};
}
Return<uint32_t> MyTest::div(uint32_t a, uint32_t b) {
// TODO implement
uint32_t result = a / b;
ALOGE("MyTest::div a = %d,b = %d,result = %d",a,b,result);
return uint32_t {};
}
// Methods from ::android::hidl::base::V1_0::IBase follow.
IMyTest* HIDL_FETCH_IMyTest(const char* /* name */) {
ALOGE("my_test service init success....");
return new MyTest();
}
//
} // namespace android::hardware::my_test::V1_0::implementation
同样需要更改命名空间
4.7 编译
现在除了需要的 rc 文件没有补充、mytest-hal-service 服务没有生成外其余均已配置好了,现在进行编译生成对应的库。进入根目录下执行如下命令:(注意是在刚刚执行过的 source build/envsetup.sh 和 lunch 的窗口下编译,若是新窗口则需要重新执行这两条命令)
mmm hardware/interfaces/my_test/1.0
编译成功后会在 out\target\product\xxxxxxx\vendor\lib64\hw 下生成 android.hardware.my_test@1.0-impl.so;在 out\target\product\xxxxxxx\system\lib64 下生成 android.hardware.my_test@1.0.so;
4.8 生成Service
接下来我们需要生成对应的 service 可执行文件,这个过程一共分为三步:
1.在 /default 下的 Android.bp 文件中追加如下内容
cc_binary {
name: "android.hardware.my_test@1.0-service",
defaults: ["hidl_defaults"],
relative_install_path: "hw",
vendor: true,
srcs: [
"service.cpp"
],
init_rc: ["android.hardware.my_test@1.0-service.rc"],
shared_libs: [
"liblog",
"libhidlbase",
"libhidltransport",
"libhwbinder",
"libutils",
"android.hardware.my_test@1.0",
],
}
2.补充 service.cpp
defaultPassthroughServiceImplementation 会帮我们自动注册服务;
#define LOG_TAG "android.hardware.my_test@1.0-service"
#include <android/hardware/my_test/1.0/IMyTest.h>
#include <hidl/LegacySupport.h>
#include "MyTest.h"
// Generated HIDL files
using android::hardware::my_test::V1_0::IMyTest;
using android::hardware::my_test::V1_0::implementation::MyTest;
using android::hardware::defaultPassthroughServiceImplementation;
using android::hardware::configureRpcThreadpool;
using android::hardware::joinRpcThreadpool;
int main() {
return defaultPassthroughServiceImplementation<IMyTest>();
}
3.补充 rc 文件
系统开机会解析该 .rc 文件,启动该服务。这里的 mytest-hal-service 相当于这个服务的别名;如果需要开机自启动,需要配置 seLinux 权限,这里暂不介绍;因此下面我们测试验证的时候通过手动启动的方式启动服务。
service mytest-hal-service /vendor/bin/hw/android.hardware.my_test@1.0-service
class hal
user system
group system
再次在根目录执行如下命令:
mmm hardware/interfaces/my_test/1.0
完成之后就会得到如下二进制可执行文件:
out\target\product\xxxxxxx\vendor\bin\hw\android.hardware.my_test@1.0-service
4.9 编写测试Client
经过一系列过程之后,我们得到了三个产物:
1、android.hardware.my_test@1.0.so
2、android.hardware.my_test@1.0-impl.so
3、android.hardware.my_test@1.0-service
接下来模拟一个客户端来测试调用;在 default 目录下创建 test 目录,并新建 client.cpp、Android.bp 文件;创建好后工程目录如下:
client.cpp内容:
#include <android/hardware/my_test/1.0/IMyTest.h>
#include <hidl/Status.h>
#include <log/log.h>
using android::sp;
using android::hardware::my_test::V1_0::IMyTest;
using android::hardware::Return;
int main(){
android::sp<IMyTest> service = IMyTest::getService();
if (service == nullptr) {
ALOGD("faile to get my_test service......");
return -1;
}
ALOGE("success to get my_test service.....");
uint32_t addResult = service->add(3,4);
ALOGE("my_test service add: result = %d",(int)addResult);
uint32_t subResult = service->sub(8,3);
ALOGE("my_test service sub: result = %d",(int)subResult);
uint32_t mulResult = service->mul(3,8);
ALOGE("my_test service mul: result = %d",(int)mulResult);
uint32_t divResult = service->div(8,2);
ALOGE("my_test service div: result = %d",(int)divResult);
return 0;
}
Android.bp内容:
cc_binary {
name: "my_hidl_test", //表示生成的 client 名称
srcs: [
"client.cpp"
],
shared_libs: [
"liblog",
"android.hardware.my_test@1.0",
"libhidlbase",
"libhidltransport",
"libhwbinder",
"libutils",
],
}
根目录执行 mmm hardware/interfaces/my_test/1.0 编译工程,成功后可在 out\target\product\xxxxxxx\system\bin 目录下找到 my_hidl_test
五、验证
5.1 将产物推入机器
现在我们一共得到 4 个产物,使用 adb 命令将其 push 到车机对应目录下:
android.hardware.my_test@1.0.so ====》 /vendor/lib64
android.hardware.my_test@1.0-impl.so ====》 /vendor/lib64/hw
android.hardware.my_test@1.0-service ====》 /vendor/bin/hw
my_hidl_test ====》 /system/bin
5.2 修改manifest.xml
HIDL 想要被 framework 获取使用还需要在 manifest.xml 中注册,该文件在车机 /vendor/etc/vintf/ 目录下(不同厂商可能不同,以实际情况为准),添加下面的内容:
<hal format="hidl">
<name>android.hardware.my_test</name>
<transport>hwbinder</transport>
<version>1.0</version>
<interface>
<name>IMyTest</name>
<instance>default</instance>
</interface>
<fqname>@1.0::IMyTest/default</fqname>
</hal>
<hal format="hidl">
5.3 运行Service
手动后台运行
adb root
adb remount
./vendor/bin/hw/android.hardware.my_test@1.0-service &
5.4 运行client
./system/bin/my_hidl_test &
运行后查看系统日志,有如下内容则成功:
01-01 00:01:58.127 13377 13377 E my_hidl_test: success to get my_test service.....
01-01 00:01:58.127 11110 11110 E android.hardware.my_test@1.0-service: MyTest::add a = 3,b = 4,result = 7
01-01 00:01:58.128 13377 13377 E my_hidl_test: my_test service add: result = 0
01-01 00:01:58.128 11110 11110 E android.hardware.my_test@1.0-service: MyTest::sub a = 8,b = 3,result = 5
01-01 00:01:58.128 13377 13377 E my_hidl_test: my_test service sub: result = 0
01-01 00:01:58.128 11110 11110 E android.hardware.my_test@1.0-service: MyTest::mul a = 3,b = 8,result = 24
01-01 00:01:58.128 13377 13377 E my_hidl_test: my_test service mul: result = 0
01-01 00:01:58.129 11110 11110 E android.hardware.my_test@1.0-service: MyTest::div a = 8,b = 2,result = 4
01-01 00:01:58.129 13377 13377 E my_hidl_test: my_test service div: result = 0
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)