Pangu and Nuwa in the Java World - Zygote, it's really a sudden enlightenment

  1. registerServerSocketFromEnv registers the server socket for cross process communication. Binder communication is not used here.
  2. preload() to perform the preload operation
  3. gcAndFinalize(), take the initiative to conduct a garbage collection before forkSystemServer
  4. forkSystemServer(), create SystemServer process
  5. runSelectLoop(), waiting for the socket request from the client

The above is basically the whole mission of Zygote. The following is a detailed analysis according to this process.

registerServerSocketFromEnv

> ZygoteServer.java

void registerServerSocketFromEnv(String socketName) {
    if (mServerSocket == null) {
        int fileDesc;
        final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;
        try {
            // Get fd of socket from environment variable
            String env = System.getenv(fullSocketName);
            fileDesc = Integer.parseInt(env);
        } catch (RuntimeException ex) {
            throw new RuntimeException(fullSocketName + " unset or invalid", ex);
        }

        try {
            FileDescriptor fd = new FileDescriptor();
            fd.setInt$(fileDesc); // Set file descriptor
            mServerSocket = new LocalServerSocket(fd); // Create a server socket
            mCloseSocketFd = true;
        } catch (IOException ex) {
            throw new RuntimeException(
                    "Error binding to local socket '" + fileDesc + "'", ex);
        }
    }
} 

First, get the file descriptor fd of the socket from the environment variable, and then create the server LocalServerSocket according to fd for IPC communication. The environment variable here is set when the init process creates the Zygote process.

preload()

> ZygoteInit.java

static void preload(TimingsTraceLog bootTimingsTraceLog) {
        ......
        preloadClasses(); // Preload and initialize the classes in / system / etc / preloaded classes
        ......
        preloadResources(); // Preload system resources
        ......
        nativePreloadAppProcessHALs(); // HAL?
        ......
        preloadOpenGL(); // Preload OpenGL
        ......
        preloadSharedLibraries(); // Preload shared libraries, including android and compiler_rt and JNI graphics
        preloadTextResources(); // Preload text resources
        // Ask the WebViewFactory to do any initialization that must run in the zygote process,
        // for memory sharing purposes.
        // Some initialization work that must be done in the zygote process in WebViewFactory is used for shared memory
        WebViewFactory.prepareWebViewInZygote();
        warmUpJcaProviders();

        sPreloadComplete = true;
    } 

The preload() method mainly preloads some classes, resources and shared libraries to improve runtime efficiency. Let's take a look at what is preloaded in turn.

preloadClasses()

> ZygoteInit.java

private static void preloadClasses() {
    ......
    InputStream is;
    try {
        // /system/etc/preloaded-classes
        is = new FileInputStream(PRELOADED_CLASSES);
    } catch (FileNotFoundException e) {
        Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES + ".");
        return;
    }

    try {
        BufferedReader br
            = new BufferedReader(new InputStreamReader(is), 256);

        int count = 0;
        String line;
        while ((line = br.readLine()) != null) {
            // Skip comments and blank lines.
            line = line.trim();
            if (line.startsWith("#") || line.equals("")) {
                continue;
            }

            try {
                // Load and explicitly initialize the given class. Use
                // Class.forName(String, boolean, ClassLoader) to avoid repeated stack lookups
                // (to derive the caller's class-loader). Use true to force initialization, and
                // null for the boot classpath class-loader (could as well cache the
                // class-loader of this class in a variable).
                Class.forName(line, true, null);
                count++;
            } catch (ClassNotFoundException e) {
                Log.w(TAG, "Class not found for preloading: " + line);
            } catch (UnsatisfiedLinkError e) {
                Log.w(TAG, "Problem preloading " + line + ": " + e);
            } catch (Throwable t) {
                ......
            }
        }
    } catch (IOException e) {
        Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e);
    } finally {
        IoUtils.closeQuietly(is);
        ......
    }
} 

