Android HIDL
基础知识,源码分析。
概念
在 Android O 8.0
后引入的 Treble
项目,目的是将 Framework
和 HAL
层分开;Google
重点关注 Framework
及以上部分,HAL
及以下交给各厂商实现, HAL
层及厂商实现都会放到新的 /vendor
分区中;这样 Google
在后续 OTA
系统升级时,可以单独升级系统部分,而不需要修改厂商实现部分。因此重新定义了 Framework
和 HAL
层的接口,即 HIDL
;以及新增了接口层的测试 VTS: Vendor Test Suite
,确保厂商实现部分接口设计符合规范并能向前兼容。简而言之,Google
这么做主要目的是:不管厂商如何实现,修改了哪些东西,需要确保 Android
原生系统都能在这台设备上升级并使用。当然这种结构也方便手机厂商做 system
升级,如果当前版本已经是 Android O
,各个分区已经分配好,可以做到仅仅更新 AOSP
部分到 P
,而其他 vendor
可以保持不变,提供了一种可能性和便利性,实际上厂商是否愿意这么做呢?
HIDL: HAL Interface Definition Language
, HIDL
发音为 hide-l
,指定了 HAL
层和 Framework
层通信的接口,目的是 Framework
和 HAL
层能相互通信;这样用户能够替换 Android
框架,而无需重新编译 HAL
。
HAL
类型
HAL
有两种类型:
Binder HAL
绑定式HAL
,通过Binder
机制实现跨进程通信。Passthrough HAL
直通式HAL
,通过dlopen
方式加载库,也就是在同一进程直接调用。这里需要注意,默认情况下通常会使用 *Binder
化直通式HAL
*,也就是说最后仍然是跨进程Binder
通信。
Binder HAL
Android O
开始,Framework
和 HAL
跨进程也是使用 Binder
互相通信,这种通信方式极大地增加了老版本中的 Binder
流量,因此新增了 Binder
域,官网推荐三个域的使用范围:
/dev/binder
用于Framework
和APP
之间的IPC
,使用AIDL
接口。/dev/hwbinder
用于Framework
和Vendor
进程之间的IPC
,使用HIDL
接口。
用于供应商进程之间的IPC
,使用HIDL
接口。/dev/vndbinder
供应商/供应商进程之间的IPC
,使用AIDL
接口。
Binder
化直通式 HAL
它实际是直通模式的 HAL
,通过 Binder
化来实现。比如 HAL
接口 a.b.c.d@M.N::IFoo
,系统会创建两个软件包:
a.b.c.d@M.N::IFoo-impl
包含HAL
的实现,并暴露函数IFoo* HIDL_FETCH_IFoo(const char* name)
。在旧版设备上,此软件包经过dlopen
处理,且实现使用HIDL_FETCH_IFoo
进行了实例化。也可以使用hidl-gen
和-Lc++-impl
以及-Landroidbp-impl
来生成基础代码。a.b.c.d@M.N::IFoo-service
打开直通式HAL
,并将其自身注册为Binder
化服务,从而使同一HAL
实现能够同时以直通模式和Binder
化模式使用。
如果有一个IFoo
,可以调用sp<IFoo> IFoo::getService(string name, bool getStub)
,以获取对IFoo
实例的访问权限。如果getStub
为True
,则getService
会尝试仅在直通模式下打开HAL
。如果getStub
为False
,则getService
会尝试找到Binder
化服务;如果未找到,则它会尝试找到直通式服务。除了在defaultPassthroughServiceImplementation
中,其余情况一律不得使用getStub
参数。(搭载Android O
的设备是完全Binder
化的设备,因此不得在直通模式下打开服务。)
网络配置工具
Android
中包含标准的 Linux
网络实用程序: ifconfig, ip, ip6tables
等,但是这些程序都在 system/bin
目录下, 在 Android O
之后,避免系统更新导致这些程序使用时不匹配,这些程序都被集成到 netutils-wrapper-1.0
工具中:
1 | ELUGA_Ray_710:/system/bin # ls *wrapper-* -l |
Android O
之后,vendor
中不能直接调用 /system/bin/netutils-wrapper-1.0
,否则会出错;也不能直接调用 /system/bin/ip <FOO> <BAR>
,而只能使用封装程序 /system/bin/ip-wrapper-1.0 <FOO> <BAR>
;如果 vendor
进程调用这些命令,需要在 SELinux policy
中添加:
1 | domain_auto_trans(VENDOR-DOMAIN-NAME, netutils_wrapper_exec, netutils_wrapper) |
转换工具
Android
源码中提供了工具将老旧版本的 hal
模块转换为 HIDL HAL
格式,工具: c2hal: system/tools/hidl/c2hal
,使用示例:
1 | c2hal -r android.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport -p android.hardware.nfc@1.0 hardware/libhardware/include/hardware/nfc.h |
转换完成后,可以进一步手动修正部分小错误。
术语
Binder
化
表示HIDL
用于进程之间的远程过程调用,并通过类似Binder
的机制来实现。- 异步回调
由HAL
用户提供、传递给HAL
(通过HIDL
方法)并由HAL
调用以随时返回数据的接口。 - 同步回调
将数据从服务器的HIDL
方法实现返回到客户端;不用于返回无效值或单个原始值的方法。 - 客户端
调用特定接口的方法的进程。HAL
进程或框架进程可以是一个接口的客户端和另一个接口的服务器。 - 扩展
也可以理解为继承;表示向另一接口添加方法和/或类型的接口,一个接口只能扩展另一个接口。可用于具有相同软件包名称的Minor
版本递增,也可用于在旧软件包的基础上构建的新软件包。 - 生成
表示将值返回给客户端的接口方法。要返回一个非原始值或多个值,则会生成同步回调函数。 - 接口
方法和类型的集合。会转换为C++
或Java
中的类。接口中的所有方法均按同一方向调用:客户端进程会调用由服务器进程实现的方法。 - 单向
应用到HIDL
方法时,表示该方法既不返回任何值也不会造成阻塞。 - 直通式
HIDL
的一种模式,使用这种模式时,服务器是共享库,由客户端进行dlopen
处理。在直通模式下,客户端和服务器是相同的进程,但代码库不同。此模式仅用于将旧版代码库并入HIDL
模型。 - 服务器
实现接口的方法的进程。 - 传输
在服务器和客户端之间移动数据的HIDL
基础架构。 - 版本
软件包的版本。由两个整数组成:Major
版本和Minor
版本。Minor
版本递增可以添加(但不会更改)类型和方法。 - 应用二进制接口 `ABI
应用编程接口 + 所需的任何二进制链接。 - 完全限定名称
fqName
用于区分hidl
类型的名称。例如:android.hardware.foo@1.0::IFoo
。 - 软件包
共用一个版本的接口和数据类型的集合。包含HIDL
接口和类型的软件包。例如:android.hardware.foo@1.0
。 - 软件包根目录
包含HIDL
接口的根目录软件包。例如:HIDL
接口android.hardware
在软件包根目录android.hardware.foo@1.0
中。 - 软件包根目录路径
软件包根目录映射到的Android
源代码树中的位置。
基础语法
规则
1 | ROOT = |
- 可以使用
/** */, /* */, //
来注释 [empty]
表示该字词可能为空?
跟在文本或字词后,表示它是可选的...
表示包含零个或多个项、用指定的分隔符号分隔的序列。HIDL
中不含可变参数- 逗号
,
用于分隔序列元素 - 分号
;
用于终止各个元素,包括最后的元素 - 大写字母是非终止符
- italics 是一个令牌系列,例如 integer 或 identifier(标准 C 解析规则)
- constexpr 是 C 样式的常量表达式(如 1 + 1 和 1L << 3)
- import_name 是软件包或接口名称,按 HIDL 版本编号中所述的方式加以限定
- 小写 words 是文本令牌
软件包
软件包名称可以具有子级,例如 package.subpackage
。软件包前缀和对应的位置如下:
软件包前缀 | 位置 |
---|---|
android.hardware.* | hardware/interfaces/* |
android.frameworks.* | frameworks/hardware/interfaces/* |
android.system.* | system/hardware/interfaces/* |
android.hidl.* | system/libhidl/transport/* |
例如 package android.hardware.example.extension.light@2.0
软件包:
可以在 hardware/interfaces/example/extension/light/2.0
下找到。软件包目录中包含扩展名为 .hal
的文件,每个文件均必须包含一个指定文件所属的软件包和版本的 package
语句;文件 types.hal
(如果存在)并不定义接口,而是定义软件包中每个接口可以访问的数据类型。
版本编号
软件包分版本,用两个整数表示: major.minor
:
Major
版本不向后兼容
递增Major
版本号将会使Minor
版本号重置为 0 。Minor
版本向后兼容
如果递增Minor
版本号,则意味着较新版本完全向后兼容之前的版本。可以添加新的数据结构和方法,但不能更改现有的数据结构或方法签名。
可同时在一台设备上提供 HAL
的多个 Major
或 Minor
版本。不过 Minor
版本应优先于 Major
版本,因为与之前的 Minor
版本接口一起使用的客户端代码也适用于同一接口的后续 Minor
版本。
支持的注解
主要用于 VTS 测试
@callflow
next=
@entry
@exit
hal
定义
1 | interface IBar extends IFoo { // IFoo is another interface |
不含显式 extends
声明的接口会从 android.hidl.base@1.0::IBase
(类似于 Java
中的 java.lang.Object
)隐式扩展。隐式导入的 IBase
接口声明了多种不应也不能在用户定义的接口中重新声明或以其他方式使用的预留方法,也就是说 hal
中不存在重载和重写!
接口只能扩展一个其他接口(不支持多重继承),具有非零 Minor
版本号的软件包中的每个接口必须扩展一个以前版本的软件包中的接口:
- 存在 1.2 版本的软件包
original
中的接口IFoo
- 小版本
Minor
升级到 1.3 版本,软件包original
中的接口IFoo
必须继承 1.2 版本的IFoo
- 假设大版本
Major
升级到 4.0 版本,而软件包derivative
中的接口IBar
是继承于 1.2 版本的软件包original
中的接口IFoo
- 小版本
Minor
升级到 4.1 版本,IBar
必须扩展 4.0 版本的IBar
;而不能继承 1.3 版本的IFoo
,因为它是与 1.2 版本的IFoo
绑定的 - 大版本
Major
再次升级到 5.0 版本,此时IBar
可以直接 1.3 版本的 `IFoo
接口扩展并不意味着生成的代码中存在代码库依赖关系或跨 HAL
包含关系,接口扩展只是在 HIDL
级别导入数据结构和方法定义。HAL
中的每个方法必须在相应 HAL
中重新实现。
import
导入
import
语句是用于访问其他软件包中的软件包接口和类型的 HIDL
机制,存在两种导入情况:
import
语句位于types.hal
文件中时,导入的内容对整个软件包可见,属于软件包级导入improt
语句位于custom.hal
接口文件中时,导入的内容只对当前接口文件可见,属于接口级导入
import
语句后,根据导入值分三种情况:
- 完整软件包导入
导入值是一个软件包名称和版本,则系统会将整个软件包导入,即可访问整个软件包的接口或类型。 - 接口导入
导入值是一个接口文件,则导入该接口以及types.hal
两个文件。 - 类型导入
导入值是types.hal
中定义的某个类型,则仅将该类型导入,而types.hal
中的其他类型不导入。
1 | import android.hardware.nfc@1.0; // 导入整个软件包 |
接口哈希 Hash
和版本控制文件 current.txt
HIDL
接口哈希,是一种旨在防止意外更改接口并确保接口更改经过全面审查的机制。因为 HIDL
接口带有版本编号,也就是说接口一经发布便不得再更改。
软件包根目录必须有版本控制文件 current.txt
;如果创建一个软件包路径,如 -r vendor.awesome:vendor/awesome/interfaces
),则还应创建文件 $ANDROID_BUILD_TOP/vendor/awesome/interfaces/current.txt
。current.txt
必须包含接口哈希及接口全限定名称,比如 system/libhidl/transport/current.txt
文件内容为:
1 | # Do not change this file except to add new interfaces. Changing |
current.txt
中的内容按照Android
大版本来分隔,比如上半部分为Android O
,下半部分为Android O-MR1
,厂商自定义的接口文件也应该遵循这个格式。
可以手动将哈希添加到 current.txt
文件中,也可以使用 hidl-gen
加上参数 -Lhash
选项添加。
如下为针对类型文件、接口、整个软件包生成哈希:
1 | $ hidl-gen -L hash -r vendor.awesome:vendor/awesome/hardware/interfaces -r android.hardware:hardware/interfaces -r android.hidl:system/libhidl/transport vendor.awesome.nfc@1.0::types |
如果是新添加的 hal
软件包,则将生成哈希及对应接口文件,一并写入 current.txt
文件中:
1 | $ hidl-gen -L hash -r vendor.awesome:vendor/awesome/hardware/interfaces -r android.hardware:hardware/interfaces -r android.hidl:system/libhidl/transport vendor.awesome.nfc@1.0 >> vendor/awesome/hardware/interfaces/current.txt |
接口文件的哈希,可以通过调用 IBase::getHashChain
来查看。 hidl-gen
编译接口时,会检查 HAL
软件包根目录中的 current.txt
文件,以查看 HAL
是否已被更改:
- 如果没有找到对应的哈希值,则继续编译,并认为接口没有发布
- 如果找到了对应哈希值,则做相应检查:
- 如果接口与哈希值匹配,则继续编译
- 如果接口与哈希值不匹配,则意味着接口被更改了,停止编译
所以,如果接口文件处于调试阶段,需要等调试完成后再发布到 current.txt
中;而接口文件一旦发布,将无法更改;如果想更改接口,必须升级版本号,也就是重新定义接口。比如 camera
相关接口的修改,必须升级版本号:
1 | // hardware/interfaces/current.txt |
数据传递
数据在传递过程中:
.hal
文件接口中定义的方法默认为阻塞模式,如果需要采用非阻塞式则在方法前面使用关键字oneway
- 方法调用和回调只能接受
in
参数,并且不支持out, inout
参数 - 方法在数据传递时超过 4KB 以上的数据便被认为是过度调用;同时跨进程调用是基于
Binder
机制,所以总的数据传输不能超过 1MB
对于跨进程通信, HIDL
只使用参数回调函数,避免了内存所有权的棘手问题,特别是不能有效通过方法返回的值可以直接通过回调函数返回。这样既不将数据传递到 HIDL
,也不从 HIDL
接收数据,改变数据的所有权。数据只需要在被调用函数的持续时间内存在,并且可以在被调用函数返回后立即销毁。
HIDL
如果不使用 Binder RPC
,可以通过两种方法来转移数据:
- 共享内存
分配的内存通过映射来共享。 - 快速消息队列
FMQ
HIDL
提供了一种可实现无等待消息传递的模板化消息队列类型。
快速消息队列 FMQ
HIDL
的远程过程调用 RPC
基础架构使用 Binder
机制,这意味着调用涉及开销、需要内核操作,并且可以触发调度程序操作。不过对于必须在开销较小且无内核参与的进程之间传输数据的情况, HIDL
提供了快速消息队列 FMQ
系统。FMQ
会创建具有所需属性的消息队列, MQDescriptorSync, MQDescriptorUnsync
对象可通过 HIDL RPC
调用发送,并可供接收进程用于访问消息队列。
它在直通式或绑定式模式下不使用内核或调度程序,从而创建可以借助内置 HIDL
类型 MQDescriptorSync
或 MQDescriptorUnsync
的参数通过 RPC
传递的对象。
MessageQueue
支持的队列类型 flavor
有两种:
- 未同步队列
flavor: kSynchronizedReadWrite
可以溢出,并且可以有多个读取器;每个读取器都必须及时读取数据,否则数据将会丢失。未同步队列只有一个写入器,但可以有任意多个读取器。此类队列有一个写入位置;不过,每个读取器都会跟踪各自的独立读取位置。执行写入操作一定会成功(不会检查是否出现溢出情况),但前提是写入的内容不超出配置的队列容量(如果写入的内容超出队列容量,则操作会立即失败)。由于各个读取器的读取位置可能不同,因此每当新的写入操作需要空间时,系统都允许数据离开队列,而无需等待每个读取器读取每条数据。 - 已同步队列
flavor: kUnsynchronizedWrite
不能溢出,并且只能有一个读取器。已同步队列有一个写入器和一个读取器,其中写入器有一个写入位置,读取器有一个读取位置。写入的数据量不可能超出队列可提供的空间;读取的数据量不可能超出队列当前存在的数据量。
这两种队列都不能下溢(从空队列进行读取将会失败),并且只能有一个写入器。
FMQ
相关源码路径如下,其中 MessageQueue
主要是模板,所以定义和代码实现都在该 .h
文件中:
1 | system/libfmq/include/fmq/MessageQueue.h |
队列类型是在 MQDescriptor.h
中定义的:
1 | // MQDescriptor.h |
创建快速队列示例:
1 |
|
MessageQueue<T, flavor>(numElements)
初始化程序负责创建并初始化支持消息队列功能的对象MessageQueue<T, flavor>(numElements, configureEventFlagWord)
初始化程序负责创建并初始化支持消息队列功能和阻塞的对象flavor
可以是kSynchronizedReadWrite
同步队列或kUnsynchronizedWrite
未同步队列uint16_t
可以是任意不涉及嵌套式缓冲区(无string
或vec
类型)、句柄或接口的HIDL
定义的类型kNumElementsInQueue
表示队列的大小(以条目数表示);它用于确定将为队列分配的共享内存缓冲区的大小
内存共享
HIDL
共享内存 MemoryBlock
是构建在 hidl_memory
, HIDL @1.0::IAllocator
和 HIDL @1.0::IMapper
之上的抽象层,专为有多个内存块共用单个内存堆的 HIDL
服务而设计。
也就是说, HIDL
共享内存块只是提供了一种使用思路,而不是像 FMQ
提供了具体实现;使用 MemoryBlock
可显著减少 mmap/munmap
数量和用户空间细分错误,从而提升性能。架构图如下,核心思想是多个内存块共用单个内存堆 hidl_memory
:
基本数据类型
HIDL
基本数据类型及对应处理,都是在 system/libhidl/base
目录中实现的,基本数据类型的定义文件为 HidlSupport.h
,目录结构为:
1 | ├── Android.bp |
hidl_death_recipient
服务死亡通知,如果客户端注册该通知,当服务端断开时,会发出通知。
1 | struct hidl_death_recipient : public virtual RefBase { |
hidl_handle
句柄基本类型,是对 native_handle_t
句柄的封装:
1 | struct hidl_handle { |
调用传递 hidl_handle
对象(复合类型的顶级或一部分)的 HIDL
接口方法时,其中包含的文件描述符的所有权如下所述:
- 将
hidl_handle
对象作为参数传递的调用程序会保留对其封装的native_handle_t
中包含的文件描述符的所有权;该调用程序在完成对这些文件描述符的操作后,必须将这些文件描述符关闭 - 通过将
hidl_handle
对象传递到_cb
函数来返回该对象的进程会保留对该对象封装的native_handle_t
中包含的文件描述符的所有权;该进程在完成对这些文件描述符的操作后,必须将这些文件描述符关闭 - 接收
hidl_handle
的传输拥有对相应对象封装的native_handle_t
中的文件描述符的所有权;接收器可在事务回调期间按原样使用这些文件描述符,但如果想在回调完成后继续使用这些文件描述符,则必须克隆原生句柄。事务完成时,transport
将自动对文件描述符执行close
操作
HIDL
不支持在 Java
中使用句柄。
hidl_string
HIDL
字符串;通过 HIDL
接口将字符串传递到 Java
或从 Java
传递字符串将会导致字符集转换,而此项转换可能无法精确保留原始编码。
1 | struct hidl_string { |
hidl_memory
memory
类型用于表示 HIDL
中未映射的共享内存。
1 | struct hidl_memory { |
hidl_vec
vec<T>
模板用于表示包含 T
实例且大小可变的缓冲区。 T
可以是任何由 HIDL
提供的或由用户定义的类型,句柄除外。( vec<T>
的 vec<>
将指向 vec<T>
结构体数组,而不是指向内部 T
缓冲区数组。) T
可以是以下项之一:
- 基本类型(例如
uint32_t
) - 字符串
- 用户定义的枚举
- 用户定义的结构体
- 接口,或
interface
关键字(vec<IFoo>,vec<interface>
仅在作为顶级参数时受支持) - 句柄
bitfield<U>
vec<U>
,其中U
可以是此列表中的任何一项,接口除外(例如,vec<vec<IFoo>>
不受支持)U[]
(有大小的U
数组),其中U
可以是此列表中的任何一项,接口除外
hidl_array
表示多维数组。
1 | template<typename T, size_t SIZE1, size_t... SIZES> |
hidl_version
HIDL
版本号。
1 | struct hidl_version { |
.hal
文件自动生成代码
基本步骤
比如 .hal
文件准备添加到 hardware/interface
目录下:
- 新建包名对应的文件夹,比如
myintere
- 在包文件夹下新建主次版本对应的文件夹,比如
1.0
- 在版本目录下定义并编写
.hal
文件 - 执行
.hal
文件所在位置的update-makefiles.sh
脚本(实际还是调用的hidl-gen
),自动生成Android.bp/mk
文件
因为是在hardware/interface
目录下添加的,则执行:./hardware/interfaces/update-makefiles.sh
,自动生成对应Android.bp
。
1 | // 1. 新建目录 |
hidl-gen
规则
hidl-gen
转换规则以及生成哪些文件都是在自动生成的 Android.bp
文件中定义的; hidl, aidl
文件自动生成 java, cpp, h
文件工具源码都是在 system/tools/
目录下:
1 | system/tools/ |
绑定式模式使用 hidl-gen
编译器并以 IFoo.hal
接口文件作为输入,它具有以下自动生成的文件:
由编译器生成的文件:
IFoo.h
描述C++
类中的纯IFoo
接口;它包含IFoo.hal
文件中的IFoo
接口中所定义的方法和类型,必要时会转换为C++
类型。不包含与用于实现此接口的RPC
机制(例如HwBinder
)相关的详细信息。类的命名空间包含软件包名称和版本号,例如::android::hardware::samples::IFoo::V1_0
。客户端和服务器都包含此标头:客户端用它来调用方法,服务器用它来实现这些方法。IHwFoo.h
头文件,其中包含用于对接口中使用的数据类型进行序列化的函数的声明。开发者不得直接包含其标头(它不包含任何类)。BpFoo.h
从IFoo
继承的类,可描述接口的HwBinder
代理(客户端)实现。开发者不得直接引用此类。BnFoo.h
保存对IFoo
实现的引用的类,可描述接口的HwBinder
存根Stub
(服务器端)实现。开发者不得直接引用此类。FooAll.cpp
包含HwBinder
代理和HwBinder
存根Stub
的实现的类。当客户端调用接口方法时,代理会自动从客户端封送参数,并将事务发送到绑定内核驱动程序,该内核驱动程序会将事务传送到另一端的存根Stub
(该存根随后会调用实际的服务器实现)。
生成的这些文件结构类似于由 aidl-cpp
生成的文件。独立于 HIDL
使用的 RPC
机制的自动生成的文件是 IFoo.h
,其他所有文件都与 HIDL
使用的 HwBinder RPC
机制相关联。因此客户端和服务器实现不得直接引用除 IFoo
之外的任何内容。为了满足这项要求,只包含 IFoo.h
并链接到生成的共享库。
在直通式模式中,在编译 IFoo.hal
文件时,标头文件除了用于 Binder
通信的标头之外, hidl-gen
还会生成一个额外的直通标头文件 BsFoo.h
;此标头定义了会被执行 dlopen
操作的函数。由于直通式 HAL
在它们被调用的同一进程中运行,因此在大多数情况下,直通方法由直接函数调用(同一线程)来调用。 oneway
方法在各自的线程中运行,因为它们不需要等待 HAL
来处理它们(这意味着,在直通模式下使用 oneway
方法的所有 HAL
对于线程必须是安全的)。 BsFoo.h
文件类似于 BpFoo.h
,不过所需函数是直接调用的,并未使用 Binder
传递调用 IPC
。未来 HAL
的实现可能提供多种实现结果,例如 FooFast HAL
和 FooAccurate HAL
。在这种情况下,系统会针对每个额外的实现结果创建一个文件(例如 PTFooFast.cpp
和 PTFooAccurate.cpp
)。
hidl-gen
语法
1 | usage: hidl-gen [-p <root path>] -o <output path> -L <language> (-r <interface root>)+ [-t] fqname+ |
fqName
: 完全限定名-L
:指定生成的语言包-r
:hal
文件路径
文件路径映射
每个 hal
文件都可以通过软件包根目录映射及其完全限定名称找到,软件包根目录以参数 -r android.hardware:hardware/interfaces
的形式指定给 hidl-gen
。例如:
1 | hidl-gen -r vendor.awesome:some/device/independent/path/interfaces vendor.awesome.foo@1.0::IFoo |
- 根目录参数为
vendor.awesome:some/device/independent/path/interfaces
表示vendor.awesome
对应的目录路径为:$ANDROID_BUILD_TOP/some/device/independent/path/interfaces
。 - 软件包为
vendor.awesome.foo@1.0::IFoo
表示接口文件应该位于vendor.awesome
目录路径下的foo
接口的1.0
版本下的IFoo.hal
文件,即:$ANDROID_BUILD_TOP/some/device/independent/path/interfaces/foo/1.0/IFoo.hal
。
软件包路径映射不得重复,比如如果同时存在 -rsome.package:$PATH_A
和 -rsome.package:$PATH_B
,则 $PATH_A
必须等于 $PATH_B
才能实现一致的接口目录(这也能让接口版本控制起来更简单)。
代码文件生成
定义
hal
文件组1
2
3
4
5
6
7filegroup {
name: "android.hardware.camera.provider@2.4_hal",
srcs: [
"ICameraProvider.hal",
"ICameraProviderCallback.hal",
],
}生成头文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21// hardware/interfaces/camera/provider/2.4/Android.bp
genrule {
name: "android.hardware.camera.provider@2.4_genc++_headers",
tools: ["hidl-gen"],
cmd: "$(location hidl-gen) -o $(genDir) -Lc++-headers -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport android.hardware.camera.provider@2.4",
srcs: [
":android.hardware.camera.provider@2.4_hal", // hal 文件组
],
out: [
"android/hardware/camera/provider/2.4/ICameraProvider.h",
"android/hardware/camera/provider/2.4/IHwCameraProvider.h",
"android/hardware/camera/provider/2.4/BnHwCameraProvider.h",
"android/hardware/camera/provider/2.4/BpHwCameraProvider.h",
"android/hardware/camera/provider/2.4/BsCameraProvider.h",
"android/hardware/camera/provider/2.4/ICameraProviderCallback.h",
"android/hardware/camera/provider/2.4/IHwCameraProviderCallback.h",
"android/hardware/camera/provider/2.4/BnHwCameraProviderCallback.h",
"android/hardware/camera/provider/2.4/BpHwCameraProviderCallback.h",
"android/hardware/camera/provider/2.4/BsCameraProviderCallback.h",
],
}生成
CPP
文件1
2
3
4
5
6
7
8
9
10
11
12
13// hardware/interfaces/camera/provider/2.4/Android.bp
genrule {
name: "android.hardware.camera.provider@2.4_genc++",
tools: ["hidl-gen"],
cmd: "$(location hidl-gen) -o $(genDir) -Lc++-sources -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport android.hardware.camera.provider@2.4",
srcs: [
":android.hardware.camera.provider@2.4_hal", // hal 文件组
],
out: [
"android/hardware/camera/provider/2.4/CameraProviderAll.cpp",
"android/hardware/camera/provider/2.4/CameraProviderCallbackAll.cpp",
],
}
HIDL
服务新增函数
HIDL
服务在代码自动生成,每个服务对应的头文件中,比如 IFoo.h
都会自动添加如下几个函数:
1 | // 获取服务 |
getService
:获取服务registerAsService
:注册服务registerForNotifications
:注册通知监听事件,服务注册成功后会发出通知
服务注册与获取
服务端注册 Binder
服务
HIDL
接口使用 IInterface::registerAsService
来注册 Binder
服务,注册的名称不需要与接口或软件包名称相关。如果没有指定名称,则默认为 default
; HIDL
接口调用 android::hardware::IInterface::getInterfaceVersion
可以查看当前接口的版本。
1 | sp<IFoo> myFoo = new Foo(); |
Binder
化直通式
首先要理解 Binder
化直通式 HAL
,指的是 HAL
服务端注册的方式:以直通的方式加载服务端,并向 hwservicemanager
注册该服务;通过函数 defaultPassthroughServiceImplementation<IFoo>
来注册:
1 | defaultPassthroughServiceImplementation<ICameraProvider>( |
下面是源码分析:
1 | // LegacySupport.h |
registerPassthroughServiceImplementation
函数中,在 getService
时,参数 getStub
为 ture
,即通过直通式加载服务端;拿到直通式服务端 Interface
后,又通过 registerAsService
向 hwservicemanager
注册该服务。
即加载服务端的当前进程,作为服务进程;客户端从 hwservicemanager
可以查到服务端,并通过 Binder
和服务进程通信。
客户端获取服务
HIDL
接口因为有版本区分,所以每个接口文件都可以被认为是单独的、唯一的。因此 IFooService
版本 1.1 和 IFooService
版本 2.2 都可以注册为 foo_service
,并且两个接口上的 getService("foo_service")
都可获取该接口的已注册服务。因此在大多数情况下,注册或发现服务均无需提供名称参数(也就是说名称为 default
)。
1 | // C++ |
客户端通过 getService
来获取服务端,而 getService
是每个 .hal
文件在自动生成源码时,都会自动添加的函数,函数原型为(这里以 IServiceManager.hal
为例):static ::android::sp<IServiceManager> getService(const std::string &serviceName="default", bool getStub=false);
除了 <>
里的接口文件类型不一样,所有的 .hal
文件都会生成同样的代码;而它的实现则是在对应的 .cpp
文件中:
1 | // ServiceManagerAll.cpp |
getService
主要是根据参数 getStub
的值来决定采用哪种方式获取服务端:
- 为
false
时
通过defaultServiceManager
获取服务端,即绑定式。 - 为
true
时
通过getPassthroughServiceManager
获取服务端,即直通式。
这两个函数下面会详细介绍。
服务死亡通知
客户端需要注册服务终止通知接收器,当服务终止时,客户端收到通知;接收器需要继承 hidl_death_recipient
子类,并实现对应的方法。
1 | class IMyDeathReceiver : hidl_death_recipient { |
源码结构
源码目录速查表
实现源码路径为:
1 | system/libhidl |
libhidl
主要包含三个动态库: libhidlbase, libhidltransport, libhidlmemory
;其中 libhidlbase
主要是 hidl
的基本类型相关; libhidlmemory
是封装了 memory
通过 IMapper
来映射;libhidltransport
包含直通式 IServiceManager.hal
的实现。
1 | libhidl/ |
libhwbinder
对应生成 libhwbinder
库,是 .hal
文件 Binder
通信相关库。
1 | libhwbinder/ |
hwservicemanager
生成可执行文件 hwservicemanager
,是 HIDL
绑定式服务的大管家。
1 | hwservicemanager/ |
libhidl
目录
libhidlbase
库
libhidlbase
库对应的源码列表如下:
1 | base/ |
HidlInternal
hidl
内部使用的一些类、字符串定义等等。hal
客户端/服务端都不会使用。HidlSupport
hidl
支持的基本数据类型(不包含C++, Java
类型)。MQDescripto
快速消息队列fmq
中相关类型。Status
表示hidl
通信的状态和返回值,比如成功、失败、异常等等。SynchronizedQueue
同步队列。TaskRunner
后台无限循环的任务,使用SynchronizedQueue
队列保存任务。
libhidlmemory
库
libhidlmemory
库对应的源码列表如下:
1 | libhidlmemory/ |
就一个有效文件 mapping
,对应头文件:
1 | // mapping.h |
只包含一个功能函数:将 hidl_memory
内存映射后,返回对应的 IMemory
。 libhidlmemory
库是对 system/libhidl/transport/memory
的封装,而 transport/memory
则是具体的实现。
android.hidl.*
软件包
system/libhidl/transport/*
目录下包含 5 个软件包,这些软件包以 android.hidl.*
开头,它们是 hidl
的基础软件包:
base
定义了IBase.hal
,它的功能类似Java Object
,也就是说Ibase.hal
是所有hal
文件的父类。每个hal
文件在自动生成源码时,都会自动添加IBase
中的函数及其默认实现。allocator
定义了内存分配接口IAllocator.hal
,内存分配的具体实现为AshmemAllocator
。memory
定义了内存映射接口IMapper.hal
以及内存接口IMemory.hal
;而内存映射的具体实现为AshmemMapper
,内存块IMemory
的具体实现为AshmemMemory
。manager
定义了IServiceManager.hal
相关功能接口;它有两个实现:直通式是在system/libhidl/ServiceManagement.cpp
中实现的,对应libhidltransport
库;绑定式是在system/hwservicemanager/ServiceManager.cpp
中实现的,对应hwservicemanager
可执行文件。token
定义了接口ITokenManager.hal
,它可以将hidl
接口转换为token
方便跨进程传输;该接口是在hwservicemanager
中实现的。
hidl
相关的内存分配和映射,都是使用的匿名共享内存机制Ashmem
;IServiceManager
服务管理分为直通式和绑定式,是在不同文件中实现的。
IBase
文件路径为 system/libhidl/transport/base/1.0/IBase.hal
,是所有 hal
的基础接口,类似 Java
中的 Object
类;因为 hidl
中不存在重写和重载,所以自定义的 hal
文件中函数名不能和下面的重复:
1 | package android.hidl.base@1.0; |
libhidltransport
库
libhidltransport
库中的头文件 #include <hidl/ServiceManagement.h>
,包含了几组重要函数,用来区分当前是客户端采用绑定式还是直通式来获取服务端:
1 | // ServiceManagement.h |
defaultServiceManager
:绑定式服务管理IServiceManager
getPassthroughServiceManager
:直通式服务管理IServiceManager
它们都是在 ServiceManagement.cpp
中实现的,先看绑定式源码:
1 | // ServiceManagement.cpp |
它的核心代码是 fromBinder
这个模板函数,这里需要注意 ProcessState::self()->getContextObject(NULL)
这句代码的含义是:获取 handle
为 0 的 IBinder
,而 handle
为 0 表示是 hwservicemanager
守护进程,后续在 hwservicemanager
进程中做详细介绍。这里 ProcessState, IPCThreadState
等,虽然都是在 libhwbinder
库中,实际上和 Framework Binder
中代码很多都是相同,实现的功能也大致相同。fromBinder
是在 HidlBinderSupport.h
头文件中定义的:
1 | // HidlBinderSupport.h |
模板代码表示,如果是远程访问,则新建代理对象即 BpHwServiceManager
,即客户端持有服务端的代理;如果是本地调用,则直接转换为 BnHwServiceManager
,换句话说这里客户端就是服务端自己。
当是远程访问时,实际的 IServiceManager
是由 hwservicemanager
进程中 ServiceManager
实现的。
再看直通式源码:
1 | // ServiceManagement.cpp |
直通模式获取服务端时,直接返回的 PassthroughServiceManager
对象,通过它获取服务端 get
方法源码如下:
1 | struct PassthroughServiceManager : IServiceManager1_1 { |
openLibs
首先根据全限定名解析出版本号,以及 .hal
接口名称,再查找其实现库即 IFoo-impl.so
库,查找路径为 HAL_LIBRARY_PATH_SYSTEM
等等,它们的定义如下:
1 | // HidlInternal.h |
也就是说在这些路径中搜索 IFoo-impl.so
库文件,直到找到为止。
IFoo.hal
的实现文件Foo.cpp
中,必须包含HIDL_FETCH_IFoo
的函数,这个是在openLibs
中代码写死的。通常在HIDL_FETCH_IFoo
中,new Foo()
来新建Foo
对象。
直通式中获取服务端的流程:先通过 openLibs
加载实现库 IFoo-impl.so
,再调用 HIDL_FETCH_IFoo
方法,得到 Foo
对象(即服务端)。
也就是说,直通式 Passthrough HAL
中客户端直接将服务端的代码库加载到当前进程中,这也是 Treble
架构中对老版本 HAL
的兼容:在 HIDL
之前, HAL
都是通过 dlopen
来直接加载的。
libhwbinder
目录
libhwbinder
库目录,主要是实现了 hwbinder
通信,它的实现方式绝大部分都和 Framework Binder
中一致,参考Android Binder 机制 。libhwbinder
目录的代码基本是从 Framework Binder
代码拷贝过来,修改了部分 Bp, Bn Binder
的名称,以及 /dev/hwbinder
驱动设备文件。
hwBinder
类图结构
BpHwRefBase
中的mRemote
指向了BpHwBinder
BpHwBinder
中的mHandle
是一个句柄,指向BHwBinder
,它们两个之间通过Binder Driver
来通信
IBase
类图结构
IBase
是所有 HIDL
服务的基类, BnHwBase
中的 _hidl_mImpl
指向了 HIDL
服务的具体实现类。
ProcessState
这里主要介绍 ProcessState
中两个函数:构造函数和 getContextObject
1 | // ProcessState.cpp |
ProcessState
构造函数中实现了如下功能:
- 打开
/dev/hwbinder
设备,该文件节点和Binder
通信 - 初始配置驱动的最大线程数为 0 ,后续可以通过
setThreadPoolConfiguration
来修改 mmap
映射内存空间,大概1MB
1 | // ProcessState.cpp |
getContextObject
函数中调用 getStrongProxyForHandle(0)
,即返回句柄为 0 的代理,而句柄为 0 表示是服务大管家,后面 hwservicemanager
中会详细介绍。
当查到句柄存在时,新建 BpHwBinder
,它是所有 Bp***Binder
的父类。
hwservicemanager
进程
hwservicemanager
是 HIDL
服务大管家,负责管理系统中的所有 HIDL
注册的绑定式服务,由 init
进程启动。
Binder
通信基础知识
先复习下 Framework Binder
通信的基础知识:
IInterface
表示服务端能够提供的服务IBinder
用来实现跨进程通信,分为BnBinder, BpBinder
客户端和服务端通信过程:
- 服务端通过
BnBinder
实现IInterface
对应功能 - 客户端通过
BpBinder
调用IInterface
对应功能;BpBinder
是BnBinder
的代理,代理的实现过程为通过Binder Driver
转发
Framework Binder
中两个重要概念:
service_manager
进程:它是服务大管家,负责保存注册的服务IServiceManager
供客户端和服务端查询和注册服务,它和service_manager
是跨进程通信
IServiceManager.cpp
作为客户端和服务大管家 service_manager
进程通过 Binder
来通信;其他 aidl
服务进程作为客户端,通过 IServiceManager.cpp
注册服务;其他 app
进程作为客户端,通过 IServiceManager.cpp
查询服务。
IServiceManager
类图结构
这个类图体现了一般的 HIDL
服务的类结构:
IServiceManager
可以看做一个标准的HIDL
服务,默认继承IBase
IServiceManager
服务的实现类有两个:PassthroughServiceManager, ServiceManager
BnHwServiceManager
中_hidl_mImpl
指向IServiceManager
的具体实现类,这里具体是ServiceManager
BpHwServiceManager
是代理类,父类BpHwRefBase
中的mRemote
指向了BpHwBinder
,而BpHwBinder
中的mHandle
是指向BHwBinder
的句柄,这里实际指向的是它的子类BnHwServiceManager
Bp**
和Bn**
是通过Binder
驱动来通信的,设备名/dev/hwbinder
hwBinder
简述
hwBinder
和 Binder
模型基本一样,而且是共用 Binder Driver
,仅仅是设备关键字不一样。 hwBinder
中,服务端持有 BnHw
并实现 IInterface
的具体功能;客户端持有 BpHw
调用 IInterface
对应功能, BpHw
是 BnHw
的代理,通过 Binder Driver
来通信。 BpHw, BnHw
的通信过程(读写 Parcel
),都是在自动生成的 IFooAll.cpp
中实现的。hwservicemanager
进程功能和 Framework Binder
中的 service_manager
进程相同,它是 HIDL
的服务大管家;但具体由 ServiceManager.cpp
来保存注册的服务, ServiceManager
属于 hwservicemanager
进程,所以通信过程是函数直接调用。
客户端和服务端通过 ServiceManagement.cpp
来和 hwservicemanager
通信。 ServiceManagement
会根据绑定式或是直通式来返回 IServiceManager
的具体实现:如果是绑定式,则 ServiceManagement
持有 BpHwServiceManager
(即 BnHwServiceManager
的代理),它会和 hwservicemanager
通过 Binder
通信,查询并返回服务接口。
rc
文件
hwservicemanager.rc
文件定义了 hwservicemanager
进程的启动方式:
1 | service hwservicemanager /system/bin/hwservicemanager |
main
方法
主进程文件为 service.cpp
,对应的 main
方法为:
1 | // service.cpp |
main
中主要实现了这些功能:
- 新建
ServiceManager
对象,并将它注册到hwservicemanager
中(注册过程实际是存储到一个map
中) - 新建
TokenManager
对象,并将它注册为服务 - 轮询
/dev/hwbinder
设备文件,监听事件 - 新建
BnHwServiceManager
对象,并将ServiceManager
传入作为IServiceManager
的实现 ioctl
向驱动发送消息,将hwservicemanager
进程,以句柄 0 向驱动注册为服务大管家BINDER_SET_CONTEXT_MGR
(这就是为什么拿到句柄 0 ,即表示为hwservicemanager
)
ServiceManager
ServiceManager
用于管理服务的注册和查询, mServiceMap
中保存了服务端相关信息,以全限定名称作为 key
保存。
1 | struct ServiceManager : public IServiceManager, hidl_death_recipient { |
mServiceMap
关键字为全限定名:package::interface
,比如:android.hidl.manager@1.0::IServiceManager
。mInstanceMap
关键字为服务名称,默认为default
;服务端也可以在注册时,指定服务名称;比如CameraProvider
中注册时,指定为"legacy/0"
。
因为先通过全限定从 mServiceMap
中取 mInstanceMap
,而全限定名称包含软件包、主次版本号、接口名称,全限定名称一定不会重复,所以拿到的是唯一的 mInstanceMap
;此时再根据服务名称获取服务端接口时,服务名已经不是很重要,所以通常服务名称使用的默认的 default
。
服务注册
服务端通过 Binder
注册,最终是在 ServiceManager
中实现的,并保存在 mServiceMap
中。
注意: add
来注册服务,是 hwbinder
机制内部使用的;服务端应该使用 registerAsService
来注册,而自动生成代码 IFooAll.cpp
中在实现 registerAsService
时,会调用 add
来完成注册。
1 | // ServiceManager.cpp |
注册的过程为:
- 先根据全限定名和服务名,查找服务是否存在
- 如果不存在,则添加并保存;如果存在则更新
服务查询
查询 Binder
服务,最终是在 ServiceManager
中实现的,也就是从 mServiceMap
中查找。
注意: get
来查询服务,是 hwbinder
机制内部使用的;客户端应该通过接口 Interface::getService
获取:它会先使用 defaultServiceManager
来获取 IServiceManager
,然后再调用 get
方法。
1 | // ServiceManager.cpp |
查询过程很简单:就是从 mServiceMap
中根据全限定名和服务名查找。
示例
hardware/interfaces/tests
中有简单的 HIDL
服务示例:
1 | tests/ |
小结
Binder
总结
Binder
域有三个,但它们都是共用了 Binder Driver
,只是设备文件名称不一样(在 kernel
编译配置中设定 CONFIG_ANDROID_BINDER_DEVICES="binder,hwbinder,vndbinder"
):
/dev/binder
标准的Framework Binder
,使用AIDL
接口;服务大管家对应的是servicemanager
进程。/dev/hwbinder
HIDL
服务相关,使用HIDL
接口;服务大管家对应的是hwservicemanager
进程。/dev/vndbinder
供应商之间的通信,使用AIDL
接口;服务大管家对应的是vndservicemanager
进程。
在 HIDL
服务中,除了使用 /dev/hwbinder
和 Framework
通信外;还可以同时使用 /dev/vndbinder
和 vendor
通信:
1 | // hardware/interfaces |
这里 vndservicemanager, servicemanager
进程对应的源码文件都是 service-manager.c
文件,只是在 Android.bp
中做了编译区分。
1 | // frameworks/native/cmds/servicemanager/Android.bp |
后续
HIDL
分别用cpp, java
实现两个示例vts
相关