Android system startup process 2 -- Analysis of Zygote process startup process

Related articles

Android system startup process 1 -- analyze init process startup process

zygote process startup process

In Android system, all application processes and system service processes system server are generated by the zygote process, which may be why we call it zygote. Because zygote process plays such an important role in Android system, this paper will analyze its startup process in detail.

1. introduction to Zygote

In Android system, DVM(Dalvik virtual machine), application process and system server process running key services of the system are all created by zygote process, which we also call incubator. It creates application process and system server process in the form of fock (replication process). Because the zygote process will create DVM when it starts, the application process and system server process created by fock can obtain an instance copy of DVM internally.
We mentioned in the previous article about init starting zygote, which will not be covered here. This article mainly analyzes the starting process of zygote process of Android 8.1 system.

2. Zygote startup script

At init.rc The file uses Import type statements to introduce Zygote startup scripts, which are written by Android Init Language. The code of Zygote script is as follows:

system/core/init/init.cpp

import /init.${ro.zygote}.rc

At init.rc Medium pass attribute ro.zygote To introduce different Zygote startup scripts. Since Android 5.0, Android has started to support 64 bit programs, and Zygote has the difference between 32-bit and 64 bit programs, so it is used here ro.zygote Property to control the use of different Zygote startup scripts, thus starting different versions of the Zygote process, ro.zygote There are four values for attributes:

  • init.zygote32.rc
  • init.zygote32_64.rc
  • init.zygote64.rc
  • init.zygote64_32.rc
These Zygote startup scripts are placed in the system/core/rootdir directory.
       

     1. init.zygote32.rc

Indicates support for pure 32-bit programs, init.zygote32 The contents of the. RC file are as follows:

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

According to the format of the Service type statement, we can know that the process name of zygote is zygote and the executing program is app_ Process, class name is main. If the audioserver cameraserver, media, netd and WiFi processes are terminated, restart is required.

      2. init.zygote32_64.rc

Indicates that both 32-bit and 64 bit programs are supported, init.zygote32_ The contents of the 64.rc file are as follows:

service zygote /system/bin/app_process32 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote
    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 zygote_secondary /system/bin/app_process64 -Xzygote /system/bin --zygote --socket-name=zygote_secondary
    class main
    priority -20
    user root
    group root readproc
    socket zygote_secondary stream 660 root system
    onrestart restart zygote
    writepid /dev/cpuset/foreground/tasks
There are two Service type statements in the script, indicating that two zygote processes will be started, the first is zygote, and the executing program is app_process32 as the main mode; the second name is zygote_secondary, the executing program is app_ Process64 as a secondary mode.

     3. init.zygote64.rc

Indicates support for pure 64 bit programs, init.zygote64 The contents of the. RC file are as follows:

service zygote /system/bin/app_process64 -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

According to the format of the Service type statement, we can know that the process name of zygote is zygote and the executing program is app_ Process64, class name is main. If the audioserver cameraserver, media, netd and WiFi processes are terminated, restart is required.

      4. init.zygote64_32.rc

Indicates that both 32-bit and 64 bit programs are supported, init.zygote64_ 32. The contents of RC file are as follows:

service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote
    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 zygote_secondary /system/bin/app_process32 -Xzygote /system/bin --zygote --socket-name=zygote_secondary --enable-lazy-preload
    class main
    priority -20
    user root
    group root readproc
    socket zygote_secondary stream 660 root system
    onrestart restart zygote
    writepid /dev/cpuset/foreground/tasks

There are two Service type statements in the script, indicating that two zygote processes will be started, the first is zygote, and the executing program is app_process64 as the main mode; the second name is zygote_secondary, the executing program is app_ Process as a secondary mode.

3. Introduction to the starting process of zygote process

Starting the zygote process in init is mainly to call App_mian.cpp The start method of AppRuntime in the main function of is used to start the zygote process_ main.cpp The main function of is analyzed. The code is as follows:

frameworks/base/cmds/app_process/App_main.cpp