Only the core logic code is retained. Read the / system / etc / preloaded classes file and load the classes declared in the file line by line through the Class.forName() method. Preloading commonly used classes in the system in advance can undoubtedly improve runtime efficiency, but the work of preloading commonly used classes is usually very heavy. Search the entire source code library and find a preloaded classes file in the / frameworks/base/config directory. Open this file with a total of 6558 lines, which means loading thousands of classes in advance, which will undoubtedly take a long time to increase the startup time of the Android system and improve the efficiency of the runtime.

preloadResources()

> ZygoteInit.java

private static void preloadResources() {
    final VMRuntime runtime = VMRuntime.getRuntime();

    try {
        mResources = Resources.getSystem();
        mResources.startPreloading();
        if (PRELOAD_RESOURCES) {
            TypedArray ar = mResources.obtainTypedArray(
                    com.android.internal.R.array.preloaded_drawables);
            int N = preloadDrawables(ar);
            ar.recycle();
            ......
            ar = mResources.obtainTypedArray(
                    com.android.internal.R.array.preloaded_color_state_lists);
            N = preloadColorStateLists(ar);
            ar.recycle();

            if (mResources.getBoolean(
                    com.android.internal.R.bool.config_freeformWindowManagement)) {
                ar = mResources.obtainTypedArray(
                    com.android.internal.R.array.preloaded_freeform_multi_window_drawables);
                N = preloadDrawables(ar);
                ar.recycle();
            }
        }
        mResources.finishPreloading();
    } catch (RuntimeException e) {
        Log.w(TAG, "Failure preloading resources", e);
    }
} 

It can be seen from the source code that the main loaded resources are:

com.android.internal.R.array.preloaded_drawables

com.android.internal.R.array.preloaded_color_state_lists

com.android.internal.R.array.preloaded_freeform_multi_window_drawables

preloadSharedLibraries()

> ZygoteInit.java

private static void preloadSharedLibraries() {
    Log.i(TAG, "Preloading shared libraries...");
    System.loadLibrary("android");
    System.loadLibrary("compiler_rt");
    System.loadLibrary("jnigraphics");
} 

Three shared libraries, libandroid.so and libcompiler, are preloaded_ Rt.so and libjnigraphics.so.

gcAndFinalize()

> ZygoteInit.java

static void gcAndFinalize() {
    final VMRuntime runtime = VMRuntime.getRuntime();

    /* runFinalizationSync() lets finalizers be called in Zygote,
     * which doesn't have a HeapWorker thread.
     */
    System.gc();
    runtime.runFinalizationSync();
    System.gc();
} 

Before forkSystemServer(), a GC operation will be initiated.

forkSystemServer()

After actively calling GC, Zygote will do its big thing - fork SystemServer process.

> ZygoteInit.java

private static Runnable forkSystemServer(String abiList, String socketName,
    
    ......
    
    /* Hardcoded command line to start the system server */
    // startup parameter
    String args[] = {
        "--setuid=1000",
        "--setgid=1000",
        "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023,1024,1032,1065,3001,3002,3003,3006,3007,3009,3010",
        "--capabilities=" + capabilities + "," + capabilities,
        "--nice-name=system_server", // Process name
        "--runtime-args",
        "--target-sdk-version=" + VMRuntime.SDK_VERSION_CUR_DEVELOPMENT,
        "com.android.server.SystemServer", // Load class name
    };
    ZygoteConnection.Arguments parsedArgs = null;

    int pid;

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

        boolean profileSystemServer = SystemProperties.getBoolean(
                "dalvik.vm.profilesystemserver", false);
        if (profileSystemServer) {
            parsedArgs.runtimeFlags |= Zygote.PROFILE_SYSTEM_SERVER;
        }

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

    /* For child process */
    // pid == 0 indicates that the child process enters the system from here_ Server process
    if (pid == 0) {
        if (hasSecondZygote(abiList)) { // If there is a second Zygote
            waitForSecondaryZygote(socketName);
        }

        zygoteServer.closeServerSocket(); // Close and release the socket from Zygote copy
        return handleSystemServerProcess(parsedArgs); // Complete the newly created system_ Remaining work of the server process
    }

    /**
     * Note that the fork() function executes once and returns twice (two executions of the same program by two processes).
     * pid > 0  Description is also the parent process. pid = 0 indicates that a child process has been entered
     * So return null here will still be executed 
     */
    return null;
} 

