注意:本文基于 Android 8.1 进行分析
Qidi 2020.07.17 (Markdown & Haroopad)
我们都知道从 Android 8.0
开始, Google 启动了 Treble 项目
,自此开始推行 Binder 化的 HAL 实现。除少数类型 HAL 外,在 Android 9.0
及其之后的版本,Google 甚至要求大部分外设(peripherals) 必须支持使用 Binder 化的 HAL,否则都不再视为合规[1]。因此,本文中的 HAL 指的是用 HIDL 语言描述、使用 Binder 方式实现的 HAL。
在阅读本系列文章前,如果你已经大致了解 Android SELinux
概念(比如 *.te
文件和 file_contexts
),以及 Binder 的基本调用方法(比如知道序列化的概念、 transact()
/onTransact()
的调用时机),对于理解本文的内容会很有帮助。
根据项目不同,有的时候我们也许希望在设备上使用一种新的外设,比如某种传感器;或者有的时候,我们可能需要在 Android native 层运行一个特定进程,用来辅助处理来自 Android Frameworks 的数据,或者用于和其它 native 层的进程进行交互。对于前一种场景,我们需要为外设编写全新的 HAL;对于后一种场景,我们不需要编写 HAL,但需要实现一个很类似的 vendor service。这二者的实现方式极其类似,区别只在于他们使用的设备节点不同(一个使用 /dev/hwbinder
,另一个使用 /dev/vndbinder
),还有各自申请的 sepolicy 权限有别。
以下正文以实现一个全新的 HAL 为例进行说明,但并不涉及对设备节点的操作。
Android 大量采用面向接口编程的理念,HAL 也不例外。常用的 HAL 模块接口已经由 Google 和业界充分讨论并预定义,比如 Audio 和 Camera,这些模块需要支持的功能也已经明确,其接口描述可以在 /hardware/interfaces/
目录下找到。
同理,我们实现自己的 HAL 时,也应该先明确新 HAL 要支持的功能,再根据需求需要设计要暴露给给其它进程的接口。
为了说明方便,我们不以真实的外设 HAL 来描述,而是做一系列假设,自己给自己设计需求。虽然这样的 HAL 不对应具体的使用场景,但道理是相通的,完全可以照猫画虎、举一反三。
我们假设新 HAL 需要支持 3 个功能:
给新外设取名为 demoComponent
,给新 HAL 的进程取名为 demoService
。给支持上述 3 项功能的接口分别取名为 setStatus()
、 registerCallback()
和 unregisterCallback()
。假设其它进程所设置的“状态”是一个 DemoData
结构体,如下:
struct {
string name;
int value;
} DemoData;
假设回调函数方法为 onCallbackEvent()
, 参数同样为 DemoData
结构。
通常,经过前述步骤明确接口之后,我们就可以写出完整的头文件来了,继而对应实现各接口。但是在 HAL 实现过程中,我们首先要写的不是头文件,而是创作出用 HIDL 语言编写的 *.hal
接口描述文件。(不过,在 Android R 上将支持用 AIDL 语言来编写接口描述文件,以后可能逐步废弃 HIDL 语言[2])
按照目前的开发规范,我们的自定义接口描述文件通常放在 /vendor/
目录下。 根据我们前文的假设,这里的
就是 demoComponent
。
可有可无,在这个例子中对应的是 demoService
。VersionCode
表示 HAL 接口的版本号,因为我们编写的是一个全新的 HAL,所以版本号是 1.0
。
因为我们的实现涉及 HAL 进程、回调函数、参数数据 3 个部分,所以对应的 hal 文件也有 3 个,分别是:
IDemoServiceDef.hal:
/*
* Copyright Notice:
* Copyright (C) 2020, Qidi.Huang
* All Rights Reserved.
*
* @author [email protected]
*/
package [email protected];
import IDemoCallback;
/**
* HIDL interface of demo service.
* By using these APIs, client side can call into demo service,
* and can receive callback.
*/
interface IDemoServiceDef {
/**
* Set a status to demo service.
*
* @param data status to pass to demo service.
* @return status 0 on success, -1 on failure.
*/
setStatus(DemoData data) generates (int32_t status);
/**
* Registercallback function in demo service.
*
* @param cb callback function extends IDemoCallback interface
* to be registered
* @return status 0 on success, -1 on failure.
*/
registerCallback(IDemoCallback cb) generates (int32_t status);
/**
* Unregister callback function in demo service.
*
* @param cb callback function extends IDemoCallback interface
* to be unregistered
* @return status 0 on success, -1 on failure.
*/
unregisterCallback(IDemoCallback cb) generates (int32_t status);
};
IDemoCallback.hal:
/*
* Copyright Notice:
* Copyright (C) 2020 Qidi.Huang
* All Rights Reserved.
*
* @author [email protected]
*/
package [email protected];
/**
* HIDL interface of demo service callback function.
*/
interface IDemoCallback {
/**
* Notify client side about status change
*
* @param payload data reflects status
* @return status return 0 on success, -1 on failed
*/
onCallbackEvent(DemoData payload) generates (int32_t status);
};
types.hal:
/*
* Copyright Notice:
* Copyright (C) 2020, Qidi.Huang
* All Rights Reserved.
*
* @author [email protected]
*/
package [email protected];
struct DemoData
{
string name;
int32_t value;
};
这里有四个基本点需要注意。
其一,数据类型描述文件的文件名是个固定名称 types.hal
;接口描述文件的文件名要以大写字母 I 开头写作 IXxxx.hal
,并且相应地以 interface IXxxx {}
进行描述;generates
后加数据类型表示接口的返回值类型。
其二,每个 *.hal
文件都需要在文件头声明它所属的包。这里的包名为 package [email protected];
。
其三,HIDL 语言所使用的数据类型和 C/C++/Java
的数据类型稍有区别。举个简单例子,对比 DemoData
结构体的原始写法与 HIDL 写法,可以看到 int32
被替换成了 int32_t
。 再比如说,HIDL 数据类型也不支持 C/C++
的原始指针。关于 HIDL 数据类型的详细介绍可以参考《HIDL 数据类型》。
另外,*.hal
文件之间相互引用时,使用 import
关键字进行声明。
这些固定形式是由 HIDL 框架决定的,我们必须遵从。
编写完成后用 ls
命令查看,就是下图中 3 个绿色文件的样子(Android.*
是自动生成的,下一节就要讲到。default/
的含义和作用留待下一篇文章进行介绍):
编写好接口描述文件后,我们需要执行脚本 /vendor/
为接口描述文件自动生成 2 个 Makefile —— Android.bp
和 Android.mk
。
有了这 2 个 Makefile,在编译阶段就可以自动从接口描述文件生成 Binder 框架的源文件、头文件以和对应的库,而无需我们手动敲一遍,十分方便。
既然接口已经定义好,那么接下来要做的就是实现 HAL 服务的主体,也即下一篇文章 《Android 8.1 从零开始写 HAL – (2) 实现 HAL 主体》的内容。
[1] [2] 《HAL 类型》, https://source.android.com/devices/architecture/hal-types