Android HIDL概述(一)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android HIDL概述(一)相关的知识,希望对你有一定的参考价值。

参考技术A

Android O(8.0) 版本之后,底层实现有了比较大的变化,最显著的一个方面就是 HIDL 机制的全面实施。本文及接下来的几篇博文将从 HIDL的基本概念 HIDL服务模拟 framework层aidl服务 应用层程序 这四个方面来全面的阐述 HIDL 工作全过程,这对于理解系统源码中 Gnss Usb Camera 等模块的工作原理有极大帮助。

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

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

framework hal 进行了解耦, framework 存在于 system.img hal 存在于 vendor.img ,进行版本升级时,分为两次升级:

正如上述所言,旧版的系统架构中, android Framework 层与 Hal 层是打包成一个 system.img 的,且 Framework 与 hal 层之间是紧密耦合的,通过链接的方式使用相应的硬件 so 库。它们之间的架构一般有如下两种方式:

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

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

上述可总结为

[ 1 ] hidl
[ 2 ] hidl trebl 演进

android treble项目&&HIDL学习总结

概念介绍

Treble项目—HIDL产生背景,独立升级framework需求

Android O(8.0) 重新设计了 Android 操作系统框架,在一个名为Treble的项目中,以便让制造商能够以更低的成本更轻松、更快速地将设备更新到新版 Android 系统。在这种新架构中,HAL 接口定义语言HIDL指定了 HAL 和其用户之间的接口,让用户能够替换 android framework,而无需重新编译 HAL。

HIDL概念—HAL 接口定义语言,目标framework和HAL解耦

HIDL: HAL 接口定义语言HAL interface definition language。在 Android 8.0 中被全面使用,用于在可以独立编译的代码库之间进行通信的系统,定义Android Framework与Android HAL实现之间的接口。把framework和hal隔离,目的是使 Android 可以在不重新编译 HAL 的情况下对 Framework 进行 OTA 升级(OTA:Over The Air,一种远程无线升级技术,在线升级系统)。

HIDL与AIDL比较类似,底层也是基于binder机制。但是也有稍微不一样的地方。为了支持HIDL,Android 对BInder做了一定程度的修改,/dev/hwbinder。

HIDL的两种模式—Passthrough直通 / Binderized绑定

为了将以往设备的 Android 版本更新到 Android O,开发者需要将传统的 HAL 封装到新的 HIDL 接口中,这个接口为 HAL 提供了 Binder 模式以及 Passthrough 模式。
(1)Passthrough直通模式:对原先HAL的包装,老版本HAL的兼容,最终的binder service 跟binder client都是活在同一个进程当中,仅对 C++ 客户端与实现适用。
(2)Binderized绑定模式:Binder化的HAL,HAL都被写成了binder service,Android framework是binder client,两者再不同的上下文。
推出时即搭载了 Android 8.0 或后续版本的设备都必须只支持绑定式 HAL。如下图:

HIDL设计分析

基于HIDL接口的HAL模块整体流程如下:调用者需要包含Client端对应so,通过getService获取服务端实例,通过对client的操作对服务端Service进程发出申请。而Serive端是开机时init进程 通过rc启动脚本执行service对应可执行文件 创建进程,并在启动过程中向HwServiceManager注册服务,来响应client的访问。而实际项目过程,往往service可执行文件并不会包含过多的业务实现,而是通过链接impl.so共享库来实现,impl.so中为真正的业务实现代码,并调用Vender底层SDK,涉及硬件驱动就通过kernel中内核驱动模块提供支持。

整个设计android的HIDL机制主要就是client与service之间的访问接口设计。 HIDL的原理大致如下:android提供了一种特定编写规则的.hal文件(类似c++和java),然后通过特定的hidl-gen工具和一些构建脚本 把.hal转换为可以编译的.cpp/.h文件、android.bp构建文件,这些生成的文件已经实现了client对应so,service可执行文件(包含了注册service过程)。而接下来只需要在对应.cpp实现内部细节,然后添加对应权限配置,加入编译过程等就完成了hal模块新增。

HAL模块与package软件包(包含.hal文件)

