android binder 机制

全文参考网址:http://light3moon.com/1986/12/20/%E6%96%87%E7%AB%A0%E7%B4%A2%E5%BC%95/


1、  Binder通信模型

通信流程图:


各个步骤流程:

1)  service 运行,阻塞于 ioctl,等待 client 发起请求

service 进程运行起来,然后通过调用 IPCThreadState 的 joinThreadLoop 在本线程中开始等待客户端请求的到来。两个前提条件:第一个,system service 必须向 service manager 注册自己;第二个,服务端的多线程支持问题。

2)  client 通过 ioctl 发起 IPC 请求,等待 service 结果

- client send BC_TRANSACTION —> kernel

客户端从SM那边获取所需要的服务,再发起IPC调用Bp的transaction 函数。然后将调用writeTransactionData向service写入请求数据。

-   kernel returnBR_TRANSACTION_COMPLETE —> client

-  client 阻塞于 ioctl,等待 service 返回结果

调用waitForResponse()函数等待返回结果.

3)  service 被唤醒,完成业务,返回结果

service被唤醒后,调用talkWithDriver() 去kernel的binder设备那里读取数据,这个数据是之前client在发起IPC请求的时候写入的。然后调用executeCommand()处理获取的数据。

-    kernel return BR_TRANSACTION—> service

-    service impl IPC call

-    service send BC_REPLY —>kernel

-    kernel returnBR_TRANSACTION_COMPLETE —> service

4)  client 被唤醒,读取 service 返回结果, IPC 结束

-    kernel return BR_REPLY —>client

-    IPC call end

2、  Binder间的数据传递

1)  小数据类型采用parcel进行传递,数据从用户空间copy到kernel空间即binder驱动中,再通过binder驱动传递给服务空间。

2)  大数据类型采用匿名共享内存(Ashmen)进行传递

3)  Binder对象采用kernel binder驱动专门处理。

3、  使用parcel传递数据

        由于在进程间传递大数据时不能传递引用(不同进程间地址表示的内容不一样),简单来说可以通过如下方式传递数据:在进程A中把类中的非默认值的属性和类的唯一标志打成包(这就叫序列化),把这个包传递到进程B,进程B接收到包后,跟据类的唯一标志把类创建出来,然后把传来的属性更新到类对象中,这样进程A和进程B中就包含了两个完全一样的类对象。由于android定位于内存受限的设备,所以parcel跟着定位于轻量级的高效的随想序列化和反序列化机制。主要特点就是利用连续的内存空间进行序列化操作,所以速度比较快。

        整个读写全是在内存中进行,主要是通过malloc()、realloc()、memcpy()等内存操作进行,所以效率比JAVA序列化中使用外部存储器会高很多;

读写时是4字节对齐的,可以看到#definePAD_SIZE(s) (((s)+3)&~3)这句宏定义就是在做这件事情;如果预分配的空间不够时newSize =((mDataSize+len)*3)/2;会一次多分配50%;对于普通数据,使用的是mData内存地址,对于IBinder类型的数据以及FileDescriptor使用的是mObjects内存地址。后者是通过flatten_binder()和unflatten_binder()实现的,目的是反序列化时读出的对象就是原对象而不用重新new一个新对象。

4、  使用Ashmem传递数据

       Binder目前为每个进程mmap接收数据的内存是1M,所以只要超过1M就会报binder无法分配内存空间的exception,所以在这种情况下就需要使用Ashmem(Anonymous Shared Memroy)。Ashmem利用了 linux 的 tmpfs 文件系统大致思路和流程是:

      Proc A 通过 tmpfs 创建一块共享区域,得到这块区域的 fd(文件描述符)。

      Proc A 在 fd 上 mmap 一片内存区域到本进程用于共享数据。首先调用JNI方法native_open来创建一个匿名共享内存文件,从而得到一个文件描述符fd,接着就以这个fd为参数调用JNI方法natvie_mmap把这个匿名共享内存文件映射在进程空间中,然后就可以通过这个映射后得到的地址空间来直接访问内存数据了.Proc A 通过某种方法把 fd 倒腾给 Proc B,由于fd在不同进程中不一样,所以这里需要进行转换成ProcB 可以识别的fd。Proc B 在接到的 fd 上同样 mmap 相同的区域到本进程,然后 A、B 在 mmap 到本进程中的内存中读、写,对方都能看到了

5、  系统服务(systemservice)绑定

       系统级别的服务system service需要先将service add到ServiceManager中去,通过函数addService() 来实现。然后等待client去getService获得服务。所有系统级别的服务都是有SM(Service Manager)来统一管理。addService 和getService的流程:


6、  普通服务绑定

       由于系统服务都是通过ServiceManager来统一管理,service将服务注册到SM中,client再从SM中取出服务。而对于普通的service是没有权限将自己注册到SM中,而是通过ActivityManager来中转。普通服务将自己挂靠在AM中,而AM是系统服务将自己注册到了SM中,这样通铺用户也可以通过binder机制来实现进程间的调用。

        实现过程:首先有个一个普通的服务叫 SA(Service A,它在 Proc A 中),另一个普通应用AppB(它在 Porc B 中)。现在 SA 要提供一些 IPC 接口,它首先得继承 Service ,然后实现 IBinder 接口,并且在 onBind 函数中返回自己实现的 IBinder 对象。 AppB 要调用 SA 的某个接口,那么它就得调用 Context 的 bindService,并且自己实现 ServiceConnection,在 onServiceConnected 中取得 SA 的 IBiner 对象,然后就可以通过取得的 IBinder 对象调用 SA 的 IPC 接口了。

由于被绑定的服务是普通服务,在client去调用服务时不一定已经启动,所以存在这三种情况:

  -     要绑定的服务所在的进程已经在运行,并且服务代码也已经执行了,这个时候只要请求绑定服务就行了。

  -     要绑定的服务所在的进程已经在运行,但是服务代码没有执行,这个时候需要执行服务代码,然后再绑定服务。

  -     要绑定的服务的进程还没运行,要先启动服务所在的进程,然后执行服务代码,最后再绑定服务。

7、使用AIDL实现进程通信

       建立在普通服务绑定的基础上,简化了人工实现普通服务注册和获取代码的编写,只需关注提供服务的内容实现部分。


posted on 2015-09-30 09:46  kma  阅读(104)  评论(0编辑  收藏  举报

导航