Zygote listens on the server

Peers ZygoteConnection is a Zygote link object used to process messages from remote sockets.

private static ZygoteConnection acceptCommandPeer(String abiList) { try { return new ZygoteConnection(sServerSocket.accept(), abiList); } catch (IOException ex) { throw new RuntimeException( "IOException during accept()", ex); }}Copy the code

This will actually create a ZygoteConnection, which will pass in the accpet of the LocalServerSocket. At this point, the socket is blocked like a normal socket.

Let me first put out the UML diagrams for LocalSocket to see how these classes relate to each other.

In fact, all localSockets, whether server LocalServerSocket or client LocalSocket, are implemented through LocalServerImpl.

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(); }}Copy the code

This Os object passes libcore.os. accept(fd, peerAddress); Call the native layer.

static jobject Posix_accept(JNIEnv* env, jobject, jobject javaFd, jobject javaSocketAddress) { sockaddr_storage ss; socklen_t sl = sizeof(ss); memset(&ss, 0, sizeof(ss)); // Check whether Java layer socket object is 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; } // Once the socket is called back, the underlying fd object is converted to a Java object (clientFd! = 1)? jniCreateFileDescriptor(env, clientFd) : NULL; }Copy the code

There are three steps:

The first step is to determine the wait duration of the block by parsing whether the address is null.

The second step, the core method, blocks the thread with the DEFINE declared NET_FAILURE_RETRY code segment

Third, once the waiting socket link has data callback incoming, it is converted to the Java layer FD return.

Here is the core code for blocking

#define NET_FAILURE_RETRY(jni_env, return_type, syscall_name, java_fd, ...) ({ \ return_type _rc = -1; \ int _syscallErrno; \ do { \ bool _wasSignaled; Conversion of \ {\ / / Java fd, to monitor Java 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; })Copy the code

Here’s a little explanation of what this block core method means. The loop code has three exit conditions:

_wasSignaled is true which means that AsynchronousCloseMonitor can be awakened through the thread lock ScopeThreadMutex, indicating that the socket is broken and thus blocking is broken.

The _rc is -1 and the _syscallErrno error flag is not EINTER. Rc is syscall_name (the accept method of the socket is passed in). This means that any exception to the accept link (which returns -1) will wait inside the loop, except for the global _syscallErrno that was not thrown by the system.

If _rc is not -1, the socket connection is successful. I continue to go down.

As you can see from this, Zygote initializes a runSelectLoop with a ZygoteConnection to block listening. Once a link comes in, the wake is added to the peers queue. On the next iteration of a dead loop, fork a new process by executing runOnce.

If you’ve ever written a server in Linux C, you’ll know that this kind of constant blocking only consumes CPU resources and is not a good option.

That’s why runSelectLoop has this code

            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);
            }
Copy the code

From this code, it is superficially clear that the descriptors are initially set into the StructPollfd isometric array. Pass this array to os.poll.

Let’s see what the StructPollfd class is.

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);
  }
}
Copy the code

Let’s look directly at the underlying implementation of the OS.poll method

static jint Posix_poll(JNIEnv* env, jobject, jobjectArray javaStructs, Jint timeoutMs) {// reflection obtains structPollfd. Java property 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"); / / into the NDK at the bottom of the file descriptor / / 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)); } // loop listener int rc; while (true) { timespec before; clock_gettime(CLOCK_MONOTONIC, &before); //poll blocking process 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; } // Update the revents flag bit in runSelectLooper after waking up, 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; }Copy the code

This code does three things:

1. Obtain fd, Revents, and Events attributes in structPollfd. Java by reflection. Set these parameters to the Pollfd [] FDS queue.

2. Set FDS to poll for listening

3. Update the structPollfd queue in the Java layer.

The core is the second step, the poll function of Linux.

The poll function does that if no change in the file descriptor is detected, the process goes to sleep until someone wakes up. Because the timeout is 0, the timeout waiting time is not set.

StructPollfd does three attributes.

The first file descriptor that polllistens for changes to the file descriptor. A zygote socket file was passed in. That is, poll is listening to see if the socket changes.

The second event, as an argument to the event mask in pollfd

The third revent represents whether the file descriptor has changed.

Therefore, after each os. poll call, if the socket wakes up, the data in the StructPollfd will be updated, resulting in the following logic

for (int i = pollFds.length - 1; i >= 0; --i) { if ((pollFds[i].revents & POLLIN) == 0) { continue; }... }Copy the code

PollFds is used to evaluate whether revents has changed or not. POLLIN (which is actually 0) indicates that the socket file has changed.

With this optimization, the process can sleep when there is no socket access, freeing up CPU resources. When the socket is connected, it wakes up the process, enters accept, and waits for data to be connected. This can greatly improve the utilization of resources. (Some common Web servers are also designed this way.)

This only explains the server side of LocalSocket.

The Zygote client

In fact, the general ZygoteSocket client, generally in SystemServer ActivitymanagerService.

In Android 7.0, the start method of Process in startProcessLocked is called when no corresponding application Process exists. It will end up calling

 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);
    }
Copy the code

The core here calls ZygoteState’s connect method once.

        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(",")));
        }
Copy the code

An attempt is made to connect to the socket named zygote using a zygoteSocket, or LocalSocket. That’s the socket name that we initially initialized registerZygoteSocket in ZygoteInit.

After the OS.poll method is called, the localServerSocket. accept method is woken up, and runOnce is called at the next point in the loop.

