使用 Binder IPC

本页介绍了 Android 8 中对 Binder 驱动程序进行的更改、提供了有关使用 Binder IPC 的详细信息,并列出了必需的 SELinux 政策。

对 Binder 驱动程序进行的更改

从 Android 8 开始,Android 框架和 HAL 现在使用 Binder 互相通信。由于这种通信方式极大地增加了 Binder 流量,因此 Android 8 包含了几项改进,旨在确保 Binder IPC 的速度。SoC 供应商和原始设备制造商 (OEM) 应直接从 android-4.4、android-4.9 及更高版本内核/通用项目的相关分支进行合并。

多个 Binder 域(上下文)

通用 4.4 及更高版本,包括上游

为了在框架(独立于设备)和供应商(特定于设备)代码之间彻底拆分 Binder 流量,Android 8 引入了“Binder 上下文”的概念。每个 Binder 上下文都有自己的设备节点和上下文(服务)管理器。您只能通过上下文管理器所属的设备节点对其进行访问,并且在通过特定上下文传递 Binder 节点时,只能由另一个进程从相同的上下文访问上下文管理器,从而确保这些域完全互相隔离。如需详细了解使用方法,请参阅 vndbindervndservicemanager

分散-集中

通用 4.4 及更高版本,包括上游

在之前的 Android 版本中,Binder 调用中的每条数据都会被复制 3 次:

  • 一次是在调用进程中将数据序列化为 Parcel
  • 一次是在内核驱动程序中将 Parcel 复制到目标进程
  • 一次是在目标进程中反序列化 Parcel

Android 8 使用分散-集中优化将副本数量从 3 减少到 1。数据保留其原始结构和内存布局,且 Binder 驱动程序会立即将数据复制到目标进程中,而不是先在 Parcel 中对数据进行序列化。在目标进程中,这些数据的结构和内存布局保持不变,并且,在无需再次复制的情况下即可读取这些数据。

精细锁定

通用 4.4 及更高版本,包括上游

在之前的 Android 版本中,Binder 驱动程序使用全局锁来防范对重要数据结构的并发访问。虽然采用全局锁时出现争用的可能性极低,但主要的问题是,如果低优先级线程获得该锁,然后实现了抢占,会导致同样需要获得该锁的优先级较高的线程出现严重的延迟。这会导致平台卡顿。

原先尝试解决此问题的方法是在保留全局锁的同时禁止抢占。但是,这更像是一种临时应对手段而非真正的解决方案,最终被上游拒绝并舍弃。后来尝试的解决方法侧重于提升锁定的精细程度,自 2017 年 1 月以来,Pixel 设备上一直采用的是更加精细的锁定。虽然这些更改大部分已公开,但后续版本中还会有一些重大的改进。

在确定了精细锁定实现中的一些小问题后,我们使用不同的锁定架构设计了一种改进的解决方案,并在所有通用内核分支中提交了相关更改。我们会继续在大量不同的设备上测试这种实现方式;由于目前看来这个方案不存在什么问题,因此建议搭载 Android 8 的设备都使用这种实现方式。

实时优先级继承

通用 4.4 和通用 4.9(即将推送到上游)

Binder 驱动程序一直支持 nice 优先级继承。随着 Android 中以实时优先级运行的进程日益增加,现在出现以下这种情形也属正常:如果实时线程进行 Binder 调用,则处理该调用的进程中的线程同样会以实时优先级运行。为了支持这些用例,Android 8 现在在 Binder 驱动程序中实现了实时优先级继承。

除了事务级优先级继承之外,“节点优先级继承”允许节点(Binder 服务对象)指定执行对该节点的调用所需的最低优先级。之前版本的 Android 已经通过 nice 值支持节点优先级继承,但 Android 8 增加了对实时调度政策节点继承的支持。

用户空间变更

Android 8 包含在通用内核中使用当前 Binder 驱动程序所需的所有用户空间变更,但有一个例外:针对 /dev/binder 停用实时优先级继承的原始实现使用了 ioctl。由于后续开发将优先级继承的控制方法改为了更加精细的方法(根据 Binder 模式,而非上下文),因此,ioctl 并非存于 Android 通用分支中,而是提交到了我们的通用内核中

此项变更的影响是,所有节点均默认停用实时优先级继承。Android 性能团队发现,为 hwbinder 域中的所有节点启用实时优先级继承会有一定好处。如需达到同样的效果,请在用户空间中按需接纳此变更

通用内核的 SHA

如需获取对 Binder 驱动程序所做的必要变更,请同步到适当的 SHA:

  • Common-3.18
    cc8b90c121de ANDROID: Binder:请勿在恢复时检查优先级权限。
  • Common-4.4
    76b376eac7a2 ANDROID: Binder:请勿在恢复时检查优先级权限。
  • Common-4.9
    ecd972d4f9b5 ANDROID: Binder:请勿在恢复时检查优先级权限。

使用 Binder IPC

