Android Binder 原理解析

一.跨进程通信

跨进程通信(IPC)是指,不同进程间不能互相直接访问,那么操作系统是如何做到的呢?

1.进程隔离

进程隔离是操作系统为了保护进程之间不互相干扰而设计的,避免进程A写入进程B的情况发生,其实现使用了虚拟地址空间,两个进程虚拟地址不同,就可以防止A进程写入数据岛B进程。

也就是说,操作系统的不同进程之间,数据不共享,在每个进程看来,自己都独享了整个系统空间,完全不知道其他进程的存在,因此一个进程想要与另一个进程进行通信,需要某种系统机制才能完成。

2.用户空间/内核空间

Linux Kernel是操作系统的核心,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。

对于Kernel这么一个高安全级别的东西,显然是不容许其它的应用程序随便调用或访问的,所以需要对Kernel提供一定的保护机制,这个保护机制用来告诉那些应用程序,你只可以访问某些许可的资源,不许可的资源是不能被访问的,于是操作系统就把Kernel和上层的应用程序抽像的隔离开,分别称之为Kernel Space和User Space,即内核空间和用户空间。

3.系统调用/内核态/用户态

虽然从逻辑上抽离出用户空间和内核空间,但是不可避免的是,总有那么一些用户空间需要访问内核的资源:比如应用程序访问文件、网络是很常见的事情,怎么办呢?

用户空间访问内核空间的唯一方式就是系统调用,通过这个统一入口,所有的资源访问都是在内核的控制下执行,以免导致用户程序对系统资源的越权访问,从而保障了系统的安全和稳定。

当一个进程执行系统调用,陷入内核代码中执行时,我们就称进程处于内核态,此时处理器处于特权级最高的内核代码中执行。当进程在执行用户自己的代码时,则称其处于用户态,此时处理器在特权级最低的用户代码中运行。处理器在特权等级高的时候才能执行那些特权CPU指令。

4.内核模块/驱动

通过系统调用,用户空间可以访问内核空间,那么如果一个用户空间想与另外一个用户空间进行通信怎么办呢?

很自然想到的是让操作系统内核添加支持,传统的Linux通信机制,比如Socket、管道等都是内核支持的,但是Binder并不是Linux内核的一部分,那它是怎么做到访问内核空间的呢?

Linux的动态可加载内核模块机制解决了这个问题:模块是具有独立功能的程序,它可以被单独编译,但不能独立运行,它在运行时被链接到内核作为内核的一部分运行在内核空间。这样,Android系统可以通过添加一个运行在内核空间的内核模块,用户进程之间通过这个模块作为桥梁,就可以完成通信了。

在Android系统中,这个运行在内核空间,负责各个用户进程之间通信的内核模块叫做Binder驱动(驱动就是操作硬件的接口)。
在这里插入图片描述

二.为何使用Binder

Android使用的Linux系统内核,拥有着非常多的跨进程通信机制,比如管道、Socket等,为什么还需要单独搞一个Binder出来呢?主要有两点,性能和安全。

  1. Binder相对于传统的Socket方式,更加高效

  2. 比如Socket通信ip地址是客户端手动填入的,都可以进行伪造,而Binder机制从协议本身就支持对通信双方做身份校检,因而大大提升了安全性

三.Binder通信原理

下面我们来看看Binder是如何实现跨进程通信的。

Android系统中有许多的服务,比如Clipboard剪切板服务、ActivityManagerService服务、Vibrator针对服务等等,这些服务都是独立的进程,通常我们的客户端程序想要使用这些系统服务,会通过ServiceManager获取对应的接口对象进行访问,这时就需要跨进程通信,也就是需要借助Binder了,那Binder的通信流程是怎样的呢?

我们把每一个服务端称为Server端,访问他的客户端称为Client端,存储Server端信息的ServiceManager称为SM,假设Client端想要访问一个叫做Test的Server端,调用其A对象的add方法,其过程如下:

  1. 首先,当Server端进程初始化时,要向SM中注册自己的信息,类似于一个map的结构:我叫Test,有一个A对象,其中有一个add方法

  2. Client端想要访问时,先向SM端查询,是否有一个叫Test的Server端,我要请求里面的A对象,此时是有的,并要将"A对象"返回

  3. 上面说过,进程间通信时,数据会流经内核控件中的Binder驱动,此时驱动会对要传递的数据做一些手脚:如果是同一个进程要获取对象,则返回对象本身接口(不需要做跨进程通信);如果是不同进程要获取对象,则将返回一个和A对象看似一样的AProxy代理对象,这个代理对象也有add方法,其方法只是包装参数,回调给Binder驱动;此处Android的设计是使用代理模式。

  4. Client端拿到这个和A"一样"的AProxy对象,无需关注其是否为代理对象,照常调用add方法传递参数

  5. 如上所述,代理对象AProxy内部的add方法会直接交给Binder驱动处理

  6. Binder驱动拿到数据,并知道AProxy对应的真实Server端其实是Test的A对象(数据流经时Binder驱动会记录每个Server端及其对象的信息),于是乎,调用Test端的A对象的add方法,并将参数传递即可

  7. Test端将add方法的返回结果返回给Binder驱动,Binder驱动再返回给对应的Client端的调用处,即可完成一次跨进程通信

