Android-HAL与HIDL分析使用总结

Android-HAL与HIDL分析使用总结

HAL接口定义语言(简称HIDL)适用于指定HAL和其用户之间的接口的一种接口描述语言(IDL),HIDL允许指定类型和方法调用。
HIDL旨在用于进程间通信(IPC)。进程之间的通信经过Binder化。对于必须与进程相关联的代码库,还可以使用直通模式。
HIDL可指定数据结构和方法签名,这些内容会整理归类到接口中,而接口会汇集到软件包中。
尽管HIDL具有一系列不同的关键字,C++和JAVA程序员对HIDL的语法并不陌生。此外,HIDL还是用JAVA样式和注释。

Android 8.0引入hidl,目的是为了将hal从system.img移除出去,方便android版本升级。
Google每次更新Android大版本,基本上都是framework的升级,与vendor改的代码理论上是可以独立开来的,
所以Google尝试通过Treble来独立更新system.img来帮助vendor更快的移植新的Android版本。

以前HAL是以so的形式存在的,作为一系列标准接口,供Android framework调用,无论是通过jni还是别的途径,
如果要被framework调用,那这些so就一定要存在于system分区,但是我们现在要把system分区独立开来,
这样vendor修改的代码全部要在vendor分区,所以引入HIDL来解决这个问题,vendor设计的HAL都以独立的service存在,
每一个HAL模块都是一个独立的binder server进程,Android framework想要调用HAL的接口就必须作为binder的client来调用.

AOSP有哪些HAL:
Camera
Audio
Sensor
等等

Google理论上只关心Android的框架层和上层软件,但是上层软件依赖于底层的硬件实现,每家手机厂商,
或者说是CPU厂商底层硬件的实现都是不一样的,所以这个HAL层基本都是手机厂商或者CPU厂商去实现的,
Google只是作为一个框架的指导,和Framework层API的接口定义,这些接口的实现都得由HAL去完成。

底层硬件都是由Linux kernel驱动控制的,提供文件读写就可以简单控制驱动,在此以虚拟驱动为例,省略kernel driver的实现;

HIDL 接口文件定义
进入代码,我们假设 DemoHal 作为标准AOSP的HAL,进入代码目录创建HIDL目录
mkdir -p hardware/interfaces/demohal/1.0/default 
进入该目录
接着创建接口描述文件 IDemoHal.hal
package android.hardware.demohal@1.0;
interface IDemoHal {
    helloWorld(string name) generates (string result);
};

这里我们定义了一个 IDemoHal 接口文件,简单的添加了一个 helloWorld 接口,入参 string name,返回string,后面再来实现这个接口。

生成HAL 相关文件
Google提供了一些工具来生成HAL层相关的代码框架和代码实例,我们只需关心接口实现部分。

使用hidl-gen工具
# PACKAGE=android.hardware.demohal@1.0
# LOC=hardware/interfaces/demohal/1.0/default/
# make hidl-gen -j64
# 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}

然后使用脚本来更新Makefile,自动生成Android.mk, Android.bp
# ./hardware/interfaces/update-makefiles.sh

现在,我们来添加两个空文件,后面再实现;
touch hardware/interfaces/demohal/1.0/default/android.hardware.demohal@1.0-service.rc
touch hardware/interfaces/demohal/1.0/default/service.cpp

现在我们的代码目录: hardware/interface/demohal:
├── 1.0
│   ├── Android.bp
│   ├── Android.mk
│   ├── default
│   │   ├── Android.bp
│   │   ├── android.hardware.demohal@1.0-service.rc
│   │   ├── DemoHal.cpp
│   │   ├── DemoHal.h
│   │   └── service.cpp
│   └── IDemoHal.hal
└── Android.bp

我们写代码就写了一个IDemoHal.hal,其余代码都是自动生成的,特别是 DemoHal.cpp 和 DemoHal.h 这两个文件是实现接口的关键文件。

实现HAL实现端的共享库
打开 DemoHal.h 和 DemoHal.cpp 文件,开始写代码
打开 DemoHal.h 文件
struct DemoHal : public IDemoHal {
    // Methods from IDemoHal follow.
    Return<void> helloWorld(const hidl_string& name, helloWorld_cb _hidl_cb) override;
    // Methods from ::android::hidl::base::V1_0::IBase follow.
};

