Android Binder 之为什么要造轮子?

Linux 已经有这么多种 IPC 机制了,为什么 Android 非要自己造 Binder 这个新轮子。KPI ?

面试中有时会被问到这个问题。标准的答案是:从性能、稳定性、安全三方面考虑,Binder 比管道、消息队列、Socket、共享内存等其他方案更合适。具体如下:

  • 1.高性能:从数据拷贝次数来看 Binder 只需要进行一次内存拷贝,而管道、消息队列、Socket 都需要两次,共享内存不需要拷贝,Binder 的性能仅次于共享内存。
  • 2.稳定性:上面说到共享内存的性能优于 Binder,那为什么不使用共享内存呢,因为共享内存需要处理并发同步问题,容易出现死锁和资源竞争,稳定性较差。而 Binder 基于 C/S 架构,客户端与服务端彼此独立,稳定性较好。
  • 3.安全性:Android 为每个应用分配了 UID,作为鉴别进程的重要标志,Android 内部依赖这个 UID 进行权限管理,包括6.0以前的固定权限和6.0以后的动态权限,传统 IPC 只能由用户在数据包里填入 UID/PID,这个标记是在用户空间控制,没有放在内核空间,因此有被恶意篡改的可能,因此 Binder 的安全性更高。

(以上答案来自网络,原始出处不详。)

其实这个问题不仅我们会在面试时被问题到,Android 早期版本刚发布那会儿 Android 团队也曾被问到这个问题。相关的答案在这里 (How it’s used by Android 那一节)。

首先是重点。LKML: Brian Swetland: Re: [PATCH 1/6] staging: android: binder: Remove some funny && usage 提到 Binder 有两个特性是内核中现有 IPC 机制所不具备的。这两点如下:

  • 避免内存拷贝。Binder 让内核直接从写入器复制数据到读取器地址空间中的环形缓冲区,从而避免内存拷贝
  • 生命周期管理。Binder 管理可在进程间共享和传递的代理远程用户空间对象的生命周期(用户空间 Binder 库据此建立远程引用计数模型)

这里的第一点的确跟性能相关,第二点则跟接口易用性相关。

再来看 Android Binder - eLinux.org 中的具体解释。

