zygote啟動流程

zerozx發表於2019-01-23

個人部落格

時序圖

zygote啟動流程

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 : 用於註冊socketpreload : 預載入類和資源forkSystemServer : 啟動system_serverrunSelectLoop : 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

:::tip注意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);
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的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函式

  1. 系統啟動時init程式會建立Zygote程式,Zygote程式負責後續Android應用程式框架層的其它程式的建立和啟動工作。

  2. Zygote程式會首先建立一個SystemServer程式,SystemServer程式負責啟動系統的關鍵服務,如包管理服務PackageManagerService和應用程式元件管理服務ActivityManagerService。

  3. 當我們需要啟動一個Android應用程式時,ActivityManagerService會通過Socket程式間通訊機制,通知Zygote程式為這個應用程式建立一個新的程式。

參考部落格 Android程式建立

參考書籍 Android核心剖析

引出知識點

  • rc檔案語法
  • Linux fork 原理
  • socket IPC通訊

來源:https://juejin.im/post/5c48107ce51d457cba6cc523

相關文章