Android Binder原理浅析

Android 要采用 Binder 作为 IPC 机制,所以在了解Binder之前我们先来了解下什么是IPC机制先…


IPC机制


IPC是Inter-Process Communication的缩写,含义为进程间通信或者跨进程通信,是指两个进程之间进行数据交换的过程。

在Linux系统之中,进程间IPC机制包含多种,如下表格:

IPC机制 优缺点
管道 在创建时分配一个page大小的内存,缓存区大小比较有限
消息队列 信息复制需要两次,额外的CPU消耗,不合适频繁或信息量大的通信
共享内存 无须复制,共享缓冲区直接付附加到进程虚拟地址空间,速度快,但进程间的同步问题操作系统无法实现,必须各进程利用同步工具解决
套接字 作为更通用的接口,传输效率低,主要用于跨网络的通信
信号 不适用于信息交换,更适用于进程中断控制
信号量 常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源,主要作为进程间以及同一进程内不同线程之间的同步手段

Android的内核也是基于Linux内核,为何不采用Linux现有的IPC机制,而选择了Binder作为IPC机制呢?答案在下面,接着往下看…

1、从性能的角度
Binder数据拷贝只需要复制一次,而管道、消息队列、Socket都需要2次(为什么是2次,下文讲解),但共享内存方式一次内存拷贝都不需要。

2、从稳定性的角度
Binder是基于C/S架构的,什么是C/S架构呢?简单来说,就是指客户端(Client)和服务端(Server)组成的架构。客户端有什么需求的话,直接发给服务端,让服务端解决,Server端与Client端相对独立,稳定性较好。

3、从安全的角度
Android为每个安装好的应用程序分配了自己的UID(用户id),故进程的UID是鉴别进程身份的重要标志。传统的Linux系统IPC的接收方无法获得对方进程可靠的UID/PID,从而无法鉴别对方身份,而Android作为一个开源的开放体系,面对广大的开发平台,手机的安全显的格外的重要。

Android系统中对外只暴露Client端,Client端将任务发送给Server端,Server端会根据权限控制策略,判断UID/PID是否满足访问权限,目前权限控制很多时候是通过弹出权限询问对话框,让用户选择是否运行。Android 6.0之前,当用户第一次启动App的时候,所有的权限对话框一一弹出让用户进行选择,就连用不上的信息权限也会包括在内,让用户无法拒绝,一旦拒绝的话整个App也就无法使用了,如果授权的话,这不就是相当于应用可以胡作非为了嘛,所以Android 6.0之后便有了动态申请权限这东西了,需要哪个权限的时候在弹出对应的全选对话框,对权限做了更细地控制,让用户有了更多的可控性。

传统IPC只能由用户在数据包里填入UID/PID;另外,可靠的身份标记只有由IPC机制本身在内核中添加。其次传统IPC访问接入点是开放的,无法建立私有通道。从安全角度,Binder的安全性更高。

所以说,Binder相当于是Android中最具有特色的跨进程方式了。


Linux IPC 原理


Linux进程间的通信原理如下所示:

  • IPC 通信的过程如下:

IPC 过程中的通信调用方和被调用放分别称为数据发送方和数据接收方,数据发送方进程将数据放在内存缓存区,通过系统调用(系统调用是用户空间访问内核空间的唯一方式)进入内核态 。内核程序在内核空间开辟一块内核缓存区,通过 copy_from_user 函数将数据从数据发送方用户空间的内存缓存区拷贝到内核空间的内核缓存区中,数据接收方进程在自己的用户空间开辟一块内存缓存区,内核程序将内核缓存区中通过 copy_to_user 函数将数据拷贝到数据接收方进程的内存缓存区,这也就是我们上文所提到的数据为什么会复制两次(从应用层拷到内核,从内核拷到应用层)。
在这里插入图片描述
1、由于进程与进程间内存是不共享的,因此导致了进程隔离
2、 Linux 采用了虚拟地址空间技术,操作系统在逻辑上将虚拟内存分为用户空间(User Space)和内核空间(Kernel Space),普通应用程序运行在用户空间,系统内核运行在内核空间,为了控制应用程序的访问范围、保证系统安全,用户空间只能通过系统调用的方式去访问内核空间。


Binder IPC 原理


Andorid引入了Binder机制,Binder在Android中相当于是进程与进程之间的桥梁,完美的解决的进程间的通信。Binder IPC 机制中涉及到的内存映射通过 mmap() 来实现,mmap() 是操作系统中一种内存映射的方法。内存映射简单的讲就是将用户空间的一块内存区域映射到内核空间,映射关系建立后,用户对这块内存区域的修改可以直接反应到内核空间,反之内核空间对这段区域的修改也能直接反应到用户空间。内存映射能减少数据拷贝次数,实现用户空间和内核空间的高效互动。

