IPC之binder机制

时间:2023-03-09 19:47:47
IPC之binder机制

我们知道,在Android系统中,每一个应用程序都运行在独立的进程中,这也保证了当其中一个程序出现异常而不会影响另一个应用程序的正常运转。在许多情况下,我们activity都会与各种系统的service打交道,很显然,我们写的程序中activity与系统service肯定不是同一个进程,但是它们之间是怎样实现通信的呢?Binder是android中一种实现进程间通信(IPC)的方式之一。

了解Binder

1.binder用来做什么?用来实现不同进程之间的通信。

2.Binder是什么?binder属于一个驱动,工作在linux层面,运行在内核态,它的操作完成是基于一段内存。所以我们开发的程序中对binder的使用都是通过系统的调用来完成的。

3.binder是怎样实现进程通信的?我们来通过Binder的架构来了解它实现进程间通信(IPC)的过程。

1、Binder是基于OpenBinder,在Android系统上使用的进程间通信机制

Binder基于Client-Server通信模式,本质上可以理解为它实现了Client对Server对象的远程调用。比如,有某个binder对象A位于Server中,该对象提供了一套函数用以实现对服务的请求,而在一个或多个Client中包含对象A的引用,Client通过该引用可以调用远端Server中对象A的接口函数,这种远端调用对Client而言,与调用本地对象并无区别。

2、Binder架构由ServiceManager、服务端、binder驱动、客户端四个部分构成

IPC之binder机制

其中Client、Service、Service Manager运行在用户空间,Binder驱动运行在内核空间。(用户空间、内核空间是内核缓冲区的两组成部分)

ServiceManager、Binder驱动由系统负责提供,Client和Service组件由应用程序来实现。

A、ServiceManager是一个守护进程,负责管理服务,即所有的Server需要向ServiceManager注册服务。同时,ServiceManager向Client提供查询和获取Server的接口。

B、服务器端。一个Binder服务器端就是一个Binder类的对象。当创建一个Binder对象后,内部就会开启一个线程,这个线程用于接收binder驱动发送的信息,收到消息后,会执行相关的服务代码。

具体做法是:1在服务器端先生成方法代理,2再通过覆写onbind方法返回一个IBinder对象(代理的实例)

     private class MyAgent extends IService.Stub{

         @Override
         public void callMethodInService() {
             methodInService();
         }
     }

     @Override
     public IBinder onBind(Intent intent) {
         return new MyAgent();
     }

Service

C、Binder驱动。当服务端成功创建一个Binder对象后,Binder驱动也会相应创建一个mRemote对象,该对象的类型也是Binder类。客户就可以借助这个mRemote对象来访问远程服务。

D、客户端。客户端要想访问Binder的远程服务,就必须获取远程服务的Binder对象在binder驱动层对应的mRemote引用。当获取到mRemote对象的引用后,就可以调用相应Binder对象的服务了。

具体做法:1通过获得ServiceConnection实例,在onServiceConnection方法中调用服务端binder代理对象,2再在客户端通过开启service配置远程服务action和连接类实例完成与远程服务端的连接,进而通过连接类达到调用服务端方法的目的

     //绑定远程 服务
     public void bindservice(View v){
         Intent intent = new Intent();
         intent.setAction("com.itheima.rms");
         bindService(intent, new MyConnection(), BIND_AUTO_CREATE);
     }

     private IService agent;
     private class MyConnection implements ServiceConnection{

         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {

             // agent =  (IService )service
             agent = IService.Stub.asInterface(service);
         }

         @Override
         public void onServiceDisconnected(ComponentName name) {

         }

     }

Client

在这里,我们可以看到,客户端是通过Binder驱动来调用服务端的相关服务。1首先,在服务端创建一个Binder对象,2然后相应在Binder驱动中创建一个Binder对象,3接着客户端通过获取Binder驱动中Binder对象的引用来调用服务端的服务。在Binder机制中正是借着Binder驱动将不同进程间的组件bind(粘连)在一起,实现通信。

3、实现一个binder通信实例,需要经过以下步骤:

  1. 获得ServiceManager的对象引用
  2. 向ServiceManager注册新的Service
  3. 在Client中通过ServiceManager获得Service对象引用
  4. 在Client中发送请求,由Service返回结果。

4、客户端和服务端的通信可以视为两次通信:

第一次是客户端获取Binder驱动中创建的代理Binder对象引用;

第二次是客户端通过获取的代理Binder对象引用,调用服务端中的服务

5、为什么android选用Binder来实现进程间通信?

5.1 可靠性

在移动设备上,通常采用基于Client-Server的通信方式来实现互联网与设备间的内部通信。目前linux支持IPC包括传统的管道,System V IPC,即消息队列/共享内存/信号量,以及socket中只有socket支持Client-Server的通信方式。Android系统为开发者提供了丰富进程间通信的功能接口,媒体播放,传感器,无线传输。这些功能都由不同的server来管理。开发都只关心将自己应用程序的client与server的通信建立起来便可以使用这个服务。毫无疑问,如若在底层架设一套协议来实现Client-Server通信,增加了系统的复杂性。在资源有限的手机 上来实现这种复杂的环境,可靠性难以保证。

5.2 传输性能

  • socket主要用于跨网络的进程间通信和本机上进程间的通信,但传输效率低,开销大。
  • 消息队列和管道采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的一块缓存区中,然后从内核缓存区拷贝到接收方缓存区,其过程至少有两次拷贝。
  • 共享内存虽然无需拷贝,但控制复杂。

