背景

在理解 Binder 的实现机制之前,需要先回顾一些操作系统方面的知识

多任务,进程和线程

多任务是同时执行程序或多个进程的能力。操作系统会为每个二进制可执行文件创建一个具有自己的堆,栈,数据和共享库的特定内存帧。操作系统还分配了特殊的内部管理结构,可以称为进程。

操作系统必须提供合理的比例,因为只有一个进程可以同时使用CPU(多核CPU下就是一个核心)。所有进程都必须是可中断的。操作系统使它们进入睡眠状态或在其时隙中唤醒它们。这项工作由调度程序完成,它为每个进程提供了最佳的时隙。

线程是在内存中没有自己地址空间的进程,它与父进程共享地址空间。进程可以具有子线程,并且线程必须属于一个进程。

进程隔离

出于安全性考虑,一个进程不能操纵另一进程的数据。因此,操作系统必须实现进程隔离的功能。在 Linux 中,虚拟内存机制通过给每个进程分配给一个线性且连续的虚拟内存空间来实现进程隔离。该虚拟内存空间由操作系统映射到物理内存。每个进程都有自己的虚拟内存空间,一个进程无法操纵另一个进程的内存空间。进程只能访问自己的虚拟内存。只有操作系统可以访问物理内存。

进程隔离可确保每个进程内存的安全性,但是在许多情况下,进程之间是需要通信的。操作系统则必须提供运行进程间通信的机制。

用户空间和内核空间

现代操作系统都分为用户空间和内核空间,又称用户态和内核态。其目的是让访问系统资源等高风险的操作限制在内核态执行。

普通进程就是运行在用户态的,这也就意味着它们无权访问物理内存或设备。更抽象地讲,操作系统安全边界的概念引入了ring一词。ring 是必须要硬件支持的功能。某组权限会分配给 ring。 英特尔硬件支持四个 ring,但是 Linux 仅使用两个 ring。它们是具有完整权限的ring0和具有最少权限的 ring3ring1ring2 未使用。系统进程在ring0 中运行,而用户进程在 ring3 中运行。如果一个进程需要更高的特权,则它必须执行从ring3ring0 的转换。该转换会通过一个网关,该网关对参数进行安全检查。这种转换也被称为系统调用(sys call),并且会产生一定量的计算开销。

这里有对处理器 ring 的介绍: https://stackoverflow.com/questions/18717016/what-are-ring-0-and-ring-3-in-the-context-of-operating-systems

Linux 上的 IPC

Android 为什么使用 Binder

  1. Android 的 libc 不支持 System V IPC(SysV IPC)

https://android.googlesource.com/platform/ndk/+/4e159d95ebf23b5f72bb707b0cb1518ef96b3d03/docs/system/libc/SYSV-IPC.TXT

SysV IPC: System V (SysV) UNIX 规范描述了以下三种 IPC 机制,它们统称为 SysV IPC

  • 消息队列
  • 信号量
  • 共享内存
  1. 传统 IPC 不容易控制
  2. 当进程容易忘记在终止时释放共享的IPC资源时
  3. System V IPC 易发生内核资源泄漏

Android 的内核

Android 的内核是在 Linux 2.6 的基础上扩展出了 Alarm, Ashnen, Binder, Power management, Low Memeory Killer 等模块。

Binder

相关术语

  • Binder (Framework):整体 IPC 架构的总称
  • Binder Driver:内核级的 Binder 驱动程序
  • Binder Protocol:用于与 Binder 驱动程序通信的低级协议(基于ioctl)
  • IBinder Interface:Binder 对象必须实现的明确定义的方法
  • AIDL:Android Interface Definition Language 用于描述 IBinder Interface 上的业务操作
  • Binder (Object):IBinder Interface 的通用实现
  • Binder Token:一个抽象的32位整数值,该值唯一地标识系统上所有进程中的Binder对象
  • Binder Service:Binder (Object) 的实际实现,用于实现业务操作
  • Binder Client:想要利用 binder 服务提供的行为的对象
  • Binder Transaction:通过 Binder 协议在远程 Binder 对象上调用方法的行为,可能涉及发送/接收数据
  • Parcel:“可以通过 IBinder 发送的消息(数据和对象引用)的容器。” 是 Binder 事务操作的数据单位
  • Marshalling:将应用程序数据结构(即请求/响应参数)转换为小包以便将其嵌入到 Binder 事务中的过程
  • Ummarshalling:从通过 Binder 事务接收的包裹中重建应用程序数据结构(即请求/响应参数)的过程
  • Proxy:AIDL接口的实现,用于重建或转换数据,并将方法调用通过引用了 Binder 对象的包装的 IBinder 映射到事务提交上
  • Stub:AIDL接口的部分实现,在重建或转换数据时将事务映射到 Binder Service 的方法调用上
  • Context Manager(a.k.a servicemanger):具有已知句柄(注册为 handler 0)的特殊 Binder 对象,用作其他Binder对象(名称→句柄的映射)的注册表或查找服务

内核实现

TODO

JNI 层实现

TODO

Java 层实现

ServiceManager

TODO

TransactionTooLargeException

当使用 Intent 或其他基于 Binder 的 IPC 通信时, 如果传递的数据太大, 就容易触发 TransactionTooLargeException 异常。

通过文档可大概了解到, TransactionTooLargeException 出现的原因是由于每个进程 IPC 通信的位于内核的 Binder buffer 大小现在固定的为 1M,同一个进程中的所有 Binder IPC 通信都使用的这同一个 buffer。比如客户端发送给服务端的 parcel 数据需要通过 buffer 传递给内核,然后内核传递给服务端进程;而服务端发送给客户端的 parcel 数据也需要通过 buffer 传递给内核,然后内核传递到客户端进程。如果一个进程同时进行多个 Binder IPC,传递的数据就可能超过 buffer 的容量,所以抛出了这个异常。