每个HAL模块设计一个接口软件包package,可以具有子级,如 package.subpackage。已发布的 HIDL 软件包的根目录为 hardware/interfaces 或 vendor/vendorName。软件包名称在根目录下形成一个或多个子目录;定义同一个软件包的所有文件都位于同一目录下。
软件包目扩展名为 .hal 的文件。每个文件均必须包含一个指定文件所属的软件包和版本的 package 语句。types.hal 文件(如果存在)并不定义接口,而是定义软件包中每个接口可以访问的数据类型。

.hal文件 语法简介

https://source.android.google.cn/devices/architecture/hidl/code-style
HIDL 语言与 C 语言类似。

注释:/** */ 表示文档注释(只用于类型、方法、字段和枚举值声明);/* */ 表示多行注释;// 表示注释一直持续到行尾;
[empty] 表示该字词可能为空。
? 跟在文本或字词后,表示它是可选的。
... 表示包含零个或多个项、使用指定的分隔符号分隔的序列。HIDL 中不含可变参数。
逗号用于分隔序列元素。
分号用于终止各个元素,包括最后的元素。
大写字母是非终止符。
italics 是一个令牌系列,如 integer 或 identifier(标准 C 解析规则)。
constexpr 是 C 样式的常量表达式(例如 1 + 11L << 3)。
import_name 是软件包或接口名称,按 HIDL 版本控制中所述的方式加以限定。
小写 words 是文本令牌。

.hal文件 (hidl-gen工具)编译转换.h/.cpp文件

hidl-gen 编译器会将 .hal 文件编译成一组 .h 和 .cpp 文件。 这些自动生成的文件可用来构建客户端/服务器实现链接到的共享库。用于构建此共享库的 Android.bp 文件由 hardware/interfaces/update-makefiles.sh 脚本自动生成。

IFoo.h :描述 C++ 类中的纯 IFoo 接口;它包含 IFoo.hal 文件中的 IFoo 接口中所定义的方法和类型,类的命名空间包含软件包名称和版本号,客户端和服务器都包含此标头:客户端用它来调用方法,服务器用它来实现这些方法。
IHwFoo.h :其中包含用于对接口中使用的数据类型进行序列化的函数的声明。开发者不得直接包含其标头(它不包含任何类)。
BpFoo.h : 从 IFoo 继承的类,可描述接口的 HwBinder 代理(客户端)实现。开发者不得直接引用此类。
BnFoo.h: 保存对 IFoo 实现的引用的类,可描述接口的 HwBinder 服务器端实现。开发者不得直接引用此类。
FooAll.cpp :包含 HwBinder 客户端和 HwBinder 服务器端的实现的类。当客户端调用接口方法时,代理会自动从客户端封送参数,并将事务发送到绑定内核驱动程序,该内核驱动程序会将事务传送到另一端的服务器端实现。

.hal文件hash值与多版本匹配

HIDL的设计目标就是 把 framework 与 vender之间 的接口固定下来,HIDL用的方法就是给每个hal文件生成一个hash值,记录到current.txt文件中,类似如下composer模块的hash值,在编译的时候就可以校验。当需要新增接口的时候,就新增一个版本,继承上一个版本的hal接口进行扩展,同时生成新版本对应的hash值。

HIDL设计举例—hwc composer的分析

hwc composer是android里面对叠加器的加速,相关概念参考:https://blog.csdn.net/runafterhit/article/details/118884165
代码:http://androidxref.com/9.0.0_r3/xref/hardware/interfaces/graphics/composer/2.2/

composer相关源码分析

/hardware/interfaces/graphics/composer/2.2/
IComposer.hal:定义了composer模块的hal接口。

package android.hardware.graphics.composer@2.2;; // 指定文件所属的软件包和版本
import @2.1::IComposer; // 导入2.1的类
interface IComposer extends @2.1::IComposer {
	// 完全继承2.1包定义的IComposer
}
// 2.1包定义的IComposer 如下:
interface IComposer {
	getCapabilities() generates (vec<Capability> capabilities); // 获取能力级别
	dumpDebugInfo() generates (string debugInfo); // 打印debug信息
	createClient() generates (Error error, IComposerClient client); // 创建clenit实例
}