// FIXME: most likely delete, this is only for passthrough implementations
// extern "C" IDemoHal* HIDL_FETCH_IDemoHal(const char* name);

HIDL的实现有两种方式,一种是Binderized模式,另一种是Passthrough模式,
我们看到上面有两行注释掉的代码,看来这个代码是关键,来选择实现方式是Binderized还是Passthrough。

我们这里使用Passthrough模式来演示,这两种方式本质是一样的,目前大部分厂商使用的是Passthrough来延续以前的很多代码,但是慢慢的都会被改掉的。

DemoHal.h
# ifndef ANDROID_HARDWARE_NARUTO_V1_0_NARUTO_H
# define ANDROID_HARDWARE_NARUTO_V1_0_NARUTO_H
# include <android/hardware/demohal/1.0/IDemoHal.h>
# include <hidl/MQDescriptor.h>
# include <hidl/Status.h>

namespace android {
namespace hardware {
namespace demohal {
namespace V1_0 {
namespace 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 DemoHal : public IDemoHal {
    // Methods from IDemoHal follow.
    Return<void> helloWorld(const hidl_string& name, helloWorld_cb _hidl_cb) override;
    // Methods from ::android::hidl::base::V1_0::IBase follow.
};

// FIXME: most likely delete, this is only for passthrough implementations
extern "C" IDemoHal* HIDL_FETCH_IDemoHal(const char* name);

}  // namespace implementation
}  // namespace V1_0
}  // namespace demohal
}  // namespace hardware
}  // namespace android

# endif  // ANDROID_HARDWARE_NARUTO_V1_0_NARUTO_H


DemoHal.cpp 实现 DemoHal::helloWorld() 接口;
# include "DemoHal.h"

namespace android {
namespace hardware {
namespace demohal {
namespace V1_0 {
namespace implementation {

// Methods from IDemoHal follow.
Return<void> DemoHal::helloWorld(const hidl_string& name, helloWorld_cb _hidl_cb) {
    // TODO implement
    char buf[100];
    ::memset(buf, 0x00, 100);
    ::snprintf(buf, 100, "Hello World, %s", name.c_str());
    hidl_string result(buf);

    _hidl_cb(result);
    return Void();
}

// Methods from ::android::hidl::base::V1_0::IBase follow.

IDemoHal* HIDL_FETCH_IDemoHal(const char* /* name */) {
    return new DemoHal();
}

}  // namespace implementation
}  // namespace V1_0
}  // namespace demohal
}  // namespace hardware
}  // namespace android

1. 我们打开了 HIDL_FETCH 的注释,让我们的 HIDL 使用 Passthrough 方式实现;
2. 添加helloWorld函数的实现(返回拼接后的字符串);

查看一下Android.bp文件
cc_library_shared {
    name: "android.hardware.demohal@1.0-impl",
    relative_install_path: "hw",
    proprietary: true,
    srcs: [
        "DemoHal.cpp",
    ],
    shared_libs: [
        "libhidlbase",
        "libhidltransport",
        "libutils",
        "android.hardware.demohal@1.0",
    ],
}

用mmm编译最终会在/vendor/lib64/hw/下生成一个 android.hardware.demohal@1.0-impl.so
$ mmm hardware/interfaces/demohal/1.0/default/

PLATFORM_VERSION_CODENAME=REL
PLATFORM_VERSION=8.1.0
TARGET_PRODUCT=hon660
TARGET_BUILD_VARIANT=userdebug
TARGET_BUILD_TYPE=release
TARGET_ARCH=arm64
TARGET_ARCH_VARIANT=armv8-a
TARGET_CPU_VARIANT=generic
TARGET_2ND_ARCH=arm
TARGET_2ND_ARCH_VARIANT=armv7-a-neon
TARGET_2ND_CPU_VARIANT=cortex-a53
HOST_ARCH=x86_64
HOST_2ND_ARCH=x86
HOST_OS=linux
HOST_OS_EXTRA=Linux-3.16.0-48-generic-x86_64-with-Ubuntu-14.04-trusty
HOST_CROSS_OS=windows
HOST_CROSS_ARCH=x86
HOST_CROSS_2ND_ARCH=x86_64
HOST_BUILD_TYPE=release
BUILD_ID=OPM1.171019.011

