Android FrameWork - Start the Zygo process

Keywords: Android Java socket

Links to related articles:
1. Android FrameWork - Learning Starter
2. Android FrameWork - Start the Init process

Related source files:

/system/core/rootdir/init.rc
/frameworks/base/cmds/app_process/App_main.cpp
/frameworks/base/core/jni/AndroidRuntime.cpp
/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
/frameworks/base/core/java/com/android/internal/os/ZygoteConnection.java

The Zygote process is composed of init process Created by parsing the init.rc file.

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    class main
    socket zygote stream 660 root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart media
    onrestart restart netd

Correspondence Finding /frameworks/base/cmds/app_process/app_main.cpp main Method in Source File

int main(int argc, char* const argv[])
{
    // AppRuntime inherits Andoird Runtime
    AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));
    // Filter the first parameter
    argc--;
    argv++;

    // Everything up to '--' or first non '-' arg goes to the vm.
    //
    // The first argument after the VM args is the "parent dir", which
    // is currently unused.
    //
    // After the parent dir, we expect one or more the following internal
    // arguments :
    //
    // --zygote : Start in zygote mode
    // --start-system-server : Start the system server.
    // --application : Start in application (stand alone, non zygote) mode.
    // --nice-name : The nice name for this process.
    //
    // For non zygote starts, these arguments will be followed by
    // the main class name. All remaining arguments are passed to
    // the main method of this class.
    //
    // For zygote starts, all remaining arguments are passed to the zygote.
    // main function.
    //
    // Note that we must copy argument string values since we will rewrite the
    // entire argument block when we apply the nice name to argv0.
    // Parse runtime arguments.  Stop at first unrecognized option.
    bool zygote = false;
    bool startSystemServer = false;
    bool application = false;
    String8 niceName;
    String8 className;

    ++i;  // Skip unused "parent dir" argument.
    // Analytical parameters
    while (i < argc) {
        const char* arg = argv[i++];
        if (strcmp(arg, "--zygote") == 0) {
            zygote = true;
            niceName = ZYGOTE_NICE_NAME;
        } else if (strcmp(arg, "--start-system-server") == 0) {
            startSystemServer = true;
        } 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;
        }
    }

    ...
    //Setting process name
    if (!niceName.isEmpty()) {
        runtime.setArgv0(niceName.string());
        set_process_name(niceName.string());
    }
    // If zygote, Android Runtime executes com.android.internal.os.ZygoteInit 
    // Look at the script parameters parsed above and execute here.
    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.");
        return 10;
    }
}

The first thing is to parse the parameters, and then to /frameworks/base/core/jni/AndroidRuntime.cpp start method:

/*
 * 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)
{
    ... 
    // Create an instance of a virtual machine
    /* start the virtual machine */
    JniInvocation jni_invocation;
    jni_invocation.Init(NULL);
    JNIEnv* env;
    if (startVm(&mJavaVM, &env, zygote) != 0) {
        return;
    }
    onVmCreated(env);

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

    /*
     * We want to call main() with a String array with arguments in it.
     * At present we have two arguments, the class name and an option string.
     * Create an array to hold them.
     */
    jclass stringClass;
    jobjectArray strArray;
    jstring classNameStr;

    // strArray= new String[options.size() + 1];
    stringClass = env->FindClass("java/lang/String");
    strArray = env->NewObjectArray(options.size() + 1, stringClass, NULL);
    // strArray[0] = "com.android.internal.os.ZygoteInit"
    classNameStr = env->NewStringUTF(className);
    env->SetObjectArrayElement(strArray, 0, classNameStr);

    // strArray[1] = "start-system-server";
    // StrArray [2]="--abi-list = type of cpu architecture for system response";
    for (size_t i = 0; i < options.size(); ++i) {
        jstring optionsStr = env->NewStringUTF(options.itemAt(i).string());
        env->SetObjectArrayElement(strArray, i + 1, optionsStr);
    }

    // slashClassName = "com/android/internal/os/ZygoteInit"
    char* slashClassName = toSlashClassName(className);
    jclass startClass = env->FindClass(slashClassName);
    if (startClass == NULL) {
        ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
        /* keep going */
    } else {
        // The main method to get ZygoteInit.java
        jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
            "([Ljava/lang/String;)V");
        if (startMeth == NULL) {
            ALOGE("JavaVM unable to find main() in '%s'\n", className);
            /* keep going */
        } else {
            // Execute the main method of ZygoteInit.java to enter the Java world from the Native world
            env->CallStaticVoidMethod(startClass, startMeth, strArray);
        }
    }
    ...
}