int main(int argc, char* const argv[])
{
    
    AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));
    
    ...

    while (i < argc) {
        const char* arg = argv[i++];
        if (strcmp(arg, "--zygote") == 0) { // ... 1
            // Set zygote to true if it is currently running in the zygote process
            zygote = true; // ... 2
            niceName = ZYGOTE_NICE_NAME;
        } else if (strcmp(arg, "--start-system-server") == 0) { // ... 3
            // Set startSystemServer to true if it is currently running in the SystemServer process
            startSystemServer = true; // ... 4
        } else if (strcmp(arg, "--application") == 0) {
            application = true;
        } else if (strncmp(arg, "--nice-name=", 12) == 0) {
            niceName.setTo(arg + 12);
        } else if (strncmp(arg, "--", 2) != 0) {
            className.setTo(arg);
            break;
        } else {
            --i;
            break;
        }
    }


    ... 

    if (!niceName.isEmpty()) {
        runtime.setArgv0(niceName.string(), true /* setProcName */);
    }

    // If running in the zygote process
    if (zygote) { // ... 5
        runtime.start("com.android.internal.os.ZygoteInit", args, zygote); // ... 6
    } 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.");
    }
}

Since the zygote process creates subprocesses through fock itself, both the zygote process and its subprocesses can enter the App_main.cpp In order to distinguish which process is currently running in, the main function will judge whether the parameter arg contains "- zygote" in annotation 1. If it does, it means that the main function is running in the zygote process and set zygote to true in annotation 2. Similarly, it will judge whether the parameter arg contains "start system" in annotation 3 Server ", which indicates that the main function runs in the SystemServer process and sets startSystemServer to true in note 4.

In note 5, if zygote is true, it means that currently running in the zygote process, the start function of runtime in Note 6 will be called. Runtime refers to AppRuntime, which is also declared in App_main.cpp In, it inherits the Android runtime, but the AppRuntime does not override the start function. That is to say, when we call the start function, we actually call the Android runtime start function, as shown below:

frameworks/base/cmds/app_process/App_main.cpp

class AppRuntime : public AndroidRuntime
 
Next, we analyze the start function in Android runtime. The code is as follows:
 
frameworks/base co e/jni/AndroidRuntime.cpp
/*
 * Start the Android runtime.  This involves starting the virtual machine
 * and calling the "static void main(String[] args)" method in the class
 * named by "className".
 *
 * Passes the main function two arguments, the class name and the specified
 * options string.
 */
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
    ...

    /* start the virtual machine */
    JniInvocation jni_invocation;
    jni_invocation.Init(NULL);
    JNIEnv* env;
    // Start java virtual machine
    if (startVm(&mJavaVM, &env, zygote) != 0) { // ... 1
        return;
    }
    onVmCreated(env);

    /*
     * Register android functions.
     * Register JNI method for java virtual machine
     */
    if (startReg(env) < 0) { // ... 2
        ALOGE("Unable to register all android natives\n");
        return;
    }
     
    ... 

  
    stringClass = env->FindClass("java/lang/String");
    assert(stringClass != NULL);
    strArray = env->NewObjectArray(options.size() + 1, stringClass, NULL);
    assert(strArray != NULL);

    // From App_ The main function of main knows that className is com.android.internal.os.ZygoteInit
    classNameStr = env->NewStringUTF(className);
    assert(classNameStr != NULL);
    env->SetObjectArrayElement(strArray, 0, classNameStr);

    for (size_t i = 0; i < options.size(); ++i) {
        jstring optionsStr = env->NewStringUTF(options.itemAt(i).string());
        assert(optionsStr != NULL);
        env->SetObjectArrayElement(strArray, i + 1, optionsStr);
    }

    /*
     * Start VM.  This thread becomes the main thread of the VM, and will
     * not return until the VM exits.
     */

    // Replace '.' of className with '/'
    char* slashClassName = toSlashClassName(className != NULL ? className : ""); // .. 4
    // Find ZygoteInit
    jclass startClass = env->FindClass(slashClassName); // ... 5
    if (startClass == NULL) {
        ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
        /* keep going */
    } else {
        // Find the main method of ZygoteInit
        jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
            "([Ljava/lang/String;)V"); // ... 6
        if (startMeth == NULL) {
            ALOGE("JavaVM unable to find main() in '%s'\n", className);
            /* keep going */
        } else {
            // Calling the main method of ZygoteInit through JNI
            env->CallStaticVoidMethod(startClass, startMeth, strArray); // ... 7

#if 0
            if (env->ExceptionCheck())
                threadExitUncaughtException(env);
#endif
        }
    }
    free(slashClassName);

    ALOGD("Shutting down VM\n");
    if (mJavaVM->DetachCurrentThread() != JNI_OK)
        ALOGW("Warning: unable to detach main thread\n");
    if (mJavaVM->DestroyJavaVM() != 0)
        ALOGW("Warning: VM did not shut down cleanly\n");
}
Call sartVm function in annotation 1 to create Java virtual machine, and call startReg function in annotation 2 to register JNI method for Java virtual machine. The value of className at note 3 is the parameter passed in, and its value is“ com.android.internal.os.ZygoteInit ”. In note 4, through the toSlashClassName function, the "." of className is "/", the replaced value is "COM / Android / Intel / OS / Zygotelnit" and assigned to the slashClassName. Then, in note 5, Zygotelnit is found according to the slashClassName, and Zygotelnit is found, and then the main method is found in Note 6. Finally, the main method of Zygotelnit will be called through JNI in annotation 7. Why use JNI here? Because the main method of Zygotelnit is written by java language, the current running logic is in Native, which requires Java call through JNI. In this way, Zygote enters the Java framework layer from the Native layer.
After we call the main method of Zygotelnit through JNI, Zygote enters the Java framework layer. Before that, no code entered the Java framework layer. In other words, Zygote created the Java framework layer. Now let's see ZygoteInit.java The code is as follows:
 
