Zygote 進(jìn)程間通信原理
不熟悉Linux編程的同學(xué)看到死循環(huán)最后這一段,可能就有點(diǎn)懵。這里我解釋一遍,在構(gòu)造一下整個(gè)流程以及模型估計(jì)就能明白了。
雖然是socket通信,但是實(shí)際上和我們常說Java的socket編程稍微有一點(diǎn)點(diǎn)不一樣。實(shí)際上更加像驅(qū)動(dòng)中的文件描述的監(jiān)聽。這里和Android4.4的有點(diǎn)不一樣,但是思路是一樣。
Zygote監(jiān)聽服務(wù)端
從上面的代碼,根據(jù)我的理論,peers這個(gè)ZygoteConnection是一個(gè)Zygote的鏈接對(duì)象,用來處理從遠(yuǎn)端的socket過來的消息。這個(gè)是一個(gè)關(guān)鍵類。我們看看這個(gè)ZygoteConnection究竟是怎么構(gòu)造的。
private static ZygoteConnection acceptCommandPeer(String abiList) {
try {
return new ZygoteConnection(sServerSocket.accept(), abiList);
} catch (IOException ex) {
throw new RuntimeException(
"IOException during accept()", ex);
}
}
實(shí)際上此處會(huì)new一個(gè)ZygoteConnection,會(huì)把LocalServerSocket的accpet傳進(jìn)去。此時(shí)就和普通的socket一樣進(jìn)入阻塞。
讓我先把LocalSocket這一系列的UML圖放出來就能明白,這幾個(gè)類之間關(guān)系。
實(shí)際上,所有的LocalSocket,無論是服務(wù)端LocalServerSocket還是客戶端LocalSocket都是通過LocalServerImpl實(shí)現(xiàn)的。
protected void accept(LocalSocketImpl s) throws IOException {
if (fd == null) {
throw new IOException("socket not created");
}
try {
s.fd = Os.accept(fd, null /* address */);
s.mFdCreatedInternally = true;
} catch (ErrnoException e) {
throw e.rethrowAsIOException();
}
}
這個(gè)Os對(duì)象通過Libcore.os.accept(fd, peerAddress);調(diào)用native層。
文件:/libcore/luni/src/main/native/libcore_io_Posix.cpp
static jobject Posix_accept(JNIEnv* env, jobject, jobject javaFd, jobject javaSocketAddress) {
sockaddr_storage ss;
socklen_t sl = sizeof(ss);
memset(&ss, 0, sizeof(ss));
//判斷java層的socket對(duì)象是否為NULL
sockaddr* peer = (javaSocketAddress != NULL) ? reinterpret_cast<sockaddr*>(&ss) : NULL;
socklen_t* peerLength = (javaSocketAddress != NULL) ? &sl : 0;
//核心,此處等待阻塞線程
jint clientFd = NET_FAILURE_RETRY(env, int, accept, javaFd, peer, peerLength);
if (clientFd == -1 || !fillSocketAddress(env, javaSocketAddress, ss, *peerLength)) {
close(clientFd);
return NULL;
}
//一旦socket回調(diào)之后,將會(huì)通過底層的fd對(duì)象轉(zhuǎn)化為java對(duì)象
return (clientFd != -1) ? jniCreateFileDescriptor(env, clientFd) : NULL;
}
此處分為三步:
- 第一步,通過解析address是否為空,來決定阻塞的等待時(shí)長(zhǎng),此時(shí)傳下來為null,為無限期的等待。
- 第二步,核心方法,通過define聲明的NET_FAILURE_RETRY代碼段,阻塞線程
- 第三步,一旦等待的socket鏈接有數(shù)據(jù)回調(diào)進(jìn)來,則轉(zhuǎn)化為java層的fd返回。
此處是阻塞的核心代碼
#define NET_FAILURE_RETRY(jni_env, return_type, syscall_name, java_fd, ...) ({ \
return_type _rc = -1; \
int _syscallErrno; \
do { \
bool _wasSignaled; \
{ \
//轉(zhuǎn)化java的fd,對(duì)Java進(jìn)行監(jiān)聽
int _fd = jniGetFDFromFileDescriptor(jni_env, java_fd); \
AsynchronousCloseMonitor _monitor(_fd); \
_rc = syscall_name(_fd, __VA_ARGS__); \
_syscallErrno = errno; \
_wasSignaled = _monitor.wasSignaled(); \
} \
if (_wasSignaled) { \
jniThrowException(jni_env, "java/net/SocketException", "Socket closed"); \
_rc = -1; \
break; \
} \
if (_rc == -1 && _syscallErrno != EINTR) { \
/* TODO: with a format string we could show the arguments too, like strace(1). */ \
throwErrnoException(jni_env, # syscall_name); \
break; \
} \
} while (_rc == -1); /* _syscallErrno == EINTR && !_wasSignaled */ \
if (_rc == -1) { \
/* If the syscall failed, re-set errno: throwing an exception might have modified it. */ \
errno = _syscallErrno; \
} \
_rc; })
這里稍微解釋一下,這段阻塞的核心方法的意思。
這循環(huán)代碼的跳出條件有三個(gè):
_wasSignaled 為true 也就是說此時(shí)AsynchronousCloseMonitor通過線程鎖ScopeThreadMutex上鎖的線程被喚醒,說明了該socket斷開,也就斷開了阻塞。
_rc 為-1 以及 _syscallErrno 錯(cuò)誤標(biāo)示位不為EINTER。rc為syscall_name(此時(shí)傳進(jìn)來的是socket的accept方法)。也就是說當(dāng)accept鏈接出現(xiàn)異常的時(shí)候(返回-1)會(huì)一直在循環(huán)里面等待,除非為全局錯(cuò)誤_syscallErrno 不是系統(tǒng)拋出的中斷,則拋出異常。
當(dāng)_rc不為-1,也就是說socket鏈接成功。則繼續(xù)向下走。
因此從這里可以知道,Zygote在初始化runSelectLoop的時(shí)候,一開始會(huì)加入一個(gè)ZygoteConnection用于阻塞監(jiān)聽。一旦有鏈接進(jìn)來,則喚醒則加入到peers隊(duì)列中。在死循環(huán)下一個(gè)輪回的時(shí)候,通過執(zhí)行runOnce執(zhí)行fork新的進(jìn)程。
雖然到這里似乎就完成整個(gè)流程了。但是實(shí)際上,google工程師寫代碼才不會(huì)這么簡(jiǎn)單就完成,而是做了一定的優(yōu)化。
如果用Linux c寫過服務(wù)器的哥們,就會(huì)明白這樣不斷的阻塞只會(huì)不斷的消耗的cpu的資源,并不是很好的選擇。
因此,runSelectLoop才有這一段代碼
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);
}
根據(jù)這段代碼,從表面上可以清楚的知道,一開始把描述符都設(shè)置進(jìn)去StructPollfd等長(zhǎng)數(shù)組中。把這個(gè)數(shù)組交給Os.poll中。
我們先看看StructPollfd這個(gè)類是個(gè)什么存在。
文件/libcore/luni/src/main/java/android/system/StructPollfd.java
public final class StructPollfd {
/** The file descriptor to poll. */
public FileDescriptor fd;
/**
* The events we're interested in. POLLIN corresponds to being in select(2)'s read fd set,
* POLLOUT to the write fd set.
*/
public short events;
/** The events that actually happened. */
public short revents;
/**
* A non-standard extension that lets callers conveniently map back to the object
* their fd belongs to. This is used by Selector, for example, to associate each
* FileDescriptor with the corresponding SelectionKey.
*/
public Object userData;
@Override public String toString() {
return Objects.toString(this);
}
}
這個(gè)類十分簡(jiǎn)單。里面只有那么3個(gè)參數(shù),events,revents,fd.分別是做什么的呢?
我們直接看看Os.poll方法底層的實(shí)現(xiàn)
文件:/libcore/luni/src/main/native/libcore_io_Posix.cpp
static jint Posix_poll(JNIEnv* env, jobject, jobjectArray javaStructs, jint timeoutMs) {
//反射獲取structPollfd.java屬性的屬性id
static jfieldID fdFid = env->GetFieldID(JniConstants::structPollfdClass, "fd", "Ljava/io/FileDescriptor;");
static jfieldID eventsFid = env->GetFieldID(JniConstants::structPollfdClass, "events", "S");
static jfieldID reventsFid = env->GetFieldID(JniConstants::structPollfdClass, "revents", "S");
//轉(zhuǎn)化為ndk底層的文件描述符
// Turn the Java android.system.StructPollfd[] into a C++ struct pollfd[].
size_t arrayLength = env->GetArrayLength(javaStructs);
std::unique_ptr<struct pollfd[]> fds(new struct pollfd[arrayLength]);
memset(fds.get(), 0, sizeof(struct pollfd) * arrayLength);
size_t count = 0; // Some trailing array elements may be irrelevant. (See below.)
for (size_t i = 0; i < arrayLength; ++i) {
ScopedLocalRef<jobject> javaStruct(env, env->GetObjectArrayElement(javaStructs, i));
if (javaStruct.get() == NULL) {
break; // We allow trailing nulls in the array for caller convenience.
}
ScopedLocalRef<jobject> javaFd(env, env->GetObjectField(javaStruct.get(), fdFid));
if (javaFd.get() == NULL) {
break; // We also allow callers to just clear the fd field (this is what Selector does).
}
fds[count].fd = jniGetFDFromFileDescriptor(env, javaFd.get());
fds[count].events = env->GetShortField(javaStruct.get(), eventsFid);
++count;
}
std::vector<AsynchronousCloseMonitor*> monitors;
for (size_t i = 0; i < count; ++i) {
monitors.push_back(new AsynchronousCloseMonitor(fds[i].fd));
}
//循環(huán)監(jiān)聽
int rc;
while (true) {
timespec before;
clock_gettime(CLOCK_MONOTONIC, &before);
//poll 阻塞進(jìn)程
rc = poll(fds.get(), count, timeoutMs);
if (rc >= 0 || errno != EINTR) {
break;
}
// We got EINTR. Work out how much of the original timeout is still left.
if (timeoutMs > 0) {
timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
timespec diff;
diff.tv_sec = now.tv_sec - before.tv_sec;
diff.tv_nsec = now.tv_nsec - before.tv_nsec;
if (diff.tv_nsec < 0) {
--diff.tv_sec;
diff.tv_nsec += 1000000000;
}
jint diffMs = diff.tv_sec * 1000 + diff.tv_nsec / 1000000;
if (diffMs >= timeoutMs) {
rc = 0; // We have less than 1ms left anyway, so just time out.
break;
}
timeoutMs -= diffMs;
}
}
for (size_t i = 0; i < monitors.size(); ++i) {
delete monitors[i];
}
if (rc == -1) {
throwErrnoException(env, "poll");
return -1;
}
//喚醒之后更新runSelectLooper中的revents標(biāo)識(shí)位,revents
// Update the revents fields in the Java android.system.StructPollfd[].
for (size_t i = 0; i < count; ++i) {
ScopedLocalRef<jobject> javaStruct(env, env->GetObjectArrayElement(javaStructs, i));
if (javaStruct.get() == NULL) {
return -1;
}
env->SetShortField(javaStruct.get(), reventsFid, fds[i].revents);
}
return rc;
}
這個(gè)代碼做了三件事情:
- 1.通過反射獲取structPollfd.java中fd屬性,revents,events屬性。把這些參數(shù)設(shè)置到pollfd[] fds隊(duì)列中。
- 把fds設(shè)置到poll進(jìn)行監(jiān)聽
- 更新java層的structPollfd隊(duì)列。
核心是第二步驟,linux的poll的函數(shù)。
而poll函數(shù)的作用就是如果沒有檢測(cè)到文件描述符的變化,則進(jìn)程進(jìn)入到睡眠狀態(tài),等到有人喚醒。由于此時(shí)傳入的timeout為0,則不設(shè)置超時(shí)等待時(shí)間。
那么我們可以清楚的知道了,structPollfd做三個(gè)屬性是什么。
第一個(gè)文件描述符,用來poll監(jiān)聽該文件描述符是否出現(xiàn)了變化。在這里還記得,傳入的是zygote的socket文件嗎?也就是說此時(shí)poll在監(jiān)聽socket是否出現(xiàn)了變化。
第二個(gè)event,作為pollfd中事件掩碼的參數(shù)
第三個(gè)revent,代表了該文件描述符是否產(chǎn)生了變化。
因此,在每一次調(diào)用完Os.poll之后,如果socket有喚醒之后,會(huì)更新StructPollfd中的數(shù)據(jù),也就有了下面這段判斷邏輯
for (int i = pollFds.length - 1; i >= 0; --i) {
if ((pollFds[i].revents & POLLIN) == 0) {
continue;
}
...
}
喚醒之后直接循環(huán)pollFds中,判斷revents是否有變化,和POLLIN(實(shí)際上是0)相于不為0則表示socket文件變化了,才有下面的加入peers列表以及通過runOnce啟動(dòng)進(jìn)程。
通過這樣的優(yōu)化,就能做到,當(dāng)沒有socket接入的時(shí)候,進(jìn)程休眠,騰出了cpu資源。當(dāng)socket接入,則喚醒進(jìn)程,進(jìn)入到accept,等待數(shù)據(jù)的接入。這樣就能大大的提升了其中的資源利用率。(一些普通的web服務(wù)器也是如此的設(shè)計(jì)的)
這里只是解釋了LocalSocket的服務(wù)端。
Zygote 客戶端
實(shí)際上一般的ZygoteSocket的客戶端,一般為SystemServer中的ActivitymanagerService.
我們看看在Android 7.0中當(dāng)不存在對(duì)應(yīng)的應(yīng)用進(jìn)程時(shí)候,會(huì)調(diào)用startProcessLocked方法中Process的start方法。
最終會(huì)調(diào)用
public static final String ZYGOTE_SOCKET = "zygote";
private static ZygoteState openZygoteSocketIfNeeded(String abi) throws ZygoteStartFailedEx {
if (primaryZygoteState == null || primaryZygoteState.isClosed()) {
try {
primaryZygoteState = ZygoteState.connect(ZYGOTE_SOCKET);
} catch (IOException ioe) {
throw new ZygoteStartFailedEx("Error connecting to primary zygote", ioe);
}
}
if (primaryZygoteState.matches(abi)) {
return primaryZygoteState;
}
// The primary zygote didn't match. Try the secondary.
if (secondaryZygoteState == null || secondaryZygoteState.isClosed()) {
try {
secondaryZygoteState = ZygoteState.connect(SECONDARY_ZYGOTE_SOCKET);
} catch (IOException ioe) {
throw new ZygoteStartFailedEx("Error connecting to secondary zygote", ioe);
}
}
if (secondaryZygoteState.matches(abi)) {
return secondaryZygoteState;
}
throw new ZygoteStartFailedEx("Unsupported zygote ABI: " + abi);
}
這里的核心會(huì)調(diào)用一次ZygoteState的connect方法。
public static ZygoteState connect(String socketAddress) throws IOException {
DataInputStream zygoteInputStream = null;
BufferedWriter zygoteWriter = null;
final LocalSocket zygoteSocket = new LocalSocket();
try {
zygoteSocket.connect(new LocalSocketAddress(socketAddress,
LocalSocketAddress.Namespace.RESERVED));
zygoteInputStream = new DataInputStream(zygoteSocket.getInputStream());
zygoteWriter = new BufferedWriter(new OutputStreamWriter(
zygoteSocket.getOutputStream()), 256);
} catch (IOException ex) {
try {
zygoteSocket.close();
} catch (IOException ignore) {
}
throw ex;
}
String abiListString = getAbiList(zygoteWriter, zygoteInputStream);
Log.i("Zygote", "Process: zygote socket opened, supported ABIS: " + abiListString);
return new ZygoteState(zygoteSocket, zygoteInputStream, zygoteWriter,
Arrays.asList(abiListString.split(",")));
}
此時(shí)會(huì)嘗試的通過zygoteSocket也就是LocalSocket 去連接名為zygote的socket。也就是我們最開始初始化的在ZygoteInit中registerZygoteSocket的socket名字。
調(diào)用connect方法,喚醒Os.poll方法之后,再喚醒LocalServerSocket.accept方法,在循環(huán)的下一個(gè),調(diào)用runOnce。
那么zygote又是怎么啟動(dòng)ActivityThread,這個(gè)應(yīng)用第一個(gè)啟動(dòng)的類呢?
第一次看runOnce代碼的老哥可能會(huì)被這一行蒙蔽了:
ZygoteConnection newPeer = acceptCommandPeer(abiList);
實(shí)際上在ZygoteConnection中,這個(gè)abiList不起任何作用。真正起作用的是ZygoteConnection.runOnce中readArgumentList
方法。
文件/frameworks/base/core/java/com/android/internal/os/ZygoteConnection.java
private String[] readArgumentList()
throws IOException {
/**
* 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.
*/
int argc;
try {
String s = mSocketReader.readLine();
if (s == null) {
// EOF reached.
return null;
}
argc = Integer.parseInt(s);
} catch (NumberFormatException ex) {
Log.e(TAG, "invalid Zygote wire format: non-int at argc");
throw new IOException("invalid wire format");
}
// See bug 1092107: large argc can be used for a DOS attack
if (argc > MAX_ZYGOTE_ARGC) {
throw new IOException("max arg count exceeded");
}
String[] result = new String[argc];
for (int i = 0; i < argc; i++) {
result[i] = mSocketReader.readLine();
if (result[i] == null) {
// We got an unexpected EOF.
throw new IOException("truncated request");
}
}
return result;
}
看吧實(shí)際上所有的字符串都是通過zygote的SocketReader讀取出來,再賦值給上層。進(jìn)行fork出新的進(jìn)程。
在ActivityManagerService的startProcessLocked
if (entryPoint == null) entryPoint = "android.app.ActivityThread";
Process.ProcessStartResult startResult = Process.start(entryPoint,
app.processName, uid, uid, gids, debugFlags, mountExternal,
app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet,
app.info.dataDir, entryPointArgs);
第一個(gè)參數(shù)就是ActivityThread,通過start方法,來打runOnce之后,進(jìn)去handleChildProc,把ActivityThread的main反射出來,開始了Activity的初始化。而實(shí)際上Process.start方法就是一個(gè)socket往Zygote中寫數(shù)據(jù)。
handleChildProc
文件:/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
因此,當(dāng)fork之后,我們繼續(xù)回到ZygoteInit的handleChildProc子進(jìn)程處理。
private void handleChildProc(Arguments parsedArgs,
FileDescriptor[] descriptors, FileDescriptor pipeFd, PrintStream newStderr)
throws ZygoteInit.MethodAndArgsCaller {
closeSocket();
ZygoteInit.closeServerSocket();
if (descriptors != null) {
try {
Os.dup2(descriptors[0], STDIN_FILENO);
Os.dup2(descriptors[1], STDOUT_FILENO);
Os.dup2(descriptors[2], STDERR_FILENO);
for (FileDescriptor fd: descriptors) {
IoUtils.closeQuietly(fd);
}
newStderr = System.err;
} catch (ErrnoException ex) {
Log.e(TAG, "Error reopening stdio", ex);
}
}
if (parsedArgs.niceName != null) {
Process.setArgV0(parsedArgs.niceName);
}
// End of the postFork event.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
if (parsedArgs.invokeWith != null) {
WrapperInit.execApplication(parsedArgs.invokeWith,
parsedArgs.niceName, parsedArgs.targetSdkVersion,
VMRuntime.getCurrentInstructionSet(),
pipeFd, parsedArgs.remainingArgs);
} else {
RuntimeInit.zygoteInit(parsedArgs.targetSdkVersion,
parsedArgs.remainingArgs, null /* classLoader */);
}
}
子進(jìn)程將關(guān)閉socket,關(guān)閉socket的觀測(cè)的文件描述符。這里就能完好的讓進(jìn)程的fdtable(文件描述符表)騰出更多的控件。接著走RuntimeInit.zygoteInit.接下來的邏輯就和SystemServer一樣。同樣是反射了main方法,nativeZygoteInit 同樣會(huì)為新的App綁定繼承新的Binder底層loop,commonInit 為App的進(jìn)程初始化異常處理事件。我們可以來看看ActivityThread中的main方法。
文件:/frameworks/base/core/java/android/app/ActivityThread.java
public static void main(String[] args) {
...
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
在這里面初始化了ActivityThread對(duì)象,并且傳入attach方法。
ActivityThread的綁定
private void attach(boolean system) {
sCurrentActivityThread = this;
mSystemThread = system;
if (!system) {
ViewRootImpl.addFirstDrawHandler(new Runnable() {
@Override
public void run() {
ensureJitEnabled();
}
});
android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
UserHandle.myUserId());
RuntimeInit.setApplicationObject(mAppThread.asBinder());
final IActivityManager mgr = ActivityManagerNative.getDefault();
try {
mgr.attachApplication(mAppThread);
} catch (RemoteException ex) {
// Ignore
}
// Watch for getting close to heap limit.
BinderInternal.addGcWatcher(new Runnable() {
@Override public void run() {
if (!mSomeActivitiesChanged) {
return;
}
Runtime runtime = Runtime.getRuntime();
long dalvikMax = runtime.maxMemory();
long dalvikUsed = runtime.totalMemory() - runtime.freeMemory();
if (dalvikUsed > ((3*dalvikMax)/4)) {
if (DEBUG_MEMORY_TRIM) Slog.d(TAG, "Dalvik max=" + (dalvikMax/1024)
+ " total=" + (runtime.totalMemory()/1024)
+ " used=" + (dalvikUsed/1024));
mSomeActivitiesChanged = false;
try {
mgr.releaseSomeActivities(mAppThread);
} catch (RemoteException e) {
}
}
}
});
} else {
...
}
// add dropbox logging to libcore
DropBox.setReporter(new DropBoxReporter());
ViewRootImpl.addConfigCallback(new ComponentCallbacks2() {
@Override
public void onConfigurationChanged(Configuration newConfig) {
synchronized (mResourcesManager) {
// We need to apply this change to the resources
// immediately, because upon returning the view
// hierarchy will be informed about it.
if (mResourcesManager.applyConfigurationToResourcesLocked(newConfig, null)) {
// This actually changed the resources! Tell
// everyone about it.
if (mPendingConfiguration == null ||
mPendingConfiguration.isOtherSeqNewer(newConfig)) {
mPendingConfiguration = newConfig;
sendMessage(H.CONFIGURATION_CHANGED, newConfig);
}
}
}
}
@Override
public void onLowMemory() {
}
@Override
public void onTrimMemory(int level) {
}
});
}
實(shí)際上這里做的事情核心有兩個(gè):
- 第一個(gè)把ApplictionThread綁定到AMS,讓之后我們startActivity能夠通過這個(gè)Binder對(duì)象找到對(duì)應(yīng)的方法,從而正確的執(zhí)行Activity中的正確的生命周期。同時(shí)為Binder添加gc的監(jiān)聽者。Binder的詳細(xì)情況會(huì)在Binder解析中了解到
- 第二個(gè)就是為ViewRootImpl設(shè)置內(nèi)存管理。這個(gè)類將會(huì)在后的view的繪制了解到。
至此,從Linux內(nèi)核啟動(dòng)到應(yīng)用的AcivityThread的大體流程就完成了。
優(yōu)化與思考
Android系統(tǒng)這么寫Zygote孵化流程真的最佳的嗎?輝哥曾經(jīng)提問過一個(gè)問題,framework的啟動(dòng)流程該怎么優(yōu)化。
我們?nèi)シ?.4的整個(gè)流程和android 7.0做對(duì)比。發(fā)現(xiàn)除了加載虛擬機(jī)是從art變成dvm之外,其他邏輯大體上一致。
唯一不同的就是runSelectLoop方法出現(xiàn)了變化。
android 4.4.4
/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
static final int GC_LOOP_COUNT = 10;
private static void runSelectLoop() throws MethodAndArgsCaller {
ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
FileDescriptor[] fdArray = new FileDescriptor[4];
fds.add(sServerSocket.getFileDescriptor());
peers.add(null);
int loopCount = GC_LOOP_COUNT;
while (true) {
int index;
/*
* Call gc() before we block in select().
* It's work that has to be done anyway, and it's better
* to avoid making every child do it. It will also
* madvise() any free memory as a side-effect.
*
* Don't call it every time, because walking the entire
* heap is a lot of overhead to free a few hundred bytes.
*/
//做一次gc為了給每個(gè)子進(jìn)程騰出內(nèi)存空間
if (loopCount <= 0) {
gc();
loopCount = GC_LOOP_COUNT;
} else {
loopCount--;
}
//每一次通過select檢測(cè)array中的fd有什么變化。
try {
fdArray = fds.toArray(fdArray);
index = selectReadable(fdArray);
} catch (IOException ex) {
throw new RuntimeException("Error in select()", ex);
}
//下面的邏輯一樣和之前的一樣
if (index < 0) {
throw new RuntimeException("Error in select()");
} else if (index == 0) {
ZygoteConnection newPeer = acceptCommandPeer();
peers.add(newPeer);
fds.add(newPeer.getFileDesciptor());
} else {
boolean done;
done = peers.get(index).runOnce();
if (done) {
peers.remove(index);
fds.remove(index);
}
}
}
}
這里稍微解釋一下,在低版本fds和peers的意義還是沒有多少變動(dòng),多了一個(gè)限制一次性最多也就4個(gè)ZygoteConnection監(jiān)聽。主要去看看下面的死循環(huán)之前的操作。
try {
fdArray = fds.toArray(fdArray);
index = selectReadable(fdArray);
} catch (IOException ex) {
throw new RuntimeException("Error in select()", ex);
}
這里面的代碼實(shí)際上和上面的Os.poll那一段的類似。是為了監(jiān)聽socket中哪些出現(xiàn)了變化,而后喚醒進(jìn)程。
這個(gè)方法直接調(diào)用的是native方法。
static jint com_android_internal_os_ZygoteInit_selectReadable (
JNIEnv *env, jobject clazz, jobjectArray fds)
{
...
FD_ZERO(&fdset);
//獲取ndk層的fd
int nfds = 0;
for (jsize i = 0; i < length; i++) {
jobject fdObj = env->GetObjectArrayElement(fds, i);
if (env->ExceptionOccurred() != NULL) {
return -1;
}
if (fdObj == NULL) {
continue;
}
int fd = jniGetFDFromFileDescriptor(env, fdObj);
if (env->ExceptionOccurred() != NULL) {
return -1;
}
FD_SET(fd, &fdset);
if (fd >= nfds) {
nfds = fd + 1;
}
}
//select死循環(huán)阻塞
int err;
do {
err = select (nfds, &fdset, NULL, NULL, NULL);
} while (err < 0 && errno == EINTR);
if (err < 0) {
jniThrowIOException(env, errno);
return -1;
}
//查看哪些fd出現(xiàn)了變化,把index回調(diào)上去
for (jsize i = 0; i < length; i++) {
jobject fdObj = env->GetObjectArrayElement(fds, i);
if (env->ExceptionOccurred() != NULL) {
return -1;
}
if (fdObj == NULL) {
continue;
}
int fd = jniGetFDFromFileDescriptor(env, fdObj);
if (env->ExceptionOccurred() != NULL) {
return -1;
}
if (FD_ISSET(fd, &fdset)) {
return (jint)i;
}
}
return -1;
}
這個(gè)函數(shù)分為三個(gè)部分:
- 從java層獲取fd的對(duì)象,通過jniGetFDFromFileDescriptor轉(zhuǎn)化為具體的fd。每一次都加一個(gè)一,為select函數(shù)做準(zhǔn)備。
- 2.調(diào)用select,監(jiān)聽所有的文件描述符中的變化
- 3.尋找變化的文件描述符(socket)對(duì)應(yīng)的index,喚醒并且接受socket。
如果不太懂Linux api select函數(shù),這里放出一個(gè)寫select的比較好的博文:
https://www.cnblogs.com/skyfsm/p/7079458.html
這里簡(jiǎn)單的解釋一下,select的參數(shù)。第一個(gè)參數(shù),代表了有多少文件描述符加入了,此時(shí)只有一個(gè),第二個(gè)參數(shù),把fd每個(gè)參數(shù)對(duì)應(yīng)的標(biāo)志位,一旦這個(gè)標(biāo)志位出現(xiàn)了變動(dòng),則代表這個(gè)文件描述符出現(xiàn)變化,socket接入了。其他先可以不管。
因此在最下面的那一段函數(shù)中,通過FD_ISSET的方法,判斷變動(dòng)的標(biāo)志位,找到對(duì)應(yīng)的fd,把對(duì)應(yīng)的index返回。
這樣就能正確找到哪個(gè)socket。并且處理對(duì)應(yīng)的ZygoteConnection。
上個(gè)圖總結(jié):
思考
經(jīng)過兩者的比較,為什么在4.4.4版本使用select()去做,而到了7.0版本使用了poll。為什么這么做?先說說兩個(gè)函數(shù)之間的區(qū)別。
簡(jiǎn)單的說,select和poll本質(zhì)上都是對(duì)文件描述符的集合進(jìn)行輪詢查找,哪些socket出現(xiàn)了變化并且告訴Zygote。然而api的不同導(dǎo)致兩者之間的策略不一樣。
在4.4時(shí)代,大部分的手機(jī)內(nèi)存吃緊(這一點(diǎn)從runLoop每隔10次就要gc一次就知道了),而select的好處就是每一次輪詢都是直接修正每一個(gè)fd對(duì)應(yīng)的標(biāo)志位,速度較快。缺點(diǎn)是,一段標(biāo)志位使用過每一個(gè)位上的0或者1來判斷,也就限制了最大連接數(shù)量。
而7.0時(shí)代,大部分手機(jī)的性能變得比較好了。資源不再吃緊了,此時(shí)更換為poll函數(shù)。該函數(shù)的作用和select很相似。不過每一次輪詢fd,都要修改pollfd結(jié)構(gòu)體內(nèi)部的標(biāo)志位。這樣就脫離標(biāo)志位的限制了。
所以說,對(duì)于不同的api的,沒有最好,只有最適用。
愚見
難道沒辦法,更好的辦法嗎?有!這只是個(gè)人看法,還記得前幾年流行的ngnx嗎?這個(gè)的底層是用epoll來實(shí)現(xiàn)的。
這種實(shí)現(xiàn)和單一的阻塞不一樣。而是異步的IO。這方法只有Linux 2.6才開始支持。這個(gè)方法相比于select和poll。不是簡(jiǎn)單的輪詢,因?yàn)楫?dāng)量級(jí)到了一定的時(shí)候,輪詢的速度必定慢下來。而是通過回調(diào)的機(jī)制去處理。每一次通過內(nèi)存映射的方式查找對(duì)應(yīng)的fd,并且回調(diào)。這樣就省去了內(nèi)存在調(diào)用fd時(shí)候造成的拷貝(從內(nèi)核空間到用戶空間)。
其次,epoll這個(gè)函數(shù)沒有數(shù)量的限制,而是由一個(gè)文件描述符去控制所有的文件描述符。
基于這兩個(gè)理由,很明顯epoll才是最佳的選擇。
但是,最佳就必須選擇嗎?不,我們只選擇了最合適的。我剛才看了下android 9.0的源碼。發(fā)現(xiàn)還是繼續(xù)使用poll機(jī)制。對(duì)于android來說zygote誕生出新的進(jìn)程的情況不多見,量級(jí)遠(yuǎn)沒有達(dá)到服務(wù)器的地步,加上使用epoll,下面的fork的機(jī)制可能變動(dòng)大,沒有選擇也是情理之中。
當(dāng)然,如果有哥們看過Handler的源碼,就知道Handler有一層ndk層,下層也是用epoll做等待死循環(huán)處理。有機(jī)會(huì)再源碼解析解析。
總結(jié)
實(shí)際上最后這一段Zygote孵化原理,我發(fā)現(xiàn)老羅的書,還有網(wǎng)上的資料都說不詳細(xì),但是這卻是最重要的一環(huán),是Zygote溝通應(yīng)用程序的核心代碼。特此在此記錄一下。
那么Zygote誕生做了什么?在Activity啟動(dòng)前的角色是什么?現(xiàn)在就明白了。
1.Zygote是init進(jìn)程之后第一個(gè)誕生出來的孵化進(jìn)程。就以Android系統(tǒng)的framework來說,Zygote是Android系統(tǒng)一切進(jìn)程的母親。
2.Zygote第一個(gè)孵化的進(jìn)程是SystemServer進(jìn)程。
3.初始化虛擬機(jī)是通過jniInvoaction,加載對(duì)應(yīng)的so庫(kù)
4.SystemServer進(jìn)程初始化,AMS,WMS,PMS,DisplayManager(顯示),InputManager(鍵盤),PowerManager(電源)...
5.Zygote 誕生新的進(jìn)程都是通過fork誕生的。
6.Zygote 開啟socket監(jiān)聽死循環(huán),在低版本使用select來阻塞,高版本使用poll來阻塞。
參考資料:
https://segmentfault.com/a/1190000003063859?utm_source=tag-newest
https://www.cnblogs.com/amanlikethis/p/6915485.html
題外話
無語了,沒辦法發(fā)長(zhǎng)一點(diǎn)的文章,只能拆開來放出來了。
寫的比較粗淺,也不是很專業(yè)。看到錯(cuò)誤可以找我糾正。估計(jì)很多人都懂這些了,更多的只是把這兩年學(xué)習(xí)的復(fù)習(xí)和整理。