Android 平台上,Binder 几乎用于核心平台中所有的跨进程通信场景。以下几个例子展示了它的核心功能:

  • Binder 用于 window manager 与它的 client 通信。一个 client 启动时使用 Binder 跟 window manager 建立新的专属的 binder connection。这是 binder 能力模型的常用场景,为 client 跟系统交互提供一个安全的连接。

  • Binder 用于 window manager 跟底层的 surface compositor 通信。有一套简单的基于 Binder 的 API 用于给窗口分配 surface,它使用 Binder 的 fd passing 和 object identity 能力来让 surface compositor 在它所管理的 heap 中分配 surface 内存:window manager 以 client 应用的名义发起分配 surface 内存的请求,之后它将 binder 对象返回给 client 进程以便应用进程直接在关联的 surface 内存中绘制图形。Binder 的 object identity 能力在这个场景下非常方便 (IBinder 官方文档提到的 meta-data 和 token)

  • Binder 的组件隔离能力。举个例子,window manager 或 surface flinger,可能运行在同一进程或不同进程。它们的代码不变,进程模型却变了。在当前的 Android 平台这两个组件运行在同一个进程,但我们之前尝试让这两个组件运行在不同进程,而且也计划将来在内存更大的高端机上让它们运行在不同进程中。这并不是 Binder 核心部分必须具备的特性,但它提供的 IPC 语义让对应的实现非常简单:将 transactions 分发到线程池,跨进程同步递归调用,等等(IBinder 官方文档提到的 synchronous 和 recursion)

  • Binder 也用于 activity manager 创建和管理应用进程中的组件。应用进程简单地创建一个 Binder 对象用作它的 token。应用进程和 window manager 都持有这个 token (应用添加窗口时会将 token 给到 window manager)。出于安全性考虑,object identity 特性让这种编程模型在系统中广泛使用:你可以给某人一个 token,也可以将 token 交给另外的人,你总是可以检查这个 token 是否你之前给出去的那一个,而不必要求对方以任何方式证明这一点。当 activity manager 对 window manager 说:“这个 token 对应的所有窗口都要隐藏”,window manager 可以使用之前提供的 token 找到对应的窗口并执行相应操作。

  • Uid-based 权限模型和 Binder 是 Android 安全性的基础。Binder 某些能力是直接提供的(我允许你调用接口),而另一些能力则是间接提供的(我给你一个 binder 对象作为 token,你拿这个 token 可以验证谁是调用方)。每个 incoming binder transaction 跟调用发起方的 uid 关联,这种编程方式大量使用于只允许特定 uid 访问特定功能的场景。比如,window manager 的 API 将上层的 input event 注入到系统,而这些 API 会检查 calling uid 以确认 API 调用方有操作权限。

  • Binder 原生支持 one-way 和 two-way 调用。two-way(sync) 方式在 system services 中用得更多,它支持多线程:这些调用来自线程池,而 service 会获取特定的 lock 以保护线程状态。one-way(async) 方式更多地用于向应用返回消息,或者是 service 向系统的更上层发送命令的场景。

  • Binder 具有生命周期管理能力。很多 system service 需要清理客户进程的关联状态。比如,一个应用进程已经不存在了,它对应的所有 window 都应当被清理。使用 binder 的 “link to death” 功能很容易实现这个操作,binder 对象的宿主进程不存在时会回调通知当前进程。For example, the window manager links to the death of a window’s callback interface, and other services have clients send a binder object token just to be able to find out when its process dies. 这一能力的实现原理是 Binder 驱动监听对象并将对象状态告知给进程。

  • Binder 的核心能力的最佳示例是 Input Method Manager:它是一个相对较小的组件,但大量使用 binder object identities, capabilities, death links 以及其他 binder 能力,to arbitrate between N applications and M IMEs securely interacting with each other in a controlled way. 具体见 InputMethodManager 文档的 “Security” 部分。值得一提的是,Input Method Manager 允许应用进程向其接口(在这里是 InputConnection)发送一个 binder 对象,然后它将这个 binder 对象又发送给运行在其他进程的 IME。之后其他进程的 IME 能以当前应用的名义直接调用 InputConnection(因为 IME 已经被授权了),而不必再经过 Input Method Manager 这个中间进程

Binder 有类似 weak reference 的机制。 Binder 协议中有一点非常赞的是 weak reference。进程可以感知到远程对象,但不会要求这个远程对象必须存在(弱引用状态)。进程可以在任意时刻将这种状态提升为强引用以进行调用(强引用状态),而这个远程调用成功还是失败,取决于远程对象是否还存在(进程内的强引用或跨进程的强引用关系让目标远程对象仍然存活)。对 native 层的 C/C++ 代码来说这一特性非常便于管理对象生命周期。

Binder 在 Android 中应用范围。这里有一个 basic system service 清单,清单中的 service 都是基于 binder 开发的:package manager, telephony manager, app widgets, audio services,
search manager, location manager, notification manager, accessibility
manager, connectivity manager, wifi manager, input method manager,
clipboard, status bar, window manager, sensor service, alarm manager,
content service, activity manager, power manager, surface compositor

这一段中通篇都是 Binder 多么多么好用(实用主义,有错吗?),压根没提性能问题。整个看下来之所以使用 Binder 最关键的几个考量是:

  • 安全性
    • 一条专属的安全连接
    • 充当 token,即 object identity
  • 易用性
    • 生命周期管理 (weak reference)
    • fd passing 和 object identity (很容易跟共享内存结合起来使用)
    • 组件隔离能力 (单进程模式和多进程模式之间切换)

综上,Android 团队最初选择造轮子的原因是:在保证安全性的前提下,选择易用性最好的那一个,另外性能不能太差