HIDL

HAL 接口定义语言或 HIDL 是一种接口描述语言 (IDL),用于指定 HAL 与其用户之间的接口。HIDL 允许指定类型和方法调用,这些类型和方法调用收集到接口和软件包中。更广泛地说,HIDL 是一种用于在可能独立编译的代码库之间进行通信的系统。

HIDL 旨在用于进程间通信 (IPC)。使用 HDL 创建的 HAL 被称为绑定式 HAL,因为它们可以使用绑定器进程间通信 (IPC) 调用与其他架构层通信。绑定式 HAL 在与使用它们的客户端不同的进程中运行。对于必须链接到进程的库,还提供直通模式(Java 中不支持)。

HIDL 指定数据结构和方法签名,这些结构和签名组织在接口(类似于类)中,这些接口收集到软件包中。HIDL 的语法对于 C++ 和 Java 程序员来说很熟悉,但关键字集不同。HIDL 还使用 Java 样式注解。

术语

本节使用以下 HIDL 相关术语

绑定式 表示 HIDL 正在用于进程之间的远程过程调用,通过类似 Binder 的机制实现。另请参阅直通
回调,异步 由 HAL 用户提供的接口,传递给 HAL(使用 HIDL 方法),并由 HAL 调用以随时返回数据。
回调,同步 将数据从服务器的 HIDL 方法实现返回给客户端。不用于返回 void 或单个原始值的方法。
客户端 调用特定接口方法的进程。HAL 或 Android 框架进程可以是一个接口的客户端,而另一个接口的服务器。另请参阅直通
extends 表示向另一个接口添加方法和/或类型的接口。一个接口只能扩展另一个接口。可用于同一软件包名称中的次要版本增量,或用于构建在较旧软件包之上的新软件包(例如,供应商扩展)。
generates 表示向客户端返回值的接口方法。要返回一个非原始值或多个值,将生成一个同步回调函数。
接口 方法和类型的集合。在 C++ 或 Java 中转换为类。接口中的所有方法都沿同一方向调用:客户端进程调用服务器进程实现的方法。
oneway 当应用于 HIDL 方法时,表示该方法不返回值且不阻塞。
软件包 共享版本的接口和数据类型的集合。
直通 HIDL 的模式,其中服务器是一个共享库,由客户端 dlopen。在直通模式下,客户端和服务器是同一进程,但代码库不同。仅用于将旧代码库引入 HIDL 模型。另请参阅绑定式
服务器 实现接口方法的进程。另请参阅直通
传输 在服务器和客户端之间移动数据的 HIDL 基础架构。
版本 软件包的版本。由两个整数组成:主版本号和次版本号。次版本号增量可能会添加(但不会更改)类型和方法。

HIDL 设计

HIDL 的目标是,无需重建 HAL 即可替换 Android 框架。HAL 由供应商或 SOC 制造商构建,并放置在设备上的 /vendor 分区中,使 Android 框架(位于其自己的分区中)可以通过 OTA 替换,而无需重新编译 HAL。

HIDL 设计平衡了以下考虑因素

  • 互操作性。在可能使用各种架构、工具链和构建配置编译的进程之间创建可靠的互操作接口。HIDL 接口是版本化的,发布后无法更改。
  • 效率。HIDL 尝试最大限度地减少复制操作的次数。HIDL 定义的数据以 C++ 标准布局数据结构的形式交付给 C++ 代码,可以直接使用而无需解包。HIDL 还提供共享内存接口,并且由于 RPC 本身速度较慢,HIDL 支持两种无需使用 RPC 调用的数据传输方式:共享内存和快速消息队列 (FMQ)。
  • 直观。HIDL 通过仅对 RPC 使用 in 参数(请参阅 Android 接口定义语言 (AIDL))避免了棘手的内存所有权问题;无法从方法有效返回的值通过回调函数返回。无论是将数据传入 HIDL 进行传输,还是从 HIDL 接收数据,都不会改变数据的所有权——所有权始终归调用函数所有。数据只需在被调用函数的持续时间内保持存在,并在被调用函数返回后立即销毁。

使用直通模式

要将运行早期版本 Android 的设备更新到 Android O,您可以将传统(和旧版)HAL 都封装在一个新的 HIDL 接口中,该接口以绑定和同进程(直通)模式为 HAL 提供服务。这种封装对 HAL 和 Android 框架都是透明的。

直通模式仅适用于 C++ 客户端和实现。运行早期版本 Android 的设备没有用 Java 编写的 HAL,因此 Java HAL 本质上是绑定的。

编译 .hal 文件时,除了用于绑定器通信的标头之外,hidl-gen 还会生成一个额外的直通标头文件 BsFoo.h;此标头定义要 dlopen 的函数。由于直通 HAL 在调用它们的同一进程中运行,因此在大多数情况下,直通方法是通过直接函数调用(同一线程)调用的。oneway 方法在它们自己的线程中运行,因为它们不打算等待 HAL 处理它们(这意味着任何在直通模式下使用 oneway 方法的 HAL 都必须是线程安全的)。

给定一个 IFoo.halBsFoo.h 封装了 HIDL 生成的方法以提供额外的功能(例如,使 oneway 事务在另一个线程中运行)。此文件类似于 BpFoo.h,但是,所需的函数是直接调用的,而不是使用绑定器传递 IPC 调用。HAL 的未来实现可能提供多种实现,例如 FooFast HAL 和 FooAccurate HAL。在这种情况下,将为每个额外的实现创建一个文件(例如,PTFooFast.cppPTFooAccurate.cpp)。

