Android学习之zygote启动流程
Posted 小陈乱敲代码
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android学习之zygote启动流程相关的知识,希望对你有一定的参考价值。
zygote 概述
在android系统中,DVM(Dalvik虚拟机)、应用程序进程以及运行系统的关键服务的SystemServer进程都是由Zygote进程来创建的,我们也将它称为孵化器。
zygote的英文名为受精卵,由于他能不断孵化进程,因此也是名字的由来。
它通过fock(复制进程)的形式来创建应用程序进程和SystemServer进程,由于Zygote进程在启动时会创建DVM,因此通过fock而创建的应用程序进程和SystemServer进程可以在内部获取一个DVM的实例拷贝
init.rc中配置
在init进程创建时,会读取 init.zygote.rc 中的配置文件
init.zygote.rc
这个是在rc文件中的配置,下面我们会一次介绍各个命令的作用,这些命令具体都会在源码分析中看到
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
class main
priority -20
user root
group root readproc
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
onrestart restart wificond
writepid /dev/cpuset/foreground/tasks
service
service指令告诉系统将zygote程序加入到系统服务中,基本语法如下
service service_name 可执行程序的路径 可执行程序自身所需的参数列表
这里我们的服务是 zygote 他的程序路径在 /system/bin/app_process ,后面那一堆,就是程序执行的参数,在 app_process 的main函数中会从argc和argv中取出
-Xzygote
作为虚拟机启动时所需要的参数,在AndroidRuntime.cpp中的 startVm() 函数中调用 JNI_CreateJavaVM 使用到
/system/bin
代表虚拟机程序所在目录,因为 app_process 可以不和虚拟机在一个目录,所以 app_process 需要知道虚拟机所在的目录
–zygote
指明以 ZygoteInit 类作为入口,否则需要指定需要执行的类名
–start-system-server
仅在有 --zygote 参数时可用,告知 ZygoteInit 启动完毕后孵化出的第一个进程是 SystemServer
其他命令
后续的命令是和socket相关,名称、类型、端口号、重启后的操作等等
app_process
该文件在 frameworks/base/cmds/app_process 中,找到 main 函数
//前面的操作都是对参数进行一些赋值等等,就是之前的命令中配置的参数
......
if (zygote)
runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
else if (className)
runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
else
fprintf(stderr, "Error: no class name or --zygote supplied.\\n");
app_usage();
LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
这个 runtime 是 AppRuntime
AppRuntime.start
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
......
JniInvocation jni_invocation;
jni_invocation.Init(NULL);
JNIEnv* env;
//创建虚拟机相关信息
if (startVm(&mJavaVM, &env, zygote) != 0)
return;
onVmCreated(env);
//JNI方法的注册
if (startReg(env) < 0)
ALOGE("Unable to register all android natives\\n");
return;
......
char* slashClassName = toSlashClassName(className != NULL ? className : "");
jclass startClass = env->FindClass(slashClassName);
if (startClass == NULL)
......
else
jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
"([Ljava/lang/String;)V");
if (startMeth == NULL)
......
else
env->CallStaticVoidMethod(startClass, startMeth, strArray);
对虚拟机和JNI方法的一些注册后,通过 CallStaticVoidMethod 来调用传过来的类名的main函数,我们传递过来的类名是 com.android.internal.os.ZygoteInit
ZygoteInit.main
该文件在 frameworks/base/core/java/com/android/internal/os 中
public static void main(String argv[])
ZygoteServer zygoteServer = new ZygoteServer();
......
final Runnable caller;
try
......
zygoteServer.registerServerSocket(socketName);
preload();
......
if (startSystemServer)
Runnable r = forkSystemServer(abiList, socketName, zygoteServer);
if (r != null)
r.run();
return;
caller = zygoteServer.runSelectLoop(abiList);
catch (Throwable ex)
......
finally
zygoteServer.closeServerSocket();
if (caller != null)
caller.run();
registerServerSocket : 用于注册socket
preload : 预加载类和资源
forkSystemServer : 启动system_server
runSelectLoop : socket的处理
registerServerSocket
注册一个socket,来接收启动新进程一些命令操作,建立socket通道,zygote作为通信的服务端,用于响应客户端请求
void registerServerSocket(String socketName)
if (mServerSocket == null)
int fileDesc;
final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;
try
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();
fd.setInt$(fileDesc);
mServerSocket = new LocalServerSocket(fd);
catch (IOException ex)
throw new RuntimeException(
"Error binding to local socket '" + fileDesc + "'", ex);
fileDesc 是一个文件描述符
最终会构造一个 LocalServerSocket ,创建socket的本地服务端
关于socket
在Linux系统中,所有的资源都可以看做是文件,socket也是,所以传递了一个文件描述符来构建socket
注意
socket也是IPC的一种方式
socket中有两种方式进行通信
阻塞式
使用listen()监听某个端口,调用read()读取数据,没有数据时,会一直等待
非阻塞式
使用select()将需要检测的文件描述符作为 select() 函数的参数,当文件描述符上出现新的数据后,自动触发一个中断,然后在中断处理函数中再去读取指定文件描述符的数据
LocalServerSocke 用的是第二种 非阻塞式,读取的文件为 /dev/socket/zygote
LocalServerSocke 对应的客户端
前面的代码讲解的是关于socket服务端的,有服务端那就有客户端
它对应的客户端是进程 SystemServer 中创建出来的
preload
为了能够让 zygote 孵化进程进行的资源加载,预加载通用类、drawable和color资源、openGL以及共享库以及WebView,用于提高app启动效率
static void preload()
//预加载位于/system/etc/preloaded-classes文件中的类
preloadClasses();
//预加载资源,包含drawable和color资源
preloadResources();
//预加载OpenGL
preloadOpenGL();
//通过System.loadLibrary()方法,
//预加载"android","compiler_rt","jnigraphics"这3个共享库
preloadSharedLibraries();
//预加载 文本连接符资源
preloadTextResources();
//仅用于zygote进程,用于内存共享的进程
WebViewFactory.prepareWebViewInZygote();
forkSystemServer
这个小节介绍SystemService的启动过程
fork
fork是Linux中的一个系统调用,作用是复制当前进程,除了进程ID不同,两个进程信息完全一致
新进程被创建后,两个进程将共享已经分配的内存空间,直到其中一个进程需要向内存写入数据的时候,系统才会复制一份目标地址空间,并将写的数据写入到新的地址中,这就是 copy-on-write 机制,“仅当写的时候才复制”,可以节省共享物理内存,调用一次,返回两次,返回值有3种类型
● 父进程中,fork返回新创建的子进程的pid
● 子进程中,fork返回0
● 当出现错误时,fork返回负数(当进程数超过上限或者系统内存不足时会出错)
fork()的主要工作是寻找空闲的进程号pid,然后从父进程拷贝进程信息,例如数据段和代码段,fork()后子进程要执行的代码等。 Zygote进程是所有Android进程的母体,包括system_server和各个App进程。zygote利用fork()方法生成新进程,对于新进程A复用Zygote进程本身的资源,再加上新进程A相关的资源,构成新的应用进程A
copy-on-write原理
写时拷贝是指子进程与父进程的页表都所指向同一个块物理内存,fork过程只拷贝父进程的页表,并标记这些页表是只读的。父子进程共用同一份物理内存,如果父子进程任一方想要修改这块物理内存,那么会触发缺页异常(page fault),Linux收到该中断便会创建新的物理内存,并将两个物理内存标记设置为可写状态,从而父子进程都有各自独立的物理内存
为什么要fork
因为两个进程存在大量的共享程序,如果使用fork,可以节省大量的共享内存
SystemServer的启动
private static Runnable forkSystemServer(String abiList, String socketName,
ZygoteServer zygoteServer)
......
String args[] =
"--setuid=1000",
"--setgid=1000",
"--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023,1032,3001,3002,3003,3006,3007,3009,3010",
"--capabilities=" + capabilities + "," + capabilities,
"--nice-name=system_server",
"--runtime-args",
//注意这里的类名,它会去装载这个类
"com.android.server.SystemServer",
;
ZygoteConnection.Arguments parsedArgs = null;
int pid;
try
parsedArgs = new ZygoteConnection.Arguments(args);
ZygoteConnection.applyDebuggerSystemProperty(parsedArgs);
ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs);
//主要的fork操作,会调用到native层的代码进行 fork
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);
zygoteServer.closeServerSocket();
return handleSystemServerProcess(parsedArgs);
return null;
在 pid == 0 的节点中,关闭socket服务端(注意这里关闭的是从父进程复制过来的子进程里的socket,因为子进程不需要这个socket),
handleSystemServerProcess 函数的作用是调用 SystemServer 的main函数以及一些配置工作(调用新进程指定的class的main函数)
一旦 SystemServer 的配置工作完成后,就会从 SystemServer 的 main 函数开始运行
到此,调用main后,新进程就完全脱离了 zygote 进程的孵化过程了,成为一个真正的应用进程
runSelectLoop
socket创建后就是进入读操作了
Runnable runSelectLoop(String abiList)
ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
//mServerSocket 是socket通信中的服务端,即zygote进程。保存到fds[0]
fds.add(mServerSocket.getFileDescriptor());
peers.add(null);
//无限循环等待AMS的请求
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
//处理轮询状态,当pollFds有事件到来则往下执行,否则阻塞在这里
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;
// 第一个就是刚才的操作 fds.add(mServerSocket.getFileDescriptor());
if (i == 0)
//接收客户端发送过来的connect()操作,Zygote作为服务端执行accept()操作。 再后面客户端调用write()写数据,Zygote进程调用read()读数据
ZygoteConnection newPeer = acceptCommandPeer(abiList);
peers.add(newPeer);
fds.add(newPeer.getFileDesciptor());
else
try
ZygoteConnection connection = peers.get(i);
final Runnable command = connection.processOneCommand(this);
......
catch (Exception e)
......
在读取数据的操作中的 processOneCommand 里,最为核心的方法就是 Zygote.forkAndSpecialize,看名字就应该知道他的作用就是fork进程,runSelectLoop 函数随时待命,当接收到请求创建新进程请求时立即唤醒并执行相应工作
总结
zygote主要做了一下几件事:
- 创建AppRuntime并调用start方法,启动zygote进程
- 创建java虚拟机并未java虚拟机注册JNI方法
- 通过JNI调用 zygoteinit的main函数进入zygote的java框架层
- 通过 registerZygoteSocket方法创建服务端 Socket,并通过 runSelectLoop 方法等待AMS的请求来创建新的应用进程
- 启动 SystemServer进程
zygote的main函数的流程可以简化为下
public static void main(String argv[])
try
caller = zygoteServer.runSelectLoop(abiList);
....
catch (RuntimeException ex)
closeServerSocket();
throw ex;
if (caller != null)
caller.run();
注意,之前在分析acceptCommandPeer部分的时候,父进程是直接返回null的,新进程是返回一个runnable,因此,这部分的代码在不同进程中的执行是不同的.对于父进程也就是Zygote进程,他是一直循环运行的,对于新进程,会完成启动操作后停止循环并调用目标类的main函数
- 系统启动时init进程会创建Zygote进程,Zygote进程负责后续Android应用程序框架层的其它进程的创建和启动工作。
- Zygote进程会首先创建一个SystemServer进程,SystemServer进程负责启动系统的关键服务,如包管理服务PackageManagerService和应用程序组件管理服务ActivityManagerService。
- 当我们需要启动一个Android应用程序时,ActivityManagerService会通过Socket进程间通信机制,通知Zygote进程为这个应用程序创建一个新的进程。
以上是关于Android学习之zygote启动流程的主要内容,如果未能解决你的问题,请参考以下文章
Android 启动过程Android 应用启动流程 | Activity 启动流程
Zygote——Android系统中java世界的受精卵(二Welcome To Java)
Android5 Zygote 与 SystemServer 启动流程分析