1.前世
其中:
/system/bin 指定程序的parent dir
大体上,zygote完成了如下的使命
----------------------------- -------------------------
| zygote process | | some process |
----------------------------- -------------------------
| fork ---------------------------------- |
+--------------------------->| system server process | |
| ---------------------------------- |
| fork command |
|<------------------------------------------<------------------------------------|
| fork -------------------------------- |
+---------------------------->| new process | |
| -------------------------------- |
| return pid |
|-------------------------------------------->---------------------------------->|
1.fork system server进程,并执行com.android.server.SystemServer的main函数
system server进程主要是启动各种service,并进入loop循环等待处理消息
2.等待其他进程的command,在收到command之后进行必要的安全检查之后,fork一个新的进程,并执行指定类的main函数
二.Zygote-受精卵-孕育生命
zygote的中文意思是受精卵,在android中它也的确起到了孕育生命的作用,下面我们将看到其孕育生命的过程
1.调用树
可参考如下的调用树来阅读下面的分析过程:
main[app_main.cpp]
+--AppRuntime.start[即AndroidRuntime.start][AndroidRuntime.cpp]
+--AndroidRuntime.startVM[AndroidRuntime.cpp]
+--JNI_CreateJavaVM[ dalvik/vm/Jni.cpp ]
+--AndroidRuntime.startReg[AndroidRuntime.cpp]
+--env->CallStaticVoidMethod
+--ZygoteInit::main[ ZygoteInit.java ]
+--ZygoteInit::registerZygoteSocket[ ZygoteInit.java ]
+--ZygoteInit::preloadClasses[ ZygoteInit.java ]
+--ZygoteInit::startSystemServer[ ZygoteInit.java ]
+--nativeForkSystemServer [ Zygote.java]
+--handleSystemServerProcess [ZygoteInit.java ]
+--zygoteInitNative[ZygoteInit.java ]
+--ApplicationInit[ZygoteInit.java ]
+--ZygoteInit::runSelectLoopMode[ ZygoteInit.java ]
2.app_main.cpp
app_main.cpp中的main函数是zygote的主函数,它的流程如下:
1)解析参数
2)调用AppRuntime.start
AppRuntime从AndroidRuntime继承的,在main函数中存在一个AppRuntime的栈变量,在这里会调用AppRuntime的构造函数,也即AndroidRuntime的构造函数,AndroidRuntime类定义于frameworks/base/core/jni/AndroidRuntime.cpp中,具体如下所示。由于这个main函数不会退出,所以这个实例是AndroidRuntime的唯一实例。从构造函数中,我们可以看出skia库就是在这里初始化的,gCurRuntime是一个全局的指针,在后面的调用中将会通过调用它来调用AndroidRuntime的虚函数实现。
AndroidRuntime::AndroidRuntime()
{
SkGraphics::Init();
// this sets our preference for 16bit images during decode
// in case the src is opaque and 24bit
SkImageDecoder::SetDeviceConfig(SkBitmap::kRGB_565_Config);
// This cache is shared between browser native images, and java "purgeable"
// bitmaps. This globalpool is for images that do not either use the java
// heap, or are not backed by ashmem. See BitmapFactory.cpp for the key
// java call site.
SkImageRef_GlobalPool::SetRAMBudget(512 * 1024);
// There is also a global font cache, but its budget is specified in code
// see SkFontHost_android.cpp
// Pre-allocate enough space to hold a fair number of options.
mOptions.setCapacity(20);
assert(gCurRuntime == NULL); // one per process
gCurRuntime = this;
}
3.AndroidRuntime.start
AppRuntime.start函数实际上是调用了基类的start的函数,
runtime.start("com.android.internal.os.ZygoteInit",//指定启动java类的类名
startSystemServer ? "start-system-server" : "");
它的流程如下:
2)AndroidRuntime::startReg
3)env->CallStaticVoidMethod
5.JNI_CreateJavaVM调用流程如下:
1).初始化gDvmJni
gDvmJni最重要的一个字段是一个指向JavaVMExt的指针
2).初始化gDvm
gDvm中的内容相当多,主要是关于虚拟机的各种设定和native函数
调用完成后,具有如下的结构:
__________
gDvmJni -------------->| JavaVM |---->gInvokeInterface
----------------- ---------------
| enList |----> | JNIEnv |----->gNativeInterface
----------------- ---------------
| next |------->JNIEnv
----------------
| prev |
----------------
这里需要说明的是从内存布局看,JavaVM和JavaVMExt具有类似C++的派生关系,JNIEnv和JNIEnvExt也具有这种关系,所以他们可以互相强制转换,上图中使用的结构定义如下所述:
struct DvmGlobals gDvm;
struct DvmJniGlobals gDvmJni;
dalvik/vm/globals.h
struct DvmJniGlobals {
bool useCheckJni;
bool warnOnly;
bool forceCopy;
// Provide backwards compatibility for pre-ICS apps on ICS.
bool workAroundAppJniBugs;
// Debugging help for third-party developers. Similar to -Xjnitrace.
bool logThirdPartyJni;
// We only support a single JavaVM per process.
JavaVM* jniVm;
};
dalvik/vm/jniinternal.h定义
struct JavaVMExt {
const struct JNIInvokeInterface* funcTable; /* must be first */
const struct JNIInvokeInterface* baseFuncTable;
/* head of list of JNIEnvs associated with this VM */
JNIEnvExt* envList;
pthread_mutex_t envListLock;
};
struct _JavaVM {
const struct JNIInvokeInterface* functions;
#if defined(__cplusplus)
jint DestroyJavaVM()
{ return functions->DestroyJavaVM(this); }
jint AttachCurrentThread(JNIEnv** p_env, void* thr_args)
{ return functions->AttachCurrentThread(this, p_env, thr_args); }
jint DetachCurrentThread()
{ return functions->DetachCurrentThread(this); }
jint GetEnv(void** env, jint version)
{ return functions->GetEnv(this, env, version); }
jint AttachCurrentThreadAsDaemon(JNIEnv** p_env, void* thr_args)
{ return functions->AttachCurrentThreadAsDaemon(this, p_env, thr_args); }
#endif /*__cplusplus*/
};
dalvik/vm/jni.cpp
static const struct JNIInvokeInterface gInvokeInterface = {
NULL,
NULL,
NULL,
DestroyJavaVM,
AttachCurrentThread,
DetachCurrentThread,
GetEnv,
AttachCurrentThreadAsDaemon,
};
------------------------------------------------------------------------------
struct JNIEnvExt {
const struct JNINativeInterface* funcTable; /* must be first */
const struct JNINativeInterface* baseFuncTable;
u4 envThreadId;
Thread* self;
/* if nonzero, we are in a "critical" JNI call */
int critical;
struct JNIEnvExt* prev;
struct JNIEnvExt* next;
};
struct _ JNIEnv {
/* do not rename this; it does not seem to be entirely opaque */
const struct JNINativeInterface* functions;
....
};
static const struct JNINativeInterface gNativeInterface = {
......
}
如果要研究java虚拟机,可以从此做进一步分析,但是总体上来说,JNIEnv表示JavaVM的运行环境
6.AndroidRuntime::startReg
AndroidRuntime::startReg函数流程
1.设置Thread的线程创建函数
2.在JNIEnv中push一个Frame
3.使用JNI机制,向JNIEnv中注册内置的java类
4.在JNIEnv中pop一个Frame
为什么需要Push和Pop Frame?
在该函数的代码中有这样一段注释:
/*
* 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.
*/
大致意思是说,每个reigister函数会调用很多返回local ref的东西,因为现在VM还没有启动,所以这些东西不会自动释放,所以这里需要手动加入Frame
这 里的Frame应该是stack frame,用来构成frame链表的,查看FindClass函数(dalvik/vm/jni.cpp),其中调用了一个 addLocalReference的函数,这个函数就是用来把FindClass返回的local ref加入到当前Frame的一个引用表中,在native函数的栈推出的时候,根据Frame的引用表释放这些local reference的。当然,由于此时VM还没有启动,Frame还没有建立起来,所以需要我们受到的push一个Frame。该函数的注释如下:
/*
* Add a local reference for an object to the current stack frame. When
* the native function returns, the reference will be discarded.
*
* We need to allow the same reference to be added multiple times.
*
* This will be called on otherwise unreferenced objects. We cannot do
* GC allocations here, and it's best if we don't grab a mutex.
*
* Returns the local reference (currently just the same pointer that was
* passed in), or NULL on failure.
*/
该函数调用com.android.internal.os.ZygoteInit类的main函数,这一步完成了从C++到java的过程,从此一去不复返,调用这个main函数的时候,传入了start-system-server作为参数。其中com.android.internal.os.ZygoteInit这个类名是在app_main.cpp的man函数中调用AndroidRuntime.start的时候指定的
8.ZygoteInit::main
该函数定义于framework/base/core/java/com/android/internal/os/ZygoteInit.java,其流程如下:
1)registerZygoteSocket函数
2)preloadClasses函数
3)startSystemServer函数
4)runSelectLoopMode函数
8.ZygoteInit::registerZygoteSocket函数
通过对init进程的分析,我们知道在启动服务的时候,会在/dev /socket目录下创建一个以服务名字命名的socket,并把这个socket的fd加入到环境变量中,命名为 ANDROID_SOCKET_[service_name],在这个函数中,我们看到的正是这个 socket,ANDROID_SOCKET_zygote,
private static void registerZygoteSocket() {
if (sServerSocket == null) {
int fileDesc;
try {
String env = System.getenv(ANDROID_SOCKET_ENV);//从环境变量中获得socket fd
fileDesc = Integer.parseInt(env);
} catch (RuntimeException ex) {
throw new RuntimeException(
ANDROID_SOCKET_ENV + " unset or invalid", ex);
}
try {
sServerSocket = new LocalServerSocket(
createFileDescriptor(fileDesc)); //createFileDescriptor 是一个native函数,对应的c函数定义于framework/base/core/jni /com_android_internal_os_zygoteinit.cpp文件中 com_android_internal_os_ZygoteInit_createFileDescriptor ,该函数的主要作用就是在JNIEnv中创建一个与fd对应的jobject,让java环境通过这个jobject来访问fd。
//LocalServerSocket是一个封装类,它定义于framework/base/core/java/android/net /LocalServerSocket.java,它的功能由LocalSocketImpl类实现,LocalServerSocket的构造函数就直 接调用了LocalSocketImpl的listen方法,该方法调用native函数实现功能,对应的c函数定义于framework/base /core/jni/android_net_LocalSocketImpl.cpp中的socket_listen
} catch (IOException ex) {
throw new RuntimeException(
"Error binding to local socket '" + fileDesc + "'", ex);
}
}
}
由 上面的代码可以知道,该函数就是启动监听/dev/socket/zygote socket,除此之外,关于java层通过jni调用native函数,我们也可以知道一些规则,如果一个java文件中的某个函数是native函 数,那么对应的C++函数实现所在的文件名的命名规则为:java文件的包名与文件名组成的路径,以LocalSocketImpl类的listen方法 为例,该类的全路径为android.net.LocalSocketImpl,则对应的native函数所在的文件的路径为 framework/base/core/jni/android_net_LocalSocketImpl.cpp,其中framework/base /core/jni是一个基准路径
9.ZygoteInit::preloadClasses函数
该函数主要用来预加载类,frameworks/base/preloaded-classes文件中,列出了需要加载的所有类名,该文件是由framworks/base/tools/preload生成的。
该函数有几点需要说明:
1.使用VMRuntime.getTargetHeapUtilization
VMRuntime 是对VM运行期的功能的封装,它封装了很多的native函数,通过getTargetHeapUtilization和 setTargetHeapUtilization方法,用来获取和设置heap的使用率。这里设置为最高0.8的使用率。这样可能控制了gc的次数,加 快加载速度
2.统计加载过程中内存分配的大小,超过阀值的时候,进行GC
10.ZygoteInit::preloadResources函数
该函数使用frameworks/base/core/java/android/content/res/Resources.java中定义的Resources类来加载
com.android.internal.R.array.preloaded_drawables
com.android.internal.R.array.preloaded_color_state_lists
11.ZygoteInit::startSystemServer函数
该函数的流程如下:
11.1.fork一个进程作为system_server
java层的nativeForkSystemServer函数 定义于libcore/dalvik/src/main/java/dalvik/system/Zygote.java,它对应dalvik/vm /native/dalvik_system_Zygote.cpp中 的 Dalvik_dalvik_system_Zygote_forkSystemServer函数,该函数的功能如下:
1).设置sigchild信号的处理为kill(petpid(),SIGKILL),即在收到子进程退出的信号后,kill自己,即zygote进程,这样init进程会重新启动zygote进程,继而由zygote进程重新启动system_server进程
2).fork一个子进程,即system_server进程,pid设置给gDvm.systemServerPid
11.2.调用handleSystemServerProcess作为这个子进程的处理函数
该函数定义于frameworks/base/core/java/com/android/internal/os/ZygoteInit.java,其流程如下:
1)调用RuntimeInit::zygoteInit(frameworks/base/core/java/com/android/internal/os/RuntimeInit.java)
public static final void zygoteInit(int targetSdkVersion, String[] argv)
throws ZygoteInit.MethodAndArgsCaller {
if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application from zygote");
redirectLogStreams();
commonInit();
//该函数是一个native函数,对应frameworks/base/core/jni/AndroidRuntime.cpp中的 com_android_internal_os_RuntimeInit_zygoteInit函数,该函数调用 gCurRuntime->onZygoteInit();其中gCurRuntime是AndroidRuntime构造函数中初始化的时候赋值 的,而AndroidRuntime实例是在app_main.cpp的main函数中作为栈变量声明的,实际上是作为AppRuntime实例存在 的,AppRuntime是AndroidRuntime的派生类。这个main函数是Zygote的主函数,不会退出。onZygoteInit是启动 线程与Binder通信
zygoteInitNative();
//该函数最 终会调用invokeStaticMain,即调用参数指定类的main函数,即在startSystemServer中指定的类名 com.android.server.SystemServer。比较有意思的是,该函数并非直接调用这个main函数,而是通过抛出异常的方式,抛出 了ZygoteInit.MethodAndArgsCaller异常,这个异常最终是在Zygote的main函数中catch住,并调用了该实例的 run方法。这种方式应该是为了释放多次调用函数的栈变量。需要注意的是,此时,异常的执行已经是在子进程中了。
applicationInit(targetSdkVersion, argv);
}
12.runSelectLoopMode函数
该函数的流程如下:
1)把注册的zygote socket加入到select的文件描述符表中
2)selectReadable,等待可读的文件描述符
3)如果zygote socket可读,则accept一个新的ZygoteConnection,并把它加入到文件描述符表中
4)如果是除zygote以外可读,则调用对应的ZygoteConnection.runOnce执行请求的fork命令。
这里有两个问题需要特别说明:
1.ZygoteConnection代表一个connection,在其构造函数中,调用了getPeerCredential函数,该函数对应getPeerCredentials_native函数,最终对应getsockopt函数,通过SO_PEERCRED选项,得到cred信息,对于这个选项和cred结构,我们通过
man 7 unix
man 7 socket
得到以下的信息
getsockopt得到的cred信息是只读的,发送端通过使用socket option SO_PASSCRED来设置是否允许获取cred,接收端通过SO_PEERCRED选项,得到cred信息,cred的结构内容如下:
struct ucred {
pid_t pid; /* process ID of the sending process */
uid_t uid; /* user ID of the sending process */
gid_t gid; /* group ID of the sending process */
};
zygote进程通过这个结构的内容来判断是否允许请求fork新的进程。
2.谁发送的fork请求
ZygoteConnection.readArgumentList方法的注释中给出了答案,frameworks/base/core/java/android/os/Process.java的 zygoteSendArgsAndGetResult函数发送参数,并获取执行结果,
/**
* See android.os.Process.zygoteSendArgsAndGetPid()
* Presently the wire format to the zygote process is:
* a) a count of arguments (argc, in essence)
* b) a number of newline-separated argument strings equal to count
*
* After the zygote process reads these it will write the pid of
* the child or -1 on failure.
*/
注释中的zygoteSendArgsAndGetPid应该是老版本的代码,在使用新的代码后,没有更新注释
再进一步查看,该函数被Process::startViaZygote调用,在这里添加了各种调用的参数,如果nice-name指定进程的名称,processClass指定执行的类名称,runtime-init说明通过RuntimeInit来运行类。
再进一步查看,Process::startViaZygote又被Process::start调用,也就是说,如果有进程通过android.os.Process.start方法启动进程的时候,实际上就是发送指定的参数给zygote进程
对比zygoteSendArgsAndGetResult方法,再来看ZygoteConnection.runOnce
1)ZygoteConnection.readArgumentList
2)安全性检查
3)Zygote.forkAndSpecialize fork一个进程
4)在子进程中,执行RuntimeInit.zygoteInit执行指定的进程
5)在父进程中,把子进程pid发送回去。
三.背景知识
1.JNI调用
在进行JNI调用的时候,会用到如下的signature定义,例如:
jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
"([Ljava/lang/String;)V");
"()V"
"(II)V"
"(Ljava/lang/String;Ljava/lang/String;)V"
实际上这些字符是与函数的参数类型一一对应的。
"()" 中的字符表示参数,后面的则代表返回值。例如"()V" 就表示void Func();
"(II)V" 表示 void Func(int, int);
具体的每一个字符的对应关系如下
字符 Java类型 C类型
V void void
Z jboolean boolean
I jint int
J jlong long
D jdouble double
F jfloat float
B jbyte byte
C jchar char
S jshort short
数组则以"["开始,用两个字符表示
[I jintArray int[]
[F jfloatArray float[]
[B jbyteArray byte[]
[C jcharArray char[]
[S jshortArray short[]
[D jdoubleArray double[]
[J jlongArray long[]
[Z jbooleanArray boolean[]
上面的都是基本类型。如果Java函数的参数是class,则以"L"开头,以";"结尾中间是用"/" 隔开的包及类名。而其对应的C函数名的参数则为jobject. 一个例外是String类,其对应的类为jstring
2.子进程与父进程子进程继承了父进程的文件描述符表,环境,运行时,与。子进程复制了父进程的环境,得到了一份拷贝,所以它对于环境的修改,并不影响父进程。但是文件描述符表却是与父进程共享的。
四.待研究的问题
1.Thread
对于Thread需要特别研究一下,在JavaVM的InvokeInterface中,有attachThread的函数,在startReg中有设置 Thread的创建函数,这些都需要详细研究一下。
五.参考文档