# OUT_DIR=out

[2/2] bootstrap out/soong/.minibootstrap/build.ninja.in
[1/1] out/soong/.bootstrap/bin/minibp out/soong/.bootstrap/build.ninja
[2/3] glob hardware/interfaces/*/Android.bp
[1/1] out/soong/.bootstrap/bin/soong_build out/soong/build.ninja
No need to regenerate ninja file
[100% 3/3] out/soong/.bootstrap/bin/soong_build out/soong/build.ninja
[100% 18/18] build 'out/target/product/hon660/obj/SHARED_LIBRARIES/android.hardware.demohal@1.0-impl_intermediates/android.hardware.demohal@1.0-impl.so.toc'

#### build completed successfully (02:35 (mm:ss))


调用流程
上面完成了实现端的代码和编译,接下来看一下整个HIDL的调用流程。
HIDL软件包中自动生成的文件会链接到与软件包同名的单个共享库。
该共享库还会导出单个头文件 IDemoHal.h,用于在binder客户端和服务端声明的接口文件;

我们这个实例会用到以下几个模块:
android.hardware.demohal@1.0-impl.so: DemoHal模块实现端的代码编译生成,binder server端
android.hardware.demohal@1.0.so: DemoHal模块调用端的代码,binder client端
demohal_hal_service: 通过直通式注册binder service,暴露接口给client调用
android.hardware.demohal@1.0-service.rc: Android native 进程入口


启动binder server端进程
回到之前创建的两个文件,先来看一下rc文件
hardware/interfaces/demohal/1.0/default/android.hardware.demohal@1.0-service.rc

service demohal_hal_service /vendor/bin/hw/android.hardware.demohal@1.0-service
    class hal
    user system
    group system
在设备启动的时候执行 /vendor/bin/hw/android.hardware.demohal@1.0-service 程序

hardware/interfaces/demohal/1.0/default/service.cpp
# define LOG_TAG "android.hardware.demohal@1.0-service"
# include <android/hardware/demohal/1.0/IDemoHal.h>
# include <hidl/LegacySupport.h>

using android::hardware::demohal::V1_0::IDemoHal;
using android::hardware::defaultPassthroughServiceImplementation;

int main() {
    return defaultPassthroughServiceImplementation<IDemoHal>();
}
这个service注册了IDemoHal接口文件中定义的接口作为binder server端,就一条return语句;
因为我们使用了passthrough的模式,Android帮我们封装了这个函数,不需要我们自己去addService啦。
编译后可以在, vendor/bin/hw/下找到对应的文件。

OK,我们server端的进程和实现端共享库已经完成了。

但是这个时候你如果烧录镜像,会发现这个进程会启动失败,原因是因为没有给这个进程配sepolicy,
所以正确的做法是要给他加上selinux的权限,可先略过,暂时可以用root权限去手动起这个service;
手动运行service
# vendor/bin/hw/android.hardware.demohal@1.0-service

HIDL Client测试代码 client.cpp
首先要注意包含HIDL接口实现端的头文件 android/hardware/demohal/1.0/IDemoHal.h
# include <android/hardware/demohal/1.0/IDemoHal.h>
# include <hidl/Status.h>
# include <hidl/LegacySupport.h>
# include <utils/misc.h>
# include <hidl/HidlSupport.h>
# include <stdio.h>

using android::hardware::demohal::V1_0::IDemoHal;
using android::sp;
using android::hardware::hidl_string;

int main()
{
    int ret;

    android::sp<IDemoHal> service = IDemoHal::getService();
    if(service == nullptr) {
        printf("Failed to get service\n");
        return -1;
    }

    service->helloWorld("JayZhang", [&](hidl_string result) {
                                        printf("%s\n", result.c_str());
                                    }
                       );
    return 0;
}
客户端通过 IDemoHal::getService() 获取binder server端代理服务对象,
然后就可以调用他的方法了,我们这里调用 helloWorld 接口,然后通过callback获取结果。

Android.mk
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_PROPRIETARY_MODULE := true
LOCAL_MODULE := demohal_test
LOCAL_SRC_FILES := \
    client.cpp \

LOCAL_SHARED_LIBRARIES := \
   liblog \
   libhidlbase \
   libutils \
   android.hardware.demohal@1.0 \

include $(BUILD_EXECUTABLE)

在manifest文件里添加vendor接口的定义,不然在client端是没法拿到service的,在相应的manifest.xml里面加入:
<hal format="hidl">
    <name>android.hardware.demohal</name>
    <transport>hwbinder</transport>
    <version>1.0</version>
    <interface>
        <name>IDemoHal</name>
        <instance>default</instance>
    </interface>
</hal>

运行测试代码:
# demohal_test

创建HIDL的server端和client端到此结束。
以上实现了 client 端到 server 端的接口调用过程;
如果需要在 server 端调用 client 端的接口来传递和处理数据,需要注册回调函数给 server 端;

注册回调
我们把HAL独立为一个单独的进程,client也是一个单独的进程,那么对于一般的模块而言,都是需要从底层(HAL以及以下)获取数据,
比如sensor,需要获取sensor数据,Camera,需要获取camera的raw、yuv等数据流,
对于软件设计而言,如果是同步的话,通过getXXX()函数来获取即可,
但是如果是异步的,比如底层的实现是中断的机制,不知道什么时候会来数据,那么这个时候通常会通过 callback 来实现异步的回调。

client                   server
regCallBack    ----->    saveCallBack
funcCallBack   <-----    onCallBack

实战演练
写一个简单的HAL模块,然后在.hal文件里面加入一个 setCallback 函数,传入一个 callback 指针,
当HAL的server端起来的时候会起一个线程,每隔5秒钟时间调用一下传入的这个回调函数,实现回调的机制。

看一下 HIDL 接口 IHello.hal
package vendor.sample.hello@1.0;
import IHelloCallback;

interface IHello {
    init();
    release();
    setCallback(IHelloCallback callback);
};

定义了三个接口
init:做一些初始化的动作
release:做一些释放的动作
setCallback:让client端设置一个callback方法到server端

下面来看看这个callback里面都定义了些啥,我们要为这个callback定义一个接口 IHelloCallback.hal
package vendor.sample.hello@1.0;
interface IHelloCallback {
 oneway onNotify(HalEvent event);
};

回调函数里面有一个回调方法,可以让server传一个 HalEvent 的结构体到client端,这个结构体也是自定义的,在types.hal,
可以定义自己喜欢的类型,这里是一个简单的int成员变量。
types.hal 文件
package vendor.sample.hello@1.0;
struct HalEvent {
 int32_t value;
};

OK,HIDL的接口定义好之后,使用hidl-gen工具生成代码框架
$ hidl-gen -o vendor/xxx/common/sample/hidl-impl/sample/ \
    -Lc++-impl -rvendor.sample:vendor/xxx/common/sample/interfaces \
    -randroid.hidl:system/libhidl/transport vendor.sample.hello@1.0

生成了代码:

├── Android.mk
├── hidl-impl
│ ├── Android.mk
│ └── sample │
├── Android.bp ***
│ ├── HelloCallback.cpp*** ***
│ ├── HelloCallback.h*** ***
│ ├── Hello.cpp*** ***
│ └── Hello.h***
└── interfaces
​ ├── Android.bp
​ └── hello
​ └── 1.0
​ ├── Android.bp
​ ├── IHelloCallback.hal
​ ├── IHello.hal
​ └── types.hal

其中有一个代码是用不到的,HelloCallback.h和HelloCallback.cpp,删掉他们。
注意,要把hidl-impl/sample/Android.bp里面的HelloCallback.cpp也要删掉
cc_library_shared {
 proprietary: true,
 srcs: [
 "Hello.cpp",
 ],
 shared_libs: [
 "libhidlbase",
 "libhidltransport",
 "libutils",
 "vendor.sample.hello@1.0",
 ],
}

接下来就是写代码了
在vendor分区,要起一个service来handle这个 HIDL 接口
service.cpp
#include <vendor/sample/hello/1.0/IHello.h>
​#include <hidl/LegacySupport.h>

using vendor::sample::hello::V1_0::IHello;
using android::hardware::defaultPassthroughServiceImplementation;

int main()
{
 return defaultPassthroughServiceImplementation<IHello>();
}

然后是makefile
cc_binary {
 name: "vendor.sample.hello@1.0-service",
 relative_install_path: "hw",
 defaults: ["hidl_defaults"],
 vendor: true,
 init_rc: ["vendor.sample.hello@1.0-service.rc"],
 srcs: [
 "service.cpp",
 ],
 shared_libs: [
 "liblog",
 "libutils",
 "libhidlbase",
 "libhidltransport",
 "libutils",
 "vendor.sample.hello@1.0",
 ],
}

然后编译,应该就能看到生产impl的库和一个可执行程序用来起server的。

下面的代码,是主体实现端的代码
#define LOG_TAG     "Sample"

#include "Hello.h"
#include <log/log.h>

namespace vendor {
namespace sample {
namespace hello {
namespace V1_0 {
namespace implementation {

sp<IHelloCallback> Hello::mCallback = nullptr;

// Methods from ::vendor::sample::hello::V1_0::IHello follow.
Return<void> Hello::init() {
 mExit = false;
 run("sample");
 return Void();
}

Return<void> Hello::release() {
 mExit = true;
 return Void();
}

Return<void> Hello::setCallback(const sp<::vendor::sample::hello::V1_0::IHelloCallback>& callback) {
 mCallback = callback;
 if(mCallback != nullptr) {
  ALOGD("setCallback: done");
 }

return Void();

}

bool Hello::threadLoop()
{
 static int32_t count = 0;
 HalEvent event;
 while(!mExit) {
  ::sleep(1);
  event.value = count ++;
  if(mCallback != nullptr) {
   mCallback->onNotify(event);
  }
 }
 ALOGD("threadLoop: exit");
 return false;
}

// Methods from ::android::hidl::base::V1_0::IBase follow.

IHello* HIDL_FETCH_IHello(const char* /* name */) {
 return new Hello();
}
//
}  // namespace implementation
}  // namespace V1_0
}  // namespace hello
}  // namespace sample
}  // namespace vendor
run函数就相当于是启动了一个线程,sample是线程的名字,具体可以看一下继承的父类 android::Thread
在init函数里面调用run方法启动线程,线程的主体是threadLoop函数,可以看到在线程里面,会每隔1秒钟循环去callback一次方法;

