对android中Zygote的理解
谈谈对Zygote的简单理解
1. Zygote的作用
启动SystemServer
孵化应用进程
SystemServer也是通过Zygote启动的,因为它也需要Zygote的资源:常用类,JNI函数,主题资源,共享库等。
2. Zygote的启动流程
2.1 Android进程启动三段式
进程启动-》 准备工作-》LOOP循环 (接受消息,处理消息。消息可能来自:socket,message queue,binder驱动)
只要是独立进程,都会是这样启动,例如Zygote进程,系统服务进程,应用进程等。
2.2 Zygote的启动流程分两步分析
1. Zygote进程怎么启动的?
init进程是Linux启动后的用户空间第一个进程,它首先会加载配置文件init.rc读取哪些系统服务需要启动,例如Zygote,Service Manager等。
Zygote启动通过fork() + execve()系统调用
execve()需要传可执行程序路径和参数,
init.rc文件里的Zygote配置如下:
service zygote /system/bin/app_process64 -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 audioserver
onrestart restart cameraserver
onrestart restart media
onrestart restart netd
writepid /dev/cpuset/foreground/tasks
重点关注第一行,格式如下:
service 名称 可执行程序路径 参数
启动进程方式代码:
pid_t pid = fork();
if(pid == 0){
//child process
execve(path, argv, env);
}else{
//parent process
}
fork()函数会返回两次,子进程返回的pid == 0,父进程返回的pid为子进程的pid(非0),默认情况下,fork()出的子进程会继承父进程的资源,但如果执行了execve(path,argv,env)新的二进制程序,那继承的资源会被清掉,
其中path为可执行程序路径,argv为配置参数,env为环境变量。
信号处理–SIGCHLD
这个信号很常见,fork出子进程后,父进程都会关注这个信号,
如果子进程死了,父进程会收到这个信号SIGCHLD,就会去重启子进程。
例如Zygote挂了,init进程会收到SIGCHLD信号,然后重启Zygote。
2. Zygote进程启动后做了什么事呢?
总的来说可以分成两个部分,一个是Zygote的Native世界,一个是Zygote的Java世界。Zygote进程启动之后执行execve()二进制可执行程序,(App_main.cpp)程序中有一个main()函数作为程序入口,在里面做了一些准备工作,然后JNI调用进入java世界。
2.1 Zygote的Native世界
启动android的虚拟机
注册android的JNI函数
JNI调用进入java世界
举个例子,如何从C++代码切换到java环境:
int main(int argc, char *argv[]){
javaVM *jvm;
JNIEnv *env;
JNI_CreateJavaVM(&jvm, (void **)&env, &vm_args); //@1,创建虚拟机
jclass clazz = env->FindClass("ZygoteInit"); //找到zygoteinit java类
jmethodID method = env->GetStaticMethodID(clazz, "Main", "([java/lang/String;)V"); //找到里面的main函数
env->CallStaticVoidMethod(clazz, method, args); //运行main函数
jvm->DestroyJavaVM();//关闭虚拟机
}
我们的应用进程里没有重新创建虚拟机,是因为Zygote进程里已经创建好,应用进程直接继承过来,再重置虚拟机状态和重启里面的守护线程就可以了。
2.2 Zygote的java世界
Navite世界中的CallStaticVoidMethod最终会调用com.android.internal.os.ZygoteInit的main函数,其中做的工作如下:
1. 建立IPC通信服务端——registerZygoteSocket
Zygote以及系统中其他程序的通信没有使用Binder,而是采用了基于AF_UNIX类型的Socket。registerZygoteSocket函数的使命正是建立这个Socket。
2. 预加载类和资源
预加载类资源preloadClass函数,主要是加载preload-classes文件中记录的类信息。
预加载资源preloadResources函数,主要是加载framework-res.apk中的资源,例如在UI编程中常使用的com.android.R.XXX资源,是系统默认的资源,它们就是由Zygote加载的。
3. 启动system_server
startSystemServer(abiList, socketName),这个函数会创建Java世界中系统Service所驻留的进程system_server,该进程是framework的核心。如果它死了,就会导致zygote自杀。
ZygoteConnection.Arguments parsedArgs = null;
int pid;
try {
parsedArgs = new ZygoteConnection.Arguments(args);
ZygoteConnection.applyDebuggerSystemProperty(parsedArgs);
ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs);
//fork一个子进程,看来,这个子进程就是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);
}
/* For child process */
if (pid == 0) {
if (hasSecondZygote(abiList)) {
waitForSecondaryZygote(socketName);
}
//处理system_server进程的工作
handleSystemServerProcess(parsedArgs);
}
这里出现了一个分水岭,即Zygote进行了一次无性繁殖,分裂出了一个system_server进程。
4. 有求必应之等待请求——runSelectLoop
当Zygote从startSystemServer返回后,将进入第四个关键函数:runSelectLoop(abiList)。前面,在第一个关键点registerZygoteSocket中注册了一个用于IPC的Socket,不过那时还没有地方用到它。它的用途将在这个runSelectLoop中体现出来,请看下面的代码:
private static void runSelectLoop(String abiList) throws MethodAndArgsCaller {
ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
//sServerSocket是我们先前在registerZygoteSocket建立的Socket
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;
}
if (i == 0) {
//如有一个客户端连接上,请注意客户端在Zygote的代表是ZygoteConnection
ZygoteConnection newPeer = acceptCommandPeer(abiList);
peers.add(newPeer);
fds.add(newPeer.getFileDesciptor());
} else {
//客户端发送了请求,peers.get返回的是ZygoteConnection
//后续处理将交给ZygoteConnection的runOnce函数完成。
boolean done = peers.get(i).runOnce();
if (done) {
peers.remove(i);
fds.remove(i);
}
}
}
}
}
runSelectLoop比较简单,就是:
· 处理客户连接和客户请求。其中客户在Zygote中用ZygoteConnection对象来表示。
· 客户的请求由ZygoteConnection的runOnce来处理,其主要代码如下:
boolean runOnce() throws ZygoteInit.MethodAndArgsCaller {
String[] args = readArgumentList(); //读取参数列表
int pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
parsedArgs.debugFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,
parsedArgs.niceName, fdsToClose, parsedArgs.instructionSet,
parsedArgs.appDataDir);; //根据参数启动子进程
if(pid == 0){
//in child
handleChildProc(args, ...);
//在子进程里面干活,其实执行的就是java类得main函数(入口函数),java类名来自上面读取的参数列表。
//参数列表是AMS跨进程发过来的,类名就是ActivityThread.main(),
//也就是说,应用程序进程执行后会马上执行ActivityThread.main()函数
return true;
}
}
2.3 关于 Zygote启动流程的总结
Zygote是创建Android系统中Java世界的盘古,它创建了第一个Java虚拟机,同时它又是女娲,它成功地繁殖了framework的核心system_server进程。做为Java语言的受益者,我们理应回顾一下Zygote创建Java世界的步骤:
· 第一天:创建AppRuntime对象,并调用它的start。此后的活动则由AppRuntime来控制。
· 第二天:调用startVm创建Java虚拟机,然后调用startReg来注册JNI函数。
· 第三天:通过JNI调用com.android.internal.os.ZygoteInit类的main函数,从此进入了Java世界。然而在这个世界刚开创的时候,什么东西都没有。
· 第四天:调用registerZygoteSocket。通过这个函数,它可以响应子孙后代的请求。同时Zygote调用preloadClasses和preloadResources,为Java世界添砖加瓦。
· 第五天:Zygote觉得自己工作压力太大,便通过调用startSystemServer分裂一个子进程system_server来为Java世界服务。
· 第六天:Zygote完成了Java世界的初创工作,它已经很满足了。下一步该做的就是调用runSelectLoop后,便沉沉地睡去了。
· 以后的日子:Zygote随时守护在我们的周围,当接收到子孙后代的请求时,它会随时醒来,为它们工作。
3. Zygote的工作原理
zygote启动过程的两个细节:
1)zygote在fork时要保证单线程,
因为不管父进程有多少个线程,子进程创建时只有一个线程,多的线程就不见了,会导致很多奇怪的问题:子进程死锁,状态不一致等。
所以不如直接,fork子进程时,停掉其他线程,创建完了子进程再重启那些线程,
zygote就是这么做的,它不只有主线程,还有与虚拟机相关的守护线程。
2)zygote的IPC没有采用binder机制
Zygote采用的是socket,所以应用的binder机制不是从zygote继承的,而是AP进程创建后自己启动的binder机制。
思考的两个问题:
1,孵化AP为什么不交给systemServer,而是专门设计一个zygote?
应用在启动的时候,需要做很多准备工作,如启动虚拟机,加载各个类系统资源,都非常耗时,如果zygote把init工作做好,再在fork时共享给子进程,那效率就非常高。这就是zygote存在的价值,systemServer不能做,因为它跑了一堆系统服务,他们不能被继承到AP进程。
而且AP启动时,内存空间除了必要的资源外,最好是干净的,不要继承一堆乱七八糟的东西,因此不如给systemServer和AP进程都要用的资源抽出来单独放在一个进程里,这就是zygote进程。
2,zyogte的IPC为什么不用binder?用binder会有问题吗?
不用binder有个原因:
1)如果用了binder,zygote要先启动binder机制,打开binder驱动,获得描述符,map进程内存映射,注册binder线程,还要创建一个binder对象注册到serviceManager,另外AMS要向zygote发起创建应用进程请求的话,要先从serviceManager查询zygote的binder对象,再发起binder调用,非常繁琐。
相比之下,zygote和systemserver本就是父子关系,对于简单的消息通信,用管道或者socket非常方便,如果对管道和socket不了解,可以参考APUE和UNP。
2)如果zygote用了binder机制,再fork systemServer,那systemServer就继承了zygote的描述符和映射的内存,这两个进程在binder驱动层就会共用一套数据结构,这肯定是不行的。那还得把旧的描述符关掉,再重新启动一遍binder机制,自找麻烦。
Binder通讯是需要多线程操作的,代理对象对Binder的调用是在Binder线程,需要通过Handler调用主线程来操作。
比如AMS与应用进程通讯,AMS的本地代理IApplicationThread通过调用ScheduleLaunchActivity,调用到的应用进程ApplicationThread的ScheduleLaunchActivity是在Binder线程,需要再把参数封装为一个ActivityClientRecord,sendMessage发送给H类(主线程Handler,ActivityThread内部类)
fork不允许存在多线程,而非常巧的是Binder通讯偏偏就是多线程,所以干脆父进程(Zygote)这个时候就不使用binder机制。
推荐阅读
-
Android开发笔记之Android中数据的存储方式(一)
-
Nuxt脚手架创建项目后对server中的index.js所做的修改
-
Android SQLite数据库中的表详解
-
Android中微信抢红包助手的实现详解
-
RMAN Recipes 中对Flash Recovery Area的总结
-
对腾讯微博动态的理解 博客分类: 微博user腾讯动态理解twitter 腾讯微博动态sina微博weibo
-
Android中Service与Activity之间通信的几种方式
-
C#中的多态深入理解
-
浅谈Java编程中string的理解与运用
-
Android中隐藏状态栏和标题栏的方法汇总(隐藏状态栏、标题栏的五种方法)