综上我们可以发现:

  • Binder机制使用代理模式,在Server端的对象是实际对象,其他各个进程端所持有的都是Proxy代理对象,由代理对象和Binder驱动完成数据传递

  • Proxy和实际对象具有同样的接口来实现数据通信,而不用关注其实现细节,因此让我们觉得Binder对象可以直接进行跨进程调用,其实是个数据中转的过程

初次之外,SM也是一个单独的进程,其与各个Server端的注册通信,也是通过同样的Binder跨进程机制完成的,Binder驱动也会对这个过程动手脚,也就是说,SM存放的各个Server端的对象,也都是Proxy代理对象。

一句话总结Binder机制就是:Client进程持有了Server端对象的代理;代理对象系统Binder驱动完成了跨进程通信。
在这里插入图片描述

四.Binder实现

知道了Binder通信的原理和流程,下面我们来看看java层是如何支持Binder的吧。

1.IBinder/IInterface

Java层提供了IBinder、IInterface两个接口

  • IBinder:提供了Binder跨进程通信的基础方法,如transact()方法用来传递数据;如queryLocalInterface()方法用来获取实际功能对象

  • IInterface:提供了Binder通信对象的基本类型,我们Server端所提供的功能对象都要实现该接口,其asBinder()方法可以获取对应的Binder对象,也是可以用来传输Binder对象的类型标识

2.Binder/BinderProxy

(1)Binder

我们要实现一个Server端的提供服务的对象,首先要实现IBinder接口,对Server端来说,其提供服务的对象都是实际的Binder对象(也叫本地对象)而不是代理对象,而Binder类就是实现了部分Server端本地对象的处理逻辑的类,也就是说Binder类就是Server端本地对象的基类,我们来看看它实现了哪些处理逻辑:

public class Binder implements IBinder {
   
  private IInterface mOwner;
  private String mDescriptor;
  
  public void attachInterface(IInterface owner, String descriptor) {
   
    mOwner = owner;
    mDescriptor = descriptor;
  }
  
  @Override
  public IInterface queryLocalInterface(String descriptor) {
   
    if (mDescriptor.equals(descriptor)) {
   
      return mOwner;
    }
    return null;
  }
  
  @Override
  public final boolean transact(int code, Parcel data, Parcel reply,
                                int flags) throws RemoteException {
   
    if (data != null) {
   
      data.setDataPosition(0);
    }
    boolean r = onTransact(code, data, reply, flags);
    if (reply != null) {
   
      reply.setDataPosition(0);
    }
    return r;
  }
  
  protected boolean onTransact(int code, Parcel data, Parcel reply,
                               int flags) throws RemoteException {
   
    if (code == INTERFACE_TRANSACTION) {
   
      reply.writeString(getInterfaceDescriptor());
      return true;
    } else if (code == DUMP_TRANSACTION) {
   
      ...
      return true;
    } else if (code == SHELL_COMMAND_TRANSACTION) {
   
      ...
      return true;
    }
    return false;
  }
}
  1. queryLocalInterface()方法里面判断,如果descriptor,也就是IInterface(服务对象)的类名相同的话,就会返回owner,而这个owner和descriptor是服务对象初始化时通过attachInterface()方法传递进来的,也就是说,如果调用服务对象本身的该方法,拿到的就是实际的服务对象本身(本地对象),因为是同一个进程不需要进行跨进程通信

  2. transact()方法,将要传递的数据,包括参数以及要调用的方法(一个int协议值)通过序列化Parcel的方式,传递并调用onTransact()方法

  3. onTransact()方法就是Server端的Binder本地对象要处理的入口方法,其会根据方法的协议int值,决定调用本地对象的哪个方法

(2)BinderProxy

Server端的Binder对象有了,下面来看看Client端的Binder对象,上面说过了,Client端拿到的Binder对象是一个代理对象BinderProxy,由Binder驱动生成并返回,我们就来看看这个类是如何进行跨进程通信的。

final class BinderProxy implements IBinder {
   
  
  	@Override
		public IInterface queryLocalInterface(String descriptor) {
   
        return null;
    }

  	@Override
    public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
   
        ...
        try {
   
            return transactNative(code, data, reply, flags);
        } finally {
   
            if (tracingEnabled) {
   
                Trace.traceEnd(Trace.TRACE_TAG_ALWAYS);
            }
        }
    }
  
    public native boolean transactNative(int code, Parcel data, Parcel reply,
              int flags) throws RemoteException;
}

我们可以看到,BinderProxy也实现了IBinder接口,说明其可以和Binder对象一样进行跨进程通信。

  1. queryLocalInterface()方法,这里由于是本地Binder对象的代理类,所以并不是本地对象,所以这里返回null,需要通过转换为IInterface对

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 13
    评论
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值