時序圖
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函式
-
系統啟動時init程式會建立Zygote程式,Zygote程式負責後續Android應用程式框架層的其它程式的建立和啟動工作。
-
Zygote程式會首先建立一個SystemServer程式,SystemServer程式負責啟動系統的關鍵服務,如包管理服務PackageManagerService和應用程式元件管理服務ActivityManagerService。
-
當我們需要啟動一個Android應用程式時,ActivityManagerService會通過Socket程式間通訊機制,通知Zygote程式為這個應用程式建立一個新的程式。
參考部落格 Android程式建立
參考書籍 Android核心剖析
引出知識點
- rc檔案語法
- Linux fork 原理
- socket IPC通訊