Java code runs on the Java virtual machine, and Java communicates with native using JNI. From here we begin to enter the Java world:

public static void main(String argv[]) {
    try {
        // Analytical parameters
        boolean startSystemServer = false;
        String socketName = "zygote";
        String abiList = null;
        for (int i = 1; i < argv.length; i++) {
            if ("start-system-server".equals(argv[i])) {
                startSystemServer = true;
            } else if (argv[i].startsWith(ABI_LIST_ARG)) {
                abiList = argv[i].substring(ABI_LIST_ARG.length());
            } else if (argv[i].startsWith(SOCKET_NAME_ARG)) {
                socketName = argv[i].substring(SOCKET_NAME_ARG.length());
            } else {
                throw new RuntimeException("Unknown command line argument: " + argv[i]);
            }
        }
        ...
        // Register socket s for Zygote for communication
        registerZygoteSocket(socketName);
        // Preloading classes and resources
        preload(); 
        // Start system_server
        if (startSystemServer) {
            startSystemServer(abiList, socketName);
        }
        // Enter the cycle mode and wait for the incubation process
        runSelectLoop(abiList); 
        closeServerSocket();
    } catch (MethodAndArgsCaller caller) {
        caller.run();
    } catch (RuntimeException ex) {
        closeServerSocket();
        throw ex;
    }
}

private static void registerZygoteSocket(String socketName) {
    if (sServerSocket == null) {
        int fileDesc;
        final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;
        try {
            String env = System.getenv(fullSocketName);
            fileDesc = Integer.parseInt(env);
        } catch (RuntimeException ex) {
            ...
        }

        try {
            FileDescriptor fd = new FileDescriptor();
            // Setting file descriptors
            fd.setInt$(fileDesc);
            // Creating Socket's Local Server
            sServerSocket = new LocalServerSocket(fd); 
        } catch (IOException ex) {
            ...
        }
    }
}

static void preload() {
    // Preloading classes in the / system/etc/preloaded-classes file
    preloadClasses();

    // Preload resources, including drawable and color resources
    preloadResources();

    // Preloading OpenGL
    preloadOpenGL();

    // Through the System.loadLibrary() method,
    // Preload the three shared libraries of "android", "compiler_rt" and "jnigraphics"
    preloadSharedLibraries();

    // Preload text connector resources
    preloadTextResources();

    // For Zygo processes only, for memory-sharing processes
    WebViewFactory.prepareWebViewInZygote();
}

private static boolean startSystemServer(String abiList, String socketName) throws MethodAndArgsCaller, RuntimeException {
    ...
    // Set some parameters 
    String args[] = {
        "--setuid=1000",
        "--setgid=1000",
        "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1032,3001,3002,3003,3006,3007",
        "--capabilities=" + capabilities + "," + capabilities,
        "--nice-name=system_server",
        "--runtime-args",
        "com.android.server.SystemServer",
    };

    ZygoteConnection.Arguments parsedArgs = null;
    int pid;
    try {
        ...
        // fork creates the system_server process, which will be analyzed later
        pid = Zygote.forkSystemServer(
                parsedArgs.uid, parsedArgs.gid,
                parsedArgs.gids,
                parsedArgs.debugFlags,
                null,
                parsedArgs.permittedCapabilities,
                parsedArgs.effectiveCapabilities);
    } catch (IllegalArgumentException ex) {
        throw new RuntimeException(ex);
    }

    //  pid == 0 represents a child process, that is, the system_server process
    if (pid == 0) {
        // Execute the initialization system_server process
        handleSystemServerProcess(parsedArgs);
    }
    return true;
}