frameworks/base/core/java/com/android/internal/os/Zygotelnit.java
public static void main(String argv[]) {
        ZygoteServer zygoteServer = new ZygoteServer()
        ...

        String socketName = "zygote";
 
        ...       

        try {
             
            ...

            // Create a Socket on the Server side. The value of socketName is "zygote"
            zygoteServer.registerServerSocket(socketName); // ... 1
            // In some configurations, we avoid preloading resources and classes eagerly.
            // In such cases, we will preload things prior to our first fork.
            if (!enableLazyPreload) {
                bootTimingsTraceLog.traceBegin("ZygotePreload");
                EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,
                    SystemClock.uptimeMillis());

                // Preload classes and resources
                preload(bootTimingsTraceLog); // ... 2

                EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
                    SystemClock.uptimeMillis());
                bootTimingsTraceLog.traceEnd(); // ZygotePreload
            } else {
                Zygote.resetNicePriority();
            }

            ...

            if (startSystemServer) {
                // Start the system server process
                Runnable r = forkSystemServer(abiList, socketName, zygoteServer);

                // {@code r == null} in the parent (zygote) process, and {@code r != null} in the
                // child (system_server) process.
                if (r != null) {
                    r.run();
                    return;
                }
            }

            Log.i(TAG, "Accepting command socket connections");

            // The select loop returns early in the child process after a fork and
            // loops forever in the zygote.
            // Waiting for AMS request
            caller = zygoteServer.runSelectLoop(abiList); // 4
        } catch (Throwable ex) {
            Log.e(TAG, "System zygote died with exception", ex);
            throw ex;
        } finally {
            zygoteServer.closeServerSocket();
        }

        // We're in the child process and have exited the select loop. Proceed to execute the
        // command.
        if (caller != null) {
            caller.run();
        }
    }

In note 1, create the Socket on the Server side through registerServerSocket method. The Socket with the name of "Zygote" is used to wait for the ActivityManagerService to request Zygote to create a new application process. In the future, an article related to AMS will be published to analyze AMS, which will not be introduced here. Preload the classes and resources at annotation 2. Start the system Server process in note 3, so that the system services are also started by the system Server process. Called at note 4 zygoteServer.runSelectLoop Method to wait for AMS to request the creation of a new application process. From this, we can see that the main method of Zygotelnit mainly does four things:

(1) create a Server Socket
(2) preload classes and resources.
(3) start the system server process.
(4) wait for AMS to request to create a new application process.
 
