Zygote原理学习
1 zygote分析
1.1 简介
Zygote本身是一个NATIVE层的应用程序,与驱动、内核无关。前面已经介绍过了,zygote由init进程根据init.rc配置文件创建。其实本质上来说,zygote就是app_process,这个名字在android.mk中指定,但是在运行的时候,app_process通过LINUX下的pctrl系统调用将自己的名字换成了“zygote”,所以通过ps命令就可以看到进程的名字为zygote。Pctrl函数相关信息见:
http://blog.csdn.net/zuokong/article/details/7318154
zygote的原型app_process在App_main.cpp中:
int main(int argc, char* const argv[]) { // These are global variables in ProcessState.cpp mArgC = argc; mArgV = argv; mArgLen = 0; for (int i=0; i<argc; i++) { mArgLen += strlen(argv[i]) + 1; } mArgLen--; AppRuntime runtime; const char* argv0 = argv[0]; …… /*将参数加入到JAVAVM中作为其参数选项*/ int i = runtime.addVmArguments(argc, argv); // Parse runtime arguments. Stop at first unrecognized option. bool zygote = false; bool startSystemServer = false; bool application = false; const char* parentDir = NULL; const char* niceName = NULL; const char* className = NULL; while (i < argc) { const char* arg = argv[i++]; if (!parentDir) { parentDir = arg; } else if (strcmp(arg, "--zygote") == 0) { zygote = true; niceName = "zygote"; } else if (strcmp(arg, "--start-system-server") == 0) { startSystemServer = true; } else if (strcmp(arg, "--application") == 0) { application = true; } else if (strncmp(arg, "--nice-name=", 12) == 0) { niceName = arg + 12; } else { className = arg; break; } } //这就是前面说的换名把戏的实现函数 if (niceName && *niceName) { setArgv0(argv0, niceName); set_process_name(niceName); } //设置runtime的mParentDir为/sysem/bin runtime.mParentDir = parentDir; if (zygote) { //★这是关键函数,怎个zygote的主要功能都由此函数启动! runtime.start("com.android.internal.os.ZygoteInit", startSystemServer ? "start-system-server" : ""); } else if (className) { // Remainder of args get passed to startup class main() runtime.mClassName = className; runtime.mArgC = argc - i; runtime.mArgV = argv + i; runtime.start("com.android.internal.os.RuntimeInit", application ? "application" : "tool"); } else { fprintf(stderr, "Error: no class name or --zygote supplied.\n"); app_usage(); LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied."); return 10; } } |
1.2 AppRuntime分析
AppRuntime是一个类,该类的声明和实现都在APP_main.cpp中,该类派生至AndroidRuntime类。
AppRuntime重载了onStarted, onZygoteInit, onExit函数。
1.1节中说道zygote的main函数中调用了AppRuntime.start函数,下面来详细分析下这个函数的功能:
1) 获取环境变量ANDROID_ROOT的值,赋给rootDir,如果该值为null,就新增该变量,并将其值设置为”/system”。 2) ★创建虚拟机: if (startVm(&mJavaVM, &env) != 0) { return; } 3) ★注册JNI函数: startReg(env); 4) 构建一个String数组,这个数组含有两个元素:classname, option。这个数组用于后面第五步的main函数的参数! stringClass = env->FindClass("java/lang/String"); assert(stringClass != NULL); //string strArray[] = new String[2]; strArray = env->NewObjectArray(2, stringClass, NULL); assert(strArray != NULL); classNameStr = env->NewStringUTF(className); assert(classNameStr != NULL); //设置第一个元素值为“com.android.internal.os.zygoteInit” env->SetObjectArrayElement(strArray, 0, classNameStr); optionsStr = env->NewStringUTF(options); //设置第二个元素值为“start-system-server”! env->SetObjectArrayElement(strArray, 1, optionsStr); 5) ★开始虚拟机,此线程就变为虚拟机的主线程,除非虚拟机退出,否则这个线程是不会返回的: ①将前面的com.android.internal.os.zygoteInit转换为斜线的形式,这样就符合jni函数的规范了,以后我们均简称其为zygoteInit类: char* slashClassName = toSlashClassName(className); ②调用该类的main(strings)函数,此函数的参数就是前面说的string数组。 /*在调用zygoteInit的main函数后,zygote便进入了java世界!所以说zygote是android系统中JAVA世界的鼻祖!*/ char* slashClassName = toSlashClassName(className); jclass startClass = env->FindClass(slashClassName); if (startClass == NULL) { ALOGE("JavaVM unable to locate class '%s'\n", slashClassName); /* keep going */ } else { jmethodID startMeth = env->GetStaticMethodID(startClass, "main", "([Ljava/lang/String;)V"); if (startMeth == NULL) { ALOGE("JavaVM unable to find main() in '%s'\n", className); /* keep going */ } else { env->CallStaticVoidMethod(startClass, startMeth, strArray); ……. |
通过上面对app_runtime的start函数的分析,我们发现了三个关键点:2、3、5。它们共同创建了整个Android的JAVA世界,下面分别对它们三个进行详细分析。
1.3 创建虚拟机——startVM
该函数就是调用JNI的虚拟机创建函数,但是创建虚拟机时的一些参数却是在此函数中确定的,详细代码在base/core/jni/Android_Runtime.cpp中:
/*此函数的绝大部分代码都是设置虚拟机的参数,这里只分析其中的两个。*/ /*下面的代码是用来设置JNI check选项的,JNIcheck就是NATIVE层调用JNI函数时,系统所做的一些检查工作。如,调用NEWUTFString函数时,系统会检测传入的字符串是否满足UTF-8编码格式。它还能检测资源是否被正确释放,但这个选项比较耗时,只有在eng版的系统里面才有,正式发布的user版是关闭了此功能的。下面几句代码就是有系统属性来控制是否使用JNI check。*/ property_get("dalvik.vm.checkjni", propBuf, ""); if (strcmp(propBuf, "true") == 0) { checkJni = true; } else if (strcmp(propBuf, "false") != 0) { /* property is neither true nor false; fall back on kernel parameter */ property_get("ro.kernel.android.checkjni", propBuf, ""); if (propBuf[0] == '1') { checkJni = true; } } …… /*开始设置虚拟机的堆栈起始大小和最大大小,默认起始大小是4MB最大大小是16MB,不过绝大多数厂商都会更改这个值*/ strcpy(heapstartsizeOptsBuf, "-Xms"); property_get("dalvik.vm.heapstartsize", heapstartsizeOptsBuf+4, "4m"); opt.optionString = heapstartsizeOptsBuf; mOptions.add(opt); strcpy(heapsizeOptsBuf, "-Xmx"); property_get("dalvik.vm.heapsize", heapsizeOptsBuf+4, "16m"); opt.optionString = heapsizeOptsBuf; mOptions.add(opt); …… /*最后调用JNI_CreateJavaVM 函数创建虚拟机,pEnv返回当前线程的JNIEnv,从这里就可以了解到VM是对进程而言的,JNIEnv是对线程而言的*/ if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) < 0) { ALOGE("JNI_CreateJavaVM failed\n"); goto bail; } |
1.4 注册JNI函数——startReg
上一节介绍了如何创建虚拟机,本节就介绍如何给这个虚拟机注册JNI函数。因为后续的JAVA世界用到的一些函数是采用NATIVE方式实现的,所以才必须提前注册这些JNI函数。详细代码同样在AndroidRuntime.cpp中:
/*static*/ int AndroidRuntime::startReg(JNIEnv* env) { /* * This hook causes all future threads created in this process to be * attached to the JavaVM. (This needs to go away in favor of JNI * Attach calls.) */ /* 设置Thread类的线程创建函数为javaCreateThreadEtc(这其实是一个hook函数),此函数的具体作用将在后面进行详细分析 */ androidSetCreateThreadFunc((android_create_thread_fn)javaCreateThreadEtc); ALOGV("--- registering native functions ---\n"); /* * Every "register" function calls one or more things that return * a local reference (e.g. FindClass). Because we haven't really * started the VM yet, they're all getting stored in the base frame * and never released. Use Push/Pop to manage the storage. */ env->PushLocalFrame(200); //注册JNI函数,gRegJNI是一个全局大数组,每个元素对应了一个JNI注册方法。 if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) { env->PopLocalFrame(NULL); return -1; } env->PopLocalFrame(NULL); //这句话听说是码农休闲时的小把戏,可以称为IT界的文物~ //createJavaThread("fubar", quickTest, (void*) "hello"); return 0; } |
下面再来看看register_jni_procs函数的详细代码:
static int register_jni_procs(const RegJNIRec array[], size_t count, JNIEnv* env) { for (size_t i = 0; i < count; i++) { if (array[i].mProc(env) < 0) {//此函数仅仅是一个封装,调用数组元素的mproc函数,完成对应的JNI注册 #ifndef NDEBUG ALOGD("----------!!! %s failed to load\n", array[i].mName); #endif return -1; } } return 0; } |
上面提到了gRegJNI全局数组,该数组元素调用mProc函数,那么它们到底是什么呢?
static const RegJNIRec gRegJNI[] = { REG_JNI(register_com_android_internal_os_RuntimeInit), REG_JNI(register_android_os_SystemClock), REG_JNI(register_android_util_EventLog), REG_JNI(register_android_util_Log), REG_JNI(register_android_util_FloatMath), REG_JNI(register_android_text_format_Time), …. 约有100项,每一项的括号里面的参数name对应一个函数name(env) } REG_JNI是一个宏,宏里面包含的就是那个mProc函数。 #ifdef NDEBUG #define REG_JNI(name) { name } struct RegJNIRec { int (*mProc)(JNIEnv*); }; #else #define REG_JNI(name) { name, #name } struct RegJNIRec { int (*mProc)(JNIEnv*); const char* mName; }; #endif 这个宏的意思就是,当我们调用gRegJNI[i].mProc(env)时,真正执行的函数就是name_i (env),这里为了方便就这样定义了,这个小技巧值得学习啊。 |
至此,我们已经分析了startVM, StartReg,了解了虚拟机的创建,学习了JNI函数的注册,下面就调用zygoteInit的main函数,进入java世界了!
1.5 Welcom to Java World
Java世界的入口函数就是zygoteInit的main函数,下面看看这个main函数的具体功能(ps:后面的代码都是java代码了~)。
public static void main(String argv[]) { try { // Start profiling the zygote initialization. SamplingProfilerIntegration.start(); //★key1注册zygote用的socket registerZygoteSocket(); EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START, SystemClock.uptimeMillis()); preload(); //★key2预加载类资源等 /* static void preload() { preloadClasses(); preloadResources(); preloadOpenGL(); } */ EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END, SystemClock.uptimeMillis()); // Finish profiling the zygote initialization. SamplingProfilerIntegration.writeZygoteSnapshot(); //强制执行一次垃圾回收 gc(); // If requested, start system server directly from Zygote if (argv.length != 2) { throw new RuntimeException(argv[0] + USAGE_STRING); } if (argv[1].equals("start-system-server")) { startSystemServer(); //★key3我们传入的参数是满足if的,所以调用此函数启动system_server进程 } else if (!argv[1].equals("")) { throw new RuntimeException(argv[0] + USAGE_STRING); } Log.i(TAG, "Accepting command socket connections"); runSelectLoop();//★key4 zygote调用这个函数 closeServerSocket(); } catch (MethodAndArgsCaller caller) { caller.run(); //★key5 很重要的caller run函数,以后分析 } catch (RuntimeException ex) { Log.e(TAG, "Zygote died with exception", ex); closeServerSocket(); throw ex; } } |
在这个main函数中,列举了5大关键点,下面一一进行分析。
1、建立IPC通信服务端:registerZygoteSocket
Zygote及系统中其他程序之间的通讯并没有使用Binder,而是采用了基于AF_UNIX类型的Socket。registerZygoteSocket的工作就是建立这个socket。
private static void registerZygoteSocket() { if (sServerSocket == null) { int fileDesc; try { /*从环境变量中获取socket的fd,回想一下zygote是如何启动的:在init进程的main函数中有一个execve函数,该函数的第三个参数就是环境变量*/ String env = System.getenv(ANDROID_SOCKET_ENV); fileDesc = Integer.parseInt(env); } catch (RuntimeException ex) { throw new RuntimeException( ANDROID_SOCKET_ENV + " unset or invalid", ex); } try { //创建服务端socket,这个socket将listen并accept Client sServerSocket = new LocalServerSocket( createFileDescriptor(fileDesc)); } catch (IOException ex) { throw new RuntimeException( "Error binding to local socket '" + fileDesc + "'", ex); } } } |
registerZygoteSocket很简单,就是创建一个socket服务器。不过我们应当思考一下:
①谁是客户端?
②服务端如何处理来自客户端的请求?
2、预加载类和资源
这里主要分析preloadClasses和preloadResources函数。
首先来看preloadClasses函数:
①预加载类的信息存储在PRELOADED_CLASSES变量中,它的值为”preloaded_classes”。这是一个文件的名字。意思就是预加载类的信息存储在这个文件中!
②读取该文件的每一行,再通过JAVA反射来加载类,每一行中存储的就是预加载类的类名。
Class.forName(line);
③一些扫尾工作。
Preloadclasses看起来很简单,但其实它要加载1268个类!!!这是相当耗时的,这也是Android系统启动慢的原因之一。Preload-class文件由frame/base/tools/preload工具生成,它会判断每个类的加载时间是否大于1250微秒,超过这个时间的话就会被写到这个文件中,再由zygote预加载。详细信息可以参考preload工具的说明。
至于preloadResources,它与preloadClasses类似,它主要加载frameword-res.apk中的资源——在UI编程中常用到的com.android.R.xxx是系统默认资源,它们就是由zygote在这里加载的。
3、启动system_server
现在开始分析第三个关键函数startSystemServer。这个函数会创建JAVA世界中系统server所驻留的进程system_server,该进程是framework的核心。
private static boolean startSystemServer() throws MethodAndArgsCaller, RuntimeException { /** Hardcoded command line to start the system server */ String args[] = { "--setuid=1000",Class.forName(line); "--setgid=1000", "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,3001,3002,3003", "--capabilities=130104352,130104352", "--runtime-init", "--nice-name=system_server", "com.android.server.SystemServer", }; ZygoteConnection.Arguments parsedArgs = null; int pid; try { //将args转换成Arguments类对象 parsedArgs = new ZygoteConnection.Arguments(args); int debugFlags = parsedArgs.debugFlags; if ("1".equals(SystemProperties.get("ro.debuggable"))) debugFlags |= Zygote.DEBUG_ENABLE_DEBUGGER; /*fork一个子进程,这个子进程就是system_server进程 */ pid = Zygote.forkSystemServer( parsedArgs.uid, parsedArgs.gid, parsedArgs.gids, debugFlags, null); } catch (IllegalArgumentException ex) { throw new RuntimeException(ex); } /** For child process */ if (pid == 0) { handleSystemServerProcess(parsedArgs); //system_server进程的工作。 } return true; } |
handleSystemServerProcess的详细代码如下:
private static void handleSystemServerProcess( ZygoteConnection.Arguments parsedArgs) throws ZygoteInit.MethodAndArgsCaller { /*首先设置兼容性*/ if (parsedArgs.uid != 0) { try { setCapabilities(parsedArgs.permittedCapabilities, parsedArgs.effectiveCapabilities); } catch (IOException ex) { Log.e(TAG, "Error setting capabilities", ex); } } //然后关闭该服务的socket closeServerSocket(); /** * 将主要的参数传递给SystemServer. * "--nice-name=system_server com.android.server.SystemServer" */ RuntimeInit.zygoteInit(parsedArgs.remainingArgs); } |
从上面的代码可以看出,zygote进行了一次fork,创建了一个子进程system_server进程(代码中的zygote.forkSystemServer),该进程的具体作用后面再讲,下面继续分析zygote进程。
4、runSelectLoop分析
当zygote从startSystemServer返回后,就进入第四个关键函数:runSelectLoop。
回想在前面的第一个关键点registerZygoteSocket中注册了一个用于IPC的socket,并提出了两个问题,第二个问题就是:服务端如何处理请求。答案就在这个函数中:
private static void runSelectLoop() throws MethodAndArgsCaller { ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>(); ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>(); FileDescriptor[] fdArray = new FileDescriptor[4]; /* sServerSocket 是我们在前面的registerZygoteSocket中建立的socket*/ fds.add(sServerSocket.getFileDescriptor()); peers.add(null); int loopCount = GC_LOOP_COUNT; while (true) { int index; if (loopCount <= 0) { gc(); loopCount = GC_LOOP_COUNT; } else { loopCount--; } try { fdArray = fds.toArray(fdArray); /* selectReadable 是一个NATIVE函数,使用多路复用I/O模型。 当有客户端连接或有数据时,该函数就返回。新的客户端连接上返回0,已连接上的客户端发送数据则返回>0,错误返回-1*/ 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) { /*有一个客户端连接上。注意,客户端在zygote的代表是zygoteConnection*/ ZygoteConnection newPeer = acceptCommandPeer(); peers.add(newPeer); fds.add(newPeer.getFileDesciptor()); } else { boolean done; /*客户端发送了请求,peers.get返回zygoteConnection类对象,然后调用该类的runOnce函数来处理客户端请求*/ done = peers.get(index).runOnce(); if (done) { /*表示客户端的请求已经得到解决,服务器就删除该连接*/ peers.remove(index); fds.remove(index); } } } } |
总结runSelectLoop:
①处理客户端的连接和请求。客户端在zygote中使用zygoteConnection对象表示。
②客户端的请求有zygoteconnection的runOnce函数处理。该函数实现代码较为复杂,主要原理是:从socket命令行里面读取一个开始命令。如果读取成功,就fork一个子进程并在这个子进程中抛出zygoteInit.MethodAndArgsCaller异常,而父进程中就正常返回;如果读取失败,那么就不会fork子进程,而是log出错误消息。至于父进程的返回值也有两种:如果在socket命令中读取到了一个EOF标志,就返回true值,如果还有命令可以读取,就返回false值——这个返回值就是标记客户端请求是否完成的~
1.6 zygote的总结
Zygote是Android系统中创建JAVA世界的盘古——它创建了第一个JAVA虚拟机;它也是女娲——它成功的fork了framework的核心system_server进程。现在回顾一下Zygote创建JAVA世界的步骤:
①创建AppRuntime对象,并调用它的start方法。此后的活动均由AppRuntime控制。
②调用startVM创建虚拟机,然后调用startReg来注册JNI函数。
③通过JNI调用com.android.internal.os.ZygoteInit类的main函数,从此进入了JAVA世界。只不过这个世界刚创造,里面什么东西都没有。
④调用registerZygoteSocket函数,这个函数相应子孙后代的请求,同时zygote调用preloadClass,preloadResource,preloadOpenGL为JAVA世界添砖加瓦。
⑤zygote觉得自己的工作量有点大,就fork了一个进程system_server来为java世界服务。
⑥zygote完成了Java世界的初创工作,它有点累了。以后的工作就由runSelectLoop函数来处理,zygote睡觉去了。
⑦以后的日子:zygote随时守护在我们的周围,当接收到子孙后代的请求时,它随时醒来,为它们工作。
Zygote原理学习的更多相关文章
- IIS原理学习
IIS 原理学习 首先声明以下内容是我在网上搜索后整理的,在此只是进行记录,以备往后查阅只用. IIS 5.x介绍 IIS 5.x一个显著的特征就是Web Server和真正的ASP.NET Appl ...
- zookkeper原理学习
zookkeper原理学习 https://segmentfault.com/a/1190000014479433 https://www.cnblogs.com/felixzh/p/58692 ...
- GIS原理学习目录
GIS原理学习目录 内容提要 本网络教程是教育部“新世纪网络课程建设工程”的实施课程.系统扼要地阐述地理信息系统的技术体系,重点突出地理信息系统的基本技术及方法. 本网络教程共分八章:第一章绪论,重点 ...
- 转:SVM与SVR支持向量机原理学习与思考(一)
SVM与SVR支持向量机原理学习与思考(一) 转:http://tonysh-thu.blogspot.com/2009/07/svmsvr.html 弱弱的看了看老掉牙的支持向量机(Support ...
- Android自复制传播APP原理学习(翻译)
Android自复制传播APP原理学习(翻译) 1 背景介绍 论文链接:http://arxiv.org/abs/1511.00444 项目地址:https://github.com/Tribler ...
- 计算机原理学习(1)-- 冯诺依曼体系和CPU工作原理
前言 对于我们80后来说,最早接触计算机应该是在95年左右,那个时候最流行的一个词语是多媒体. 依旧记得当时在同学家看同学输入几个DOS命令就成功的打开了一个游戏,当时实在是佩服的五体投地.因为对我来 ...
- Dubbo原理学习
Dubbo源码及原理学习 阿里中间件团队博客 Dubbo官网 Dubbo源码解析 Dubbo源码解析-掘金 Dubbo源码解析-赵计刚 Dubbo系列 源码总结+最近感悟
- XGBoost原理学习总结
XGBoost原理学习总结 前言 XGBoost是一个上限提别高的机器学习算法,和Adaboost.GBDT等都属于Boosting类集成算法.虽然现在深度学习算法大行其道,但很多数据量往往没有太 ...
- Git原理学习记录
Git原理学习记录 1.git init git-test git init 实际上就是在特定的目录下创建对应的目录和文件 2.object $ echo "V1" > ...
随机推荐
- 查看numpy的类型
查看一个变量的类型:type(img) 查看array中的数据值的类型:img.dtype 查看array的形状:img.shape
- python_85_sys模块
import sys print(sys.version)#当前python版本的详细信息 print(sys.argv)#脚本中运行,读取参数
- kubernetes-深入理解pod对象(七)
Pod中如何管理多个容器 Pod中可以同时运行多个进程(作为容器运行)协同工作.同一个Pod中的容器会自动的分配到同一个 node 上.同一个Pod中的容器共享资源.网络环境和依赖,它们总是被同时调度 ...
- C# 替换去除HTML标记方法(正则表达式)
[from] http://blog.csdn.net/sgear/article/details/6263848/// <summary> /// 将所有HTML标签替换成"& ...
- bug汇总
bug 2018年8月23日 bug 1:散点图画不出来. plt.scatter(validation_examples["longitude"], validation_exa ...
- new和delete的动态分配。
c++对象模型 视频的实际操作 note: 1.虚函数有虚指针,所以是4,不管有几个虚函数, 都只有一个vptr来存放调用的虚函数的地址. 2.子类的内存是父类内存的加自己的数据内存. 3.clas ...
- XAMPP vhost 配置(403问题解决)
<VirtualHost *:80> DocumentRoot "C:/xampp/htdocs/" ServerName localhost </Virtual ...
- Spring Boot 应用 快速发布到linux服务器的脚本代码示例
前提说明:spring boot 应用打包成jar包之后要部署到Linux服务器上面运行,我用的nohup java -jar 命令,但是代码更新之后重新部署的时候覆盖原来的项目,又要手动运行ps - ...
- 10GNU C语言函数调用
6. C 函数调用机制概述 在 Linux 内核程序 boot/head.s 执行完基本初始化操作之后,就会跳转区执行 init/main.c 程序.那么 head.s 程序时如何把执行控制转交给 ...
- How to Install Zabbix Server on Centos6.7
Prerequisite Environment First you must use your Subscription Manager to enable SCL: [root@fileserve ...