private static void runSelectLoop(String abiList) throws MethodAndArgsCaller {
    ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
    ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
    // sServerSocket is created in registerZygoteSocket, the zygote process. Save to fds[0]
    fds.add(sServerSocket.getFileDescriptor());
    peers.add(null);

    while (true) {
        // Set a parameter for pollFds, fds.size is 1, that is to say, only sServerSocket.getFileDescriptor() is in pollFds. 
        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 {
            // Processing polling status, when pollFds have an event, it executes downward, otherwise it is blocked here
            Os.poll(pollFds, -1);
        } catch (ErrnoException ex) {
            ...
        }
        
        for (int i = pollFds.length - 1; i >= 0; --i) {
            if (i == 0) {
                // fds[0], which represents sServerSocket, means that there are client connection requests;
                // Then create the ZygoteConnection object and add it to fds.
                ZygoteConnection newPeer = acceptCommandPeer(abiList);
                peers.add(newPeer);
                //Add to fds.
                fds.add(newPeer.getFileDesciptor()); 
            } else {
                // I > 0, which means receiving data from the opposite end through socket and performing corresponding operations
                boolean done = peers.get(i).runOnce();
                if (done) {
                    peers.remove(i);
                    // Remove the file descriptor from fds after processing
                    fds.remove(i); 
                }
            }
        }
    }
}

boolean runOnce() throws ZygoteInit.MethodAndArgsCaller {
    String args[];
    Arguments parsedArgs = null;
    FileDescriptor[] descriptors;

    try {
        // Read the parameter list sent by the socket client
        args = readArgumentList();
        descriptors = mSocket.getAncillaryFileDescriptors();
    } catch (IOException ex) {
        ...
        return true;
    }
    ...

    try {
        // The parameters passed by the binder client are parsed into Arguments object format
        parsedArgs = new Arguments(args);
        ...
        // fork creates a new process
        pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
                parsedArgs.debugFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,
                parsedArgs.niceName, fdsToClose, parsedArgs.instructionSet,
                parsedArgs.appDataDir);
    } catch (Exception e) {
        ...
    }

    try {
        if (pid == 0) {
            // Pid = 0 executes the logic of the child process
            IoUtils.closeQuietly(serverPipeFd);
            serverPipeFd = null;
            // Enter the sub-process flow
            handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr);
            return true;
        } else {
            // PID > 0 executes the logic of the parent process
            IoUtils.closeQuietly(childPipeFd);
            childPipeFd = null;
            return handleParentProc(pid, descriptors, serverPipeFd, parsedArgs);
        }
    } finally {
        IoUtils.closeQuietly(childPipeFd);
        IoUtils.closeQuietly(serverPipeFd);
    }
}

I believe a lot of guys have the same feeling as me. At first, some of the code seems obscure and difficult to understand. In fact, at the beginning, there is no need to cut corners in many places. First, we understand the whole process, then we slowly gnaw at the details. In conclusion, the Zygote process is created by the init process parsing init.rc script. Its specific execution source code is the App_main.main method. First, it creates a virtual machine instance, then registers the JNI method. Finally, it enters the Java world through JNI calls to the ZygoteInit.main method. In the Java world, we register socket s for Zygote for interprocess communication, preload some generic classes and resources, start the system_server process, and wait iteratively for incubation to create new processes.

As for how to start the system_server process and how to incubate the new process, we will go back to the specific analysis. At present, we only need to know what zygote has done. Finally, let's consider a question: Why does Android use zygote to incubate the process?

Posted by ahoo on Sat, 10 Aug 2019 03:13:24 -0700