Android进阶学习——Binder是如何工作的?

随着Android相关技术的不断发展,动态化,可插拔,插件化等越来越多的高端名词不断地涌现在我们的视野当中,那么如何来学习这些技术,也就成为了Android开发工程师需要重点关注的内容。

当然,万物不离其原理,刚刚提到的这些技术,其实都基于Android系统的IPC机制,也就是我们今天的主角——Binder。

提到Binder,相信很多人都熟悉,但可能真正用到的人很少。因为,除了一些特殊需求,如保活,后台运行等之外,几乎很少用到多进程,更不用提进程间通信了。但是技术总是在不断更新的,新技术或多或少地都用到了Binder的知识,所以学习Binder的原理还是很重要的。

  • 什么是Binder?

Binde的本意是“胶水”,“黏合剂”的意思,那么对应到Android操作系统上,顾名思义,就是起到粘合不同进程的作用。在我们所熟知的Android四大组件,插件化,AIDL中,都有他的身影。

  • 为什么是Binder?

熟悉Android的人都知道,Android是基于Linux系统而来的,那么我们自然能够想到,对于Linux的IPC有管道,消息队列,共享内存,Socket等,那么为什么Android还要单独开发一个Binder呢?

首先就是性能:

 

从上面的图我们可以看到,使用了内存映射技术的Binder,他的数据拷贝次数是最小的,从而提升了通信效率,这对于一个移动操作系统来说,可是相当重要的,毕竟,哪个用户都不能忍受APP点下去长时间没反应的状况出现。

其次就是稳定性:

Binder基于C/S架构,Client的任何请求都由ServiceManager完成,并由Binder驱动实现数据传递,得益於单次数据拷贝和内存映射技术,系统可靠性也得到了大幅度的保障。

最后就是安全性:

传统的IPC方式,没有任何安全措施,完全依靠上层协议来保障,并且UID是由用户来填入的,很容易被恶意应用利用进而无法鉴别对方身份。而Binder则规避了这一点,UID由IPC在内核机制中进行添加。同时,Binder同时支持实名Binder和匿名Binder,安全性极高。

最后用一张表来展示下Binder的优势:

 

  • Linux下的进程间通信原理

这里有几个比较重要的概念需要大家先来了解一下:

     1.进程隔离:

进程隔离的意思就是对于操作系统来说,进程间的内存是不共享的,不能进行直接通信,想要通信的话就只能借助于IPC机制。

     2.进程控件划分:用户空间、内核空间

简单地讲,用户空间就是供用户进行应用运行的空间。而内核空间就是系统内核运行的空间,后者可以直接操作计算机的底层硬件。并且为了保证安全性,他们两个之间是相互隔离的。

     3.系统调用:用户态、内核态

虽然用户空间和内核空间相互隔离,但他俩之间还是不可避免地要产生一些数据交换,尤其是当用户空间访问内核空间时,例如我们所熟知的网络访问,数据存取等操作,都是通过用户空间对内核空间的访问和操作构成的。同时,只允许内核空间操作底层硬件这样的操作也对系统提供了一种保护。

在Linux系统中,有一个两级保护机制:0级供内核使用,3级供用户程序使用。

当一个进程执行系统调用进入内核代码时,此时进程处于内核态。每个进程都有自己的内核态。

当进程在执行用户代码的时候,进程处于用户态,此时CPU在最低的3级用户代码中运行。

系统调用主要通过两个命令来实现:

Copy_to_user();//从用户空间拷贝到内核空间

Copy_from_user();//从内核空间拷贝到用户空间

 

通过以上的了解,我们已经对Linux的IPC有了一个感性的认识,那么在实际的操作过程中,他究竟是怎样的一种运行机制呢?

先放一张图:

 

可以看到,在数据发送进程和数据接收进程的IPC过程中,出现了两次数据拷贝过程:

  1. 数据发送进程将数据通过copy_to_user从其用户空间拷贝到了内核空间中的内核缓存区
  2. 数据接收进程将数据通过copy_from_user从内核缓存区拷贝到了其所在的用户空间

 

