Binder笔记

  • 进程隔离:一个进程占一个内存空间,各个进程的内存空间完全隔离,数据不共享。
  • 内核空间(Kernel)是系统内核运行的空间,用户空间(User Space)是用户程序运行的空间。为了保证安全性,它们之间是隔离的。
  • Linux Kernel独立于普通的应用程序,可以访问任何内存空间。
  • 应用程序访问内核的唯一方式就是系统调用,当应用程序发起系统调用时,内核会检查应用程序是否越权访问,若否,则执行内核代码。当应用程序成功调用起内核代码时则称应用程序的进程处于内核态,否则处于用户态。
  • 传统的 IPC 机制如管道、Socket,它们本身就是内核的一部分,所以通过内核来支持进程间通信自然是没问题的。但是 Binder 并不是 Linux 系统内核的一部分,那怎么办呢?这时候就通过动态添加一个内核模块,并由该模块支持Binder IPC,然后两个用户进程就可以以该模块为桥梁进行通信了。我们把这个模块称为Binder驱动。
  • Client进程和Server进程通信时ServiceManager相当于通讯录,Binder驱动相当于基站
  • Binder的通信模型如下图:
    这里写图片描述

    整个通信步骤如下:

    1、SM建立(建立通信录);首先有一个进程向驱动提出申请为SM;驱动同意之后,SM进程负责管理Service(注意这里是Service而不是Server,因为如果通信过程反过来的话,那么原来的客户端Client也会成为服务端Server)不过这时候通信录还是空的,一个号码都没有。

    2、各个Server向SM注册(完善通信录);每个Server端进程启动之后,向SM报告,我是zhangsan, 要找我请返回0x1234(这个地址没有实际意义,类比);其他Server进程依次如此;这样SM就建立了一张表,对应着各个Server的名字和地址;就好比B与A见面了,说存个我的号码吧,以后找我拨打10086;

    3、Client想要与Server通信,首先询问SM;请告诉我如何联系zhangsan,SM收到后给他一个号码0x1234;Client收到之后,开心滴用这个号码拨通了Server的电话,于是就开始通信了。

  • 从面向对象的角度来说,Client进程获得了Server进程的Binder的远程引用后才能进行IPC通信的。可是每个Server进程生成的Binder对象的内存地址都是随机的,Client是无法知道这个Binder地址的,怎么办?所以才需要一个SM进程,Server启动时将自己Binder引用注册到SM中,Client进程再从SM中查询获取。但是这样又出现了一个问题:不论是Client进程向SM查询,还是Server进程向SM注册,也都要先获得SM进程的Binder的远程引用才行。那么SM进程的Binder的远程引用从哪里来的呢?原来当一个进程使用BINDER_SET_CONTEXT_MGR命令将自己注册成SM时Binder驱动会自动为它创建Binder实体,其次这个Binder的引用在其它进程中都固定为0而无须通过其它手段获得。也就是说,一个Server若要向SM注册自己Binder就必需通过0这个引用号和SM的Binder通信,Client也只能利用保留的0号引用向SM请求访问某个Binder。

  • 上述提到的0引用号,就是指binder_ref中的handle = 0(即:binder_ref.desc)。ServiceManager是第一个Server进程。之后有新的进程启动时,Binder驱动会立即为它分配一个handle = 0的binder_ref,该binder_ref指向ServiceManager的binder_node。然后该进程若想访问其它server进程时,则Binder驱动会再给它分配一个handler比原先大1的binder_ref,所以进程中handle = 0的binder_ref必然是指向ServiceManager的binder_node的,也就无需再查询了。
  • Binder实体只存在于Server进程,Client进程只不过是持有了Server端的代理而已。对于同一个Binder实体,一个Client进程只会持有它的一个Binder代理。但是一个Binder实体,可以存在多个Binder代理,即多个Client进程都持有它的Binder代理。
  • 在Binder驱动中保存了Binder实体和Binder代理的映射关系。在驱动中,Binder本地对象的代表是一个叫做binder_node的数据结构,Binder代理对象是用binder_ref代表的;有的地方把Binder本地对象直接称作Binder实体,把Binder代理对象直接称作Binder引用(句柄),其实指的是Binder对象在驱动里面的表现形式
  • 在Binder驱动中是如何维护Binder实体和Binder代理的映射关系的
  • IBinder是一个接口,只要实现了这个接口,就表示该对象可以跨进程传输数据。Binder和BinderProxy均实现了IBinder,所以它们都可以跨进程传输数据。有些文章中认为实现了IBinder,就表示该对象可以被跨进程传递,其实是不对的,因为不论是Binder还是BinderProxy都没有从一个进程被传递到另一个进程中,Binder始终是在Server进程,BinderProxy始终是在Client进程。真正被跨进程传输的只有接口方法的参数和返回值。
  • BinderProxy可以理解成Binder的远程引用。
  • 这里写图片描述
    我们编写的ICompute.aidl文件经过编译后会生成接口文件ICompute.java。
public interface ICompute extends android.os.IInterface {
    ...
    public int add(int a, int b) throws android.os.RemoteException;
}

这里Stub和Stub.Proxy均实现了接口ICompute,但目的不同。Stub实现ICompute是为了提供具体的实现步骤,而Stub.Proxy实现ICompute则为了封装数据并传递给Binder驱动。

客户端调用代理IBinder.transact(),Binder驱动会转换成实体IBinder.transact()。实体IBinder接口的实现类是Binder,在Binder的transact()如下:

