美文网首页
图说Android Binder机制

图说Android Binder机制

作者: 风雪围城 | 来源:发表于2017-09-19 23:31 被阅读0次

    背景


      Binder机制,是Android系统跨进程协作的核心。我们知道,每个应用独立地运行在自己的进程虚拟地址空间中,就像是独自占有CPU、内存等资源,相互之间不可见。Android系统中的一个个应用,亦如一个个信息孤岛。正是Binder这样的跨进程访问机制,提供了孤岛之间沟通的桥梁。

    Binder是应用之间沟通的桥梁
      如上图所示,鉴于安全和效率的考虑,Android系统服务提供是一种典型的C/S架构,如果希望获取系统服务,就必须进程IPC请求,而Binder正是数据交互的核心。
      平时做应用开发,其实很少会操心这种底层机制,但是设计思想却是通用的,为了学习它的这种思想,同时弄懂Binder机制,更好地进行应用开发,这几天找了很多资料。现在开始,才有那么点绕出来的感觉。这里,不谈源码实现,通过对各种资料的汇集,更多的以图的形式通俗展现Binder的内部机制。

    Binder通信基础


      就整个通信过程,将围绕着数据传递的前提,数据如何传递,以及传递背后的协议来展开。

    用户空间和内核空间---谁来中转数据

      我们知道,进程运行在虚拟地址空间,鉴于安全考虑,这块空间被分为用户空间和内核空间。其中,用户空间的执行权限较低,凡是需要访问物理设备、IO等,都需要通过系统调用的方式,由内核代码在内核空间运行。如下图所示:

    用户空间和内核空间的关系
      由上图可知,每个进程通过系统调用进入内核,Linux内核空间由系统内的所有进程共享。从进程的角度看,每个进程拥有4G的虚拟空间。每个进程有各自的私有用户空间(0-3G),这个空间对系统的其他进程是不可见的。最高的1G内核空间则为所有进程以及内核所共享。这段共享的内核空间,就构成了传统Linux系统中数据不同进程之间数据交换的基础。更进一步,是依赖两个函数: 进程空间、内核空间与物理地址之间的转换关系
    数据通过 copy_from_user 来到内核空间,通过copy_to_user 返回给用户空间。
    由上可知,数据的传输过程至少需要对数据进行两次拷贝,但是在 Android 中又有不同,为了数据的高效传输,它对数据的拷贝仅需要一次,原因在于:Server 进程在通过 open 打开 Binder 驱动(在此处传入了进程信息,即 binder_proc),使用 mmap 进行内存映射的时候,映射出的物理内存同时存在于内核空间和 Server 所运行的 Binder 进程的用户空间。而内核空间和用户空间都是虚拟逻辑空间。这样,当数据 copy_from_user 之后存在内核空间,这页内存空间所在的实际物理空间实际上也对应于用户空间。这样,就无需在执行 copy_to_user的操作。 Android 数据跨进程传输过程
    序列化与反序列化---什么样的数据完成进程穿透

      那么,什么样的数据,能够方便、安全地穿透进程间壁垒,完成进程间通信呢?
      序列化数据。在应用层,就是一个Parcel对象。
      关于序列化和反序列化,下面的这张图,描述的很清楚。

    数据的转换过程
      如上图所示,我们都做过将一张纸裁剪成正方体的游戏。现在将正方体拆开,还原成平面的纸,然后通过传真机发送到远端,在远端接收后,安装纸面上的轨迹,还原这个正方体。
      实际上,这给了我们启示,在面向对象的世界里,一个复杂的对象该如何在不同进程中传递的思路,这是说我们需要把一个复杂的对象的简化了,变得平滑,转换成基础数据结构(int ,float,long等),发送到远端后,再从远端复原成复杂对象。
     public class MyParcelable implements Parcelable {
         private int mData;
    
         public int describeContents() {
             return 0;
         }
         //转换成Parcel对象,完成序列化
         public void writeToParcel(Parcel out, int flags) {
             out.writeInt(mData);
         }
         //从Parcel转换成原对象,完成反序列化
         public static final Parcelable.Creator<MyParcelable> CREATOR
                 = new Parcelable.Creator<MyParcelable>() {
             public MyParcelable createFromParcel(Parcel in) {
                 return new MyParcelable(in);
             }
    
             public MyParcelable[] newArray(int size) {
                 return new MyParcelable[size];
             }
         };
         
         private MyParcelable(Parcel in) {
             mData = in.readInt();
         }
     }
    
    来一发服务请求---协议保证

      什么是协议?协议就是双方的约定,就是规矩,就是格式。下面,我们来看看一个实际远程调用过程的数据穿透协议。

    跨进程调用
      上图实际执行了系统服务MediaPlayer的setVolume(float,float)函数调用过程,而执行的实体实际上是远端的服务进程。
      数据write_buffer实际上是通过copy_from_user传递到内核空间。传递的内容,在上图中一目了然,主要包括类(android.media.IMediaPlayer),方法(通过数字26映射),参数(1.0,1.0)。以上,就是函数调用从用户空间被传递到内核空间,通过Binder驱动发送到实际执行的远程服务进程完成执行操作。

    Binder通信机制


      上面,我们该对整个通信过程有了一个大概的印象,应该已经清楚,数据从哪里来,如何穿透进程壁垒以及穿透的基础。接下来,我们先来通过下图澄清几个概念。

    Binder通信的粗略过程
    • Binder驱动:一个内核层级的驱动,促成了进程间通信。有人把它比作网络通信中的路由器,而路由器的作用,就是从原地址存储转发数据目的地址,很贴切。
    • Binder对象:是IBinder接口的实现。实际上就是远程服务动作的实际执行者。
    • BinderToken:也有叫 handler 的。实际上就是一个32位的整型值,唯一的代表了一个远端Binder对象。
    • Binder Service:实际持有Binder对象,处理业务逻辑。
    • Binder Client:请求Binder服务者。
    • Proxy:实现了AIDL接口,能够序列化/反序列化数据结构,同时能够通过Binder引用进程远程调用。
    • Stub:能够序列化和反序列化数据,并把转换过程映射到实际的服务端的方法调用。
    • Context Manager:一个注册 handler(句柄)为0的特殊Binder对象。通过name到handler的映射关系,注册和查找别的Binder对象。也有人把它对应为DNS服务器。DNS服务器的作用,就是通过IP地质到域名的映射,提供查找和注册。即我们向DNS服务器注册自己的IP,并把它映射到一个域名;这样,别人就可以通过域名来访问我们的主机,因为DNS服务器将把这个域名转换为IP。

    把上图再细致一点,放大,如下图所示:


    Binder通信详细过程

    Client:

    1. 首先,客户端从ContextManager中通过服务名,拿到远程服务的handler(句柄),也就是binder token。
    2. 调用远程服务的 foo 方法,然后序列化参数;
    3. 通过 transact 把调用相关的资料提交给 libbinder处理;
    4. 由 libbinder 通过ioctl进程系统调用,将foo调用请求提交给binder驱动;
    5. binder驱动通过handler找到真正的远端服务进程,然后通过ioctl函数将foo调用传递给远端服务进程的 libbinder;
    6. 远端libbinder将调用交给Stub;
    7. 在Stub中,反序列化调用信息,还原;
    8. Stub找到实际服务提供者,执行客户端的请求;
    9. 将结果序列化
      ......
      再通过Binder驱动,返回给客户端。

    Server:
    主要是在ContextManager中完成注册(name -> handler);等待接收Binder驱动发来的请求。

    Binder限制


    在使用Binder进程跨进程调用的时候,有两个重要的限制需要注意:

    • 一个服务进程中最多同时支持15个binder线程处理请求;
    • 一个进程中用户交互穿透数据的缓存大小最多为1M,这就意味着,在传递的数据是有限的,如果资源耗尽,会抛出异常。

    最后


    作为一个应用开发者,对本质对底层的C实现机制,有些技痒,但是久未使用过C,想从源码的角度深入分析,需要花费更多精力,目前似乎又没有这个必要,那么,就先浅尝辄止,不求甚解。如有不对的地方,还请指出。
    收获,就在于对序列化、反序列化的认识;对数据结构化的认识;从Binder框架的认识;以后再读源码的时候,不会被各种服务调用搞的晕头转向;再有就是对AIDL的使用上,不用再死记硬背如何通过AIDL进程跨进程调用了。
    后面,会写一下AIDL跨进程调用过程。
    本文参考链接:
    https://blog.checkpoint.com/wp-content/uploads/2015/02/Man-In-The-Binder-He-Who-Controls-IPC-Controls-The-Droid-wp.pdf
    https://events.linuxfoundation.org/images/stories/slides/abs2013_gargentas.pdf
    https://www.dre.vanderbilt.edu/~schmidt/cs282/PDFs/android-binder-ipc.pdf
    想要深入源码和数据结构的同学,可以参考以下链接:
    http://gityuan.com/2015/11/01/binder-driver/
    http://www.cloudchou.com/android/post-507.html
    http://www.jianshu.com/p/1050ce12bc1e
    http://blog.csdn.net/universus/article/details/6211589#comments
    https://github.com/xdtianyu/SourceAnalysis/blob/master/Binder%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90.md
    或者,自己去找binder.c 和 binder.h 这两个文件来看吧。

    相关文章

      网友评论

          本文标题:图说Android Binder机制

          本文链接:https://www.haomeiwen.com/subject/flmbsxtx.html