这其中有两个问题:

  1. 数据的传递过程经历了两次数据拷贝,消耗、占用的资源较多,从用户的角度看,就是系统的响应速度变慢了
  2. 接收数据的缓存区由数据接收进程提供,但其并不知道需要开辟多大的空间来进行数据接收,所以只能尽可能地开辟更大的空间来满足接收需求。从用户的角度来看,就是进程所占用的内存会更大,造成比较严重的系统卡顿现象,相信这是每个开发者都不愿意看到的状况。

 

  • Binder的跨进程通信原理

首先要说的是,Binder通信是基于mmap来实现的。

传统意义上的mmap是用来在物理存储介质和用户内存空间中建立映射关系的,用以减少数据的拷贝次数,直接从内存进行读写,提升文件吞吐效率。

在Binder中并不存在物理存储介质,但我们仍可以利用他的映射特性来在内核空间和数据接收缓存空间中建立映射关系。

一次完整的Binder IPC通信过程通常是这样:

  1. Binder驱动在内核空间创建一个数据接收缓冲区
  2. 接着在内核开辟一块内核缓存区,并建立内核缓存区和内核中数据接收缓存区,以及内核中数据接收缓存区和接收用户进程之间的映射关系。
  3. 发送方进程通过copy_from_user将数据copy到内核中的内核缓存区,由于映射的关系,数据在接收方的缓存区中也是可见的,减少了传统意义上进程通信的数据接收拷贝过程,提升了数据传输效率。

流程图如下:

 

五、ServiceManager与Client、Server的纠缠

通过上面的介绍,我们已经对Binder的数据通信过程有了一个感性的认识。

在实际的Android操作系统中,除了Client、Server、Binder驱动外,还有一个在用户空间中的关键组件——ServiceManager,他的作用就相当于我们在HTTP访问过程中的DNS服务器,用来将字符形式的Binder名称转化成Client中对Binder的引用,这样,Client通过名称就可以获得对Server中Binder实体的引用。

具体的运行原理如下:

   1.Server的注册过程:

Server创建了Binder实体,并为这个实体取一个字符名字,在创建完之后,还需要申请向ServiceManager进行注册,注册完之后的Binder相当于同时在Server和ServiceManager中具有了引用对象.

值得注意的是,ServiceManager也是一个单独的进程,但他与传统意义上的进程略有区别,他的引用号是默认的,为0,这样,在它对外提供服务的时候,Client就可以通过0号引用来获得对目标Server的引用了。

   2.Client的实名Binder获取过程:

在Server向ServiceManager注册完成之后,Client就可以通过它来获取实体Binder的引用了,通过向0号进程,也就是我们的ServiceManager请求实体Binder的字符串名称,ServiceManager就可以通过查询的方式从之前注册好的表中取出Binder实体,并将这个实体引用传递给Client进行使用。

 

  • Client如何调用Server中的对象

在Client获得对Server中Binder对象的引用后,Client该如何调用Server中的对象及其方法呢?

当 A 进程想要获取 B 进程中的 object 时,驱动并不会真的把 object 返回给 A,而是返回了一个跟 object 看起来一模一样的代理对象 objectProxy,这个 objectProxy 具有和 object 一摸一样的方法,但是这些方法并没有 B 进程中 object 对象那些方法的能力,这些方法只需要把把请求参数交给驱动即可。对于 A 进程来说和直接调用 object 中的方法是一样的。

当 Binder 驱动接收到 A 进程的消息后,发现这是个 objectProxy 就去查询自己维护的表单,一查发现这是 B 进程 object 的代理对象。于是就会去通知 B 进程调用 object 的方法,并要求 B 进程把返回结果发给自己。当驱动拿到 B 进程的返回结果后就会转发给 A 进程,一次通信就完成了。

 

写在最后:Binder到底是什么?

  • 通常意义下,Binder指的是一种通信机制;我们说AIDL使用Binder进行通信,指的就是Binder这种IPC机制
  • 对于Server进程来说,Binder指的是Binder本地对象
  • 对于Client来说,Binder指的是Binder代理对象,它只是Binder本地对象的一个远程代理;对这个Binder代理对象的操作,会通过驱动最终转发到Binder本地对象上去完成;对于一个拥有Binder对象的使用者而言,它无须关心这是一个Binder代理对象还是Binder本地对象;对于代理对象的操作和对本地对象的操作对它来说没有区别。
  • 对于传输过程而言,Binder是可以进行跨进程传递的对象;Binder驱动会对具有跨进程传递能力的对象做特殊处理:自动完成代理对象和本地对象的转换。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章