一、定义hal 层文件接口
https://blog.youkuaiyun.com/sevenjoin/article/details/107619014
进入代码,我们假设Naruto作为标准AOSP的HAL,我们就把代码揉进标准HAL(hardware/interface)层去,进入代码目录创建HIDL目录:
mkdir -p hardware/interfaces/naruto/1.0/default
接着创建接口描述文件INaruto.hal,放在刚才创建的1.0/default目录中:
// INaruto.hal
package android.hardware.naruto@1.0;
interface INaruto {
helloWorld(string name) generates (string result);
};
二、生成hdil 文件
2. 生成HAL相关文件
如上,hal接口文件已经定义完成,接下来我们需要继承实现这个接口,那么我们应该如何继承如何编写呢,幸运的是Google还是帮我们提供了一套工具hidl-gen来生成HAL层相关的代码框架和代码实例,这样子我们只需要关心实现部分,而不需要写一堆无用代码,浪费时间在搞Makefile和一些低级错误上。
hidl-gen 可以自动帮我们生成hal层实例框架和Android.mk/bp编译控制
因此,系统已经帮我们提供了一个执行脚本hardware/interface/update-makefiles.sh, 如下执行将会自动为我们生成所需要的文件:
./hardware/interface/update-makefiles.sh
查看我们最新的代码目录: hardware/interface/naruto:
├── 1.0
│ ├── Android.bp //自动生成
│ ├── Android.mk //自动生成
│ ├── default
│ │ ├── Android.bp //需要自己编写
│ │ ├── android.hardware.naruto@1.0-service.rc //用于加入开机启动服务
│ │ ├── Naruto.cpp //自动生成,可以自动修改。
│ │ ├── Naruto.h //自动生成
│ │ └── service.cpp // hidl服务端注册入口程序
│ └── INaruto.hal //定义接口
└── Android.bp //自动生成
特别是Naruto.cpp和Naruto.h这两个文件是实现接口的关键文件。
三、实现HAL服务端的共享库
3.1 修改源文件,实现接口
打开Naruto.h文件:
// Naruto.h
#ifndef ANDROID_HARDWARE_NARUTO_V1_0_NARUTO_H
#define ANDROID_HARDWARE_NARUTO_V1_0_NARUTO_H
#include <android/hardware/naruto/1.0/INaruto.h>
#include <hidl/MQDescriptor.h>
#include <hidl/Status.h>
namespace android {
namespace hardware {
namespace naruto {
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 Naruto : public INaruto {
// Methods from INaruto 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" INaruto* HIDL_FETCH_INaruto(const char* name);
} // namespace implementation
} // namespace V1_0
} // namespace naruto
} // namespace hardware
} // namespace android
#endif // ANDROID_HARDWARE_NARUTO_V1_0_NARUTO_H
我们知道,HIDL的实现有两种方式,一种是Binderized模式,另一种是Passthrough模式,我们看到上面有两行注释掉的代码,看来这个代码是关键,来选择实现方式是Binderized还是Passthrough。
我们这里使用Passthrough模式来演示,其实大家后面尝试这两种方式后会发现其实这两种本质是一样的,目前大部分厂商使用的都是Passthrough来延续以前的很多代码,但是慢慢的都会被改掉的,所以我们来打开这个注释 - extern “C” INaruto* HIDL_FETCH_INaruto(const char* name);
打开并修改Naruto.cpp:
// Naruto.cpp
#include "Naruto.h"
namespace android {
namespace hardware {
namespace naruto {
namespace V1_0 {
namespace implementation {
// Methods from INaruto follow.
Return<void> Naruto::helloWorld(const hidl_string& name, helloWorld_cb _hidl_cb) {
// TODO implement
char buf[100];
memset(buf, 0, 100);
snprintf(buf, 100, "HelloWorld, %s", name.c_str());
hidl_string result(buf);
_hidl_cb(result);
return Void();
}
// Methods from ::android::hidl::base::V1_0::IBase follow.
INaruto* HIDL_FETCH_INaruto(const char* /* name */) {
return new Naruto();
}
} // namespace implementation
} // namespace V1_0
} // namespace naruto
} // namespace hardware
} // namespace android
这个文件我们做了两处改动:
打开了HIDL_FETCH_I的注释,让我们的HIDL使用Passthrough方式去实现
添加helloWorld函数的实现,简单的做了字符串拼接(学过C/C++)的同学应该都看得懂
3.2 编写bp文件,生成so库
接下来我们编写一下default/Android.bp文件生成我们需要impl实现的so库。
default/Android.bp
cc_library_shared {
name: "android.hardware.naruto@1.0-impl",
relative_install_path: "hw",
proprietary: true,
srcs: [
"Naruto.cpp",
],
shared_libs: [
"libhidlbase",
"libhidltransport",
"libutils",
"android.hardware.naruto@1.0",
],
}
最终会生成一个android.hardware.naruto@1.0-impl.so, 生成在/vendor/lib/hw/下,如果是64bits的平台会生成在/vendor/lib64/hw,在这里我用的是32bits平台,最终会生成在/vendor/lib/hw/目录下;
我们可以用mmm编译生成共享库
mmm hardware/interface/naruto
这样将会生成两个so文件,一个是INaruto接口框架库文件:android.hardware.naruto@1.0.so 一个是INaruto实现接口库文件:android.hardware.naruto@1.0-impl.so
四、 Hal server端启动注册程序
现在,我们有了HIDL接口及接口的实现实例,接下来需要将这个服务端注册到HWServiceManager中,客户端才能通过binder接口getService()获取到服务端的接口并使用它。
如需使服务器接口的实现可供客户端使用,您可以:
-
向
hwservicemanager
注册接口实现(详情见下文) 或者
-
将接口实现作为接口方法的参数进行传递。异步回调
还记得我们之前创建的两个文件吗,我们还没有去实现呢,先来看一下rc文件
# android.hardware.naruto@1.0-service.rc
service naruto_hal_service /vendor/bin/hw/android.hardware.naruto@1.0-service
class hal
user system
group system
很简单,就是在设备启动的时候执行/vendor/bin/hw/android.hardware.naruto@1.0-service程序,这个程序就是去注册服务的到serviceManager管理中的,具体实现如下:
service.cpp文件:
// service.cpp
# define LOG_TAG "android.hardware.naruto@1.0-service"
# include <android/hardware/naruto/1.0/INaruto.h>
# include <hidl/LegacySupport.h>
using android::hardware::naruto::V1_0::INaruto;
using android::hardware::defaultPassthroughServiceImplementation;
int main() {
return defaultPassthroughServiceImplementation<INaruto>();
}
这个service是注册了INaruto接口文件里面的接口,作为binder server端,很简单就一句话,因为我们使用了passthrough的模式,Android帮我们封装了这个函数,不需要我们自己去addService啦,直接调用defaultPassthroughServiceImplementation即可。
还有另一种注册方式是针对binderies的:
接口可作为对象传递。“接口”一词可用作 android.hidl.base@1.0::IBase
类型的语法糖;此外,当前的接口以及任何导入的接口都将被定义为一个类型。
持有接口的变量应该是强指针:sp<IName>
。接受接口参数的 HIDL 函数会将原始指针转换为强指针,从而导致不可预料的行为(可能会意外清除指针)。为避免出现问题,请务必将 HIDL 接口存储为 sp<>
。
https://source.android.google.cn/docs/core/architecture/hidl-cpp/types
::android::sp<IFoo> myFoo = new FooImpl();
::android::sp<IFoo> mySecondFoo = new FooAnotherImpl();
status_t status = myFoo->registerAsService();
status_t anotherStatus = mySecondFoo->registerAsService("another_foo");
hwservicemanager
会将 [package@version::interface, instance_name]
组合视为唯一,以使不同的接口(或同一接口的不同版本)能够采用完全相同的实例名称无冲突地注册。如果您调用的 registerAsService()
具有完全相同的软件包版本、接口和实例名称,则 hwservicemanager
将丢弃对先前注册的服务的引用,并使用新的服务。
接下来,在Android.bp中添加对应的编译控制逻辑:
cc_binary {
name: "android.hardware.naruto@1.0-service",
defaults: ["hidl_defaults"],
proprietary: true,
relative_install_path: "hw",
srcs: ["service.cpp"],
init_rc: ["android.hardware.naruto@1.0-service.rc"],
shared_libs: [
"libhidlbase",
"libhidltransport",
"libutils",
"liblog",
"android.hardware.naruto@1.0",
"android.hardware.naruto@1.0-impl",
],
}
编译后可以在, vendor/bin/hw/下找到对应的文件。
OK,我们server端的进程和实现端共享库已经完成了。
五、selinux 权限
所以正确的做法是要给他加上selinux的权限,我们这里就不去做了,因为我们可以用root权限(直接push到设备)去手动起这个service。
六、客户端实现
# include <android/hardware/naruto/1.0/INaruto.h>
# include <hidl/Status.h>
# include <hidl/LegacySupport.h>
# include <utils/misc.h>
# include <hidl/HidlSupport.h>
# include <stdio.h>
using android::hardware::naruto::V1_0::INaruto;
using android::sp;
using android::hardware::hidl_string;
int main()
{
int ret;
android::sp<INaruto> service = INaruto::getService();
if(service == nullptr) {
printf("Failed to get service\n");
return -1;
}
service->helloWorld("SvenCheng", [&](hidl_string result) {
printf("%s\n", result.c_str());
});
return 0;
}
Makefile吧
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_PROPRIETARY_MODULE := true
LOCAL_MODULE := naruto_test
LOCAL_SRC_FILES := \
naruto_client.cpp \
LOCAL_SHARED_LIBRARIES := \
liblog \
libhidlbase \
libutils \
android.hardware.naruto@1.0 \
include $(BUILD_EXECUTABLE)
以上,通过mmm vendor/mediatek/proprietary/external/naruto编译完成后会生成在/system/bin/naruto_test中。
七、添加接口
记得在manifest文件里添加vendor接口的定义,不然在client端是没法拿到service的,在相应的manifest.xml里面加入
<hal format="hidl">
<name>android.hardware.naruto</name>
<transport>hwbinder</transport>
<version>1.0</version>
<interface>
<name>INaruto</name>
<instance>default</instance>
</interface>
</hal>
一般都是在device///manifest.xml
修改manifest.xml文件后需要重新编译整个aosp并烧录镜像到设备中才可以,否则启动程序时会报: 无法找到接口getService()。
八、文件的生成和对应的位置
通过以上,我们总共产出了如下文件:
android.hardware.naruto@1.0.so: Naruto模块hal框架接口,暴露给客户端使用。
android.hardware.naruto@1.0-impl.so: Naruto模块服务端的代码实现,接口被调用后在binder server端的真正行为。
android.hardware.naruto@1.0-service: Naruto服务端启动注册入口程序
android.hardware.naruto@1.0-service.rc: Android native 进程入口
naruto_test: 客户端调用程序
我们需要将上述文件分别push到对应目录中:
adb push android.hardware.naruto@1.0.so /system/lib/
adb push android.hardware.naruto@1.0-impl.so /vendor/lib/hw/
adb push android.hardware.naruto@1.0-service /system/bin
adb push naruto_test /vendor/bin/hw #必须这个目录,否则会出现dlsysm失败问题
九、aidl for hal
如果 HAL 使用 AIDL 在框架组件(例如 system.img
中的组件)和硬件组件(例如 vendor.img
中的组件)之间进行通信,必须使用稳定的 AIDL。不过,如需在分区内进行通信(例如从一个 HAL 到另一个 HAL),则对需要使用的 IPC 机制没有任何限制。
为了最大限度提高代码可移植性并避免出现潜在问题(例如不必要的额外库),建议停用 CPP 后端。
在 AOSP 中,适用于 HAL 的稳定 AIDL 接口所在的基础目录与 HIDL 接口所在的基础目录相同,位于 aidl
文件夹中。
-
hardware/interfaces
-
frameworks/hardware/interfaces
-
system/hardware/interfaces
您应将扩展接口放入 vendor
或 hardware
下的其他 hardware/interfaces
子目录中。
Android 的每个版本都提供了一套官方 AOSP 接口。如果 Android 合作伙伴需要向这些接口添加功能,应注意不能直接更改这些接口,否则会造成自己的 Android 运行时与 AOSP Android 运行时不兼容。对于 GMS 设备,也应避免直接更改这些接口,以确保 GSI 映像能够正常运行。
扩展可以通过两种不同的方式进行注册:
-
在运行时注册(请参阅附加的扩展)。
-
独立注册(在全局注册和在 VINTF 内注册)。
无论以哪种方式注册扩展,当特定于供应商(即不属于上游 AOSP 的组成部分)的组件使用接口时,都不可能出现合并冲突
AIDL 具有三个不同的后端:Java、NDK、CPP。如需使用稳定的 AIDL,您必须始终使用位于 system/lib*/libbinder.so
的 libbinder 的系统副本,并能够访问 /dev/binder
。
对于供应商映像中的代码,这意味着 libbinder
(来自 VNDK)无法使用:此库包含不稳定的 C++ API 和不稳定的内件。
而原生供应商代码必须使用 AIDL 的 NDK 后端,链接到 libbinder_ndk
(由系统 libbinder.so
提供支持)以及由 aidl_interface
条目创建的 -ndk_platform
库。
IPC 域 | 说明 |
/dev/binder | 框架/应用进程之间的 IPC,使用 AIDL 接口 |
/dev/hwbinder | 框架/供应商进程之间的 IPC,使用 HIDL 接口 供应商进程之间的 IPC,使用 HIDL 接口 |
/dev/vndbinder | 供应商/供应商进程之间的 IPC,使用 AIDL 接口 |
只有一个自动生成的文件独立于 HIDL 使用的 RPC 机制,那就是 IFoo.h
;其他所有文件都与 HIDL 使用的 HwBinder RPC 机制相关联。因此,客户端和服务器实现不得直接引用除 IFoo
之外的任何内容。为了满足这项要求,请只包含 IFoo.h
并关联到生成的共享库。