IComposerClient.hal:定义了composer的client的hal接口:

package android.hardware.graphics.composer@2.1; // 指定文件所属的软件包和版本
interface IComposerClient extends @2.2::IComposerClient {
	// 继承2.1包定义的IComposerClient ,同时专门新增了2.2相关功能接口,部分如下:
	getReadbackBufferFence(Display display) generates (Error error,handle acquireFence); // 获取buf的fence
}
// 2.1包定义的IComposerClient 如下:
interface IComposerClient {
	// 重点相关函数如下:
	registerCallback(IComposerCallback callback); // 回调注册接口
	createVirtualDisplay(/* 省略*/); // 创建虚拟display通道
 	createLayer(Display display, uint32_t bufferSlotCount) generates (Error error,Layer layer);	// 创建layer
 	destroyLayer(Display display, Layer layer) generates (Error error); // 销毁layer
 	setVsyncEnabled(Display display, Vsync enabled) generates (Error error); // 使能通道vsync
 	setInputCommandQueue(fmq_sync<uint32_t> descriptor) generates (Error error);// 设置输入命令到queue
 	executeCommands(/* 省略*/); // 直行命令queue中命令
 	// 还有各种设置属性接口
}

IComposerCallback.hal:定义client注册的回调类型,定义在2.1包中

package android.hardware.graphics.composer@2.1;
interface IComposerCallback {
	onHotplug(Display display, Connection connected); // display通路的拔插 回调
	oneway onRefresh(Display display); // display的刷新回调,强制刷新
	oneway onVsync(Display display, int64_t timestamp); // display的vsync信号回调
}

composer相关权限配置分析

Selinux相关概念可以参考: https://blog.csdn.net/runafterhit/article/details/119920733
composer要能被访问,或者要访问其他资源,需要进行selinux相关权限配置。这里完全是更具具体产品项目的权限设置,往往需要设置:1、对应服务service进程的权限管理(可以访问那些资源,包括binder);2、service可执行文件的安全上下文,谁能访问它也需要添加allow权限;3、涉及硬件设备节点访问,需要在service追加相关权限。
举例如下:
/device/google/marlin/sepolicy/hal_graphics_composer_default.te :主要设置service的文件访问、binder访问等

