Binder原理解析

230 阅读6分钟

Binder

进程空间划分

  • 用户空间:不可共享空间
  • 内核空间:可共享空间,所有进程共用一个内核空间

用户空间和内核空间之间的交互

  • copy_from_user():将用户空间数据拷贝到内核空间
  • copy_to_user():将内核空间数据拷贝到用户空间

Binder模型

角色作用备注
Client进程使用服务进程客户端
Server进程提供服务进程服务端
ServiceManager进程管理Server注册,查询将字符串形式的Binder名字转换成具体引用
Binder驱动1.用于连接Client进程,Server进程以及ServiceManager进程的虚拟驱动
2.通过内存映射(mmap)实现进程之间的数据传递
3.实现线程控制,Binder内部对线程池进行管理
Binder驱动持有每一个Server进程在内核空间的Binder实例,并提供给Client进程实例的引用

工作流程

  1. Server进程注册服务

    1. Server进程Binder驱动发起注册服务请求
    2. Binder驱动将Server信息转发给ServiceManager进程
    3. ServiceManager进程进行注册
  2. Client进程获取服务

    1. Client进程通过服务名称Binder驱动获取Server服务
    2. Binder驱动将请求转发ServiceManager进程
    3. ServiceManager通过服务名称进行查询,如果查询到就向Binder驱动发送对应的Server服务
    4. Binder将ServiceManager发送的Server进程返回给Client进程
    5. 此时Client进程Server进程建立关系
  3. 创建接收缓存区

    1. Binder驱动创建出接收缓存区

    2. 通过mmap(),实现地址映射关系

      1. 将通过ServiceManager查找到的Server进程内核缓存区接收缓存区进行内存地址映射

        只是建立了内存地址映射,无法传输数据

  4. Client进程开始请求

    1. Client进程通过系统进行调用copy_from_user()方法发送数据给内核缓存区并挂起当前线程

    2. Binder驱动通知Server进程进行解包

      由于内核缓存区接收缓存区都与Server进程用户地址区域存在地址映射,所以通过copy_from_user()方法,就相当于发送到了Server进程用户地址区域

  5. Server进程进行处理

    1. Server进程通过自己内部的线程进行解包,并且调用目标方法

    2. 将结果写入至自己的用户地址区域

      由于内核缓存区接收缓存区都与Server进程用户地址区域存在地址映射,就相当于发送到了内核缓存区

  6. Client进程获取数据

    1. Binder驱动唤醒Client进程处理的线程
    2. Client线程通过系统调用copy_to_user()方法获取内核缓存区映射的Server进程的结果数据

Binder有自己的线程池,内部默认为16个,超出数量的请求会被阻塞,知道有空闲Binder线程处理

Client进程,Server进程,ServiceManager都是用户进程,不能直接调用,都必须通过Binder驱动调用

aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzk0NDM2NS1iNDcwMDhhMDkyNjViOWM2LnBuZz9pbWFnZU1vZ3IyL2F1dG8tb3JpZW50L3N0cmlwJTdDaW1hZ2VWaWV3Mi8yL3cvMTI0MA.png

代码执行流程

aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzk0NDM2NS0yZjUzMGU5NjRmZmFiOGQ3LnBuZz9pbWFnZU1vZ3IyL2F1dG8tb3JpZW50L3N0cmlwJTdDaW1hZ2VWaWV3Mi8yL3cvMTI0MA.png

  1. Client进程将需要传输的数据写入到Parcel之中,也就是目标方法的参数以及接口标识符
  2. 通过代理对象的transact()方法,将数据发送给Binder驱动
  3. 当前Client线程被挂起
  4. Binder驱动根据代理对象找到对应的Server进程
  5. 发通至对应的Server进程,并通知进行数据解包
  6. Server进程从当前线程池里取出处理线程,调用onTransact()方法进行解包,并通过标识符执行对应的方法,返回结果
  7. Binder驱动根据代理对象返回结果
  8. 唤醒Clent进程处理线程,并勇敢代理对象获取到结果