一直以来,供应商进程都使用 Binder 进程间通信 (IPC) 技术进行通信。在 Android 8 中,/dev/binder 设备节点成为框架进程的专有节点,这意味着供应商进程无法再访问此节点。供应商进程可以访问 /dev/hwbinder,但必须将其 AIDL 接口转为使用 HIDL。对于想要继续在供应商进程之间使用 AIDL 接口的供应商,Android 会按以下方式支持 Binder IPC。 在 Android 10 中,稳定的 AIDL 允许所有进程使用 /dev/binder,同时还力求解决 HIDL 和 /dev/hwbinder 已解决的稳定性保证。如需了解如何使用 Stable AIDL,请参阅适用于 HAL 的 AIDL

vndbinder

Android 8 支持供应商服务使用的新 Binder 域,访问此域需要使用 /dev/vndbinder(而非 /dev/binder)。添加 /dev/vndbinder 后,Android 现在拥有以下 3 个 IPC 域:

IPC 域 说明
/dev/binder 框架/应用进程之间的 IPC,使用 AIDL 接口
/dev/hwbinder 框架/供应商进程之间的 IPC,使用 HIDL 接口
供应商进程之间的 IPC,使用 HIDL 接口
/dev/vndbinder 供应商/供应商进程之间的 IPC,使用 AIDL 接口

为了显示 /dev/vndbinder,请确保内核配置项 CONFIG_ANDROID_BINDER_DEVICES 设为 "binder,hwbinder,vndbinder"(这是 Android 通用内核树的默认设置)。

通常,供应商进程不直接打开 Binder 驱动程序,而是链接到打开 Binder 驱动程序的 libbinder 用户空间库。为 ::android::ProcessState() 添加方法可为 libbinder 选择 Binder 驱动程序。供应商进程应该在调用 ProcessState,IPCThreadState 或发出任何普通 Binder 调用之前调用此方法。如需使用该方法,请在供应商进程(客户端和服务器)的 main() 后放置以下调用:

ProcessState::initWithDriver("/dev/vndbinder");

vndservicemanager

以前,Binder 服务通过 servicemanager 注册,其他进程可从中检索这些服务。在 Android 8 中,servicemanager 现在专供框架使用,而应用进程和供应商进程无法再对其进行访问。

不过,供应商服务现在可以使用 vndservicemanager,这是一个使用 /dev/vndbinder(作为构建基础的源代码与框架 servicemanager 的相同)而非 /dev/binderservicemanager 的新实例。供应商进程无需更改即可与 vndservicemanager 通信;当供应商进程打开 /dev/vndbinder 时,服务查询会自动转至 vndservicemanager

vndservicemanager 二进制文件包含在 Android 的默认设备 Makefile 中。

SELinux 政策

想要使用 Binder 功能相互通信的供应商进程需要满足以下要求:

  1. 能够访问 /dev/vndbinder
  2. 将 Binder {transfer, call} 接入 vndservicemanager
  3. 针对想要通过供应商 Binder 接口调用供应商域 B 的任何供应商域 A 执行 binder_call(A, B) 操作。
  4. 有权在 vndservicemanager 中对服务执行 {add, find} 操作。

如需满足要求 1 和 2,请使用 vndbinder_use() 宏:

vndbinder_use(some_vendor_process_domain);

如需满足要求 3,需要通过 Binder 通信的供应商进程 A 和 B 的 binder_call(A, B) 可以保持不变,且不需要重命名。

要满足要求 4,您必须按照处理服务名称、服务标签和规则的方式进行更改。

有关 SELinux 的详细信息,请参阅 Android 中的安全增强型 Linux。如需详细了解 Android 8.0 中的 SELinux,请参阅 SELinux for Android 8.0

服务名称

以前,供应商进程在 service_contexts 文件中注册服务名称并添加用于访问该文件的相应规则。来自 device/google/marlin/sepolicyservice_contexts 文件示例:

AtCmdFwd                              u:object_r:atfwd_service:s0
cneservice                            u:object_r:cne_service:s0
qti.ims.connectionmanagerservice      u:object_r:imscm_service:s0
rcs                                   u:object_r:radio_service:s0
uce                                   u:object_r:uce_service:s0
vendor.qcom.PeripheralManager         u:object_r:per_mgr_service:s0

在 Android 8 中,vndservicemanager 会改为加载 vndservice_contexts 文件。迁移到 vndservicemanager(且已经在旧的 service_contexts 文件中)的供应商服务应该添加到新的 vndservice_contexts 文件中。

服务标签

以前,服务标签(例如 u:object_r:atfwd_service:s0)在 service.te 文件中定义。例如:

type atfwd_service,      service_manager_type;

在 Android 8 中,您必须将类型更改为 vndservice_manager_type,并将规则移至 vndservice.te 文件。例如:

type atfwd_service,      vndservice_manager_type;

Servicemanager 规则

以前,规则会授予域访问权限,以向 servicemanager 添加服务或在其中查找服务。示例:

allow atfwd atfwd_service:service_manager find;
allow some_vendor_app atfwd_service:service_manager add;

在 Android 8 中,这样的规则可继续存在并使用相同的类。示例:

allow atfwd atfwd_service:service_manager find;
allow some_vendor_app atfwd_service:service_manager add;