比较各种IPC方式的数据拷贝次数。

  • 共享内存:0次。
  • Binder:1次。
  • Socket/管道/消息队列:2次。

5.3 安全性

Android是一个开放式的平台,所以确保应用程序安全是很重要的。Android对每一个安装应用都分配了UID/PID,其中进程的UID是可用来鉴别进程身份。传统的只能由用户在数据包里填写UID/PID,这样不可靠,容易被恶意程序利用。而我们要求由内核来添加可靠的UID。

所以,出于可靠性、传输性、安全性。android建立了一套新的进程间通信方式。

6、ServiceManager 与实名Binder

  和DNS类似,SMgr的作用是将字符形式的Binder名字转化成Client中对该Binder的引用,使得Client能够通过Binder名字获得对Server中Binder实体的引用。注册了名字的Binder叫实名Binder,就象每个网站除了有IP地址外还有自己的网址。Server创建了Binder实体,为其取一个字符形式,可读易记的名字,将这个Binder连同名字以数据包的形式通过Binder驱动发送给SMgr,通知SMgr注册一个名叫张三的Binder,它位于某个Server中。驱动为这个穿过进程边界的Binder创建位于内核中的实体节点以及SMgr对实体的引用,将名字及新建的引用打包传递给SMgr。SMgr收数据包后,从中取出名字和引用填入一张查找表中。

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

7、Client 获得实名Binder的引用

  Server向SMgr注册了Binder实体及其名字后,Client就可以通过名字获得该Binder的引用了。Client也利用保留的0号引用向SMgr请求访问某个Binder:我申请获得名字叫张三的Binder的引用。SMgr收到这个连接请求,从请求数据包里获得Binder的名字,在查找表里找到该名字对应的条目,从条目中取出Binder的引用,将该引用作为回复发送给发起请求的Client。从面向对象的角度,这个Binder对象现在有了两个引用:一个位于SMgr中,一个位于发起请求的Client中。如果接下来有更多的Client请求该Binder,系统中就会有更多的引用指向该Binder,就象java里一个对象存在多个引用一样。而且类似的这些指向Binder的引用是强类型,从而确保只要有引用Binder实体就不会被释放掉。通过以上过程可以看出,SMgr象个火车票代售点,收集了所有火车的车票,可以通过它购买到乘坐各趟火车的票-得到某个Binder的引用。

8、匿名 Binder

  并不是所有Binder都需要注册给SMgr广而告之的。Server端可以通过已经建立的Binder连接将创建的Binder实体传给Client,当然这条已经建立的Binder连接必须是通过实名Binder实现。由于这个Binder没有向SMgr注册名字,所以是个匿名Binder。Client将会收到这个匿名Binder的引用,通过这个引用向位于Server中的实体发送请求。匿名Binder为通信双方建立一条私密通道,只要Server没有把匿名Binder发给别的进程,别的进程就无法通过穷举或猜测等任何方式获得该Binder的引用,向该Binder发送请求。

9、IBinder、BpBinder、BBinder

9.1 IBinder

  Android对Binder机制进行了抽象,定义了IBinder接口,该接口是对跨进程对象的抽象,在C/C++和Java层都有定义。IBinder定义了一套使用Binder机制来实现客户端和服务端的通信协议。

  一个普通对象只能在当前进程中被访问,若希望它被其他进程访问,就必须实现IBinder接口。IBinder接口可指向本地对象,也可指向远程对象,关键在于IBinder对象的transact函数。若IBinder指向一个服务端代理,那么transact只负责把请求发送给服务器;若IBinder指向一个服务端,那么transact只负责提供服务即可。因此,不论是服务端还是服务端代理对象,都必实现该接口,这样才能进行Binder通信

9.2 服务端代理对象BpBinder

  BpBinder是服务端代理对象,即远程对象在当前进程的代理。实际上,它也是Binder通信存在于客户端的进程,实现了IBinder接口。

  需要注意的是,transact方法是同步方法,将会挂起客户进程的当前线程,直到Service把请求处理完成并返回结果。

9.3 服务端BBinder

  服务端同样需要实现IBinder接口(具体案例见Android技术内幕·系统卷3.3.2.3 P140)

10、Binder实体对象生命周期

  1. Client进程通过ServiceManager获得一个Service组件代理对象接口;
  2. Binder驱动程序会找到与该Service组件对应的Binder实体对象,再创建一个Binder引用对象来引用它;
  3. Client进程不再引用一个Service组件时,会请求Binder驱动程序释放之前为它创建的一个Binder引用对象

11、ServiceManager启动过程

  1. 调用函数binder_open打开设备文件/dev/binder,以及将它映射到本进程的地址空间;
  2. 调用函数binder_become_context_manager将自己注册为Binder进程间通信机制的上下文管理者;
  3. 调用函数binder_loop来循环等待和处理Client进程的通信请求

http://8599981.blog.51cto.com/8589981/1404229

http://blog.****.net/yueliangniao1/article/details/7188549

http://blog.****.net/coding_glacier/article/details/7520199

http://www.linuxidc.com/Linux/2012-07/66195.htm

http://blog.****.net/chaihuasong/article/details/10071903

Android系统源代码情景分析P145