Android HIDL概述与绑定模式的实现

 一、前言

Android O(8.0) 版本之后,底层实现有了比较大的变化,最显著的一个方面就是 HIDL 机制的全面实施。本文对于理解系统源码中 GnssUsbCamera 等模块的工作原理有极大帮助。

二、HIDL 设计目的

Android O(8.0) 之前系统的升级牵扯多方协作,极为麻烦,HIDL机制的推出就是将 frameworkhal 层分开,使得框架部分可以直接被覆盖、更新,而不需要重新对 HAL 进行编译,这样在系统升级时,OEM 厂商 跳过 SoC 厂商,先对 framework 进行升级。

2.1、8.0 之前

frameworkhal 紧紧耦合存在于 system.img 中,因此在版本升级时需要: OEM 厂商适配 frameworkSoC厂商 适配 hal, 之后将修改打包到 system.img,生成 OTA 升级包,推送到手机进行 OTA 升级

2.2、8.0 之后

frameworkhal 进行了解耦, framework 存在于 system.imghal 存在于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 库。它们之间的架构一般有如下两种方式:

Android HIDL概述与绑定模式的实现_第1张图片

3.2 HIDL 类型介绍

为了解决两者之间这种紧耦合所带来的弊端,google 引入 HIDL 来定义 Framework 与 HAL 之间的接口,可以用下图来描述:

Android HIDL概述与绑定模式的实现_第2张图片

事实上虽然 google 推出了这种机制,但是很多厂商没有很快的跟上节奏,因此为了向前兼容, google 定义了三种类型:

Android HIDL概述与绑定模式的实现_第3张图片

  • ① 是 Treble Project 之前使用的实现架构,使用的是传统 HAL 和旧版 HAL
  • ② 直通模式,passthrough mode。如图所示,Framework 和 HAL 层工作在同一个进程当中,下面的 HAL 是使用 HIDL 封装后的库,是直通式 HAL。这些库文件也可用于 ③ 绑定模式
  • ③ 绑定模式,binderized mode。是直通式 HAL binder 化,变为绑定式 HAL。Framework 和 HAL 层工作在不同的进程,之间通过 Binder 进行 IPC
  • ④ 纯绑定式。相对于 ③ 来说,绑定式 HAL 中并不包含直通式 HAL,因此称为纯绑定式

上述可总结为

Android HIDL概述与绑定模式的实现_第4张图片

上述介绍参考此处

四、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 [email protected];

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
[email protected]
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

现在我们的工程目录结构如下:

Android HIDL概述与绑定模式的实现_第5张图片

接下来我们需要再创建两个文件:

touch hardware/interfaces/my_test/1.0/default/[email protected]
touch hardware/interfaces/my_test/1.0/default/service.cpp

最终我们的工程目录结构如下:

Android HIDL概述与绑定模式的实现_第6张图片

4.3 调用流程分析

上述过程已经将 HIDL 服务所需要的文件创建完成,虽然其中很多文件还没有具体实现,我们先放在一边,先来对整体的调用流程及各个文件的作用略作说明:

Android HIDL概述与绑定模式的实现_第7张图片

Application 上层应用
JNI 指 framework 层,getService 获取 hal 层 service
[email protected] 接口库,由hardware/interfaces/my_test/1.0/Android.bp 通过 IMyTest.hal 生成,这样只要这个接口库不变,那么 framework 的更新和 hal 层就隔绝开了
[email protected] 实现库,上层应用的最终调用
mytest-hal-service service的名称
[email protected] 设备开机时通过解析 rc 文件启动此服务

 4.4 接口库生成

[email protected] 由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 '-' etc to distinguish it.
    name: "[email protected]",
    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",
        "[email protected]",
    ],
}

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 
#include 
#include 
#include 

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 add(uint32_t a, uint32_t b) override;
    Return sub(uint32_t a, uint32_t b) override;
    Return mul(uint32_t a, uint32_t b) override;
    Return 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 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 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 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 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 下生成 [email protected];在 out\target\product\xxxxxxx\system\lib64 下生成 [email protected]

4.8 生成Service

接下来我们需要生成对应的 service 可执行文件,这个过程一共分为三步:

1.在 /default 下的 Android.bp 文件中追加如下内容

cc_binary {
    name: "[email protected]",
    defaults: ["hidl_defaults"],
    relative_install_path: "hw",
    vendor: true,
    srcs: [
        "service.cpp"
    ],
    init_rc: ["[email protected]"],
    shared_libs: [
		"liblog",
        "libhidlbase",
        "libhidltransport",
        "libhwbinder",
        "libutils",
        "[email protected]",
    ],
}

2.补充 service.cpp 

defaultPassthroughServiceImplementation 会帮我们自动注册服务;

#define LOG_TAG "[email protected]"
 
#include 
#include 
#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();
} 

3.补充 rc 文件

系统开机会解析该 .rc 文件,启动该服务。这里的 mytest-hal-service 相当于这个服务的别名;如果需要开机自启动,需要配置 seLinux 权限,这里暂不介绍;因此下面我们测试验证的时候通过手动启动的方式启动服务。

service mytest-hal-service /vendor/bin/hw/[email protected]
    class hal
    user system
    group system

再次在根目录执行如下命令:

mmm  hardware/interfaces/my_test/1.0

完成之后就会得到如下二进制可执行文件:

out\target\product\xxxxxxx\vendor\bin\hw\[email protected]

4.9 编写测试Client 

经过一系列过程之后,我们得到了三个产物:

1、[email protected]
2、[email protected]
3、[email protected]

接下来模拟一个客户端来测试调用;在 default 目录下创建 test 目录,并新建 client.cpp、Android.bp 文件;创建好后工程目录如下:

Android HIDL概述与绑定模式的实现_第8张图片

client.cpp内容:

#include 
#include 
#include 

using android::sp;
using android::hardware::my_test::V1_0::IMyTest;
using android::hardware::Return;

int main(){
    android::sp 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",
        "[email protected]",
        "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 到车机对应目录下:

[email protected]             ====》     /vendor/lib64
[email protected]     ====》     /vendor/lib64/hw
[email protected]     ====》     /vendor/bin/hw
my_hidl_test                                                ====》     /system/bin

5.2 修改manifest.xml

HIDL 想要被 framework 获取使用还需要在 manifest.xml 中注册,该文件在车机 /vendor/etc/vintf/ 目录下(不同厂商可能不同,以实际情况为准),添加下面的内容:


		android.hardware.my_test
        hwbinder
        1.0
        
            IMyTest
            default
        
        @1.0::IMyTest/default
	

5.3 运行Service

手动后台运行

adb root
adb remount
./vendor/bin/hw/[email protected] &

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 [email protected]: 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 [email protected]: 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 [email protected]: 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 [email protected]: 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

你可能感兴趣的:(#,车载知识,android)