The second thing is to preload classes and resources. Interested readers can check the source code, and other major events will be analyzed here.

      1 . registerZygoteSocket

First, let's see what the registerZygoteSocket method of ZygoteServer has done. The code is as follows:

frameworks/base/core/java/com/android/internal/os/ZygoteServe.java

    /**
     * Registers a server socket for zygote command connections
     *
     * @throws RuntimeException when open fails
     */
    void registerServerSocket(String socketName) {
        if (mServerSocket == null) {
            int fileDesc;
            // Name of splicing Socket
            final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName; 
            try {
                // Get the value of Socket's environment variable
                String env = System.getenv(fullSocketName); 
                // Convert the value of Socket environment variable to the parameter of file descriptor
                fileDesc = Integer.parseInt(env); 
            } catch (RuntimeException ex) {
                throw new RuntimeException(fullSocketName + " unset or invalid", ex);
            }

            try {
                // Create file descriptor
                FileDescriptor fd = new FileDescriptor(); 
                // Set parameters for file descriptors
                fd.setInt$(fileDesc);
                // Create server Socket
                mServerSocket = new LocalServerSocket(fd); // ... 1
            } catch (IOException ex) {
                throw new RuntimeException(
                        "Error binding to local socket '" + fileDesc + "'", ex);
            }
        }
    }
Note 1 is used to create the LocalServerSocket, which is the Socket on the server side. After the zygote process starts the system server process, it will wait for the ActivityManagerService to request the zygote process to create a new application process on the server Socket.

2. Start the SystemServer process

Next, look at the forkSystemServer method. The code is as follows:

frameworks/base/core/java/com/android/internal/os/Zygotelnit.java

    /**
     * Prepare the arguments and forks for the system server process.
     *
     * Returns an {@code Runnable} that provides an entrypoint into system_server code in the
     * child process, and {@code null} in the parent.
     */
    private static Runnable forkSystemServer(String abiList, String socketName,
            ZygoteServer zygoteServer) {
        
        ... 
 
        /* Hardcoded command line to start the system server */

        // Create the args array, which is used to save the startup parameters of the system server process
        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",
        };  // ... 1
 
        ZygoteConnection.Arguments parsedArgs = null;

        int pid;

        try {
            parsedArgs = new ZygoteConnection.Arguments(args); // ... 2
            ZygoteConnection.applyDebuggerSystemProperty(parsedArgs);
            ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs);

            /* Request to fork the system server process */
            // Create system_server process
            pid = Zygote.forkSystemServer(
                    parsedArgs.uid, parsedArgs.gid,
                    parsedArgs.gids,
                    parsedArgs.debugFlags,
                    null,
                    parsedArgs.permittedCapabilities,
                    parsedArgs.effectiveCapabilities); // ... 3
        } catch (IllegalArgumentException ex) {
            throw new RuntimeException(ex);
        }

        /* For child process */
        // The current code logic runs in a subprocess
        if (pid == 0) {
            if (hasSecondZygote(abiList)) {
                waitForSecondaryZygote(socketName);
            }

            zygoteServer.closeServerSocket();
            // Processing system_server process
            return handleSystemServerProcess(parsedArgs); // ... 4
        }

        return null;
    }

The code in note 1 is used to create the args array, which is used to save the startup parameters of the system server process. It can be seen that the user id and user group id of the system server process are set to 1000, and the user group 1001-1010, 1018, 1021, 1032, 3001-3010 has permissions. The process name is system_server; started class name com.android server.SystemServer . Encapsulate the args array as an Arguments object at annotation 2 and make it available for the forksystemserver function call at annotation 3. Called at note 3 Zygote.forkSystemServer Method, the nativeForkSystemServer will be called internally. The nativeForkSystemServer method will eventually create a subprocess in the current process through the fork function, that is, the systemserver process. If the pid returned by the forksystemserver method is A value of 0 indicates that the current code is running in the newly created subprocess, and the handleSystemServerProcess in note 4 is executed to process the systemserver process.

      3. runSelectloop

After starting the Systemserver process, the runSelectLoop method of ZygoteServer will be executed to wait for the request of AMS. The code is as follows:

frameworks/base/core/java/com/android/internal/os/ZygoteServe.java

