談談對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機制。