背景
从字面上看,zygote是受精卵的意思,它的主要工作就是进行细胞分裂。
在Android中,zygote的行为就如同受精卵一样,在系统发出请求时,负责分裂出其它的进程。
Android为什么要这么设计呢?
要知道从受精卵分裂出来的细胞,将继承受精卵的DNA。这些细胞只需要按照规则复制DNA就行了,而不需要重新思考DNA应该怎么排序。
大概也可以按照这个思路来理解zygote进程吧。
zygote进程启动时,将在内部启动Dalvik虚拟机,注册JNI函数,继而加载一些必要的系统资源和必要类等,然后进入到监听状态。
在后续的运作中,当其它系统模块希望创建新进程时,只需向zygote进程发出请求。zygote进程监听到该请求后,会相应地“分裂”出新的进程。
于是新创建出的进程,从一开始就具有了自己的Dalvik虚拟机以及一些必要的系统资源。这将减少每个进程启动消耗的时间。进一步来说,由于fork的copy-on-write策略,zygote的这种分裂方式还有可能减少系统整体内存的占用。
版本
android 6.0
主要流程分析
在分析init进程时,我们知道init进程会解析init.rc文件,然后加载对应的进程。zygote就是以这种方式,被init进程加载的。
在system/core/rootdir/init.rc中,可以看到:
import /init.${ro.zygote}.rc
从import的文件命名方式,我们可以看出在不同的平台(32、64及64_32)上,init.rc将包含不同的zygote.rc文件。不同的zygote.rc内容大致相同,主要区别体现在启动的是32位,还是64位的进程。
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
class main
socket zygote stream 660 root system
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart media
onrestart restart netd
writepid /dev/cpuset/foreground/tasks
以上是init.zygote32.rc中的内容。
从init.zygote32.rc可以看出,zygote实际上对应于app_process进程,对应源文件为frameworks/base/cmds/app_process/app_main.cpp,其main函数对应的传入参数为:–zygote, –start-system-server 。
这里需要补充说明的是:
1、在init.zygote64.rc中,启动的进程为app_process64;在init.zyote64_32.rc中,会启动两个进程,分别为app_process64和app_process32(目前,不太清楚这种设置的实际含义)。
2、从zyote的rc文件,我们可以看到zygote启动的时候,创建了一个socket(后文将介绍这个socket)。
接下来我们按照zygote进程启动涉及文件的先后顺序,一起来看看zygote的主要工作情况。
1、app_main.cpp
首先从入口文件app_main.cpp的main函数开始分析。
int main(int argc, char* const argv[])
{
//AppRuntime定义于app_main.cpp中,继承自AndroidRuntime
//个人感觉该对象就是对Android运行时环境的一种抽象
AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));
..........
//开始解析输入参数
while (i < argc) {
const char* arg = argv[i++];
if (strcmp(arg, "--zygote") == 0) {
//init.zygote.rc中定义了该字段
zygote = true;
//记录app_process进程名的nice name,即zygote
niceName = ZYGOTE_NICE_NAME;
} else if (strcmp(arg, "--start-system-server") == 0) {
//init.zygote.rc中定义了该字段
startSystemServer = true;
} else if (strcmp(arg, "--application") == 0) {
application = true;
} else if (strncmp(arg, "--nice-name=", 12) == 0) {
niceName.setTo(arg + 12);
} else if (strncmp(arg, "--", 2) != 0) {
className.setTo(arg);
break;
} else {
--i;
break;
}
}
..........
//准备下一个函数调用所需的参数
Vector<String8> args;
if (!className.isEmpty()) {
//启动zygote时,class name is empty,不进入该分支
........
} else {
//创建dalvikCache所需的目录,并定义权限
maybeCreateDalvikCache();
if (startSystemServer) {
//增加参数
args.add(String8("start-system-server"));
}
.........
for (; i < argc; ++i) {
//将main函数未处理的参数都递交给下个调用函数处理
args.add(String8(argv[i]));
}
}
if (!niceName.isEmpty()) {
//将app_process的进程名,替换为zygote
runtime.setArgv0(niceName.string());
set_process_name(niceName.string());
}
if (zygote) {
//调用Runtime的start函数
runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
} else if (className) {
//启动zygote没有进入这个分支,但这个分支说明,通过配置init.rc文件,其实是可以不通过zygote来启动一个进程
runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
} else {
...............
}
}
由于AppRuntime继承自AndroidRuntime,且没有重写start方法,因此zygote的流程进入到了AndroidRuntime.cpp。
2、AndroidRuntime.cpp
我们一起来看看在AndroidRuntime的start函数中,进行了哪些重要操作:
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote) {
.........
JniInvocation jni_invocation;
jni_invocation.Init(NULL);
JNIEnv* env;
//创建虚拟机,其中大多数参数由系统属性决定
//最终,startVm利用JNI_CreateVM创建出虚拟机
if (startVm(&mJavaVM, &env, zygote) != 0) {
return;
}
...........
//注册JNI函数
if (startReg(env) < 0) {
ALOGE("Unable to register all android natives\n");
return;
}
......
==============================以下非主干部分============================
我们先来看看startReg具体是在干什么。
int AndroidRuntime::startReg(JNIEnv* env) {
..........
if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
env->PopLocalFrame(NULL);
return -1;
}
........
}
从上述代码可以看出,startReg函数中主要是通过register_jni_procs来注册JNI函数。
其中,gRegJNI是一个全局数组,该数组的定义类似于:
static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_util_SeempLog),
REG_JNI(register_com_android_internal_os_RuntimeInit),
REG_JNI(register_android_os_SystemClock),
REG_JNI(register_android_util_EventLog),
............
其中,REG_JNI的宏定义及RegJNIRec结构体的定义为:
#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
根据宏定义可以看出,宏REG_JNI将得到函数名;定义RegJNIRec数组时,函数名被赋值给RegJNIRec结构体,于是每个函数名被强行转换为函数指针。
明白宏及数组的定义后,我们再回头来看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) {
#ifndef NDEBUG
ALOGD("----------!!! %s failed to load\n", array[i].mName);
#endif
return -1;
}
}
return 0;
}
结合前面的分析,容易知道register_jni_procs函数,实际上就是调用函数指针对应的函数,以进行实际的JNI函数注册。
我们随意举个例子,看看register_android_util_SeempLog被调用时的情况:
/*
* JNI registration.
*/
static JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
{ "seemp_println_native", "(ILjava/lang/String;)I",
(void*) android_util_SeempLog_println_native },
};
int register_android_util_SeempLog(JNIEnv* env)
{
jclass clazz = env->FindClass("android/util/SeempLog");
if (clazz == NULL) {
return -1;
}
return AndroidRuntime::registerNativeMethods(env, "android/util/SeempLog", gMethods, NELEM(gMethods));
}
可以看到,这实际上是自己定义JNI函数并进行动态注册的标准写法。
int AndroidRuntime::registerNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods)
{
return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}
最后,还是利用jniRegisterNativeMethods进行实际的注册工作。
==============================以上非主干部分============================
在介绍完AndroidRuntime.cpp中注册JNI的工作后,我们将思路拉回到它的start函数。
........
//将"com.android.internal.os.ZygoteInit"替换为"com/android/internal/os/ZygoteInit"
char* slashClassName = toSlashClassName(className);
jclass startClass = env->FindClass(slashClassName);
if (startClass == NULL) {
ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
/* keep going */
} else {
//通过反射找到ZygoteInit的main函数
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 {
//调用ZygoteInit的main函数
env->CallStaticVoidMethod(startClass, startMeth, strArray);
........
当调用ZygoteInit.java的main函数后,zygote进程进入了java世界。
其实我们仔细想一想,就会觉得zygote的整个流程实际上是非常符合实际情况的。
在Android中,每个进程都运行在对应的虚拟机上,因此zygote首先就负责创建出虚拟机。
然后,为了反射调用java代码,必须有对应的JNI函数,于是zygote进行了JNI函数的注册。
当一切准备妥当后,zygote进程才进入到了java世界。
3、ZygoteInit.java
public static void main(String argv[]) {
try {
.........
String socketName = "zygote";
.........
//注册server socket
registerZygoteSocket(socketName);
.........
//预加载
preload();
.........
if (startSystemServer) {
//启动system server
startSystemServer(abiList, socketName);
}
//zygote进程进入无限循环,处理请求
runSelectLoop(abiList);
closeServerSocket();
} catch (MethodAndArgsCaller caller) {
//通过反射调用新进程函数的地方
//后续介绍新进程启动时,再介绍
caller.run();
} catch (RuntimeException ex) {
closeServerSocket();
throw ex;
}
}
上面是ZygoteInit的main函数的主干部分,接下来我们进一步分析它们的主要工作。
3.1 创建server socket
private static void registerZygoteSocket(String socketName) {
if (sServerSocket == null) {
int fileDesc;
//此处的socket name,就是zygote
final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;
try {
//记得么?在init.zygote.rc被加载时,就会创建一个名为zygote的socket
String env = System.getenv(fullSocketName);
fileDesc = Integer.parseInt(env);
} catch (RuntimeException ex) {
throw new RuntimeException(fullSocketName + " unset or invalid", ex);
}
try {
FileDescriptor fd = new FileDescriptor();
//获取zygote socket的文件描述符
fd.setInt$(fileDesc);
//将zygote socket包装成一个server socket
sServerSocket = new LocalServerSocket(fd);
} catch (IOException ex) {
throw new RuntimeException("Error binding to local socket '" + fileDesc + "'", ex);
}
}
}
zygote进程与系统中其它进程的通信没有使用Binder,而是采用了基于AF_UNIX类型的socket。(实际上,在这个时候Binder根本无法使用)。
3.2 预加载
static void preload() {
Log.d(TAG, "begin preload");
//读取文件framework/base/preloaded-classes,然后通过反射加载对应的类
//需要加载数千个类,启动慢的原因之一
preloadClasses();
//负载加载一些常用的系统资源
preloadResources();
//图形相关的
preloadOpenGL();
//一些必要库
preloadSharedLibraries();
//好像是语言相关的字符信息
preloadTextResources();
// Ask the WebViewFactory to do any initialization that must run in the zygote process, for memory sharing purposes.
WebViewFactory.prepareWebViewInZygote();
Log.d(TAG, "end preload");
}
为了让系统实际运行时更加流畅,在zygote启动时候,调用preload函数进行了一些预加载操作。
Android 通过zygote fork的方式创建子进程。zygote进程预加载这些类和资源,在fork子进程时,仅需要做一个复制即可。
这样可以节约子进程的启动时间。同时,根据fork的copy-on-write机制可知,有些类如果不做改变,甚至都不用复制,子进程可以和父进程共享这部分数据,从而省去不少内存的占用。
3.3 启动SystemServer进程
private static boolean startSystemServer(String abiList, String socketName) {
//准备capabilities参数
........
String args[] = {
"--setuid=1000",
"--setgid=1000",
"--setgroups=.........",
"--capabilities=" + capabilities + "," + capabilities,
"--nice-name=system_server",
"--runtime-args",
"com.android.server.SystemServer",
};
ZygoteConnection.Arguments parsedArgs = null;
int pid;
try {
//将上面准备的参数,按照ZygoteConnection的风格进行封装
parsedArgs = new ZygoteConnection.Arguments(args);
...........
//通过fork"分裂"出system server,具体的过程在介绍system server时再分析
/* 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);
}
if (pid == 0) {
............
//pid = 0, 在进程system server中
//system server进程处理自己的工作
handleSystemServerProcess(parsedArgs);
}
return true;
}
3.4 处理请求信息
创建出SystemServer进程后,zygote进程利用函数runSelectLoop,处理server socket收到的命令。
private static void runSelectLoop(String abiList) throws MethodAndArgsCaller {
ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
//首先将server socket加入到fds
fds.add(sServerSocket.getFileDescriptor());
peers.add(null);
while (true) {
StructPollfd[] pollFds = new StructPollfd[fds.size()];
for (int i = 0; i < pollFds.length; ++i) {
pollFds[i] = new StructPollfd();
pollFds[i].fd = fds.get(i);
//关注事件到来
pollFds[i].events = (short) POLLIN;
}
try {
//等待事件到来
Os.poll(pollFds, -1);
} catch (ErrnoException ex) {
throw new RuntimeException("poll failed", ex);
}
//注意这里是倒序的
for (int i = pollFds.length - 1; i >= 0; --i) {
if ((pollFds[i].revents & POLLIN) == 0) {
continue;
}
//server socket最先加入fds, 因此这里是server socket收到数据
if (i == 0) {
//收到新的建立通信的请求,建立通信连接
ZygoteConnection newPeer = acceptCommandPeer(abiList);
//加入到peers和fds
peers.add(newPeer);
fds.add(newPeer.getFileDesciptor());
} else {
//其它通信连接收到数据,runOnce执行对应命令
boolean done = peers.get(i).runOnce();
if (done) {
//对应通信连接不再需要执行其它命令,关闭并移除
peers.remove(i);
fds.remove(i);
}
}
}
}
}
从上面代码可知,初始时,fds中仅有server socket,因此当有数据到来时,将执行i等于0的分支。
此时,显然是需要创建新的通信连接,因此acceptCommandPeer将被调用。
private static ZygoteConnection acceptCommandPeer(String abiList) {
try {
return new ZygoteConnection(sServerSocket.accept(), abiList);
} catch (IOException ex) {
throw new RuntimeException("IOException during accept()", ex);
}
}
acceptCommandPeer封装了socket的accpet函数。于是我们知道,对应的新的连接,zygote将会创建出一个新的socket与其通信,并将该socket加入到fds中。因此,一旦通信连接建立后,fds中将会包含有多个socket。
当poll监听到这一组sockets上有数据到来时,就会从阻塞中恢复。于是,我们需要判断到底是哪个socket收到了数据。
在runSelectLoop中采用倒序的方式轮询,由于server socket第一个被加入到fds,因此最后轮询到的socket才需要处理新建连接的操作;其它socket收到数据时,仅需要调用zygoteConnection的runonce函数执行数据对应的操作。
若一个连接处理完所有对应消息后,该连接对应的socket和连接等将被移除。
socket编程中,accept()调用主要用在基于连接的套接字类型,比如SOCK_STREAM和SOCK_SEQPACKET。
它提取出所监听套接字的等待连接队列中第一个连接请求,创建一个新的套接字,并返回指向该套接字的文件描述符。
新建立的套接字不在监听状态,原来所监听的套接字的状态也不受accept()调用的影响。
结束语
以上是自己对zygote进程的一些初步分析,我们知道了zygote如何由init进程加载后,一步一步地进入到java世界。由于经验原因,自己的分析难免存在纰漏和不够详细的地方,欢迎大家指正。