从Activity的启动细窥BinderIPC(1)

来源:互联网 发布:大数据产业园规划 编辑:程序博客网 时间:2024/04/30 16:23

android基于linux内核,有丰富的进程通信机制,比如传统的pipe,signal,trace机制,然而android毅然放弃祖传遗产 另起一招,名曰BinderIPC。。。

更新

下面来进行详细的讨论:

本文章从最简单最基本的activity的启动说起,通过观察callstack,发现activity的启动要从native的main函数,即kernel层的系统调用启动,然后通过callstack可以发现,在我们触摸图标,系统将会启动一个luancher进程,这个进程会给一个消息给ActivityManagerService,这是一个service binder(在内核中存在于system server进程的一个线程中,这个进程还有很多不同的binderserver,不过都运行在不同的线程)ActivityManagerService会检测这个应用程序的入口activity,一般这个应用理应获得一个属于它自己的进程,有时候事情很不幸,app并没有获得自己应得的.
这时候有个重要的角色要登场啦,那就是一个叫做zygote的进程,看过callstack的估计对这个进程会觉得有点熟悉,看这个家伙的名字这么奇葩,查查字典也并不知道是什么意思,其实它一点都不神秘,在kernel层看来,它只干两件事情,一个就是调用下进程的fork函数,fork出来的进程给谁呢,当然是给我们的app使用啦,只是调用个fork函数要它何用,好歹是个进程,所以它还干了一件伟大的任务,那就是完成了堆内存的共享,zygote首先会android的公用类进行预加载以及一个初始化过程,数据保存在它的堆内存中(heap),我们的系统在zygote上安装了个间谍,本质上就是一个socket,对于socket,肯定有一个用于监听的端口,当ActivityManagerService通知它还没有分配进程给app时(以一个startcommand的形式),就会触发回调进行fork新的进程理论上是跟zygote共享堆空间的,怎么共享呢,首先我们知道在linux的内核空间,内存都是以一种映射关系存在的,每个进程都拥有自己的一块线性并且连续的虚拟内存,系统会完成内存的映射(mapping),将虚拟的的内存映射到真实的一块物理地址,以此来保证每个进程的安全性(跟线程池的线程安全同样重要哈),进程间的交换信息,在android中叫做transaction,都是通过binder IPC机制实现,在kernel层,binder Driver就是相当于构造一块虚拟的映射空间来共享两个进程的数据,对于zygote和我们的app也是类似的道理,构造一块虚拟的共享虚拟内存来共享zygote的heap空间数据,这样就可以将zygote跟app的新进程连接起来啦,下面分析两种情况,
首先,我们的app要请求读取heap空间,这个毫无疑问,看了就走,好像上面的整个体系没有什么变化,好吧,那如果app要往内存中写入什么东西呢,这下有事情要发生了,heap中东西改变了,显然共享区的数据也会被修改,那zygote呢,神奇的地方就是zygote也被修改了而且是直接放弃原来的内存页,选择重开天下,不过系统比较机智,在另开辟一个内存页时,不忘把上次的zygote heap内存中的数据备份,这样相当于把整个体系重构了一遍,一个新的link就产生了,可见,zzygote是可以允许app肆无忌惮的读写的,而且还会很聪明地备份。
有人会说,zygote这么做是何苦啊,这就是dvm(dalvik vm)的巧妙之处之一了,app通过java语言编写总是需要dvm的运行环境才能转化为android平台的可执行程序代码,然而这么多的app都需要,dvm就觉得忙不过来了,至于为什么要搞这么多进程,所有app就不能共用一个dvm进程么,当然android不是这么玩得为了考虑安全性,还是要把进程隔离开来啦,但这样也累啊,android最后就巧妙的想了个办法,人在忙不过来的时候总会想要是我可以分身该多好啊,有句话叫分身乏术,然而dvm却有这个技能,它找到zygote进程(其实也是dvm的一个server),每次系统启动dvm只完成一次实例化,后面都是通过zygote进程的克隆体,对于heap空间也是一笔不小的节约啊,这就是所谓的一次实例化,然而多次复用,zygote因此有个外号,叫进程孵化器。
我们来看看zygote进程是怎样被构造的,ZygoteInit就是它的启动类,调用个main()方法就好啦,再注册一个ZygoteSocket用于监听进程孵化的请求,runSelectLoop()就是用于轮询端口对请求事件进行监听,read the funcking code,好,我们来看下这个轮询函数的源代码,看代码时关注注释的地方就可以了,精髓就在哪些地方。

private static void runSelectLoopMode() throws MethodAndArgsCaller {  //死循环果断用于对事件的轮询  while (true) {     //...      try {          //linux内核的fd文件描述符的形式来进行时间轮询          fdArray = fds.toArray(fdArray);          index = selectReadable(fdArray);      } catch (IOException ex) {          throw new RuntimeException("Error in select()", ex);      }      if (index < 0) {          //这个明显不可能发生,马上抛出异常          throw new RuntimeException("Error in select()");      } else if (index == 0) {          //这个代表有client端提交连接请求,这里的client端就是ActivityManagerService啦          //构造一个用于连接的对象,进行连接          ZygoteConnection newPeer = acceptCommandPeer();          //连接这个地方显然要阻塞那么一段时间,就耐心等下咯          //连接成功后就往client列表中添加新加入的伙伴          peers.add(newPeer);          fds.add(newPeer.getFileDesciptor());      } else {         //这代表client发送孵化进程的请求过来         //此时便需要调用ZygoteConnection的runOnce方法孵化进程,在内核其实就是fork一个进程          boolean done;          done = peers.get(index).runOnce();          if (done) {              //完事了就把人家client踢掉咯              peers.remove(index);              fds.remove(index);          }      }  }}
2 0
原创粉丝点击