特别声明:本系列文章LiAnLab.org著作权所有,转载请注明出处。by  @宋宝华Barry

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

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

Java世界的“固有领土”

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

struct task_struct {屌丝名: comm屌丝id: pid屌丝房子:mm_struct屌丝车子:fs_struct屌丝工资: signal_struct…屌丝状态:  睡、干活、僵尸等
}

所以task_struct天生就是针对当今社会而生的,一个结构体把所有你资产全部囊括。

线程是CPU调度的单元,虽然是一套房子mm_struct、一部车子fs_struct、一个人工资signal_struct等,如果被2个或者多个屌丝所共享(这里就是这些结构体指针指向完全相同,pthread_create透过clone实现了这个功能),那么这几个屌丝就是同一个进程里面的多个线程了。

Linux中每个屌丝都是个task_struct,内核并不区分进程和线程,只是通过一个房子挂到2个task_struct的头上来实现线程的。简单地说,您和您老婆是2个task_struct,但是有个 房产证mm_struct是同一个。但是作为2个线程,厕所(CPU)这个唯一资源还是要轮着来被调度的。

一般情况下,Linux的进程通过fork诞生,这个时候,子进程的mm_struct指针并不等于父进程的mm_struct,而是重新分配一个mm_struct内存并让它等于负进程的mm_struct,所以子进程这个时候也share了父进程的资源。关于这个继承,原因很简单,因为几千年前咱有几个屌丝去那边打过鱼,属于咱们固有的资源。这些继承的资源是只读的,如果要写,就会造成一个“写时拷贝”,内核会为写的进程重新申请1个page并拷贝老的page,在新的page上再进行写。

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

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

static void zap_stdio(void)
{int fd;fd = open("/dev/null", O_RDWR);dup2(fd, 0);dup2(fd, 1);dup2(fd, 2);close(fd);
}
void service_start(struct service *svc, const char *dynamic_args)
{…pid = fork();if (pid == 0) {…if (needs_console) {…} else {zap_stdio();}…execve(svc->args[0], (char**) arg_ptrs, (char**) ENV);}
}

可以看出init创建的service都是被fork+exec整出来的,一般情况下,printf的东西就这样没了,因为在exec()前就被 dup到了/dev/null。

但是 init并非工作在Java的世界,而Java的世界通过Zygote产生。Java程序最终不会如同C/C++ native代码那样可以被编译和连接为一个可执行文件,Java源程序经过编译得到的并非可执行程序而是中间码,由Java虚拟机解释执行,所以没有办法被exec()。Java的性能下降了,但是,由于没有exec(),却成就了 fork()对资源的继承性,否则,被exec()后,userspace会被整体替换。

Zygote启动的SystemServer 和apk都只是先 fork,而后寻找到相应目标类的main()函数并执行,这个过程没有exec()。既然如此,在Android的Java世界里,必然存在某些资源是可能被许多进程所共同需要的,这个机会绝对不会被我天朝的渔民放过,肯定要先去打个渔以便小白兔直接宣传其为“固有领土”,这个过程主要是preloadClasses和preloadResources。

要preload的class存放在frameworks/base/preloaded-classes文件中,这个文件快2000行了,咱天朝几千年前的屌丝真牛b啊,到处打渔,黄岩岛、钓鱼岛该去的都去了,还有很多礁什么的,也跑了一遭,直接把天朝打成高富帅了:

android.R$styleable
android.accounts.Account
android.accounts.Account$1
android.accounts.AccountManager
岛太多,下面省略

preloadResources则主要加载framework-res.apk中的资源。这2个preload的过程实在很慢啊,你用bootchart观察Android启动过程,可能发现5000年文明史有一半都被Zygote拿去打渔去了:

有人说,既然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了,这个过程实际上就是:

    public static void execApplication(String invokeWith, String niceName,FileDescriptor pipeFd, String[] args) {StringBuilder command = new StringBuilder(invokeWith);command.append(" /system/bin/app_process /system/bin --application");if (niceName != null) {command.append(" '--nice-name=").append(niceName).append("'");}command.append(" com.android.internal.os.WrapperInit ");command.append(pipeFd != null ? IoUtils.getFd(pipeFd) : 0);Zygote.appendQuotedShellArgs(command, args);Zygote.execShell(command.toString());}

其中最关键的就是/system/bin/app_process /system/bin –application,实际上还由app_process 这个可执行文件(可以被exec了)去创建一个运行Java的进程。同志们,Zygote其实就这app_process改名来的。app_process是正宗的Java class启动者,Zygote可以看作一个马甲。

因此我们可以以这样的方式让valgrind可以跟踪SystemServer:

adb root
adb shell setprop wrap.system_server "logwrapper valgrind"
adb shell stop && adb shell start