So how did Zygote launch ActivityThread, the first class the app launched?

If you’re looking at runOnce code for the first time, you might be fooled by this line:

ZygoteConnection newPeer = acceptCommandPeer(abiList);
Copy the code

In fact, this abiList has no effect in ZygoteConnection. What really works is the readArgumentList method in Zygoteconnection.runonce.

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;
    }
Copy the code

See, all strings are actually read by Zygote’s SocketReader and assigned to the upper layer. Fork out the new process.

In 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);
Copy the code

The first parameter is an ActivityThread. After typing runOnce in the start method, handleChildProc reflects the main of the ActivityThread to initiate the Activity. The process. start method is actually a socket that writes data to Zygote.

handleChildProc

So, after the fork, we continue back to the handleChildProc child of ZygoteInit.

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 */); }}Copy the code

The child closes the socket, closing the observed file descriptor for the socket. This will allow the process fdTable (file descriptor table) to free up more controls. Follow with runtimeinit.zygoteinit. the logic is the same as SystemServer. Also mirroring the main method, nativeZygoteInit also inherits the new Binder loop for the new App binding,commonInit initializes the exception handling event for the App process. Take a look at the Main method in ActivityThread.

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");
    }
Copy the code

Inside, the ActivityThread object is initialized and the Attach method is passed in.

ActivityThread binding

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) { } }); }Copy the code

There are actually two core things to do here:

The first binding binds ApplictionThread to AMS so that our startActivity can then use the Binder object to find the corresponding method to correctly execute the correct lifecycle of the Activity. Also add GC listeners for Binder. Binder details are found in Binder parsing

The second is to set up memory management for viewroot PL. This class will be learned later in the view drawing. At this point, the general flow from Linux kernel startup to AcivityThread for the application is complete.

Optimization and Thinking

Let’s go over the entire process of 4.4 and compare it to Android 7.0. The logic is generally the same, except that the virtual machine is loaded from ART to DVM.

The only difference is that the runSelectLoop method has changed.

Android 4.4.4

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. */ / (loopCount <= 0) { gc(); loopCount = GC_LOOP_COUNT; } else { loopCount--; } // Check the fd changes in the array each time by select. 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); }}}}Copy the code

To clarify a bit, the meaning of FDS and peers remains unchanged in the lower version, adding a limit of up to 4 ZygoteConnection listeners at a time. Take a look at the operations that precede the loop below.

            try {
                fdArray = fds.toArray(fdArray);
                index = selectReadable(fdArray);
            } catch (IOException ex) {
                throw new RuntimeException("Error in select()", ex);
            }
Copy the code

This code is actually similar to the os.poll section above. To listen for changes in the socket and wake up the process.

This method calls native methods directly.

static jint com_android_internal_os_ZygoteInit_selectReadable ( JNIEnv *env, jobject clazz, jobjectArray fds) { ... FD_ZERO(&fdset); NDK layer 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 dead loop block int err; do { err = select (nfds, &fdset, NULL, NULL, NULL); } while (err < 0 && errno == EINTR); if (err < 0) { jniThrowIOException(env, errno); return -1; } 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; }Copy the code

This function is divided into three parts:

1. Fd was obtained from the Java layer object, through jniGetFDFromFileDescriptor into specific fd. Add a one each time to prepare for the select function.

2. Call SELECT to listen for changes in all file descriptors

3. Find the index corresponding to the changed file descriptor (socket), wake up and accept the socket.

Here’s a brief explanation of the parameters of select. The first parameter indicates how many file descriptors have been added to the socket. The second parameter indicates the flag bit corresponding to each fd parameter. If this flag bit changes, it indicates that the file descriptor has changed, and the socket is connected. We can ignore the rest.

Therefore, in the bottom section of the function, through the method of FD_ISSET, judge the changed flag bit, find the corresponding FD, and return the corresponding index.

In this way, the correct socket can be found. And processing the corresponding ZygoteConnection.

thinking

After comparing the two, why did 4.4.4 use select() and 7.0 use poll? Why do you do that? Let’s talk about the difference between the two functions.

In simple terms, both select and poll essentially poll a collection of file descriptors to find out which sockets have changed and tell Zygote. However, API differences lead to different policies between the two.

In the 4.4 era, most mobile phones were memory constrained (see runLoop gc every 10 times), and the advantage of SELECT was that each poll directly corrected the flag bits corresponding to each FD, which was faster. The disadvantage is that a segment of flag bits uses zeros or ones in each bit, which limits the maximum number of connections.

In the 7.0 era, the performance of most phones is better. When resources are no longer tight, the poll function is used instead. This function is very similar to select. However, each time the FD is polled, the flag bit inside the PollFD structure is changed. So you get out of the flag bit.

conclusion

In fact, the last section of Zygote incubation principle, I found that Lao Luo’s book and online materials are not detailed, but this is the most important part, is the core code of Zygote communication application. I hereby put it on record.

So what did Zygote do? What is the role of the Activity before it starts? Now it’s clear.

Zygote is one of the first incubators created after init. In terms of the Android framework, Zygote is the mother of all Android processes.

2. The first Zygote process to hatch is the SystemServer process.

3. Initialize the VIRTUAL machine using jniInvoaction and load the corresponding SO library

4.SystemServer process initialization, AMS, WMS, PMS, DisplayManager, InputManager,PowerManager…

5.Zygote spawns new processes by fork.

6.Zygote enables the socket to listen for an infinite loop, using select to block in earlier versions and poll to block in older versions.