/**
     * Runs the zygote process's select loop. Accepts new connections as
     * they happen, and reads commands from connections one spawn-request's
     * worth at a time.
     */
    Runnable runSelectLoop(String abiList) {
        ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
        ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();

        fds.add(mServerSocket.getFileDescriptor()); // ... 1
        peers.add(null);

        // Wireless loop waiting for AMS request
        while (true) {
            StructPollfd[] pollFds = new StructPollfd[fds.size()];
            for (int i = 0; i < pollFds.length; ++i) { // ... 2
                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) { // ... 3
                if ((pollFds[i].revents & POLLIN) == 0) {
                    continue;
                }

                if (i == 0) {
                    ZygoteConnection newPeer = acceptCommandPeer(abiList); // ... 4
                    peers.add(newPeer);
                    fds.add(newPeer.getFileDesciptor());
                } else {
                    try {
                        ZygoteConnection connection = peers.get(i);
                        final Runnable command = connection.processOneCommand(this); // ... 5

                        if (mIsForkChild) {
                            // We're in the child. We should always have a command to run at this
                            // stage if processOneCommand hasn't called "exec".
                            if (command == null) {
                                throw new IllegalStateException("command == null");
                            }

                            return command;
                        } else {
                            // We're in the server - we should never have any commands to run.
                            if (command != null) {
                                throw new IllegalStateException("command != null");
                            }

                            // We don't know whether the remote side of the socket was closed or
                            // not until we attempt to read from it from processOneCommand. This shows up as
                            // a regular POLLIN event in our regular processing loop.
                            if (connection.isClosedByPeer()) {
                                connection.closeSocket();
                                peers.remove(i);
                                fds.remove(i);
                            }
                        }
                    } catch (Exception e) {
                        if (!mIsForkChild) {
                            // We're in the server so any exception here is one that has taken place
                            // pre-fork while processing commands or reading / writing from the
                            // control socket. Make a loud noise about any such exceptions so that
                            // we know exactly what failed and why.

                            Slog.e(TAG, "Exception executing zygote command: ", e);

                            // Make sure the socket is closed so that the other end knows immediately
                            // that something has gone wrong and doesn't time out waiting for a
                            // response.
                            ZygoteConnection conn = peers.remove(i);
                            conn.closeSocket();

                            fds.remove(i);
                        } else {
                            // We're in the child so any exception caught here has happened post
                            // fork and before we execute ActivityThread.main (or any other main()
                            // method). Log the details of the exception and bring down the process.
                            Log.e(TAG, "Caught post-fork exception in child process.", e);
                            throw e;
                        }
                    }
                }
            }
        }
    }
}
The mserversocket in note 1 is the server Socket we created in the registerZygoteSocket function. Call mServerSocket.getFileDescriptor Method is used to obtain the value of the fd field of the Socket and add it to the list FDS of fd. The next infinite loop is used to wait for AMS to request the Zygote process to create a new application process. In Note 2, the information stored in FDS is transferred to the pollFds array by traversal. Traverse the pollFds array in note 3. If i==0, the server Socket is connected to the client. In other words, the current Zygote process is connected to AMS. In note 4, get the ZygoteConnection class through the acceptCommandPeer method and add it to the Socket connection list peers. Then add the fd of the ZygoteConnection to the fd list fds so that you can receive the request sent by AMs. If the value of i is not equal to 0, AMS sends a request to the Zygote process to create an application process. Call the processOneCommand function of ZygoteConnection in note 5 to create a new application process, and clear the connection from the Socket connection list peers fd list FDS after the successful creation.

4. Summary of Zygote process startup

Start the Zygote process through the AppRuntime class in the init process and call its start method.
The Zygote process has done the following:
(1) create Java virtual machine and register JNI method for Java virtual machine
(2) call the main function of Zygotelnit through JNI to enter the Java framework layer of Zygote.
(3) create a server-side Socket through registerZygoteSocket method, and create a new application process through the request of AMS such as runSelectLoop method.
(4) start the system server process.

Tags: socket Android Java Attribute

Posted on Fri, 12 Jun 2020 05:43:17 -0400 by bmarinho