下面是client的实现
#define LOG_TAG     "TestHello"

#include <log/log.h>
#include <vendor/sample/hello/1.0/types.h>
#include <vendor/sample/hello/1.0/IHello.h>
#include <vendor/sample/hello/1.0/IHelloCallback.h>
#include <hidl/Status.h>
#include <hidl/HidlSupport.h>

using android::sp;
using android::hardware::Return;
using android::hardware::Void;

using vendor::sample::hello::V1_0::HalEvent;
using vendor::sample::hello::V1_0::IHello;
using vendor::sample::hello::V1_0::IHelloCallback;

class HelloCallback: public IHelloCallback {
public:
 HelloCallback() {

}

~HelloCallback() {

}

Return<void> onNotify(const HalEvent& event) {

 ALOGD("onNotify: value = %d", event.value);

 return Void();
}

};

int main(void)
{
 sp<IHello> service = IHello::getService();
 if(service == nullptr) {
  ALOGE("main: failed to get hello service");
  return -1;
 }

sp<HelloCallback> callback = new HelloCallback();
service->setCallback(callback);
service->init();

::sleep(10);
service->release();

return 0;

}
在client端就是简单的打印了callback回来的event里面的数据

下面是makefile的代码
cc_binary {
 name: "test_hello",
 srcs: [
 "test_hello.cpp",
 ],
 shared_libs: [
 "liblog",
 "libutils",
 "libhidlbase",
 "libhidltransport",
 "libutils",
 "vendor.sample.hello@1.0",
 ],
}

编译后手动运行测试程序
并通过logcat来获取log输出并观察;


实例总结
未完待续...


参考资料
https://www.jianshu.com/p/b3a2c7117ccc
https://www.jianshu.com/p/b80865c61d8e

  • 5
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值