Binder IPC 通信过程如下:
Binder 驱动在内核空间创建一个数据接收缓存区 ,然后在内核空间开辟一块内存缓存区并与数据接收缓存区建立映射关系,数据发送方通过系统调用 copy_from_user 函数将数据从内存缓存区拷贝到内核缓存区,由于内核缓存区通过数据接收缓存区跟数据接收方的内存缓存区存在间接的映射关系,相当于将数据直接拷贝到了接收方的用户空间,这样便完成了进程与进程的通信(简单来说就是数据从发送方的缓存区拷贝到内核的缓存区,而接收方的缓存区与内核的缓存区是映射到同一块物理地址的,所以只要拷一次)。
在这里插入图片描述
Binder机制是​ Android系统中进程间通讯(IPC)的一种方式,Android中ContentProvider、Intent、AIDL都是基于Binder来实现的。


Binder通信模型


一次完整的进程间通信必然至少包含两个进程,通常我们称通信的双方分别为客户端进程(Client)和服务端进程(Server),由于进程隔离机制的存在,通信双方必然需要借助 Binder 来实现。
在这里插入图片描述
由上图我们可以看出, Client、Server、Service Manager 运行在用户空间,Binder 驱动运行在内核空间。

  • Binder 驱动就如同路由器一样,是整个通信的核心,驱动负责进程之间 Binder 通信的建立,Binder 在进程之间的传递,Binder 引用计数管理,数据包在进程之间的传递和交互等一系列底层支持。
  • ServiceManager 和 DNS 类似,作用是将字符形式的 Binder 名字转化成 Client 中对该 Binder 的引用,使得 Client 能够通过 Binder 的名字获得对 Binder 实体的引用。注册了名字的 Binder 叫实名 Binder。Server 创建了 Binder,并为它起一个字符形式名字,将这个 Binder 实体连同名字一起以数据包的形式通过 Binder 驱动发送给 ServiceManager ,通知 ServiceManager 注册一个名为“xx”的 Binder,它位于某个 Server 中。驱动为这个穿越进程边界的 Binder 创建位于内核中的实体节点以及 ServiceManager 对实体的引用,将名字以及新建的引用打包传给 ServiceManager。ServiceManger 收到数据后从中取出名字和引用填入查找表。

这里需要注意的是:ServierManager 是一个进程,Server 是另一个进程,Server 向 ServiceManager 中注册 Binder 必然涉及到进程间通信。当前实现进程间通信又要用到进程间通信,这就好像蛋可以孵出鸡的前提却是要先找只鸡下蛋!Binder 的实现比较巧妙,就是预先创造一只鸡来下蛋。ServiceManager 和其他进程同样采用 Bidner 通信,ServiceManager 是 Server 端,有自己的 Binder 实体,其他进程都是 Client,需要通过这个 Binder 的引用来实现 Binder 的注册,查询和获取。ServiceManager 提供的 Binder 比较特殊,它没有名字也不需要注册。当一个进程使用 BINDERSETCONTEXT_MGR 命令将自己注册成 ServiceManager 时 ,Binder 驱动会自动为它创建 Binder 实体(这就是那只预先造好的那只鸡)。其次这个 Binder 实体的引用在所有 Client 中都固定为 0 而无需通过其它手段获得。也就是说,一个 Server 想要向 ServiceManager 注册自己的 Binder 就必须通过这个 0 号引用和 ServiceManager 的 Binder 通信。类比互联网,0 号引用就好比是域名服务器的地址,你必须预先动态或者手工配置好。要注意的是,这里说的 Client 是相对于 ServiceManager 而言的,一个进程或者应用程序可能是提供服务的 Server,但对于 ServiceManager 来说它仍然是个 Client。

  • Client 获得实名 Binder 的引用

Server 向 ServiceManager 中注册了 Binder 以后, Client 就能通过名字获得 Binder 的引用了。Client 也利用保留的 0 号引用向 ServiceManager 请求访问某个 Binder: 我申请访问名字叫张三的 Binder 引用。ServiceManager 收到这个请求后从请求数据包中取出 Binder 名称,在查找表里找到对应的条目,取出对应的 Binder 引用作为回复发送给发起请求的 Client。


Binder 中的代理模式


在这里插入图片描述

  • Service端通过Binder驱动在ServiceManager的查找表中注册Object对象的add方法
  • Client端通过Binder驱动在ServiceManager的查找表中找到Object对象的add方法,并返回proxy对象的add方法,add方法是个空实现,proxy对象也不是真正的Object对象,是通过Binder驱动封装好的代理类的add方法
  • 当Client端调用add方法时,Client端会调用proxy对象的add方法,通过Binder驱动去请求ServiceManager来找到Service端真正对象,然后调用Service端的add方法

这也就说明了原本从ServiceManager中拿到的Binder对象,通过Binder驱动层的处理之后,返回给了Client一个代理对象,如果Client和Server处于同一个进程之中,那么返回的就是当前的Binder对象,反之返回的就是Binder的代理对象。

总结下:

  1. 从进程间通信的角度看,Binder 是一种进程间通信的机制
  2. 从 Server 进程的角度看,Binder 指的是 Server 中的Binder 实体对象
  3. 从 Client 进程的角度看,Binder 指的是对 Binder 代理对象,是 Binder实体对象的一个远程代理