其实就是把启动SystemServer的过程变成了fork ->exec(/system/bin/app_process /system/bin –applicationlogwrapper valgrind system_server)的过程。

本回书就说到这里,预知后事如何,请听下回分解。

谨以本回,献给最可爱的人——中国军人,无论是国军还是共军,愿日后皆以守土卫国为己任,炎黄子孙永不再兵戎相见。

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

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

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

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

 

——宋宝华

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

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

    By LiAnLab.org / 宋宝华 <Android架构纵横谈之一--软件自愈能力>已经谈地告了一个段落.接下来这个系列二我们谈Android性能方面的考虑.Android系 统组件 ...

  2. Android架构纵横谈之——软件自愈能力 (1)

    本系列2012年的时候发表在我的blog上面,现搬到公众号 笔者决定,从今天开始,连载Android架构纵横谈系列.之所以叫纵横谈而不是叫别的题目,是因为整个系列是横着竖着乱弹琴,可以说是阴阳不分,黑 ...

  3. Android架构组件(二):LiveData

    前言 上篇文章我们分析了Lifecycle的使用和原理,相信我们已经学会了用Lifecycle将你所需的类添加声明周期管理,如果只是寥寥阅读也没关系,这里奉上(双膝跪地)上篇地址,Android架构组 ...

  4. 云时代架构阅读笔记二——Java性能优化(二)

    承接上文Java性能优化(一)https://www.cnblogs.com/guo-xu/p/11019267.html 4)尽量确定StringBuffer的容量 在说和这个标题相关之前,先说一下 ...

  5. Android架构纵横谈之——软件自愈能力 (3)

    特别声明:本系列文章LiAnLab.org著作权所有,转载请注明出处.by  @宋宝华Barry 最近马不停蹄往返进行Linux技术讲座,所以本回delay了一周.上回书说到Android中生死与共的 ...

  6. Android架构 系统如何保证运行无误

    Android架构纵横谈之--软件自愈能力 来自:http://www.uml.org.cn/mobiledev/201209255.asp 笔者决定,从今天开始,连载Android架构纵横谈系列.之 ...

  7. Android架构组件(三)——ViewModel

    Android架构组件(三)--ViewModel 上一篇文章讲到了Android架构组件之LiveData(Android架构组件(二)--LiveData),现在我们再来看看另一个成员ViewMo ...

  8. 【Android架构师java原理详解】二;反射原理及动态代理模式

    前言: 本篇为Android架构师java原理专题二:反射原理及动态代理模式 大公司面试都要求我们有扎实的Java语言基础.而很多Android开发朋友这一块并不是很熟练,甚至半路初级底子很薄,这给我 ...

  9. Android bluetooth介绍(二): android 蓝牙代码架构及其uart 到rfcomm流程

    关键词:蓝牙blueZ  UART  HCI_UART H4  HCI  L2CAP RFCOMM  版本:基于android4.2之前版本 bluez内核:linux/linux3.08 系统:an ...

最新文章

  1. 构建并购重组服务链 蚁合拟推出三大数据平台
  2. 基于XML的IOC案例
  3. Django中自定义过滤器的使用
  4. 设计模式快速学习(五)原型模式
  5. Ibatis ISqlMapper工厂类案例
  6. mysql 小辉_小辉-MySQL数据库教程 完整高清版40集全!(主流)
  7. 第三方支付-手续费系统设计与实现
  8. java wps haspassword,#在WPS表格里面设置了密码怎么取消#excel解除密码保护
  9. matlab 高精度无理数,如何从MatLab上的无理数生成近似分数?
  10. 第53天-代码审计-TP5 框架及无框架变量覆盖反序列化(待续)
  11. 关于Git使用详细教程
  12. VS2010出现的转换COFF期间失败问题
  13. NC Cloud参照过滤(主子表、单表、查询区、报表)
  14. SDN北向接口之REST API
  15. LOESS 局部加权回归介绍
  16. 气体报警器为什么需要定期校准
  17. 步进电机驱动芯片 DRV8825
  18. 初识micro:bit
  19. InfoPath学习
  20. 微信小程序数据绑定与模板语法

热门文章

  1. CAD中导入.pat文件
  2. 计算机毕业设计系列基于SSM的个人财务收支管理系统
  3. 输入输出管理:输入输出应用程序接口、设备驱动程序接口
  4. Linux学习——awk
  5. 数字化转型 VS 数字化挖坑!
  6. labelImg的使用(详细教程)
  7. Linux Makefile strip函数的使用
  8. python input函数怎么用_Python input函数使用实例解析
  9. libra 学习笔记
  10. 绩效面谈中的优质提问(一)