userdebug_or_eng(`
  allow hal_graphics_composer_default diag_device:chr_file rw_file_perms;
')
dontaudit hal_graphics_composer_default diag_device:chr_file rw_file_perms;

# misc
typeattribute hal_graphics_composer_default data_between_core_and_vendor_violators;
allow hal_graphics_composer_default display_data_file:dir create_dir_perms;
allow hal_graphics_composer_default display_data_file:file create_file_perms;

# persist
allow hal_graphics_composer_default persist_file:dir search;

# persist/display
allow hal_graphics_composer_default persist_display_file:dir r_dir_perms;
allow hal_graphics_composer_default persist_display_file:file create_file_perms;

vndbinder_use(hal_graphics_composer_default);
add_service(hal_graphics_composer_default, qdisplay_service)

# HWC_UeventThread
allow hal_graphics_composer_default self:netlink_kobject_uevent_socket create_socket_perms_no_ioctl;

# Access /sys/devices/virtual/graphics/fb0/mdp/caps and maybe others
r_dir_file(hal_graphics_composer_default, sysfs_type)

composer相关编译文件分析

在/hardware/interfaces/graphics/composer/目录下mm编译,会生成:
1、android.hardware.graphics.composer@2.2-service. rc : 启动配置脚本,直接从copy到vendor/etc/init。
2、android.hardware.graphics.composer@2.*. so:客户端client使用时需要链接的客户端so。
3、android.hardware.graphics.composer@2.2-service :service的可执行文件,生成在vendor/bin/hw/,见2.2/default/Android.mk中编译规则,service 服务的作用就是向 hwservicemanager 注册 HAL,并链接impl .so调用实现。
4、 android.hardware.graphics.composer@2.1-impl .so :service编译时链接的共享库,在service进程加载时会链接进去。

composer启动流程分析

1、开机init进程会读取vendor/etc/init下的xxx_service. rc初始脚本文件,执行vendor/bin/hw/xx_service可执行文件,从而启动service进程:

// 脚本内容:
service vendor.hwcomposer-2-2 /vendor/bin/hw/android.hardware.graphics.composer@2.2-service
    class hal animation
    user system
    group graphics drmrpc
    capabilities SYS_NICE
    onrestart restart surfaceflinger

进程状态:

1|HWRVL:/vendor/etc/init $ ps -A | grep composer
system         610     1 2248748   2824 0                   0 S android.hardware.graphics.composer@2.2-service

2、进程启动后,servie执行main函数,建立Binder IPC 通信
/hardware/interfaces/graphics/composer/2.2/default/service.cpp,HIDL 框架为了支持供应商 HAL 进程,提供了 libbinder 用户空间库用于操作 Binder 设备节点(hwbinder & vndbinder)。然后调用HwcLoader::load()进行hwc模块加载

int main() {
    // the conventional HAL might start binder services
    android::ProcessState::initWithDriver("/dev/vndbinder");
    android::ProcessState::self()->setThreadPoolMaxThreadCount(4);
    android::ProcessState::self()->startThreadPool();
    // same as SF main thread
    struct sched_param param = {0};
    param.sched_priority = 2;
    if (sched_setscheduler(0, SCHED_FIFO | SCHED_RESET_ON_FORK, &param) != 0) {
        ALOGE("Couldn't set SCHED_FIFO: %d", errno);
    }
    android::hardware::configureRpcThreadpool(4, true /* will join */);
    android::sp<IComposer> composer = HwcLoader::load();
    if (composer == nullptr) {
        return 1;
    }
    if (composer->registerAsService() != android::NO_ERROR) {
        ALOGE("failed to register service");
        return 1;
    }
    return 1;
}

3、HWC模块load,这里最终调用到createComposer,其中主要实现调用hal::Composer::create(std::move(hal)) 来调用到ComposerHal 类中的
composer/2.2/utils/passthrough/include/composer-passthrough/2.2/HwcLoader.h

class HwcLoader : public V2_1::passthrough::HwcLoader {
   public:
    static IComposer* load() {
        const hw_module_t* module = loadModule();
        if (!module) {
            return nullptr;
        }
        auto hal = createHalWithAdapter(module);
        if (!hal) {
            return nullptr;
        }
        return createComposer(std::move(hal)).release();
    }
    /* 省略 */
    static std::unique_ptr<IComposer> createComposer(std::unique_ptr<hal::ComposerHal> hal) {
        return hal::Composer::create(std::move(hal));
    }
}

新增基于HIDL的HAL模块流程

以下梳理添加一个基于hidl的hal模块test的大致流程,作为开发参考。具体例子可以看参考中的实例。
整个流程大致为:写.hal文件通过工具和配置文件生成hidl相关编译文件,实现内部细节,添加xml的接口配置和current接口hash版本,按使用需求编写client访问,设置selinux配置权限,添加包编译构建关系。

1. 建立模块包目录、编写.hal文件创建HIDL接口

在接口目录添加对应模块目录,如hardware/interfaces/test/1.0/,表示新增test模块,默认版本号1.0。创建文件ITest.hal,参考内容如下(如果有必要,还可以types.hal定义复杂结构体,或者拆解为多个.hal文件):

package android.hardware.test@1.0; // 声明包的名称test,版本1.0
interface ITest {
    init(TestID id);
    //无返回值
    helloWorld(string name) generates (string result);
};

2. 执行构建脚本,生成对应makefile和default目录与文件

使用hidl-gen生成default目录 里的C++文件

PACKAGE=android.hardware.test@1.0
LOC=hardware/interfaces/test/1.0/default
hidl-gen -o $LOC -Lc++-impl -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport $PACKAGE

根目录使用update-makefiles.sh生成1.0目录下的Android.bp

./hardware/interfaces/update-makefiles.sh

生成文件如下:

├── 1.0
│   ├── default
│   │   ├── Android.bp
│   │   ├── Test.cpp // 需要实现service的实现代码
│   │   ├── Test.h
│   ├── Android.bp
│   ├── ITest.hal

再新增android.hardware.test@1.0-service. rc启动脚本文件

service test_service /vendor/bin/hw/android.hardware.test@1.0-service
    class hal
    user system
    group system

3. 在xml文件添加接口声明、在current.txt添加版本hash值

在manifest.xml文件中添加新增hal模块的接口ITest声明。

	<hal format="hidl">
        <name>android.hardware.test</name>
        <transport>hwbinder</transport>
        <version>1.0</version>
        <interface>
            <name>ITest</name>
            <instance>default</instance>
        </interface>
    </hal>

在current.txt中添加对应软件版本的hash值。用hidl-gen -L hash命令生成。

// 参考命令,xxx为供应商vender名称,和产品使用具体版本相关:
hidl-gen -L hash -r vendor.xxx.harware:vendor/xxx/hardware -r android.hardware:hardware/interfaces -r android.hidl:system/libhidl/transport vendor.xxx.harware.test@1.0

生成后写入current.txt,类似如下composer模块的hash值:

4. 实现service服务端hal注册与功能实现代码

在工具生成的Test.cpp中实现service的内部实现。实际项目中,经常再单独实现一个impl .so,service本身是一个轻量级的可执行文件,service编译时链接的共享库impl .so包含真正的实现,在service进程加载时会链接进去。

// 部分代码如下
Return<void> Test::init(const ::android::hardware::test::V1_0::TestID& id) {
    mExit = false;
	mName = id.name;
	mID = id.id;
	ALOGD("init:");
    return Void();
}
Return<void> Test::helloWorld(const hidl_string& name, helloWorld_cb _hidl_cb) {
	ALOGD("helloWorld:");
	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();
}

5. 实现client客户端调用hal模块代码

从framework层 当需要调用的对应hal接口,Android.bp添加test编译时链接对应.so的模块。
然后通过getService获取client实例。参考类似如下:

private ITest halService ;
halService = ITest.getService();//获取service对应实例
halService.helloWorld(str); // 调用对应client方法,向service发出申请

6. 配置新增hal模块Selinux配置权限

Selinux相关概念可以参考: https://blog.csdn.net/runafterhit/article/details/119920733
一般新增一个模块,往往需要添加已下几处典型selinux配置:
1)新增一个hal_xxx安全策略文件,定义服务进行,追加服务对binder使用等权限等(若涉及property写需要自己添加);
2)在file_contexts中,新增service可执行文件的安全上下文;
3)在hwservice_context中,新增服务相关安全上下文,及其关联type类型;
4)如果涉及设备节点的访问,还需要对应hal_xxx安全策略文件添加响应访问权限;

7. 在mk文件添加编译关系

在对应的包(项目不同配置文件有差异)编译文件添加新增hal模块相关包,举例:/build/target/product/emulator.mk

PRODUCT_PACKAGES += \\
	/* 省略 */
    android.hardware.test@1.0-impl \\
    android.hardware.test@1.0-service \\

参考

官方文档:
https://source.android.google.cn/devices/architecture/hidl
https://source.android.google.cn/devices/architecture/hidl/code-style
HIDL概述:https://blog.csdn.net/u013357557/article/details/84561457
AndroidO Treble架构下Hal进程启动及HIDL服务注册过程:https://blog.csdn.net/yangwen123/article/details/79854267
Android P HAL层添加HIDL实例: https://blog.csdn.net/sinat_18179367/article/details/95940030
https://blog.csdn.net/qq_19923217/article/details/88398660
https://blog.csdn.net/qq_19923217/article/details/89173162
https://blog.csdn.net/kuang_tian_you/article/details/86599869

以上是关于Android HIDL概述(一)的主要内容,如果未能解决你的问题,请参考以下文章

Android HIDL 详解

android treble项目&&HIDL学习总结

Android 10 HAL 层添加HIDL实现过程

Android Camera Provider启动流程 (androidP) (HIDL流程)

Android HIDL 介绍学习之客户端调用

Android P HIDL demo代码编写