As can be seen from the above startup parameters, the uid and gid of the SystemServer process are both 1000, and the process name is system_server. The last class name to be loaded is com.android.server.SystemServer. After preparing a series of parameters, splice them through ZygoteConnection.Arguments(), and then call the Zygote.forkSystemServer() method to actually fork out the child process system_server.

> Zygote.java

public static int forkSystemServer(int uid, int gid, int[] gids, int runtimeFlags,
        int[][] rlimits, long permittedCapabilities, long effectiveCapabilities) {
    VM_HOOKS.preFork();
    // Resets nice priority for zygote process.
    resetNicePriority();
    int pid = nativeForkSystemServer(
            uid, gid, gids, runtimeFlags, rlimits, permittedCapabilities, effectiveCapabilities);
    // Enable tracing as soon as we enter the system_server.
    if (pid == 0) {
        Trace.setTracingEnabled(true, runtimeFlags);
    }
    VM_HOOKS.postForkCommon();
    return pid;
}

native private static int nativeForkSystemServer(int uid, int gid, int[] gids, int runtimeFlags,
        int[][] rlimits, long permittedCapabilities, long effectiveCapabilities); 

The last fork () operation is done in the native layer. Go back to ZygoteInit.forkSystemServer() and perform the logical processing after fork():

if(pid == 0){
    ......
    return handleSystemServerProcess(parsedArgs);
}

return null; 

According to normal logic, these two returns will only be executed once. In fact, they are not. The fork() function is executed once and returned twice. To be more precise, two processes execute a program twice. When pid == 0, it indicates that it is in the child process. When PID > 0, it indicates that it is in the parent process. When the child process was first forked out, the data structure of the parent and child processes was basically the same, but then they parted ways and implemented their own logic. Therefore, there will be two return values in the above code segment, the sub process (system_server) will return the result of executing handleSystemServerProcess(parsedArgs), and the parent process (zygote) will return null. What will be done for two different return values? Let's go back to ZygoteInit.main():

if (startSystemServer) {
        Runnable r = forkSystemServer(abiList, socketName, zygoteServer);

        // {@code r == null} in the parent (zygote) process, and {@code r != null} in the
        // child (system_server) process.
        // r == null indicates that it is in the zygote process
        // r != null description is in system_server process
        if (r != null) {
            r.run(); 
            return;
        }
    }
    
    // Loop waiting to process client requests
    caller = zygoteServer.runSelectLoop(abiList); 

Subprocess system_ The server returns a Runnable. Execute r.run() and return directly. The parent process zygote returns null, so it does not meet the judgment conditions of if. Continue to run runSelectLoop. The father and son process parted ways and did their own things.

Let's analyze the runSelectLoop() and handleSystemServerProcess() methods to see what the parent-child processes Zygote and SystemServer continue to do.

handleSystemServerProcess

In fact, it's out of the scope of Zygote. I'm going to introduce it in the next system server source code analysis, but I don't write it here and feel that the introduction of Zygote is incomplete, so I'll just say it together.

> ZygoteInit.java

