Android Binder机制

为什么是Binder?

Binder架构也是采用分层架构设计, 每一层都有其不同的功能:

  • Java 应用层: 对于上层应用通过调用 AMP.startService, 完全可以不用关心底层,经过层层调用,最终必然会调用到 AMS.startService。
  • Java IPC层: Binder 通信是采用C/S架构, Android系统的基础架构便已设计好 Binder 在 Java framework 层的 Binder 客户类 BinderProxy和服务类Binder;
  • Native IPC层: 对于 Native 层,如果需要直接使用 Binder(比如 media 相关), 则可以直接使用 BpBinder 和 BBinder (当然这里还有 JavaBBinder)即可, 对于上一层 Java IPC 的通信也是基于这个层面.
  • Kernel物理层: 这里是 Binder Driver, 前面3层都跑在用户空间,对于用户空间的内存资源是不共享的,每个 Android 的进程只能运行在自己进程所拥有的虚拟地址空间, 而内核空间却是可共享的. 真正通信的核心环节还是在 Binder Driver.

各种IPC方式数据拷贝次数:

IPC数据拷贝次数
共享内存0
Binder1
Socket/管道/消息队列2

通过上表数据的对比,我们可以了解到传统IPC的一些局限性:

  • 管道和消息队列:因为采用存储转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,所以至少需要拷贝2次数据,效率低;
  • socket:作为一款通用接口,其传输效率低,开销大,主要用在跨网络的进程间通信和本机上进程间的低速通信。
  • 共享内存:虽然在传输时没有拷贝数据,但其控制机制复杂,比如跨进程通信时,需获取对方进程的pid,得多种机制协同操作,难以使用。

Binder形式的优势:

  • 有更好的传输性能。
  • 采用C/S的通信模式。而在 linux 通信机制中,目前只有socket支持C/S的通信模式,但socket有其劣势,具体参看第二条。
  • 安全性更高。 首先传统 IPC 的接收方无法获得对方进程可靠的 UID 和 PID(用户ID进程ID),从而无法鉴别对方身份。Binder 机制是由 Android 内核自动分配的,无法修改,所以安全性更高。

源码中,宏定义:#define BINDER_SERVICE_MANAGER ((void)0)*
表示 ServiceManager 对应的句柄为0,表面自己是服务器管理者。其他的Server进程句柄值都是大于0的。

Binder 通信机制使用句柄来代表远程接口,这个句柄的意义和 Windows 编程中用到的句柄是差不多的概念。前面说到,Service Manager 在充当守护进程的同时,它充当 Server 的角色,当它作为远程接口使用时,它的句柄值便为0,这就是它的特殊之处,其余的 Server 的远程接口句柄值都是一个大于0 而且由 Binder 驱动程序自动进行分配的。

几点疑问

(1)是谁,怎么样成为SM守护进程,handle为0的binder实体什么时候创建?

当一个进程使用 BINDER_SET_CONTEXT_MGR 命令将自己注册成 SM 时 Binder 驱动会自动为它创建 Binder 实体(这就是那只预先造好的鸡)。其次这个Binder的引用在所有Client中都固定为0而无须通过其它手段获得。也就是说,一个 Server 若要向 SM 注册自己 Binder 就必需通过0这个引用和 SM 的 Binder 通信。

(2)binder引用和实体是如何创建的?在驱动中如何实现的通信?

Server 创建了 Binder 实体,为其取一个字符形式,可读易记的名字,将这个 Binder 连同名字以数据包的形式通过 Binder 驱动发送给 SM,通知 SM 注册一个名叫张三的 Binder,它位于某个 Server 中。驱动为这个穿过进程边界的 Binder 创建位于内核中的实体节点以及 SM 对实体的引用,将名字及新建的引用传递给 SM。 SM 收数据包后,从中取出名字和引用填入一张查找表中。

Server 向 SM 注册了 Binder 实体及其名字后(service_manager.c),Client 就可以通过名字获得该 Binder 的引用了。Client 也利用保留的0号引用向SMgr请求访问某个 Binder:我申请获得名字叫张三的 Binder 的引用。SM 收到这个连接请求,从请求数据包里获得 Binder 的名字,在查找表里找到该名字对应的条目,从条目中取出 Binder 的引用,将该引用作为回复发送给发起请求的 Client。

(3)在SM中,binder实体是怎样转换成为引用的?

首先,XXXServer(XXX代表某个)在自己的进程中向Binder驱动申请创建一个 XXXService 的 Binder 的实体,

Binder 驱动为这个 XXXService 创建位于内核中的 Binder 实体节点以及 Binder 的引用,注意,是将名字和新建的引用打包传递给 SM(实体没有传给 SM),通知SM注册一个名叫 XXX的Service。

SM 收到数据包后,从中取出 XXXService 名字和引用,填入一张查找表中。

此时,如果有 Client 向 SM 发送申请服务 XXXService 的请求,那么SM就可以在查找表中找到该 Service 的 Binder 引用,并把 Binder 引用(XXXBpBinder)返回给 Client。

(4)Server是如何注册服务,Client是如何获取服务的?

对于Server进程,调用IServiceManager::addService这个接口来和Binder驱动进行交互,即BpServiceManager::addService。然后BpServiceManager::addService通过调用其基类BpRefBase的成员函数remote获取原先创建的BpBinder实例;接着调用BpBinder::transact成员函数。在transact函数中,又会调用IPCThreadState::transact成员函数与Binder驱动程序进行交互。原因是IPCThreadState的成员变量mProcess是ProcessState类型,其可以打开Binder设备。

对于 Client 进程,调用 IServiceManager::getService 接口来和 Binder 驱动进行交互。后续步骤跟 Server 进程一样。

对一般的 Service 组件来说,Client 进程首先要通过 Binder 驱动程序来获取它的一个句柄值,然后通过这个句柄创建一个 Binder 代理对象,最后将这个 Binder 代理对象封装成一个实现特定接口的代理对象。由于 ServiceManager 的句柄值为0,因此获取它的代理对象不需要跟 Binder 驱动程序进程交互。

(5)为什么Binder机制只用一次拷贝?

Binder 机制会同时使用进程虚拟地址空间和内核虚拟地址空间来映射同一个物理页面。这也是 Binder 进程间通信机制的精髓所在了,同一个物理页面,一方映射到进程虚拟地址空间,一方面映射到内核虚拟地址空间,这样,进程和内核之间就可以减少一次内存拷贝了,提到了进程间通信效率。举个例子如,Client 要将一块内存数据传递给 Server,一般的做法是,Client 将这块数据从它的进程空间拷贝到内核空间中,然后内核再将这个数据从内核空间拷贝到 Server 的进程空间,这样,Server 就可以访问这个数据了。但是在这种方法中,执行了两次内存拷贝操作,而采用我们上面提到的方法,只需要把 Client 进程空间的数据拷贝一次到内核空间,然后 Server 与内核共享这个数据就可以了,整个过程只需要执行一次内存拷贝,提高了效率。

参考

文章目录
  1. 1. 为什么是Binder?
  2. 2. 几点疑问
  3. 3. 参考
|