代码流程结构

  1. Server进程的定义

    • interface是进程间通信定义的通用接口,通过定义接口,然后再服务端实现接口、客户端调用接口,就可实现跨进程通信,内部只有一个方法,返回当前接口关联的 Binder 对象
    • 继承自interface的实现接口内部定义需要实现的接口方法,即Client进程需要调用的方法
    public interface IIntf extends IInterface {
             public int fun(int a,int b);
    }
    public interface IInterface
    {
        public IBinder asBinder();
    }
    
  2. Server进程的实现

    • interface实现类实现了定义的方法的具体实现,并且返回了IBinder
    IInterface iintf = new IIntf(){
        //定义的方法
         public int add(int a,int b) {
              return a+b;
         }
        //转为Binder
         public IBinder asBinder(){ 
              return myBinder ;
         }
    };
    
  3. Server进程的注册

    • 将(parameter,descriptor)作为(key,value)对存入到Binder对象中的一个Map<String,IInterface>对象中
    • Binder对象 可根据descriptor通过queryLocalIInterface()获得对应IInterface对象(即plus)的引用,可依靠该引用完成对请求方法的调用
    //向Binder驱动注册服务(最终到ServiceManager中注册)
    binder.attachInterface(iintf,"descriptor");
    
  4. Server进程的处理

    • Binder的子类stub实现了onTransact()方法
    • 用于执行Client进程所请求的目标方法
    • code:Client进程请求方法标识符。即Server进程根据该标识确定所请求的目标方法
    • data:目标方法的参数。
    • reply:目标方法执行后的结果,最终返回给Client进程
    • 当Client进程发起远程请求时,远程请求会要求系统底层调用该方法,运行在Server进程的Binder线程池中
     public class Stub extends Binder {
              @Override
              boolean onTransact(int code, Parcel data, Parcel reply, int flags){
              switch (code) { 
                    case Stub.add: { 
                           //解包数据
                           data.enforceInterface("descriptor"); 
                           int  arg0  = data.readInt();
                           int  arg1  = data.readInt();
                           //通过参数获取真正执行方法的代理
                           int  result = this.queryLocalIInterface("descriptor").fun(arg0,arg1); 
                           //写入返回值
                           reply.writeInt(result); 
                           return true; 
                      }
               } 
          return super.onTransact(code, data, reply, flags); 
    }
    
    • 方法内部首先会通过code进行过滤操作,在指定操作中,通过queryLocalInterface(data.enforceInterface)获取IInterface对象,再把对应的参数获取并传入,最终把返回值写至reply对象中。
  5. Client进程获取服务

    • 通过bindServer()绑定Server进程中的Server
    • 在onServiceConnected()回调之中,得到BinderProxy对象
    private void bindRemoteService() {
            // 远程服务具体名称
            ComponentName componentName = new ComponentName(this, "com.me.dmn.mybinder.server.MyService");
            Intent intent = new Intent();
            intent.setComponent(componentName);
            // 绑定到服务
            bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
        }
     
        ServiceConnection serviceConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
                // 持有binder的引用
                mBinder = iBinder;
                tvConnectState.setText("Connected");
            }
     
            @Override
            public void onServiceDisconnected(ComponentName componentName) {}
        };
    
  6. Client使用服务

    • 将需要传送的数据写入到Parcel对象中,目标方法的参数+IInterface接口对象的标识符descriptor
    • 通过调用代理对象的transact() 将数据发送到Binder驱动,在发送数据后,Client进程的该线程会暂时被挂起
    • Binder驱动根据代理对象找到对应的真身Binder对象所在的Server 进程(系统自动执行)
    • Binder驱动把数据发送到Server 进程中,并通知Server进程执行解包(系统自动执行)
    • Binder驱动根据代理对象沿原路将结果返回并通知Client进程获取返回结果,之前挂起的线程被唤醒
    //将需要传送的数据写入到Parcel对象中
    android.os.Parcel data = android.os.Parcel.obtain();
    data.writeInt(1); 
    data.writeInt(2); 
    data.writeInterfaceToken("descriptor");;
    android.os.Parcel reply = android.os.Parcel.obtain();
    
    //将数据发送到Binder驱动
    binderproxy.transact(Stub.add, data, reply, 0)
    
    //获取数据
    binderproxy.transact(Stub.ADD, data, reply, 0);
    reply.readException();
    result = reply.readInt();