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的創建函數,這些都需要詳細研究一下。
五.參考文檔