Android架构纵横谈之二——基于性能的思忖(1)

Android架构纵横谈之二——基于性能的考虑(1)《Android架构纵横谈之一——软件自愈能力》已经谈地告了一个段落。

Android架构纵横谈之二——基于性能的考虑(1)

《Android架构纵横谈之一——软件自愈能力》已经谈地告了一个段落。接下来这个系列二我们谈Android性能方面的考虑。Android系统组件繁杂,盘根错节,若非在性能上进行充分的考虑,恐怕会慢如蜗牛。Android有独具特色的Dalvik虚拟机,启动过程中即加载许多资源以便子进程进行继承的Zygote,广泛使用共享内存的AudioFlinger、 SurfaceFlinger、Property Service,应用程序对图形的direct render,简单高效的新增的IPC 方式binder等。我们大概还是分成多回来谈。

今天我们先谈AndroidJava 世界女娲Zygote 的高妙之处,歌颂其在广阔深蓝到处打渔的壮美举动。广大读者仍然可以透过新浪微博“@宋宝华Barry ”进行交流,写技术博客是个非常痛苦的过程,所以无论是板砖的也好,喝彩的也好,欢迎都上来吆喝几声。我这边特别要声明的是,本系列不着眼于谈细小的知识点,而更多的是谈设计思想上的考虑。

Java世界的“固有领土”

同志们,进程是一个资源封装的单位,所谓进程,就是讲屌丝们的房子、车子, task_struct是Linux  内核里用于描述进程的数据结构,进程就是资源,故task_struct就是封装了一个个的资源以及进程的属性(如pid等 ),它的定义如下:

一般情况下,子进程被fork出来后,会调用exec()对userspace进行替换,典型地Android的init使用了该模型:

Android架构纵横谈之二——基于性能的思忖(1)

exec()用一个可执行文件替换当前子进程的用户空间。注意在exec()对userspace替换后,除pid等id信息保留外,0、1、2这3个代表标准输入、输出、错误输出的fd依然保留原来的含义,这使得我们Android的 init进程可以启动init.rc的service的时候,将0、 1、2重定向到/dev/null,看看 Android的init启动service的过程:

     有人说,既然preload东东这么慢,严重影响了开机速度,那我们不要preload不就好了吗?

      同志们啊,preload的意义就是先hold住打个渔,fork()子进程都发生在此之后,子进程如果用的时候直接就可以用了。如果这个过程不做的话,那么就需要每个子进程自己用的时候再去load,那该多耗费多少内存以及多慢呢?祖先们去打渔好处是明显的。

      最后,我们要说的是没了exec(),Java语言对应的进程就失去了一种能力,举个例子,你如果要透过valgrind检查SystemServer的native层内存泄露、溢出或者某个 apk的native层内存泄露、溢出 ,你不可能敲个命令行叫:valgrind --tool=memcheck --leak-check=full systemserver吧?

      而这样的需求却真实地存在着,于是Jeff Brown jeffbrown@google.com提交了让Android Java程序以exec方式被启动的patch ,这些patch分布于对dalvik_system_Zygote.c、Zygote.java、app_process/app_main.cpp、RuntimeInit.java,并新增加了一个WrapperInit.java文件,使得我们可以通过exec()的方式启动Java程序,这样我们在 Java程序启动前插入 wrap(如valgrind)就很easy了,这个过程实际上就是:

剑外忽传收蓟北,初闻涕泪满衣裳。

却看妻子愁何在,漫卷诗书喜欲狂。

白日放歌须纵酒,青春作伴好还乡。

即从巴峡穿巫峡,便下襄阳向洛阳。

 

——宋宝华