纪伯伦说:“你无法同时拥有青春和关于青春的知识;因为青春忙于生计,没有余暇去求知;而知识忙于寻求自我,无法享受生活。”
序言
跑去图书馆自习了一天,看了一天Binder,效率还是蛮高的。特别推荐,同时想在以后每篇博文写几句文艺或者鸡汤或者高逼格的话,提高各类姿势水平吧。回到正题,讲讲Binder,我将从为什么要有Binder,Binder的底层实现,然后通过一个客户端调用服务进程的整个过程来进一步阐述其作用。
Binder用来干什么
进程间通信,嗯?然后你就想知道这些??进程间通信为什么要借助一个中间件,直接不可以吗?
来看下Linux内核空间和用户空间,再慢慢的谈谈为什么?这里要先从Linux下的内存空间划分来讲起了。
Linux中,给每个用户进程的空间是4个G,内核一个G,当然这是虚拟的内存,也就是不足的地方是借助于硬盘来实现虚拟,对于用户进程,用户进程空间是不可以共享的,但是对于内核空间彼此之间是可以共享的,用户空间不可以共享,因此两个进程之间进行通信就会有问题了,所以在通信上要借助于内核空间来进行通信。引出来内核了,通过内核进行通信,具体如何进行呢?如果让你来设计你会怎么设计呢?
跟每一个进程划分一块区域,然后每个进程对其内存进行loop,有消息后作出反馈将内容发送至对方的内存中,然后。。。感觉优雅吗?但是对于中间部分怎么寻找,如何定位,内存不可能是事先划分好,动态划分了,进程间如何知道彼此的内存中的地址,这是一个问题,中间的数据如何进行传递,等等问题。
再考虑一个问题,什么样的进程间需要通信,不难发现,需要进行通信的进程一般是可见或不可见进程和服务进程之间进行的通信,所以我们需要去找服务进程,然后和它通信,先看一个例子,当我们在手机中通过进度条调节手机音量的时候。
现在你可以对Binder有一个这样的认识,通过它可以进行进程间通信,当然Binder是Android在基于Linux上进行一个IPC的新方式,虽为新方式,也不过是在基于共享内存上进行了更进一步的封装和优化,而且其不仅仅是进行IPC,还可以RPC,即为远程调用,在进程A中可以像调用自己的函数一样,调用进程B的函数。
Binder的实现机制
既然是借助于内核空间的一块内存,既然要进行通信,彼此之间有数据交流,有哪些数据呢?要找到该进程,然后要让该进程知道自己调用的方法,然后还要将结果传递回来,
IPC数据
Handle即为所要寻找的服务的编号,RPC数据,即为我们在调用函数的时候所要传递的参数,RPC代码即为我们的调用代码,Binder协议,也就是我们在和内存进行交互的时候,告知其我们是要接收还是要发送,然后内核空间给对于Binder管理的这块区域起了个什么名字呢?Binder Driver,通过其对Binder进行管理。现在我们对于进程间通信有了更进一步的理解。Binder Driver的生成则是在init进程中创建。
现在我们要更深入一步来了解这个过程的具体细节了,因为本篇主要是对这个机制进行一个概述,所以不准备通过代码细节来对该过程进行一个细节化的描述。只讲述整个流程。
前面提到了,进程间的通信一般是在和服务进程进行通信,现在我的一个进程和某个服务进程进行通信,那么我给出服务名即可,然后系统如何根据这个服务名找到服务,然后调用服务的相关方法,然后再将结果返回呢?来看看Android中是如何实现的。
回顾上一篇文章中讲述的Android启动流程中,讲到的一个Context Manager进程,这个进程在所有的Service启动之前启动,然后所有的服务进程需要向其进行注册,那么又牵扯到一个问题,既然Context Manager也是一个进程,其它进程向其进行注册时候不是也需要进程间通信了,先从Context Manager来看。
Context Manager
Context Manager和其它的Android服务不同,采用C语言编写,为了方便和Binder进行紧密衔接,Context Manager,首先打开Binder Driver,然后在内存中创建一块内存,用来接收一写IPC数据,在内存中创建一个Binder节点,将自己置为0号节点,然后对自己的内存区域进行轮询,当接收到数据的时候,对其中的数据进行解析,然后根据相应的协议执行相应的操作。Context Manager的主要作用是服务注册和服务检索。它给每一个服务分配了一个称为Handle的编号。这个过程讲的比较粗了。
从服务注册和使用流程看Binder
再谈Context Manager
Context Manager在mediaserver和system_server之前运行,每当有service server来注册服务的时候,Context Manager会把服务的名称和该服务在Binder Driver 中分配的Binder节点编号注册到自身服务目录中。Service程序以IPC应答数据的形式结束Context Manager服务目录中的服务名称,然后输出我们所看到的一系列的服务。
调用binder_open函数,引起open和mmap的调用,调用open打开Binder Driver,而调用mmap则生成结束IPC数据的Buffer,系统给予其128k的大小用来接收IPC数据。其具体代码实现,则是通过一个结构体来记录,然后存放了该块内存的引用。
Context Manager在访问Binder Driver,将自身的Binder节点设置为0号节点。
调用loop函数,来轮询检测Binder,接收IPC数据,当出现IPC数据后,调用binder_parse,来解析IPC数据。
-
因为Context Manager的两个主要作用是服务的检索和服务的注册,所以接收到的IPC数据也就是用来做这两件事情的。
服务检索:通过RPC数据中传递的服务名称,从do_find_service()函数自身的服务列表中获取带有指定名称的服务编号,而后通过bio_put_ref()函数来生成binder_object结构体,该结构体被包含到IPC引导数据的RPC数据中,然后调用binder_send_reply()函数,将数据传递给Binder Driver.
服务注册:当解析出为服务注册的时候,调用do_add_service()函数,将IPC数据的RPC数据包含的服务名称和Binder节点编号注册到自身的服务目录中,然后调用binder_send_reply,将IPC应答数据传递给Binder Driver。
我们对于服务的注册和检索,又有了更进一步的了解了,现在我们要更深入一些。跟具体一些。
服务向Context Manager注册
当Service Server向Context Manager注册自身的服务时,Binder Driver会进行Binder Addressing。Binder Driver会首先找到Handle值为0的Binder节点,Handle值为0的Binder节点为Context Manager,然后Binder Driver会生成一个Binder节点,用来表示前来注册的服务,接下来生成Binder引用数据,以便Context Manager识别所生成的Binder节点,并将其关联起来,根据生成顺序,引用数据会被编号,然后将编号插入到IPC数据,传递给Context Manager,Context Manager会根据服务名称将Binder节点进行关联,这样我们服务注册过程便完成了。
调用服务
首先是Handle值为0的IPC数据经过Binder Driver传递至Context Manager,Context Manager根据服务编号查找对应的引用数据,然后在服务客户端生成引用数据,并将其和ConText Manager的引用数据所指向的Binder节点连接起来,连接后的Binder节点和Service Server自身服务的Binder节点相对应,Binder Driver将根据生成顺序为引用数据编号,并传递给服务客户端,服务端将引用数据编号保存到Handle中,把与服务相关的RPC代码,RPC函数包含进IPC数据中,发送给Service Sever。然后Service server在和其进行数据交互。
通过上述两个步骤我们实现了本地客户端通过Binder对于服务的调用。通过上述,你对Binder会有了一个初步的了解和大体上对整个过程有了一个面貌。接下来将写一篇关于android 生成apk过程和apk安装过程。
本人现在每日一道算法题的Github地址。
https://github.com/Jensenczx/CodeEveryday
附带OJ地址和本人的代码