绑定直通 HAL

您可以绑定支持直通模式的 HAL 实现。给定一个 HAL 接口 a.b.c.d@M.N::IFoo,将创建两个软件包

  • a.b.c.d@M.N::IFoo-impl。包含 HAL 的实现并公开函数 IFoo* HIDL_FETCH_IFoo(const char* name)。在旧版设备上,此软件包是 dlopened,并且使用 HIDL_FETCH_IFoo 实例化实现。您可以使用 hidl-gen-Lc++-impl 以及 -Landroidbp-impl 生成基本代码。
  • a.b.c.d@M.N::IFoo-service。打开直通 HAL 并将其自身注册为绑定服务,从而使同一 HAL 实现可以同时用作直通和绑定服务。

给定类型 IFoo,您可以调用 sp<IFoo> IFoo::getService(string name, bool getStub) 以访问 IFoo 的实例。如果 getStub 为 true,则 getService 尝试仅在直通模式下打开 HAL。如果 getStub 为 false,则 getService 尝试查找绑定服务;如果失败,则尝试查找直通服务。getStub 参数绝不应使用,除非在 defaultPassthroughServiceImplementation 中。(使用 Android O 启动的设备是完全绑定的设备,因此不允许在直通模式下打开服务。)

HIDL 语法

按照设计,HIDL 语言类似于 C(但不使用 C 预处理器)。下面未描述的所有标点符号(除了显式使用的 =|)都是语法的一部分。

注意: 有关 HIDL 代码样式的详细信息,请参阅代码样式指南

  • /** */ 表示文档注释。这些注释只能应用于类型、方法、字段和枚举值声明。
  • /* */ 表示多行注释。
  • // 表示行尾注释。除了 // 之外,换行符与任何其他空格相同。
  • 在下面的示例语法中,从 // 到行尾的文本不是语法的一部分,而是语法的注释。
  • [empty] 表示该术语可能为空。
  • ? 跟在文字或术语后面表示它是可选的。
  • ... 表示包含零个或多个项目的序列,并带有指示的分隔标点符号。HIDL 中没有可变参数。
  • 逗号分隔序列元素。
  • 分号终止每个元素,包括最后一个元素。
  • 大写表示非终结符。
  • italics 是一个标记族,例如 integeridentifier(标准 C 解析规则)。
  • constexpr 是 C 风格的常量表达式(例如 1 + 11L << 3)。
  • import_name 是包名称或接口名称,其限定方式如 HIDL 版本控制中所述。
  • 小写 words 是字面量标记。

示例

ROOT =
    PACKAGE IMPORTS PREAMBLE { ITEM ITEM ... }  // not for types.hal
  | PACKAGE IMPORTS ITEM ITEM...  // only for types.hal; no method definitions

ITEM =
    ANNOTATIONS? oneway? identifier(FIELD, FIELD ...) GENERATES?;
  |  safe_union identifier { UFIELD; UFIELD; ...};
  |  struct identifier { SFIELD; SFIELD; ...};  // Note - no forward declarations
  |  union identifier { UFIELD; UFIELD; ...};
  |  enum identifier: TYPE { ENUM_ENTRY, ENUM_ENTRY ... }; // TYPE = enum or scalar
  |  typedef TYPE identifier;

VERSION = integer.integer;

PACKAGE = package android.hardware.identifier[.identifier[...]]@VERSION;

PREAMBLE = interface identifier EXTENDS

EXTENDS = <empty> | extends import_name  // must be interface, not package

GENERATES = generates (FIELD, FIELD ...)

// allows the Binder interface to be used as a type
// (similar to typedef'ing the final identifier)
IMPORTS =
   [empty]
  |  IMPORTS import import_name;

TYPE =
  uint8_t | int8_t | uint16_t | int16_t | uint32_t | int32_t | uint64_t | int64_t |
 float | double | bool | string
|  identifier  // must be defined as a typedef, struct, union, enum or import
               // including those defined later in the file
|  memory
|  pointer
|  vec<TYPE>
|  bitfield<TYPE>  // TYPE is user-defined enum
|  fmq_sync<TYPE>
|  fmq_unsync<TYPE>
|  TYPE[SIZE]

FIELD =
   TYPE identifier

UFIELD =
   TYPE identifier
  |  safe_union identifier { FIELD; FIELD; ...} identifier;
  |  struct identifier { FIELD; FIELD; ...} identifier;
  |  union identifier { FIELD; FIELD; ...} identifier;

SFIELD =
   TYPE identifier
  |  safe_union identifier { FIELD; FIELD; ...};
  |  struct identifier { FIELD; FIELD; ...};
  |  union identifier { FIELD; FIELD; ...};
  |  safe_union identifier { FIELD; FIELD; ...} identifier;
  |  struct identifier { FIELD; FIELD; ...} identifier;
  |  union identifier { FIELD; FIELD; ...} identifier;

SIZE =  // Must be greater than zero
     constexpr

ANNOTATIONS =
     [empty]
  |  ANNOTATIONS ANNOTATION

ANNOTATION =
  |  @identifier
  |  @identifier(VALUE)
  |  @identifier(ANNO_ENTRY, ANNO_ENTRY  ...)

ANNO_ENTRY =
     identifier=VALUE

VALUE =
     "any text including \" and other escapes"
  |  constexpr
  |  {VALUE, VALUE ...}  // only in annotations

ENUM_ENTRY =
     identifier
  |  identifier = constexpr