public final boolean transact(int code, Parcel data, Parcel reply,
            int flags) throws RemoteException {
        if (false) Log.v("Binder", "Transact: " + code + " to " + this);

        if (data != null) {
            data.setDataPosition(0);
        }
        boolean r = onTransact(code, data, reply, flags);
        if (reply != null) {
            reply.setDataPosition(0);
        }
        return r;
    }

Stub会重写onTransact(),并且在该方法中调用ICompute.java中的接口方法。返回值最终会通过Binder驱动传递到客户端的代理IBinder,作为代理IBinder的返回值返回。

  • 这种传统的 IPC 通信方式有两个问题:

性能低下,一次数据传递需要经历:内存缓存区 –> 内核缓存区 –> 内存缓存区,需要 2 次数据拷贝;

接收数据的缓存区由数据接收进程提供,但是接收进程并不知道需要多大的空间来存放将要传递过来的数据,因此只能开辟尽可能大的内存空间或者先调用 API 接收消息头来获取消息体的大小,这两种做法不是浪费空间就是浪费时间。

  • 内存映射:Binder IPC 机制中涉及到的内存映射通过 mmap() 来实现,mmap() 是操作系统中一种内存映射的方法。内存映射简单的讲就是将用户空间与内核空间的内存区域建立起映射。映射关系建立后,用户对这块内存区域的修改可以直接反应到内核空间;反之内核空间对这段区域的修改也能直接反应到用户空间。
  • Binder IPC通过内存映射减少了一次的拷贝,且内核程序可以访问接收进程的内存空间,并直接在接收进程开辟出所需大小的空间,最后将该空间与内核中的数据接收缓存区建立映射。从而解决了传统IPC机制的两个痛点。简而言之,传统的IPC是接收进程访问内核获取所需的内存大小,然后自己再开辟出空间。现在是,内核直接在接收进程开辟出了大小合适的内存空间。
  • 在Binder IPC通信时,之所以要在内核空间中开辟出内核缓存区数据接收缓存区,是因为在进程间通信时为了防止读写错乱,而给每个进程分别开辟两个缓存区,一个专门“读”,一个专门“写”。内核缓存区开辟出来专门给数据发送进程写数据,数据接收缓存区开辟出来专门给数据接收进程读数据。
  • Binder框架定义了四个角色:Server,Client,ServiceManager(以后简称SMgr)以及Binder驱动。其中 Server,Client,SMgr 运行于用户空间,驱动运行于内核空间。这四个角色的关系和互联网类似:Server是服务器,Client是客户终端,SMgr是域名服务器(DNS),驱动是路由器。
    这里写图片描述
    这里写图片描述
  • SM是开机的时候通过init.rc文件启动的,这就保证了它是系统中第一个注册成服务大管家的Server,而WMS,AMS这些则是在SystemServer中启动的

  • 获取SM服务,调用ServiceManager.getService(name),比如ServiceManager.getService("activity");

public static IBinder getService(String name) {
        try {
            IBinder service = sCache.get(name);
            if (service != null) {
                return service;
            } else {
                return getIServiceManager().getService(name);
            }
        } catch (RemoteException e) {
            Log.e(TAG, "error in getService", e);
        }
        return null;
    }

private static IServiceManager getIServiceManager() {
        if (sServiceManager != null) {
            return sServiceManager;
        }

        // Find the service manager
        sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
        //其中BinderInternal.getContextObject()是native方法:
        //public static final native IBinder getContextObject();
        return sServiceManager;
    }

static public IServiceManager asInterface(IBinder obj)
    {
        if (obj == null) {
            return null;
        }
        IServiceManager in =
            (IServiceManager)obj.queryLocalInterface(descriptor);
        if (in != null) {
            return in;
        }

        return new ServiceManagerProxy(obj);
    }

public IBinder getService(String var1) throws RemoteException {
        Parcel var2 = Parcel.obtain();
        Parcel var3 = Parcel.obtain();
        var2.writeInterfaceToken("android.os.IServiceManager");
        var2.writeString(var1);
        this.mRemote.transact(1, var2, var3, 0);
        IBinder var5 = var3.readStrongBinder();
        var3.recycle();
        var2.recycle();
        return var5;
    }
  • BinderInternal.getContextObject()
public static final native IBinder getContextObject();
sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& /*caller*/)
{
 return getStrongProxyForHandle(0);  
}

因为和Binder驱动打交道,最终都得通过JNI调用本地代码来实现。
而在native层中getContextObject方法通过ProcessState把 创建的对象BpBinder转换为Java层的IBinder对象。

IBinder只是一个接口类,
在native层,IBinder实现类为BpBinder(由ProcessState创建)
而在Java层,IBinder实现类则是BinderProxy。

  • Binder驱动为每个进程分配一个数据结构binder_proc,进程中每个用到了Binder通信的线程,Binder驱动也会为该线程分配一个数据结构binder_thread
  • Binder 服务在调用期间抛出了RuntimeException异常,服务端会Crash么?
    服务端并不会Crash,RuntimeException被Binder服务端线程捕捉,随后将异常信息写入到reply中,发回Binder客户端进程,最后会客户端binder线程抛出这个异常,如果没有捕捉这个RuntimeException,那么Binder客户端进程会Crash。

  • system_server与zygote的通信使用的是socket

  • 在应用调用Application.onCreate之前,Binder驱动的初始化就已经完成了,所以直接就可以使用Binder来通信。
  • -
IPCBinder实体Binder代理
Java层BinderBinderProxy
Native层BBinderBpBinder
Binder驱动层binder_nodebinder_ref

- 列表内容

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值