Zygote 是android系统应用中一个相当重要的进程,其主要功能是执行Android应用程序。在android系统中运行新的应用,需要跟Zygote进程(拥有应用程序运行时所需要的各种元素和条件)结合后才能执行。
Zygote进程运行时,会初始化Dalvik虚拟机,并启动它。android的应用程序是由java编写的,不能直接以本地进程的形态运行在linux上,只能运行在Dalvik虚拟机中。并且每个应用程序都运行在各自的虚拟机中,应用程序每次运行都要重新初始化并启动虚拟机,这就相当耗时。在android中,应用程序运行前,Zygote进程通过共享已运行的虚拟机的代码与内存信息,缩短应用程序运行所耗费的时间。并且,它会事先将应用程序要使用的android Fromework中的类和资源加载到内存中,并组织形成所用资源的链接信息。新运行的android应用程序在使用所需要的资源时不必每次重新形成资源的链接信息,这样提高程序运行速度。
在android中,使用Zygote进程的目的?对于手机,为了是应用程序在有限的资源型有更快的运行响应速度,提高资源利用率和设备使用时间。android使用
Zygote来有效的减少系统负担,提高运行速度。
下面讲解zygote进程是如何运行,初始化,提高应用的运行速度的。
(1) 由 Zygote孵化进程
init进程是在系统启动后运行在用户空间中的首个进程。init进程启动完系统运行所需的各种 Daemon(守护线程)后,启动
Zygote,如下表所示;
Zygote进程启动后,android的服务与应用程序都由Zygote进程启动运行。
连接手机,使用adb工具,ps查看系统运行的进程。 根据父进程的PID,android设备中运行的进程大致有 Daemon 进程以及 Dalvik虚拟机中运行的android应用程序两大类。下图中,PID 为1 的进程。还有 PPID(父进程)为 1的进程,就是init 进程启动的 Daemon进程。Zygote进程就是其中之一,其进程的PID 为 30,下图可以看到,而父进程为30的进程,都是Zygote进程的子进程,由其创建并启动,大都是android应用程序。
注:linxu系统创建并运行一个进程,与android系统中通过Zygote来创建并运行一个进程的区别
上图为linux创建并运行一个进程的过程,父进程A调用fork()函数创建新的子进程A`。新创建的进程A` 共享父进程的内存结构信息和库连接信息。而后子进程A`调用 exec('B'),将新进程B的代码加载到内存中。此时父进程A的内存信息被清除,并重新分配内存,以便运行被装载的B进程,接着形成新的库连接信息,以供进程B使用。若进程B使用的共享库已被装载至内存,则只需更新连接信息。不然,还要添加一个步骤,即使存储器中的相关库装入内存中。每当运行新进程时,就会重复以上过程。
那么,android怎么创建并运行一个新进程呢?
Zygote是android系统的一个主要特征,它通过COW(copy on write)方式对运行在内存中的进程实现了最大程度的复用,并通过库共享有效的降低了内存的使用量(foot print)。具体简介如下:
如上图,Zygote进程调用fork()函数创建出
Zygote`子进程,子进程Zygote` 共享父进程Zygote的代码区域与连接信息。注意新的进程A并非通过fork()来重新装载已有进程的代码区,而是被动态地加载到复制出的Dalvik虚拟机上。而后,Zygote`进程将执行流程交给应用程序A类的方法,android应用程序开始运行。新生的应用程序A会使用已有的Zygote进程的库与资源的连接信息,所以运行速度很快。下图为Zygote运行后,新的android应用程序A的运行过程。
如上图所述,Zygote启动后,初始化并运行Dalvik虚拟机,而后将需要的类与资源加载到内存中。随后调用fork()创建出Zygote`子进程,接着
Zygote`子进程动态加载并运行android应用程序A。运行android应用程序A会使用Zygote已经初始话并启动的Dalvik虚拟机,通过使用已加载至内存中的类与资源来加快运行速度。
****************************************************************************************************
TIP: COW ( copy on write)
在创建新进程后,新进程会共享父进程的内存空间,即新的子进程会复制所有与父进程内存空间相关的信息并使用它。COW就是针对内存复制的一种技术。一般,复制内存的开销非常大,因此,创建的子进程在引用父进程的内存空间时,不要进行复制,而要直接共享父进程的内存空间。而当需要修改共享内存中的信息时,子进程才会将父进程中相关的内存信息复制到自身的内存空间,并进行修改,这就是COW技术。
注意当调用fork()直接运行 exec()时,新进程的内存空间与父进程的内存空间内容不同,此时复制符进程内存空间的做法就毫无意义,并且会增加新进程运行的系统开销。
****************************************************************************************************
(2)由 app_process运行 ZygoteInit class
与本地服务或Daemon不同的是,Zygote由java编写而成,不能直接由Init进程启动运行。若想运行Zygote类,必须先生成 Dalvik虚拟机,再在Dalvik虚拟机上装载运行ZygoteInte类,执行这一任务的就是 app_process 进程。
分析 /system/bin/app_process 源码(具体路径在 frameworks/base/cmds/app_process/app_main.cpp) 中的main()函数,可发现app_process进程首先生成一个 AppRuntime 对象,而后分析main()函数传递进来的参数,并传递给AppRuntime对象。然后生成并初始化Dalvik虚拟机,再调用执行 ZygoteInit 类的main方法。
分析 app_main()源码
****************************************************************************************************
int main( int argc, const char* const argv[] ){
.......
AppRuntime runtime; //1AppRuntime类继承自AndroidRuntime类,
AndroidRuntime类用于初始化并运行Dalvik虚拟机,为运行android应用程序做好准备,
......
int i = runtime.addVmArguments( argc, argv ); //2在运行Dalvik虚拟机之前,通过AppRuntime对象,分析环境变量以及运行的参数,并以此生成虚拟机选项。
//Next arg is parent directory
if ( i < argc ){
runtime.mParentDir = argv[ i++ ]; //3
}
}
****************************************************************************************************
分析 init.rc文件,可以发现Init进程在运行app_process时,根据如下规则传递参数,app_process参数形式如下:
app_process [java-options] cmd-dir start-class-name [options]
[ java-options ]: 传递给虚拟机的选项,必须以“-”开始
cmd-dir: 所有运行的进程所在的目录。
start-class-name : 要在虚拟机中运行的类的名称。 app_process会将指定的类加载到虚拟机中,而后调用类的main()方法。
[ options ]:要传递给类的选项
app_process 服务运行时,init.rc文件中的运行命令如下:
/system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
说明: -Xzygote指要传递给VM的选项,用来区分在虚拟机中运行的类是Zygote,还是 Zygote中运行的其他android应用程序,它会被保存到 AppRuntime 的mOption变量中,如在上述代码2中。在代码3中,运行目录参数 “/system/bin”被保存到 AppRuntime 的 mParentDir变量中。第三个参数用来指定加载到虚拟机中的类的名称,“--zygote”表示加载 com.android.internal.os.ZygoteInit类。最后一个参数“--start-system-server”作为选项传递给生成的类,用于启动运行系统服务器。
调用AppRuntime 对象
分析完传递给虚拟机的参数,并保存到AppRuntime类的对象中,而后加载对象,调用对象的main()方法。
****************************************************************************************************
****************************************************************************************************
上述代码中 1中检查类名称是否为“--zygote“,处理过程过程略微不同,但最后都是将给定的类加载至虚拟机中。加入传递过来的是”--zygote“,程序将继续执行1中的代码。 3中代码,调用AppRuntime的start()成员函数,生成并初始化虚拟机,而后将ZygoteInit类加载至虚拟机中,执行其中的main()函数。
传递给runtime.start()函数的第一个参数为"com.android.internal.os.ZygoteInit" 是完全限定名(FQN),在FQN中包含类所在的包以及类的名称,当FQN被传递给类加载器时,类加载器就会将包名称解析为相应的路径,而后在改路径下查找并加载名称为”ZygoteInit“ 的类。”--start-system-server“作为最后一个参数传递给app_process。
创建Dalvik虚拟机
在运行Dalvik虚拟机之前,除了接收从app_process传递过来的虚拟机选项外,AppRuntime的start()函数还要获取与虚拟机运行相关的各种系统属性与环境变量,而后更改虚拟机的运行选项。
****************************************************************************************************
int property_get ( const char *key, char *value, const char *default_value )
****************************************************************************************************
为了设置虚拟机的运行选项,需要调用 property_get()函数来访问系统中设置的相关值,上述代码为property_get()函数的函数原型,用来访问系统的属性域。系统属性与通过 init.rc 的 setprop语句有init进程或其他进程进行设置。若想变更 Dalvik虚拟机的运行选项,只需在运行虚拟机之前,参照调用 property_get()函数的代码,设置 init.rc 相关属性或通过 app_process 参数传递虚拟机的运行选项即可。
****************************************************************************************************
void AndroidRuntime::start( const char* className, const bool startSystemServer ){
if ( JNI_CreateJavaVM( &mJavaVM, &env, &initArgs ) < 0 ){
goto bail;
}
}
****************************************************************************************************
start()函数中,调用 JNI_CreateJavaVM() 函数来创建并运行虚拟机,JNI_CreateJavaVM() 函数原型如下:
jint JNI_CreateJavaVM ( JavaVM** p_vm, JNIEnv** p_env, void* vm_args )
>第一个参数:生成的javaVM类的接口指针
>第二个参数:JNIEnv类的接口指针,方便访问虚拟机
>第三个参数:已设置的虚拟机选项
接下来,注册要在虚拟机中使用的JNI函数。
****************************************************************************************************
****************************************************************************************************
调用2中的startReg()函数,将调用1中 static const RegJNIRecgRegJNI[] 数组中的函数。而后运行在虚拟机中的java类就可以调用本地函数了。
运行 ZygoteInit类
在创建完VM之后,接着加载要运行的类。如前所述,根据参数的不同,app_process也会加载执行Zygote以外的其他类。如下面代码,AppRuntime的start()函数会查找指定的类,并调用指定类的main()方法。
****************************************************************************************************
****************************************************************************************************
此时程序的执行,转至了虚拟机上执行的java程序。后面本地域中的C++代码执行流会一直等待,知道虚拟机运行停止。
(4)ZygoteInit类的功能
至此已经创建好了虚拟机,将zygoteInit类加载到了虚拟机中。接下来看看zygoteInit类的作用。
ZygoteInit的main()方法的功能如上图所示。其源码如下:
**********************************************************************************************************
public static void main( String argv[ ] ){
try{
//绑定套接字,接收新android应用程序运行请求
registerZygoteSocker(); //为了从ActivityManager接收新android应用程序的运行请求,Zygote使用UDS,init进程在运行app_process时,使用init.rc文件中以”/dev/zygote“形式注册的套接字。
//加载android Application Framework使用的类与资源
proloadClasses(); //用于将应用程序框架中的类,平台资源预先加载到内存中。新进程直接使用这些类与资源,不需要重新加载他们。
proloadResources();
//运行SystemServer
if ( argv[1].equals("true") ) {
startSystemServer(); //通过app_process运行zygote时,参数"--start-system-server"会调用startSystemServer()方法启动系统服务器,系统服务器用来运行Android平台需要的一些主要的本地服务。
}
if ( ZYGOTE_FORK_MODE ){
runForkMode();
}else{
//处理新android应用程序运行请求
runSelectLoopMode(); //监视UDS,若收到新android应用程序生成请求,则进入处理循环
}
closeServerSocket();
}catch( MethodAndArgsCaller caller ){
caller.run();
}catch( RuntimeException ex ){
closeServerSocket();
throw ex;
}
}
**********************************************************************************************************
下面详细分析这些功能
a. 绑定/dev/socket/zygote套接字
ZygoteInit 类使用由 /dev/socket/zygote 生成的UDS套接字,从ActivityManager 接收新android应用程序的生成请求。该套接字在系统启动过程中由 init 进程生成,在 init.rc 文件中有生成该套接字的相关内容。下面是关于 zygote service 定义部分,套接字的名称,种类,访问权限在第二行中标出了。 *******************************************************************************************************
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
socket zygote stream 666
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
******************************************************************************************************
ZygoteInit类中的main()方法首先调用 registerZygoteSocket()方法,代码如下:
******************************************************************************************************
*****************************************************************************************************
在2中创建一个LocalServerSocket类的对象并将其赋值给sServerSocket静态变量中。代码1中调用System.getenv()方法,获取套接字的文件描述符,该套接字由init进程记录在ANDROID_SOCKET_zygote环境变量中。应用程序Framework使用套接字文件描述符创建 LocalServerSocket 类的实例,并将其与 /dev/socket/zygote绑定在一起。
当创建新android进程的请求到达ZygoteInit对象时,创建出LocalServerSocket实例接收生成新android进程的信息,并在最后循环语句中进行处理。
b. 加载应用程序Framework 中的类与平台资源
ZygoteInit 类会调用 preloadClasses()与 preloadResources()两个方法,这两个方法分别用于将应用程序Framework中的类,以及图标,图像,字符串等资源加载到内存中,并对装载的类与资源生成链接信息。新生成的android应用程序在使用这些已经装载的类或资源时,直接使用即可,不需要重新生成链接信息。
preloadClasses()方法主要代码如下:
******************************************************************************************************
******************************************************************************************************
代码解析:1中获取一个输入流,以便读取”preloaded-classes“文件中记录的类。”preloaded-classes“文件的部分内容代码如下:
*****************************************************************************************************
#Classes which are preloaded by com.android.internal.os.ZygoteInit
#Automatically generated by frameworks/base/tools/preload/WritePreloadedClassFile.java.
#MIN_LOAD_TIME_MICROS=1250
SQLite.Blob
SQLite.Database
SQLite.FunctionContext
SQLite.Stmt
SQLite.Vm
android.R$styleable
android.accounts.IAccountsService$Stub
android.app.Acitivity
.......
******************************************************************************************************
2代码,在获取输入流的基础上,创建BufferedReader对象,并读取”preloaded-classed“文件的内容
3代码中,忽略所读取内容中的注释与空行,而后开始读取下一行。
4代码中,调用Class.forName()方法,将读到的类动态地加载到内存中。Class.forName()方法并非真在内存中生成指定类的实例,它只是把类的信息加载到内存中,并初始化静态变量。
新应用程序运行时,由于使用的类已经加载到内存中,所以程序的启动运行速度回相当快。
加载应用程序Framework中包含的资源
在android应用程序Framework中使用的字符串,颜色,图像文件,音频文件等都成为资源。应用程序不能直接访问这些资源,需要通过Android开发工具自动生成的R类来访问。通过R类可访问的资源组成信息记录在XML中。
preloadResources方法是用于加载资源,代码如下:
******************************************************************************************************
******************************************************************************************************
资源分为系统资源与应用程序资源,使用系统资源时,需要先调用Resources类的方法getSystem(),而后使用其返回对象,预加载系统资源。
至此,Dalvik虚拟机已经启动并完成初始化,还绑定了套接字,以便接收应用程序创建请求。并且Framework中的类与资源也被加载到内存中,由此ZygoteInit类做好了接收请求创建应用程序并运行的准备。但ZygoteInit类在处理应用程序创建请求之前,还要运行SystemServer工作。
c. 运行 SystemServer
Zygote启动Dalvik虚拟机后,会再生成一个Dalvik虚拟机实例,以便运行名称为 SystemServer的java服务,SystemServer 用于运行 Audio Flinger与 Surface Flinger本地服务。在运行完所需的本地服务后,
SystemServer 开始运行Android Framework的服务,如ActivityManager, PackageManager等。
startSystemServer的代码如下:
******************************************************************************************************
private static boolean startSystemServer(String abiList, String socketName)
throws MethodAndArgsCaller, RuntimeException {
long capabilities = posixCapabilitiesAsBits(
OsConstants.CAP_BLOCK_SUSPEND,
OsConstants.CAP_KILL,
OsConstants.CAP_NET_ADMIN,
OsConstants.CAP_NET_BIND_SERVICE,
OsConstants.CAP_NET_BROADCAST,
OsConstants.CAP_NET_RAW,
OsConstants.CAP_SYS_MODULE,
OsConstants.CAP_SYS_NICE,
OsConstants.CAP_SYS_RESOURCE,
OsConstants.CAP_SYS_TIME,
OsConstants.CAP_SYS_TTY_CONFIG
);
/* Hardcoded command line to start the system server */
String args[] = {
"--setuid=1000",
"--setgid=1000",
"--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1032,3001,3002,3003,3006,3007",
"--capabilities=" + capabilities + "," + capabilities,
"--runtime-init",
"--nice-name=system_server",
"com.android.server.SystemServer",
};
ZygoteConnection.Arguments parsedArgs = null;
int pid;
try {
parsedArgs = new ZygoteConnection.Arguments(args);
ZygoteConnection.applyDebuggerSystemProperty(parsedArgs);
ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs);
/* Request to fork the system server process */
pid = Zygote.forkSystemServer(
parsedArgs.uid, parsedArgs.gid,
parsedArgs.gids,
parsedArgs.debugFlags,
null,
parsedArgs.permittedCapabilities,
parsedArgs.effectiveCapabilities);
} catch (IllegalArgumentException ex) {
throw new RuntimeException(ex);
}
/* For child process */
if (pid == 0) {
if (hasSecondZygote(abiList)) {
waitForSecondaryZygote(socketName);
}
handleSystemServerProcess(parsedArgs);
}
return true;
}
*****************************************************************************************************
该方法用于运行SystemServer。代码1定义保存SystemServer的启动参数的字符串数组,属于硬编码数组。最后一个参数指定SystemServer类。 与运行其他应用程序不同,该方法会调用 forkSystemServer()方法来创建新进程,并运行SystemServer。系统在运行普通android应用程序时,只负责创建应用程序进程,至于进程是否创建成功并不检查。与此不同,SystemServer是必须运行的,因此在
forkSystemServer()方法中必须检查生成的SystemServer 进程工作是否正常。3代码中,运行SystemServer类的main()方法,此时会加载android_servers本地库。
******************************************************************************************************
******************************************************************************************************
本地库加载完毕以后,然后调用JNI本地方法nativeInit(),加载系统需要的本地服务。然后设置android.server.SystemServer主线程,加载framework层主要服务,启动消息处理循环。
d. 运行新的android应用程序
在systemserver运行后,程序会进入一个循环,,处理来自所绑定的套接字的请求。程序运行runSelectLoopMode()方法启动消息处理循环,处理套接字请求。
上图描述了zygoteInit类运行新应用的过程,接下来代码详细分析:
******************************************************************************************************
private static void runSelectLoop(String abiList) throws MethodAndArgsCaller {
ArrayList fds = new ArrayList();
ArrayList peers = new ArrayList();
FileDescriptor[] fdArray = new FileDescriptor[4];
fds.add(sServerSocket.getFileDescriptor());//将套接字与dev/socket/zygote绑定在一起,本行首先将套接字的描述符添加到描述符数组中,保存在数组的第0个index中,程序将使用该描述符处理来自外部的连接请求。
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);
index = selectReadable(fdArray);//本身是一个被注册为JNI本地方法的本地函数。用来监视参数传递过来的文件描述符数组,若描述符目录中存在相关事件,则返回其在数组中的索引。
} catch (IOException ex) {
throw new RuntimeException("Error in select()", ex);
}
if (index < 0) {
throw new RuntimeException("Error in select()");
} else if (index == 0) {//处理index为0的套接字描述符中发生的输入输出事件。为了处理传递给dev/socket/zygote套接字的链接请求,程序先创建了Z对象。在zygoteConnection构造方法中创建输入输出流,而后生成Credentials,检查请求连接一方的访问权限,为了处理zygoteConnection对象的输入输出事件,将套接字描述符添加到套接字描述符数组fds中。被添加的套接字描述符的输入输出事件在下一个循环中,由selectReadable()方法进行检查。
ZygoteConnection newPeer = acceptCommandPeer(abiList);
peers.add(newPeer);
fds.add(newPeer.getFileDescriptor());
} else {
boolean done;
done = peers.get(index).runOnce();//用于处理新连接的输入输出套接字,并生成新的android应用程序。
if (done) {
peers.remove(index);
fds.remove(index);
}
}
}
}
******************************************************************************************************
在runOnce()方法中,方法用来创建新的进程。
以上是ZygoteInte类加载新应用程序类并调用main()方法执行的整个过程。新运行的应用程序由ZygoteInit类动态加载,共用装载到父进程生成的虚拟机中的代码。共用应用程序Framework中的类与资源的链接信息,大大加快了应用程序创建于启动的速度。