相关文档参考:
打开链接 http://blog.csdn.net/lin20044140410/article/details/51106372
打开链接 http://blog.csdn.net/lin20044140410/article/details/73739901
Android 进程间通信 Binder,
学习过程中的做的一些记录,不足之处,还请多指教!
相关代码可以查看相关文件。
我们知道同一个应用程序中的函数、变量是可以直接互访的,根本原因是它们处于相同的内存空间,在一个内存空间中,虚拟地址的映射是完全一致,所以函数间的调用关系很简单。如果是两个不同的进程中应用,是没有办法直接通过内存地址来访问到对方内部的数据的,因为每个进程的逻辑地址和物理地址都不是直接对应的,任何进程都没有办法访问到它管辖范围外的内存空间,即使是刻意产生的内存越界与非法访问,操作系统也会马上阻止并强行关闭程序,这也有力的保障了应用程序和操作系统的安全稳定。
既然无法“直接”访问到对方进程的内存空间,可以通过“间接”的方式实现,这就是Binder要做的工作。
Binder作为Android中使用最广泛的IPC机制,涉及的东西很多,主要的组成元素有:Binder驱动,Service Manager,Binder Client,Binder Server,有人把这些主要元素跟TCP/IP做了类比,Binder驱动 == 路由器,Service Manager == DNS,Binder Client == 客户端,Binder Server == 服务器,客户端如果要访问服务器,首先从DNS查询服务器的IP地址,然后通过路由器把数据包发给服务器。
Binder的本质目标就是进程1(客户端)希望与进程2(服务器)进行互访,但是因为它们是跨进程(跨网络)的,所以必须借助于Binder驱动(路由器)来把请求正确的投递到对方所在的进程中,而参与的这些进程需要Binder颁发的唯一标志(IP地址)。在这个过程中DNS不是必须的,只要客户端知道要访问的服务器的IP就可以,但是IP地址很难记,所以DNS就是用于管理IP(Binder标志)与域名间的对应关系,方便查询。如同DNS的IP地址是预先设置的,service manager在Binder通信过程中的唯一标志永远是0.
一,进程间该如何传递数据
如果是一个int类型数值,或者说基础类型数值,不断的复制直到目标进程即可。但是如果是一个对象呢?我们知道同一个进程间的对象传递都是通过引用来做的,因而本质上传递的就是一个内存地址。这种方式在跨进程的情况下是无能为力的。因为采用了虚拟内存机制,两个进程都有自己独立的内存地址空间,所以跨进程传递的地址值是无效的。
android中担任进程间数据传递重任的是percel,percel是一种数据的载体,用于承载希望通过Ibinder发送的相关信息。它把对象在进程A中占据的内存相关数据打包起来,然后寄送到进程B中,有B在自己的进程空间“复现”这个对象。percel就具备这种打包和重组的能力。
我们知道Activity的oncreate方法的参数是:Bundle savedInstanceState,它可以保存一些数据等待下次进入时恢复。这个Bundles就是遵循percelable协议的对象,它实现了percelable接口, public final class Bundle extends BaseBundle implements Cloneable, Parcelable;Bundle的最大特点就是采用键值对的方式存储数据,在一定程度上优化了读取效率。
二,Binder驱动,
由于Android系统是基于Linux内核的,所以它所依赖的Binder驱动也是一个标准的Linux驱动,具体而言,Binder Drive将自己注册成一个misc device,向上层提供一个/dev/binder节点,Binder驱动运行于内核态,提供了open(),ioctl(),mmap等常用的文件操作,对应的函数在文件Binder.c中。
上层应用在访问Binder驱动时,首先要打开Binder设备,这个操作的实现在binder_open()中;
然后通过binder_mmap()把设备指定的内存块直接映射到应用程序的内存空间中;
Binder_ioctl()实现了应用进程和binder驱动间的信息交互,承载了binder驱动中的大部分业务,binder并没有提供read(),write()这类常规的文件操作,使用ioctl()来替代了它们。
三,Service Manager,
Service Manager是为了完成Binder Server Name(域名)和Server Handle(IP)间的对应关系的查询而存在的,必须保证在有应用使用binder机制前就处于正常的工作状态,好比DNS也要在你查看网页前就要到位一样,它的启动是在init程序解析init.rc脚本是启动的。
Service manager(简称SM)自身也是Binder server,对应的文件:
Frameworks/native/cmds/Servicemanager/Service_manager.c中main()函数,
它启动后主要做了这么几件事:
1,打开binder设备,做些初始化 binder_open()
2,把自己设置为Binder大管家 binder_become_context_manager()
3,进入主循环 binder_loop()
接下来,我们看下java层的应用程序如何使用ServiceManager服务。
如果要访问SM的服务,流程通常是这样的:
1,打开binder设备
2,执行内存映射mmap
3,通过binder驱动向SM发送请求
4,获得结果
核心的工作就这么多,一些具体的细节还是有点复杂的:
如:向SM发送请求的Binder client可能是app,所以SM必须提供java层的接口。如果BinderClient都亲力亲为执行以上步骤获取SM服务,那么会很浪费很多时间。如果app每次使用SM服务,都要做一遍上述步骤,后果就是消耗很多系统资源。一个有效的办法是每个进程只允许打开一次Binder设备,只做一次内存映射,所有需要使用Binder驱动的线程共享这一资源。
但是在实际使用中,这些操作对上层用户都是透明的,并且上层也没有如此亲力亲为的执行这些步骤,因为Android创建了一个类ProcessState来完成了这一系列的命令,当然只有ProcessState是不够的,因为进程中的每一个线程都应该有与binder驱动自由沟通的权利,所以与Binder驱动进行实际命令通信的就是 IPCThreadState。有了这2个类,应用程序就可以与binder驱动通信了,原则上可以发送binder支持的命令来与其交互,不过这种做法比较繁琐,所以Android对SM提供的服务进行了封装,把这个封装取名为ServiceManagerProxy,就是SM的代理。
当某个Binder server在启动时,会把自己的名称 name与对应的binder句柄值保存在serviceManager中,调用者只知道Binder server的名称,所以要向SM发起查询请求,这就是getService(name)。
SM在起来之后,把自己注册成binder大管家,做完一系列初始化后,在最后一次read操作中进入睡眠等待,直到有客户端BinderClient发起请求而被Binder驱动叫醒。
SM醒来后,会分两条线索:(一)SM端接着执行read操作,把调用者的具体请求读出来,然后利用binder_parse解析,在根据实际情况填写transaction信息,最后把结果通过BR_REPLY命令(ioctl)返回Binder驱动;(二)发起getService请求的BinderClient在等待SM回复的过程中进入睡眠,直到被binder驱动再次叫醒,他和SM一样也是在read中睡眠的,因为醒来后继续执行读取操作,这一次得到是SM对请求的执行结果,程序通过层层返回到ServiceManagerProxy,最终传给调用者。
这样就完成了ServiceManager.getService()的流程。