private static Runnable handleSystemServerProcess(ZygoteConnection.Arguments parsedArgs) {
    // set umask to 0077 so new files and directories will default to owner-only permissions.
    // umask is generally used to give permissions to a directory or file when you initially create it
    Os.umask(S_IRWXG | S_IRWXO);

    // Set the current process name to "system"_ server"
    if (parsedArgs.niceName != null) { 
        Process.setArgV0(parsedArgs.niceName);
    }

    final String systemServerClasspath = Os.getenv("SYSTEMSERVERCLASSPATH");
    if (systemServerClasspath != null) {
        // dex optimization operation
        performSystemServerDexOpt(systemServerClasspath);
        // Capturing profiles is only supported for debug or eng builds since selinux normally
        // prevents it.
        boolean profileSystemServer = SystemProperties.getBoolean(
                "dalvik.vm.profilesystemserver", false);
        if (profileSystemServer && (Build.IS_USERDEBUG || Build.IS_ENG)) {
            try {
                prepareSystemServerProfile(systemServerClasspath);
            } catch (Exception e) {
                Log.wtf(TAG, "Failed to set up system server profile", e);
            }
        }
    }

    if (parsedArgs.invokeWith != null) { // invokeWith is generally empty
        String[] args = parsedArgs.remainingArgs;
        // If we have a non-null system server class path, we'll have to duplicate the
        // existing arguments and append the classpath to it. ART will handle the classpath
        // correctly when we exec a new process.
        if (systemServerClasspath != null) {
            String[] amendedArgs = new String[args.length + 2];
            amendedArgs[0] = "-cp";
            amendedArgs[1] = systemServerClasspath;
            System.arraycopy(args, 0, amendedArgs, 2, args.length);
            args = amendedArgs;
        }

        WrapperInit.execApplication(parsedArgs.invokeWith,
                parsedArgs.niceName, parsedArgs.targetSdkVersion,
                VMRuntime.getCurrentInstructionSet(), null, args);

        throw new IllegalStateException("Unexpected return from WrapperInit.execApplication");
    } else {
        ClassLoader cl = null;
        if (systemServerClasspath != null) {
            // Create a class loader and assign it to the current thread
            cl = createPathClassLoader(systemServerClasspath, parsedArgs.targetSdkVersion);
                
            Thread.currentThread().setContextClassLoader(cl);
        }

        /*
         * Pass the remaining arguments to SystemServer.
         */
        return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl);
    }

    /* should never reach here */
} 

Set the process name to system_server, performs dex optimization, sets the class loader for the current thread, and finally calls ZygoteInit.zygoteInit() to continue processing the remaining parameters.

public static final Runnable zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader) {
    ......
    // Redirect System.out and System.err to the Android log.
    // Redirect System.out and System.err to Android log
    RuntimeInit.redirectLogStreams();

    RuntimeInit.commonInit(); // Some initialization work
    ZygoteInit.nativeZygoteInit(); // native layer initialization
    return RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader); // Call entry function
} 

Redirect the Log and perform some initialization. I won't elaborate on this part. Click the source code link given at the beginning of the article, and most of them have been annotated. Finally, call RuntimeInit.applicationInit() and continue to look inside.

> RuntimeInit.java

protected static Runnable applicationInit(int targetSdkVersion, String[] argv,
        ClassLoader classLoader) {
        
    ......
    final Arguments args = new Arguments(argv); // Analytical parameters

    ......
    // Find the main() method of startClass. The startClass here is com.android.server.SystemServer
    return findStaticMain(args.startClass, args.startArgs, classLoader);
} 

The startClass parameter here is com.android.server.SystemServer. The findStaticMain() method can be known by name. Its function is to find the main() function. Here is to find the main() method of com.android.server.SystemServer class.

protected static Runnable findStaticMain(String className, String[] argv,
        ClassLoader classLoader) {
    Class<?> cl;

    try {
        cl = Class.forName(className, true, classLoader);
    } catch (ClassNotFoundException ex) {
        throw new RuntimeException(
                "Missing class when invoking static main " + className,
                ex);
    }

    Method m;
    try {
        // Find the main() method
        m = cl.getMethod("main", new Class[] { String[].class });
    } catch (NoSuchMethodException ex) {
 

Keywords: Java Android Design Pattern

Added by tfburges on Thu, 02 Sep 2021 10:57:45 +0300