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 [email protected];
interface IDemoHal {
helloWorld(string name) generates (string result);
};
这里我们定义了一个 IDemoHal 接口文件,简单的添加了一个 helloWorld 接口,入参 string name,返回string,后面再来实现这个接口。
生成HAL 相关文件
Google提供了一些工具来生成HAL层相关的代码框架和代码实例,我们只需关心接口实现部分。
使用hidl-gen工具
# [email protected]
# 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/[email protected]
touch hardware/interfaces/demohal/1.0/default/service.cpp
现在我们的代码目录: hardware/interface/demohal:
├── 1.0
│ ├── Android.bp
│ ├── Android.mk
│ ├── default
│ │ ├── Android.bp
│ │ ├── [email protected]
│ │ ├── 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
// 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
# include
# include
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
// 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
// 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: "[email protected]",
relative_install_path: "hw",
proprietary: true,
srcs: [
"DemoHal.cpp",
],
shared_libs: [
"libhidlbase",
"libhidltransport",
"libutils",
"[email protected]",
],
}
用mmm编译最终会在/vendor/lib64/hw/下生成一个 [email protected]
$ 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/[email protected]_intermediates/[email protected]'
#### build completed successfully (02:35 (mm:ss))
调用流程
上面完成了实现端的代码和编译,接下来看一下整个HIDL的调用流程。
HIDL软件包中自动生成的文件会链接到与软件包同名的单个共享库。
该共享库还会导出单个头文件 IDemoHal.h,用于在binder客户端和服务端声明的接口文件;
我们这个实例会用到以下几个模块:
[email protected]: DemoHal模块实现端的代码编译生成,binder server端
[email protected]: DemoHal模块调用端的代码,binder client端
demohal_hal_service: 通过直通式注册binder service,暴露接口给client调用
[email protected]: Android native 进程入口
启动binder server端进程
回到之前创建的两个文件,先来看一下rc文件
hardware/interfaces/demohal/1.0/default/[email protected]
service demohal_hal_service /vendor/bin/hw/[email protected]
class hal
user system
group system
在设备启动的时候执行 /vendor/bin/hw/[email protected] 程序
hardware/interfaces/demohal/1.0/default/service.cpp
# define LOG_TAG "[email protected]"
# include
# include
using android::hardware::demohal::V1_0::IDemoHal;
using android::hardware::defaultPassthroughServiceImplementation;
int main() {
return defaultPassthroughServiceImplementation
}
这个service注册了IDemoHal接口文件中定义的接口作为binder server端,就一条return语句;
因为我们使用了passthrough的模式,Android帮我们封装了这个函数,不需要我们自己去addService啦。
编译后可以在, vendor/bin/hw/下找到对应的文件。
OK,我们server端的进程和实现端共享库已经完成了。
但是这个时候你如果烧录镜像,会发现这个进程会启动失败,原因是因为没有给这个进程配sepolicy,
所以正确的做法是要给他加上selinux的权限,可先略过,暂时可以用root权限去手动起这个service;
手动运行service
# vendor/bin/hw/[email protected]
HIDL Client测试代码 client.cpp
首先要注意包含HIDL接口实现端的头文件 android/hardware/demohal/1.0/IDemoHal.h
# include
# include
# include
# include
# include
# include
using android::hardware::demohal::V1_0::IDemoHal;
using android::sp;
using android::hardware::hidl_string;
int main()
{
int ret;
android::sp
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 \
[email protected] \
include $(BUILD_EXECUTABLE)
在manifest文件里添加vendor接口的定义,不然在client端是没法拿到service的,在相应的manifest.xml里面加入:
运行测试代码:
# 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 [email protected];
import IHelloCallback;
interface IHello {
init();
release();
setCallback(IHelloCallback callback);
};
定义了三个接口
init:做一些初始化的动作
release:做一些释放的动作
setCallback:让client端设置一个callback方法到server端
下面来看看这个callback里面都定义了些啥,我们要为这个callback定义一个接口 IHelloCallback.hal
package [email protected];
interface IHelloCallback {
oneway onNotify(HalEvent event);
};
回调函数里面有一个回调方法,可以让server传一个 HalEvent 的结构体到client端,这个结构体也是自定义的,在types.hal,
可以定义自己喜欢的类型,这里是一个简单的int成员变量。
types.hal 文件
package [email protected];
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 [email protected]
生成了代码:
├── 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",
"[email protected]",
],
}
接下来就是写代码了
在vendor分区,要起一个service来handle这个 HIDL 接口
service.cpp
#include
#include
using vendor::sample::hello::V1_0::IHello;
using android::hardware::defaultPassthroughServiceImplementation;
int main()
{
return defaultPassthroughServiceImplementation
}
然后是makefile
cc_binary {
name: "[email protected]",
relative_install_path: "hw",
defaults: ["hidl_defaults"],
vendor: true,
init_rc: ["[email protected]"],
srcs: [
"service.cpp",
],
shared_libs: [
"liblog",
"libutils",
"libhidlbase",
"libhidltransport",
"libutils",
"[email protected]",
],
}
然后编译,应该就能看到生产impl的库和一个可执行程序用来起server的。
下面的代码,是主体实现端的代码
#define LOG_TAG "Sample"
#include "Hello.h"
#include
namespace vendor {
namespace sample {
namespace hello {
namespace V1_0 {
namespace implementation {
sp
// Methods from ::vendor::sample::hello::V1_0::IHello follow.
Return
mExit = false;
run("sample");
return Void();
}
Return
mExit = true;
return Void();
}
Return
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
#include
#include
#include
#include
#include
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
ALOGD("onNotify: value = %d", event.value);
return Void();
}
};
int main(void)
{
sp
if(service == nullptr) {
ALOGE("main: failed to get hello service");
return -1;
}
sp
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",
"[email protected]",
],
}
编译后手动运行测试程序
并通过logcat来获取log输出并观察;
实例总结
未完待续...
参考资料
https://www.jianshu.com/p/b3a2c7117ccc
https://www.jianshu.com/p/b80865c61d8e